为了应对高并发场景下的数据库访问需求,读写分离架构是现代数据库架构的一个重要组成部分。今天,我就和你一起来学习 ShardingSphere 中所提供的读写分离机制,以及这一机制如何与前面介绍的分库分表和强制路由整合在一起使用。
ShardingSphere 中的读写分离
为了应对数据库读写分离,ShardingSphere 所提供的解决方案还是依赖于强大的配置体系。为了更好地理解这些读写分离相关的配置,我们有必要对读写分离与主从架构有一定的了解。
读写分离与主从架构
目前,大部分的主流关系型数据库都提供了主从架构的实现方案,通过配置两台或多台数据库的主从关系,可以将一台数据库服务器的数据更新自动同步到另一台服务器上。而应用程序可以利用数据库的这一功能,实现数据的读写分离,从而改善数据库的负载压力。
可以看到,所谓的读写分离,实际上就是将写操作路由到主数据库,而将读操作路由到从数据库。对于互联网应用而言,读取数据的需求远远大于写入数据的需求,所以从数据库一般都是多台。当然,对于复杂度较高的系统架构而言,主库的数量同样也可以是多台。
读写分离与 ShardingSphere
就 ShardingSphere 而言,支持主从架构下的读写分离是一项核心功能。目前 ShardingSphere 支持单主库、多从库的主从架构来完成分片环境下的读写分离,暂时不支持多主库的应用场景。
在数据库主从架构中,因为从库一般会有多台,所以当执行一条面向从库的 SQL 语句时,我们需要实现一套负载均衡机制来完成对目标从库的路由。ShardingSphere 默认提供了随机(Random)和轮询(RoundRobin)这两种负载均衡算法来完成这一目标。
另一方面,由于主库和从库之间存在一定的同步时延和数据不一致情况,所以在有些场景下,我们可能更希望从主库中获取最新数据。ShardingSphere 同样考虑到了这方面需求,开发人员可以通过 Hint 机制来实现对主库的强制路由。
配置读写分离
实现读写分离要做的还是配置工作。通过配置,我们的目标是获取支持读写分离的 MasterSlaveDataSource,而 MasterSlaveDataSource 的创建依赖于 MasterSlaveDataSourceFactory 工厂类:
1
2
3
4
5
6
|
public final class MasterSlaveDataSourceFactory {
public static DataSource createDataSource(final Map<String, DataSource> dataSourceMap, final MasterSlaveRuleConfiguration masterSlaveRuleConfig, final Properties props) throws SQLException {
return new MasterSlaveDataSource(dataSourceMap, new MasterSlaveRule(masterSlaveRuleConfig), props);
}
}
|
在上面这段代码中,我们可以看到 createDataSource 方法中传入了三个参数,除了熟悉的 dataSourceMap 和 props 之外,还有一个 MasterSlaveRuleConfiguration,而这个 MasterSlaveRuleConfiguration 包含了所有我们需要配置的读写分离信息:
1
2
3
4
5
6
7
8
9
10
11
|
public class MasterSlaveRuleConfiguration implements RuleConfiguration {
//读写分离数据源名称
private final String name;
//主库数据源名称
private final String masterDataSourceName;
//从库数据源名称列表
private final List<String> slaveDataSourceNames;
//从库负载均衡算法
private final LoadBalanceStrategyConfiguration loadBalanceStrategyConfiguration;
…
}
|
从 MasterSlaveRuleConfiguration 类所定义的变量中不难看出,我们需要配置读写分离数据源名称、主库数据源名称、从库数据源名称列表以及从库负载均衡算法这四个配置项,仅此而已。
系统改造:如何实现读写分离?
在掌握了读写分离的基本概念以及相关配置项之后,我们回到案例,看如何在单库单表架构中引入读写分离机制。
第一步,仍然是设置用于实现读写分离的数据源。为了演示一主多从架构,我们初始化了一个主数据源 dsmaster 以及两个从数据源 dsslave0 和 dsslave1:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
spring.shardingsphere.datasource.names=dsmaster,dsslave0,dsslave1
spring.shardingsphere.datasource.dsmaster.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.dsmaster.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsmaster.url=jdbc:mysql://localhost:3306/dsmaster
spring.shardingsphere.datasource.dsmaster.username=root
spring.shardingsphere.datasource.dsmaster.password=root
spring.shardingsphere.datasource.dsslave0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.dsslave0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsslave0.url=jdbc:mysql://localhost:3306/dsslave0
spring.shardingsphere.datasource.dsslave0.username=root
spring.shardingsphere.datasource.dsslave0.password=root
spring.shardingsphere.datasource.dsslave1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.dsslave1.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.dsslave1.url=jdbc:mysql://localhost:3306/dsslave1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
spring.shardingsphere.datasource.dsslave1.username=root
spring.shardingsphere.datasource.dsslave1.password=root
|
有了数据源之后,我们需要设置 MasterSlaveRuleConfiguration 类中所指定的 4 个配置项,这里负载均衡算法设置的是 random,也就是使用的随机算法:
1
2
3
4
|
spring.shardingsphere.masterslave.name=health_ms
spring.shardingsphere.masterslave.master-data-source-name=dsmaster
spring.shardingsphere.masterslave.slave-data-source-names=dsslave0,dsslave1
spring.shardingsphere.masterslave.load-balance-algorithm-type=random
|
现在我们来插入 User 对象,从控制台的日志中可以看到,ShardingSphere 执行的路由类型是 master-slave ,而具体 SQL 的执行是发生在 dsmaster 主库中:
1
2
3
4
5
6
7
|
2020-05-25 19:58:08.721 INFO 4392 --- [ main] ShardingSphere-SQL : Rule Type: master-slave
2020-05-25 19:58:08.721 INFO 4392 --- [ main] ShardingSphere-SQL : SQL: INSERT INTO user (user_id, user_name) VALUES (?, ?) ::: DataSources: dsmaster
Insert User:1
2020-05-25 19:58:08.721 INFO 4392 --- [ main] ShardingSphere-SQL : Rule Type: master-slave
2020-05-25 19:58:08.721 INFO 4392 --- [ main] ShardingSphere-SQL : SQL: INSERT INTO user (user_id, user_name) VALUES (?, ?) ::: DataSources: dsmaster
Insert User:2
…
|
然后,我们再对 User 对象执行查询操作并获取 SQL 执行日志:
1
2
|
2020-05-25 20:00:33.066 INFO 3364 --- [main] ShardingSphere-SQL : Rule Type: master-slave
2020-05-25 20:00:33.066 INFO 3364 --- [main] ShardingSphere-SQL : SQL : SELECT * FROM user; ::: DataSources: dsslave0
|
可以看到,这里用到的 DataSource 是 dsslave0,也就是说查询操作发生在 dsslave0 从库中。由于设置的是随机负载均衡策略,当我们多次执行查询操作时,目标 DataSource 会在 dsslave0 和 dsslave1 之间交替出现。
系统改造:如何实现读写分离+分库分表?
我们同样可以在分库分表的基础上添加读写分离功能。这时候,我们需要设置两个主数据源 dsmaster0 和 dsmaster1,然后针对每个主数据源分别设置两个从数据源:
1
|
spring.shardingsphere.datasource.names=dsmaster0,dsmaster1,dsmaster0-slave0,dsmaster0-slave1,dsmaster1-slave0,dsmaster1-slave1
|
这时候的库分片策略 default-database-strategy 同样分别指向 dsmaster0 和 dsmaster1 这两个主数据源:
1
2
|
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=dsmaster$->{user_id % 2}
|
而对于表分片策略而言,我们还是使用在 07 课时中介绍的分片方式进行设置:
1
2
3
|
spring.shardingsphere.sharding.tables.health_record.actual-data-nodes=dsmaster$->{0..1}.health_record$->{0..1}
spring.shardingsphere.sharding.tables.health_record.table-strategy.inline.sharding-column=record_id
spring.shardingsphere.sharding.tables.health_record.table-strategy.inline.algorithm-expression=health_record$->{record_id % 2}
|
完成这些设置之后,同样需要设置两个主数据源对应的配置项:
1
2
3
4
|
spring.shardingsphere.sharding.master-slave-rules.dsmaster0.master-data-source-name=dsmaster0
spring.shardingsphere.sharding.master-slave-rules.dsmaster0.slave-data-source-names=dsmaster0-slave0, dsmaster0-slave1
spring.shardingsphere.sharding.master-slave-rules.dsmaster1.master-data-source-name=dsmaster1
spring.shardingsphere.sharding.master-slave-rules.dsmaster1.slave-data-source-names=dsmaster1-slave0, dsmaster1-slave1
|
这样,我们就在分库分表的基础上添加了对读写分离的支持。ShardingSphere 所提供的强大配置体系使得开发人员可以在原有配置的基础上添加新的配置项,而不需要对原有配置做过多调整。
系统改造:如何实现读写分离下的强制路由?
在上个课时中我们介绍了强制路由,在这个基础上,我将给出如何基于 Hint,完成读写分离场景下的主库强制路由方案。
要想实现主库强制路由,我们还是要使用 HintManager。HintManager 专门提供了一个 setMasterRouteOnly 方法,用于将 SQL 强制路由到主库中。我们把这个方法也封装在 HintManagerHelper 帮助类中:
1
2
3
4
5
6
|
public class HintManagerHelper {
static void initializeHintManagerForMaster(final HintManager hintManager) {
hintManager.setMasterRouteOnly();
}
}
|
现在,我们在业务代码中加入主库强制路由的功能,下面这段代码演示了这个过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Override
public void processWithHintValueMaster() throws SQLException, IOException {
DataSource dataSource = DataSourceHelper.getDataSourceForMaster();
try (HintManager hintManager = HintManager.getInstance();
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
HintManagerHelper.initializeHintManagerForMaster(hintManager);
ResultSet result = statement.executeQuery("select user_id, user_name from user");
while (result.next()) {
System.out.println(result.getLong(1) + ": " + result.getString(2));
}
}
}
|
执行这段代码,可以在控制台日志中获取执行的结果:
1
2
3
4
5
|
2020-05-25 22:06:17.166 INFO 16680 --- [ main] ShardingSphere-SQL : Rule Type: master-slave
2020-05-25 22:06:17.166 INFO 16680 --- [ main] ShardingSphere-SQL : SQL: select user_id, user_name from user ::: DataSources: dsmaster
1: user_1
2: user_2
…
|
显然,这里的路由类型是 master-slave,而执行 SQL 的 DataSource 只有 dsmaster,也就是说,我们完成了针对主库的强制路由。
小结
继续承接上一课时的内容,今天我们讲解 ShardingSphere 中的读写分离机制。在日常开发过程中,读写分离是应对高并发数据访问的一种有效技术手段。而在ShardingSphere中,读写分离既可以单独使用,也可以和分库组合在一起使用。ShardingSphere的另一个强大之处还在于提供了针对主库的强制路由机制,这在需要确保获取主库最新数据的场景下非常有用。
这里给你留一道思考题:如果我们想要在主从架构中只访问主库中的数据,在 ShardingSphere 中有什么方法可以做到这一点?
-– ### 精选评论 ##### **生: > 老师你好,我公司项目目前使用了shardingsphere的分表功能(只分表未分库),现在想加入主从架构,但是增加masterslave的配置后,我看shardingsphere就会生成masterslaveDataSource,sql只会走主从规则,而没有走分片规则。导致表找不到。我目前用的版本是4.00-RC2。请问有什么解决方式吗? ###### 讲师回复: > ShardingSphere是支持主从+分表的架构的,在配置时,除了指定masterslave的配置项之外,还需要指定分表的配置项,你确认一下是否添加了合适的分表配置内容 ##### **兴: > 老师,请问一个问题,ShardingSphere读写分离的场景,如果1主2从,读从库策略的轮训的,但是一个从库挂掉了,ShardingSphere会有这用失败判断吗,它判断一个从库挂了,会查询其他的从库,或者直接查询主库? ##### **龙: > 再mybatis和jpa中怎么使用强制路由 ###### 讲师回复: > 强制路由还是比较偏原生的操作,貌似ORM框架应该还不支持 ##### **龙: > 请问没有配置中心的情况,主从在不重启服务的情况下怎么动态添加从库数据源。 ###### 讲师回复: > 配置中心说白了就是一个存储配置信息的数据库,只是具备了一些变更通知等的机制。如果没有配置中心的话,我们可以把各个服务实例的信息存储在某一个地方,然后自己实现一些对这些配置数据进行管理的方法,跟服务实例是否重启没有必然联系。只是自己实现这样一套机制显然也不是很容易,也没有必要,所以一般都推荐使用一些现有的配置中心 ##### **华: > 示例代码在哪里呀 ###### 讲师回复: > https://github.com/tianyilan12/shardingsphere-demo ##### *晨: > 老师,我们postgtesql单表近1000w 直接分表可以吗 ###### 讲师回复: > 可以的 ##### *媛: > 老师请问一个mysql主从复制的问题。从库一直读取的是主库的server_id是怎么回事,用mysqld install mysqls1 –defaults-file=这个命令指定了my.ini,但是貌似不起作用,用主从分别执行set global server_id=1,set global server_id=2,都会同时改变另一个库。环境是win10,mysql版本5.5 ###### 讲师回复: > 这个光看描述不大清楚了,属于Mysql自身的主从机制,推荐多看一下官方文档了 ##### logan: > 只读主库中的数据,在架构属于上应该叫强制读主。没用过ShardingSphere,从之前学的知识推测,还是要通过HintManager来实现。 ###### 讲师回复: > 对的,你推测的没错,强制路由是通过HintManager来实现。 ##### **生: > 老师你好,项目使用spring boot并通过ymal文件配置了主从+分表,但是shardingsphere在这种情况下只会加载masterSlaveDatesource,导致路由只会走主从规则而不走分片规则。这种情况怎么处理呢? ###### 讲师回复: > ShardingSphere是支持主从+分表的架构的,在配置时,除了指定masterslave的配置项之外,还需要指定分表的配置项,你确认一下是否添加了合适的分表配置内容,如果添加了,看一下执行时的日志,看配置规则是否生效