暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

解决ShardingJDBC读写分离后查询延时问题

码酱 2021-02-28
1955
接上篇shardingjdbc实现分库分表shardingjdbc实现读写分离

一般熟知 Mysql 数据库的朋友知道,当表的数据量达到千万级时,SQL 查询会逐渐变的缓慢起来,往往会成为一个系统的瓶颈所在。为了提升程序的性能,除了在表字段建立索引(如主键索引、唯一索引、普通索引等)、优化程序代码以及 SQL 语句等常规手段外,利用数据库主从读写分离(Master/Slave)架构,是一个不错的选择。但是在这种分离架构中普遍存在一个共性问题:数据读写一致性问题。


数据读写一致性问题
主从库同步原理
主库 master (负责“写”),主库将变更写binlog日志,然后从库slave(负责“读”)连接到主库之后,从库有一个IO线程,将主库的binlog日志拷贝到自己本地,写入一个中继日志中。接着从库中有一个SQL线程会从中继日志读取binlog,然后执行binlog日志中的内容,也就是在自己本地再次执行一遍SQL,这样就可以保证自己跟主库的数据是一样的。这样每当业务系统发送 select 语句时,会直接路由到从库去查询数据,而不是主库。

但是这种同步逻辑有一个比较严重的缺陷:数据延时问题

我们可以想象一下这样的场景:

当一段程序在更新完数据后,需要立即查询更新后的数据,那么真的能查询到更新后的数据吗?

答案是:不一定!

这是因为主从数据同步时是异步操作,主从同步期间会存在数据延时问题,平常主库写数据量比较少的情况下,偶尔会遇到查询不到数据的情况。但是随着时间的推移,当使用系统的用户增多时,会发现这种查询不到数据的情况会变的越来越糟糕。

Sharding-JDBC
想必大家并不陌生,Sharding-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。

它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC。

  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP 等。

  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,Oracle,SQLServer,PostgreSQL 以及任何遵循 SQL92 标准的数据库。

读写分离特性

  • 提供了一主多从的读写分离配置,可独立使用,也可配合分库分表使用。

  • 同个调用线程,执行多条语句,其中一旦发现有非读操作,后续所有读操作均从主库读取。

  • Spring命名空间。

  • 基于Hint的强制主库路由。


ShardingSphere-JDBC 官方提供 HintManager 分片键值管理器, 通过调用hintManager.setMasterRouteOnly() 强制路由到主库查询,这样就解决了数据延时问题,无论什么时候都能够从主库 Master 查询到最新数据,而不用走从库查询。

  HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();

1.默认查询:

    @Test
public void getGoodsById() {
QueryWrapper<Goods> query = new QueryWrapper<>();
        query.eq("gid",14L);
Goods one = goodsService.getOne(query);
System.out.println(one);
}

日志可以看出由从库slave查询:

2.强制路由到主库查询:

    @Test
public void getGoodsById() {
QueryWrapper<Goods> query = new QueryWrapper<>();
query.eq("gid",14L);
//强制路由到主库
HintManager hintManager = HintManager.getInstance();
        hintManager.setMasterRouteOnly();
Goods one = goodsService.getOne(query);
System.out.println(one);
    }

日志可以看出由主库master查询:

通过强制路由到主库查询有个风险,对于更新并实时查询业务场景比较多,如果都切到主库查询,势必会对主库服务器性能造成影响,可能还会影响到主从数据同步,所以要根据实际业务场景评估采用这种方式带来的系统性能问题。
另外,如果业务层面可以做妥协的话,尽量减少这种更新并实时查询方式,一种思路是实时更新库,利用 Java Future 特性异步查询(例如更新后,睡眠1-2秒再查询),代码如下:
    @Test
public void sleepGetGoodsById() throws ExecutionException, InterruptedException {
Callable c = () -> {
QueryWrapper<Goods> query = new QueryWrapper<>();
query.eq("gid",14L);
Goods one = null;
try {
Thread.sleep(2000);
one = goodsService.getOne(query);
}catch (InterruptedException e){
e.printStackTrace();
}
Thread.sleep(2000);
return one;
        };
FutureTask<Goods> futureTask = new FutureTask<Goods>(c);
new Thread(futureTask).start();
Goods goods = futureTask.get();
System.out.println(goods);
}

喜欢就加个关注吧,


往期精选

使用并行流(ParallelStream)避坑
shardingjdbc实现分库分表
SpringCloud配置中心的使用
SpringCloud-Eureka高可用搭建
SpringCloud-Zuul服务网关
SpringCloud-Ribbon负载均衡
SpringCloud-Feign远程调用
SpringCloud-Hystrix解决雪崩
SpringCloud-Bus消息总线


文章转载自码酱,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论