项目集成ShardingSphere遇到的问题

提示:真实项目中,如何使用shardingSphere;shardingSphere的面试题,真实分库分表的细节问题、真实开发中分库分表的问题及解决方案、分库分表后事务失效怎么解决,分库分表后@Transactional失效、,mq多节点下无法保证消息的有序性、分片后的分页、shardingSphere的分页、洗数据时洗不动怎么办。


前言

背景是负责的项目中有个表,主键Id已经新增到3亿+,真实数据也已经1亿+。在某些特殊场景下,一次insert需要耗时14秒。因此要优化这个表,也就是分库分表。
我这里多说一句:

如果你的表数据量占大约20GB时,加字段/洗数据,应该是在2-3分钟内结束;如果你的表数据量占大约20GB时,加字段/洗数据,应该是在30-40分钟内结束;如果表数据量占20GB以上的空间,就不推荐再加上字段,或者洗数据了。就建议加新的关联表去实现业务吧。前一阵,有一个大约1亿+的表加字段,加了7,8个小时,都没加上新字段。最后是拿的一个没用的字段rename了一下,才算是解决,不然那期需求相当于没做。


一、问题及解决

从网上搜文章的时候,感觉很简单,引入个依赖,加几个配置,完事了, 几乎不用你做啥。但真正往自己项目中实现的时候,一个很坑的点就是版本问题。用哪个版本能跟你项目的版本适配。哪个版本可以支持批量操作。

1、版本适配

我项目中引入的是公司内部自己封装的一套版本,是基于spring-boot-starter1.1.10的,最开始使用的sharding5.x,然后发现不支持加解密组件,因此最终使用的sharding3.x。

解决方案:

将shardingSphere和springbootstart兼容。修改版本后解决:spring-boot-starter1.1.10的时候,使用sharding-jdbc-core3.1.0可以

2、单个insert好使,批量insert报错

改完以后,发现批量新增报错,单个新增好使。(shardingjdbc4.x不支持批量,5.x以后支持),也得注意一下。

解决方案

最终使用的是shardingSphere3.x

3、部分复杂的sql会报错

其他不涉及到分片的表(我给a表加的分片,但是b表的查询报错了),部分复杂的sql会报错,报resultmap接收的一个类型不对。不接入shardingSphere后,则不报错。

解决方案:

需要加入多数据源解决

4、事务,分库后@Transactional失效了

因为接入了多数据源,所以又产生了事务问题。因为事务的commit、rollback都是针对于connection来的,你现在俩链接了,至少@Transaction是无效的

解决方案

针对事务,市面上主流的有好几种解决方案:

  • 分布式事务seata
  • 手动提交、回滚事务
  • 写一个方法, 判断俩事务是否全部成功、失败来决定是提交还是回滚。

因为seata,在公司内部的项目中未使用过,为了生产数据的安全性,没敢使用seata,毕竟生产环境数据丢了,那直接灾难了。所以写了个方法,判断这俩事务,如果同事成功,则commit,一个失败,则rollback。由于不方便贴代码,因此就写一个思路:不涉及分片表的,用一个datasource1,根据这个datasource去new DataSourceTransactionManager(datasource1),单独的分片表,用datasource2去new DataSourceTransactionManager(datasource2),然后@Transactional,修改为自定义的@ShardingTransactional, 然后aop去处理,如果俩sql都成功了,就提交事务,俩sql有一个失败,则同时回滚。

5、启动报错(内网、外网驱动对sharding的适配)

5、使用shardingsphere的时候,启动报错,是因为大写的“Mysql”和小写的“mysql”不匹配,导致直接报错了。

解决方案

解决方案是重写的io包,其实有一种更好的方案,是直接往这个枚举里面加一个(https://gitee.com/xchao/j-im/blob/master/jim-core/src/main/java/org/jim/core/utils/DynamicEnumUtil.java)
毕竟还有外网、内网,也需要考虑到

jdbc:mysql://localhost:3306/mytest

jdbc:nsd://localhost:3306/mytest

在这里插入图片描述

6、数据准确性

由于是生产数据,还是要重视一下的。因此我们就打算,主表(未分片的表)、分片表,并行运行。并且每次查询,同时查询主表、分片表,并对比除时间以外的,其他关键字段的值(其实就是封装一个util,然后忽略几个字段,然后对比。其实可以优化为比较几个关键字段,毕竟要一样只会一模一样,要不一样,几个关键字段已经不一样了)。然后也是aop实现,只要查到了主表的select语句,就aop同步执行一下分表后的语句,然后拿到结果集,去对比。一致的直接通过,不一致的记录到一个表里,去对比。

6.1、校验数据一致性时,遇到的问题及解决

上面的6的方案,去比较数据一致性的时候,有个事务的问题:像insert、update、delete操作,涉及到事务,只能是移动到代码的最后一行发送。不然本地报错回滚了,mq已经发送了,分片表那边消费完新增/更新/删除完了,就数据不一致了。那最后比的时候,肯定对不上。

解决方案

解决方案:将insert、update、delete操作,移动到代码的最后一行发送mq

6.2、mq多节点下无法保证消息的有序性

多节点下无法保证消息的有序性。由于我们是mq来实现的数据对比,但是生产环境有8个service节点。那就会有一个问题:像复杂流程的。比如a、b、c、十来步操作的,比如我第2步先delete然后在insert。到第8步了,符合某个场景,又删了一遍。(正常业务如果是编辑后提交的话,就是先删除,在提交日历。但是特殊场景,点对点驳回,就从审批中变成保存未提交了,又得删一遍日历表。就是mq的顺序有序性。我先发mq1去删除、新增。又发了mq2,去删除。多个节点下,我可能mq的2都删了,mq1的新增才执行完。导致脏数据了。)

解决方案

解决方案:1、从业务上避免,比如说我先进行校验,如果不满足,不往下走了,自然不会先发送、后撤回了。2、rabbitmq有一个配置,加上那个配置后(就一行配置,我忘了是啥了),即使你生产环境有8个节点,8个相同队列的mqlistener,那么也只会有一个人消费。3、将mq分组,group完了以后,能达到类似于单节点的效果。4、将8个service的节点改为1个service节点(哈哈,是不是过于简单粗暴了)

7、分页

分页这玩意,是真没法搞。尤其是自己写分片的那种。(比如,每页20条,让查第10页数据。听起来还好?再关联查询,再分片查询。别的不说,单单分片查询,让你查第二页,可不是每个分片查第10-20条数据,而是把每个分片表的前20条拿出来,然后在排序,再拿到排序后的第10-20条数据。这第2页还好,那第200页呢,把每个分片表的前100页通通拿出来,在分页?那2000页呢!)集成的shardingSphere,也不好搞。单表查询还好,一旦关联查询,并且还涉及到分片表。只能改业务了或者改sql了(改业务就是改app、改pc端的页面,去掉某个查询条件或者字段;改sql就是从关联查询,改为不关联查询,然后子查询关联表,取出来结果)。

解决方案

解决方案:修改业务,让相关的业务点,不在那么强关联。

8、分片后union太多

  • 3.x 是单表查,(select *from my_test_2018;select *from my_test_2019;)
  • 5.x 是union all 全部的分片表(select *from my_test_2018 union all select *from my_test_2019;)

如果查询条件恰好不涉及到分片查询时,那现象可就是:select *from my_test_2018 union all select *from my_test_2019 union … my_test_2020 union …my_test_2024。从2018到2024,小20张表呢,这么搞,那效率真不一定能快。

解决方案

通过业务分割,强制带上分片表的查询条件。比如说默认带上createTime=2024,或者根据当前业务/查询其他关联表登方式,能确定到涉及分片方案的字段,来保证只查某个表或某些表,避免全量查询全部的分片表。

9、洗数据

洗数据这个,有俩方案推荐:

  1. 洗数据的时候,最好直接给一个id自增初始值。(ALTER TABLE table_name AUTO_INCREMENT = 100;
    这个代码是说把主键自增的初始值设置为100)。因为你一旦上线,业务就一直在跑,就是程序再跑,业务数据在自增,如果从0开始的话,那程序自己自增的id分别为:
    1,2,3。。。 然后洗数据的时候,id也会1,2,3, 然后就会主键重复,从而插入失败。insert ino
    tablename_2024 select * from tablename where createTime = ‘2024’;
  2. 洗数据的时候不带id,让自增,不太推荐。没发根据id区分了。(方案1可以明确,比如说id在20w呢的,就是旧数据,20w以后的,为新数据)
create table my_test_2024 like my_test;
create table my_test_2025 like my_test;
...

# 发版前,获取截止到目前为止,最大的id,需要记录该id!!!
select id from my_test order by id desc limit 1;

# 洗数据,可以发版前一天洗
insert into my_test_2018
select * from my_test where  createTime <  '2019-01-01' and createTime >= '2018-01-01';


# 发版后处理发版期间的数据
insert into my_test_2018
select * from my_test where  createTime <  '2019-01-01' and createTime >= '2018-01-01' and  id > createTime ;

二、延伸

总之,还是需要真正的开发一通,经得住线上系统的压力,才能够暴露出来足够多的问题,才能真正的给后续开发积累宝贵经验。比如说:

  1. shardingsphere的时候,比如说强隔离。是jdbc:nds,你直接变成了jdbc:mysql 。你的查询是好使了,,没问题,那行。那么shardingsphere自己有没有什么定时,去扫描某个包,然后去做了些别的操作,别的操作会不会报错,从而影响数据的准确性。
  2. 比如你的分片,有分片条件的,就是对应的24表、23表,要是分片条件不涉及的,能不能让他尽量涉及到。业务上能不能给个默认值或者默认区间,或者从技术手段上能不能给过滤掉一部分一定不包含数据的分片表
  3. 双写、双读的开关要不要nacos动态配置一下。双写就是主表也写一份,分片表也写一份;双读就是主表也读、分片表也读。单写主表单读主表单写分片表单读分片表要不要都安排上。

总结

分表的方式很多,我甚至还遇到过,在controller层次分表的操作。之前跟远光调接口的时候,他那边,必须是我们传参2022,2023才能查不同的表。当时我们的业务流程是,远光发送数据给A系统,A系统发送数据给B系统。然后A系统和远光交互的时候,需要公司id关联。我们这边的id和远光的不一致。需要匹配。我们是建立了一个中间表。然后参数还需要我们自己传2022、2023。如果传2019就报错了,因为远光没有2019的表。但是用户上传的发票可能ocr识别出来的日期确实是2019。所以很奇怪,居然还有在controller层传参分片的这种分表。大部分的分表,都是shardingjdbc加配置,框架就帮我们分表了。即使是自己分表。那也是service层自己做不同的dao。像这种直接提出去到controller层的,确实是奇怪。只能说,经历的还是少,还是需要不断的拓宽我们的视野。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值