上一节介绍了分库分表的的产生背景,以及遇到的一些问题,本节对上节遇到问题进行一个总结并给出一些解决方案。
问题列表
引入分布式事务的问题
跨节点join的问题
跨节点排序分页的问题
高并发下原子性的问题
以上是对分库分表遇到一些问题进行了汇总,下面对这些问题以及对应的解决方案一一讲解。
引入分布式事务的问题
同一应用系统-引入分布式事务的问题
如图所示,这是一个注册操作步骤,注册服务由两个原子服务组成,分别为:
- 登录标识原子服务
- 认证信息原子服务。
其中登录标识对应的登录标识表对应分片键为登录标识,因为为了快速登录,所以不会以用户ID作为分片键。认证信息原子服务对应的分片键为用户ID,那么这种流程,就会引发分布式事务问题,那么针对此类场景是怎么解决。
同一应用系统-分布式事务解决方案
以上为注册服务的注册流程,首先调用登录标识原子服务,如果处理成功,则调用认证信息原子服务,如果失败则返回。
假如处理成功,并调用了认证信息原子服务,此时成功,则流程注册完成,给前端返回结果。
如果操作失败,那么就需要调用冲正服务,对第一步登录标识进行删除,一般情况都会成功,如果遇到极端情况,假如数据库down机,这会推送异常消息至监控平台进行报警,从而人为介入解决。
多系统交互-分布式事务问题
上图所示,为开户服务操作步骤,涉及到三个系统。
通过分析,如果采用上篇介绍的方案同步调用,那么会使处理时间拉长,对客户体验不好,同时系统处理能力也会下降。
如果在调用环节中有一个系统出现问题,就会导致各系统中数据状态不一致,而如果采用上篇介绍的冲正方案,处理时间会更加长,从而导致超时,那么针对此类问题,一般是基于消息最终一致性来解决的。
分布式事务解决方案-基于消息的最终一致性
我们知道分布式系统中的CAP理论,分别为
- Consistency(一致性), 数据一致更新,所有数据变动都是同步的
- Availability(可用性), 好的响应性能
- Partition tolerance(分区容错性) 可靠性
任何分布式系统只可同时满足二点,没法三者兼顾。
如果满足一致性,那么就需要在可用性和分区容错性,做出选择。
如果选择可用性,也就需要避免分区容错性的发生,那么需要将所有事务相关的东西都放在一台机器上,也就说把开户设计的表全部放在一个库中,由一个系统完成。这种情况下虽然分区容错性的可以避免,但是我们系统从分布式系统退化成了单机系统,从根本上失去了扩展性。
如果选择分区容错,一旦遇到分区事件,受影响的服务需要等待数据一致,因此等待期间,无法对外提供服务。而一个系统如果对外提供不了服务,是非常重大的事故,所以是不能接受的。
那么有什么方法,能解决这些问题呢,eBay架构师源于对大规模分布式系统的实践总结,在ACM上发表了BASE理论,
BASE理论是指
- Basically Available(基本可用)
- Soft state(软状态)
- Eventually consistent(最终一致性)
BASE理论是对CAP的延伸,核心思想是即使无法做到强一致性,但应用可以采取适当的方式来达到最终一致性。
我们这里分享的基于消息中间件的处理方式,就是最终一致性的体现。
如图所示,假如这里A系统为电子账户系统,B系统为客户信息系统,A系统开立电子账户后,保存需要发送消息的数据至消息发送表中,并提交事务后发送至MQ,本次交易就完成了。
剩下的操作由MQ推送至客户信息系统或者客户合约系统,这里以客户信息系统为例,系统拿到消息后,进行数据保存,保存成功,并调用MQ的签收操作,同时拿到此消息进行客户等级升级操作,如果操作失败,那么会有定时检查任务,检查消息消费状态,并进行调起,再次进行升级操作。
因为这种方式是有一定延时,所以业务上要多做一些考量,从而不影响客户体验。
跨节点Join的问题1-解决方案
以上是一个联表查询中,以一张关联表的分片键做为查询条件的场景,那么针对此类场景解决方案,就是拆分为多步。
- 根据分片键获取到对应另一张表的分片键。
- 根据返回分片键查询另一张表的数据。
- 对多张表的结果进行聚合。
这样跨节点join的问题就解决了。
跨节点Join的问题2-解决方案
以上是一个联表查询中,是没有分片键做为查询条件的场景,那么针对此类场景解决方案,也是拆分为单表的查询语句,并进行并行调用,同时从多个节点进行获取,然后在进行聚合。
注意,如果关联的表比较多,不建议所有的表都进行并行调用,因为这样很占数据库的连接数,可以采用以下方式操作,例如:首先拆分多个表语句,然后第一条语句并行访问,获取结果,并根据结果获取到关联表的分片键,在进行数据获取,最后在进行多张表的结果聚合
跨节点排序分页的问题
test表有数据[1,2,3,4,5,6,7,8],在单库的时候,查询第2页数据并且显示2条,语句是这样的
select * from test order by id limit 2,2;
数据返回[3,4],但是数据切分以后,如果要查询,这样语句就可能就会有问题,例如:在节点1执行此语句,返回[5,7],节点2返回[6,8],然后在进行排序,返回了[5,6],所以结果就不准确了,所以应该对sql语句改写为:
select * from test order by id limit 0,4;
然后在根据各节点返回的数据,在进行排序,筛选出第2页的2条。
这种有什么问题呢,
- 每个节点返回更多的数据,增大了网络传输量
- 服务层还需要进行二次排序,增大了服务层的计算量
- 随着页码的增大,性能会急剧下降
所以开发时,应尽量避免这种需求,以其它的方式来满足,如果非要实现,可以采用业务折中的方式,如静止跳页
select * from test where id>0 limit 2;
根据id值进行排序,返回对应的条数,在内存中对各个节点返回的数据进行排序,得到需要的数据,相比以前的方案,貌似跟以前处理流程一样,
但是在查询第二页时,根据上一页的id的最大值id_max,作为第二页的最小值,会将
select * from test order by id limit 2,2;
改写成:
select * from test order by id> $id_max limit 2
这样每个节点不用返回4页数据了,只需要返回跟第一页一样页数的数据,可以看到通过对业务的折中,性能得到大大的提升。
高并发下多节点原子性问题
如上图所以,这种一个实名认证的交易场景,实名认证成功则插入到对应的客户表中,客户表分片键为用户ID,身份证是唯一索引,但是它只能保证在单个节点的唯一性,不能保证多个节点的唯一性,所以以上场景有可能会插入两条重复的数据。
为了解决保证多个节点的数据的唯一性,可以通过分布式锁来解决,redis、zookeeper都有提供分布式锁的功能
如上图,在进行认证时,首先登记身份证号至redis,如果缓存中没有此身份证则登记成功,在进行下一步操作,否则失败。