上篇文章中,在两个 windows 系统的电脑上安装了最新版 8.0.21 MySQL 数据库,并且配置了主从。MySQL如何配置读写分离?
主从复制的原理思想也很简单,就是从库不断地同步主库的改动,保持与主库数据一致;应用仅在从库中读数据。
在项目中,使用读写分离本质上就是,增加数据库服务器资源 + 网络带宽,来分摊对数据库的读写请求,从而提高了性能和可用性。主从复制实现读写分离最大的缺点就是从库同步到主库的数据存在延迟,网络不好的时候,延迟比较严重。
解决了数据库怎么配的问题,继续探索一下,项目中如何实现读写分离。
在我们平时开发中,一般不会自己去控制 select 请求从从库拿 Connection,insert、delete、update 请求从主库拿 Connection。当然也有这么干,就是把读写请求按规则命名方法,然后根据方法名通过反射统一处理请求不同的库。
大部分企业在项目中是使用中间件去实现读写分离的,如 mycat、atlas、dbproxy、cetus、Sharding-JDBC......,每种中间件各有优缺点。
Sharding-JDBC 是 apache 旗下的 ShardingSphere 中的一款产品,轻量,引入 jar 即可完成读写分离的需求,可以理解为增强版的 JDBC,现在被使用的较多。
既然数据库主从环境准备好了,那就搭建项目玩一把。
maven 依赖的库
<dependency> <groupId>org.apache.shardingspheregroupId> <artifactId>sharding-jdbc-coreartifactId> <version>4.1.1version>dependency><dependency> <groupId>com.zaxxergroupId> <artifactId>HikariCPartifactId> <version>3.4.5version>dependency><dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <version>8.0.21version>dependency>
参照官网文档配置与修改读写分离的 demo 程序
获取数据源的工具类
package constxiong;import com.zaxxer.hikari.HikariDataSource;/** * 获取 DataSource 工具类,使用了 Hikari 数据库连接池 */import javax.sql.DataSource;public final class DataSourceUtil { private static final int PORT = 3306; /** * 通过 Hikari 数据库连接池创建 DataSource * @param ip * @param username * @param password * @param dataSourceName * @return */ public static DataSource createDataSource(String ip, String username, String password, String dataSourceName) { HikariDataSource result = new HikariDataSource(); result.setDriverClassName(com.mysql.jdbc.Driver.class.getName()); result.setJdbcUrl(String.format("jdbc:mysql://%s:%s/%s?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8", ip, PORT, dataSourceName)); result.setUsername(username); result.setPassword(password); return result; }}
测试 Sharding-JDBC 读写分离
主库:172.31.32.184
从库:172.31.32.234
观察通过 Sharding-JDBC 获取的 DataSource 是否会自动写入到主库,从库是否主动同步,从库同步数据的延迟时间
测试代码
package constxiong;import org.apache.shardingsphere.api.config.masterslave.MasterSlaveRuleConfiguration;import org.apache.shardingsphere.shardingjdbc.api.MasterSlaveDataSourceFactory;import javax.sql.DataSource;import java.sql.Connection;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;import java.time.LocalTime;import java.util.*;/** * 测试 ShardingSphere 读写分离 * 主库:172.31.32.184 * 从库:172.31.32.234 * * 观察通过 ShardingSphere 获取的 DataSource 是否会自动写入到主库,从库是否主动同步,从库同步数据的延迟时间 */public class Test { //主库 DataSource private static DataSource dsSlave = DataSourceUtil.createDataSource("172.31.32.234", "root", "constxiong@123", "constxiong"); //从库 DataSource private static DataSource dsMaster = DataSourceUtil.createDataSource("172.31.32.184", "root", "constxiong@123", "constxiong"); public static void main(String[] args) throws SQLException { //启动线程打印主库与从库当前 cuser 数据量与时间,观察从库同步数据延迟 printMasterAndSlaveData(); //从 ShardingSphere 获取 DataSource,出入数据,观察插入数据的库是否为主库 DataSource ds = getMasterSlaveDataSource(); Connection conn = ds.getConnection(); Statement stt = conn.createStatement(); stt.execute("insert into cuser values(2, 'fj')"); } /** * 启动线程打印,两个主从库 cuser 表的信息、数据量、当前时间 * @throws SQLException */ private static void printMasterAndSlaveData() throws SQLException { Connection masterConn = dsMaster.getConnection(); Connection slaveConn = dsSlave.getConnection(); new Thread(() -> { while (true) { try { System.out.println("------master------" + LocalTime.now()); print(masterConn); System.out.println("------slave------" + LocalTime.now()); print(slaveConn); } catch (SQLException e) { } } }).start(); } private static void print(Connection conn) throws SQLException { Statement statement = conn.createStatement(); statement.execute("select * from cuser"); ResultSet rs = statement.getResultSet(); int count = 0; while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); System.out.println(id + "-" + name); count++; } System.out.println("total: " + count); } /** * 设置 ShardingSphere 的主从库 * @return * @throws SQLException */ private static DataSource getMasterSlaveDataSource() throws SQLException { MasterSlaveRuleConfiguration masterSlaveRuleConfig = new MasterSlaveRuleConfiguration("ds_master_slave", "ds_master", Arrays.asList("ds_slave")); return MasterSlaveDataSourceFactory.createDataSource(createDataSourceMap(), masterSlaveRuleConfig, new Properties()); } /** * 用 主从库的 DataSource 构造 map * @return */ private static Map<String, DataSource> createDataSourceMap() { Map<String, DataSource> result = new HashMap<>(); result.put("ds_master", dsMaster); result.put("ds_slave", dsSlave); return result; }}
分析延迟信息
数据默认配置的情况,在内网从库同步的时间延迟,在 200ms 左右,当然这个统计是不精确的,只是看个大概情况。
为了更能看出 Sharding-JDBC 原汁原味的作用,这里并未使用 Spring。结合使用 Spring、Spring Boot 使用 Sharding-JDBC 的配置与代码示例,官网中也已给出,可参照文档实现。
参考文档:
https://shardingsphere.apache.org/document/legacy/4.x/document/en/manual/sharding-jdbc/configuration/config-java/#read-write-split
https://shardingsphere.apache.org/document/legacy/4.x/document/en/manual/sharding-jdbc/usage/read-write-splitting/
代码上传至:
https://github.com/ConstXiong/toy/tree/master/demo/shardingsphere-read-write-split
面试题均收录于小程序