1. 水平划分
1.1 数据库切分方法和优缺点
- 根据日期或ID分段切分
- 优点:数据量相对可控,能够很好地扩容,增加节点,可直接增加
- 缺点:热点数据集中,无法分散至不同分片
- 根据hash函数求余进行切分
- 优点:数据分布均匀
- 缺点:扩容麻烦,需要rehash,需要对数据考虑平滑迁移
1.2 数据库分库分表跨表查询,分页查询,聚合函数,排序等问题
- 一般解决方式:当排序字段非分片字段时,需要在不同的分片节点数据进行排序返回,再将不同分片返回的结果集进行汇总和再次排序,返回用户。
- 一般解决方式问题:当页数很大时,会查询每个分片的大量数据进行合并,耗费CPU和内存资源,性能会很差。
- 特殊解决方式:1.尽量不使用非分片字段进行排序,或者不用聚合函数等;2.对于存在常用的两种字段排序查询的情况,备份两份数据,使用不同的字段进行分片,但这样两份数据的一致性需要维护
2.垂直划分
2.1 理论基础
- ACID:
- A(Atomicity):原子性
- C(Consistency):一致性
- I(Isolation):隔离性
- D(Durability):持久性
- CAP:只能选择其中两个,一致性和可用性是矛盾的,在服务调用另一服务,迟迟无法获取返回值时,要么立即返回失败,保证可用性,牺牲一致性,要么进行等待或者补偿操作,牺牲可用性,保证一致性
- C(Consistency):一致性,不同节点同一时刻读取的值相同
- A(Avaliablity):可用性,系统具有好的响应性能
- P(Partition tolearance):分区容忍性,尽管有部分数据丢失,但是系统仍能继续工作
- BASE:
- BA(Basically Avalilable):基本可用
- S(Soft State):软状态,该状态可以数据不一致
- E(Eventually Consistent):最终一致,在最后通过补偿的方式保证数据一致
- 酸碱平衡理论:如果能够通过数据库的扩展或者开源工具Mycat等保证数据一致,则保持,若无法保持绝对的一致性,则可通过记录软状态的方式,保证最终一致性
2.2 一致性协议
-
2PC
- 实现方式:分为准备阶段和提交阶段。有一个协调者会对各个参与者发起指令。
- 在准备阶段写入redo、undo日志、锁定资源不提交;
- 在提交阶段,若有一个准备失败,则执行undo日志并释放资源,若每个参与者都准备成功,则提交并释放资源
- 优点:实现强一致性
- 缺点:性能差;参与者未返回会阻塞;协调者单点故障
-
TCC
- 实现方式:分为 try confirm cancel 三个阶段。
- 首先进行try,各节点查看资源是否存在并锁定,若该阶段成功进入 confirm,失败进入 cancel 阶段;
- confirm 阶段,各节点进行事务提交,若该阶段各节点都成功,则完成事务释放资源,有节点失败会进行重试,若一直失败,则进如 cancel 阶段;
- cancel 阶段会执行 cancel 操作,若执行失败会进行重试,执行完成释放资源。
- 优点:实现强一致性,效率相对于2PC更高,出现问题的概率降低
- 缺点:在极端情况还是会出现数据不一致的可能,如取消时,只取消了部分
注: 分布式协议能够保证强一致性,但实现较为复杂,性能一般,故而尽量将需要分布式事务的多个服务聚合成单机服务,使用数据库本地事务,如果一定需要进行拆分的情况,大部分也可以通过最终一致性的方式保证,可看以下方案。
2.3 保证最终一致性的模式
- 查询-补偿模式:查询其他模块的操作状态,再根据操作状态进行补偿操作,例如重试、取消或通知运营人员进行修复。
- 异步确认模式:通过发送消息的方式,其他节点的事务保证可通过消息服务保证事件不丢失的方式保证事务正常执行。
- 定时校对模式:第三方核对系统对唯一的调用链ID进行校对,校对后再通过补偿方式进行完善
2.4 常见不一致问题
- 调用超时——查询-补偿模式解决
- 异步调用超时——查询-补偿模式解决
- 掉单(一个系统存在请求,另一个系统没有请求)——定时校对-补偿模式解决
- 系统间状态不一致——定时校对-补偿模式解决
2.5 mq消息接受失败
- 消息发送前持久化,标记待发送,发送成功后,改为成功,若失败,则使用定时校对模式进行发送(保证mq消息接受的幂等)
- 消息生产者发送消息
- MQ收到消息,将消息进行持久化,在存储中新增一条记录
- 返回ACK给生产者
- MQ push 消息给对应的消费者,然后等待消费者返回ACK
- 如果消息消费者在指定时间内成功返回ack,那么MQ认为消息消费成功,在存储中删除消息,即执行第6步;如果MQ在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新push消息,重复执行4、5、6步骤
- MQ删除消息
分布式全局唯一ID生成
- UUID,算法生成随机无序的ID
- 数据库自增生成,多实例,可设置不同起始值和步长
- redis通过incr自增生成,多实例,可设置不同起始值和步长
- 雪花算法(SnowFlake ),通过时间戳+机器信息+序列号方式生成
分布式锁
- mysql
- 实现:通过 select * from order_table where id = ‘xxx’ for update 进行加行锁,如果没有查询到数据则写入数据获取锁,如果查询到则等待;释放锁则是删除数据
- 锁超时:通过定时任务对锁获取时间判断,进行删除,又或者别的模块获取锁时判断是否超时
- 优点:理解简单
- 缺点:性能较低,实现繁琐,需要考虑锁超时和加事务
- zookeeper
- 实现:分布式锁的加锁操作,同时写入一个zookeeper的节点,写入成功,则获取锁成功;释放锁操作,删除zookeeper的节点,删除完成,则释放锁,其他服务监听到删除时,重新竞争锁。(若想实现公平锁,则可通过在节点下创建有序临时节点,谁的顺序靠前则获取到锁)(参看zookeeper总结)
- 锁超时:无需考虑,服务宕机后,临时节点自动删除,自动释放锁
- 优点:无需考虑锁超时,且可以实现公平锁
- 缺点:性能较低,学习成本大
- redis
- 实现:通过setNx实现加锁的操作,若设置成功,则获取锁,若已被设置,则等待;释放锁操作则是删除redis的数据(参看redis 复习一);也可以通过Redission封装的API调用使用分布式锁
- 锁超时:通过expire命令进行锁超时时间的设置
- 优点:性能较好,实现简单
- 缺点:需要加入锁超时