分布式
# 分布式
# CAP理论
CAP理论又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。
一致性(Consistency):指的是分布式系统中所有节点在同一时刻对某个数据的访问和修改的结果是一致的。
可用性(Availability):指的是分布式系统中任意节点的故障都不会导致整个系统停止对外服务。
分区容错性(Partition tolerance):指的是分布式系统中,当出现网络分区时,仍然可以对外提供服务。
Redis的主从架构是AP,即可用性和分区容错性。不能保证强一致性。
ZooKeeper的主从架构是CP,即一致性性和分区容错性。不能保证可用性。
# BASE理论
BA:Basically Available(基本可用) S:Soft state(软状态) E:Eventually consistent(最终一致性)
# 分布式锁
# 数据库
# 实现
- 创建一张锁表,数据库对字段作唯一性约束
- 加锁的时候,在表锁增加一条记录即可
- 释放锁的时候删除这条记录即可
# 优缺点
# 缺点
- 比其他的方式更消耗资源,性能低下
- 设计复杂,容易出错
- 数据库宕机会导致锁失效
# ZooKeeper
# 实现
- 某个资源为目录
- 在这个目录下面的节点就是我们需要获取锁的客户端,每个服务在目录下创建节点,如果的它的节点,序号在目录下最小,那么就获取锁,否则等待
- 释放锁就是删除服务创建的节点
# 优缺点
# 缺点
- 死锁
- 羊群效应
- 性能没有redis高
# Redis
# 实现
- set key value ex 30 nx
- Redisson
# 优缺点
# 优点
- 性能高,并发量大
# 高并发优化
把独占锁优化成分段锁
提前缓存库存
1000个库存拆成10个每个100个库存
- product_101_stock=1000
- product_101_stock_1=100
- ...
怎么减库存
- 轮询
- 随机
- 权重
# 分布式事务
# 基于XA协议,需要数据库支持
# 2PC两阶段提交(prepare,commit)
- 组成
- 事务协调者(TC)
- 事务参与者(TM)
- 阶段
- 准备阶段(prepare):协调者先问各位参与者是否可以提交,等待回应
- 提交阶段(commit):如果参与者都同意,协调者发送提交请求给参与者,参与者执行事务提交;如果有一个不同意或者超时未回应,则发送中止请求,参与者执行事务回滚
- 优缺点
- 优点
- 缺点
- 单点问题
- 事务管理器(协调者)在整个流程扮演重要角色,如果宕机,比如第一节点已完成,在第二节点,正准备提交的时候事务管理器宕机,资源管理器就会一致阻塞,导致数据库无法使用
- 同步阻塞
- 在准备就绪后,资源管理器中的资源就一直处于阻塞,直到提交完成,释放资源。
- 数据不一致
- 虽然是强一致的设计,但仍然存在数据不一致的可能,比如第二阶段中,假设协调者发出了事务commit通知,但是网络问题仅一部分参与者收到了并执行了commit,其余参与者没有收到通知一直处于阻塞状态,这就产生了数据不一致。
- 单点问题
# 3PC三阶段提交(canCommit,preCommit,doCommit)
- 组成
- 事务协调者(TC)
- 事务参与者(TM)
- 阶段
- 准备阶段(canCommit):协调者向参与者发送commit请求,参与者如果可以提交就返回yes,否则返回no,让所有参与者做出是否可以提交的决策
- 预提交阶段(preCommit):所有参与者都返回yes,则向所有参与者发送preCommit请求,进入如preCommit阶段,参与者收到会执行提交事务,并将undo和redo信息记录到日志,如果参与者执行了提交事务,就返回ack,同时等待最终指令。为了确保最后提交阶段之前,各个节点的状态是一致的
- 提交阶段(doCommit):协调者根据提交阶段的返回,如果全部都执行了提交事务,则向所有参与者发送doCommit请求,并在事务提交之后释放所有事务资源,参与者收到doCommit请求之后,向协调者发送ack,这个阶段的目的时最终确认并提交事务。
- 优缺点
- 优点
- 缺点
- 无法保证数据100%强一致
# 基于TCC,刚性事务(ACID),柔性事务(BASE),需要手动编写逻辑
- 阶段
- try:主要对业务系统进行检测及资源预留,业务系统执行其分支操作,但不真正提交,如果执行成功,则进入下一阶段,如果执行失败,则直接进行cancel操作
- commit:如果try成功,则进入commit阶段,执行真正的业务逻辑提交,并释放之前预留的资源
- cancel:如果try失败,则进入cancel阶段,执行预留资源的释放
# 本地消息表+MQ,最大努力通知
# seata
支持XA,两阶段提交 支持AT,最终一致性的两阶段提交 支持TCC,最终一致性 SAGA,长事务模式
# 幂等性
# 举例
- 用户填写表单,保存按钮不小心快速点了两次,表中竟然产生了两条重复的记录,只是id不一样
- 开发人员在项目中为了解决接口超时问题,通常引入重试机制,第一次请求接口超时了,请求方没能及时获取返回结果,于是会对该请求重试几次,这样也会产生重复数据
- MQ消费者在读消息时候,有时候会读取到重复消息,也会产生重复数据
# 解决方案
# 1.insert前先select
在保存数据的接口中,在insert前先根据requestId等字段先select一下数据,如果该数据已存在,直接返回,如果不存在,才执行insert
# 2.唯一索引
加唯一索引是个非常简单但有效的办法,如果重复插入数据,会抛出异常,为了保证幂等性,一般要捕获这个异常,Java-DuplicateKeyException。Spring-MySQLIntegerityConstraintViolationException
# 3.加悲观锁
更新逻辑,比如更新用户余额,可以加悲观锁,把对应用户的那一行数据锁住。同一时刻只允许一个请求获得锁,其他请求则等待
select * from user id = 123 for update;
缺点:获取不到锁的请求一般只能报失败,比较难保证接口返回相同值
# 4.加乐观锁
更新逻辑,可以用乐观锁,性能更好。可以在表中增加一个timestamp或者version字段
在更新前,先查询数据,将version作为更新的条件,同时也更新version
update user set amount = amount + 100,version = version + 1 where id = 123 and version = 1
# 5.建防重表
有时候表中并非所有场景都不允许产生重复数据,只有某些特定场景才不允许。这时候可以使用防重表的方式
例如消息消费中,创建防重表,存储消息的唯一id,消费时候先去查询是否已经消费,已经消费直接返回成功。
# 6.状态机
有些业务表是有状态的,比如订单表中有:1-下单,2-已支付,3-完成,4-撤销。可以通过限制状态的流动来完成幂等。
# 7.分布式锁
直接在数据库上加锁的做法性能不友好,可以使用分布式锁的方式,目前最流行的分布式锁是通过Redis,具体一般是用Redisson
# 8.token机制
请求接口之前需要先获取一个唯一的token,再带着这个token去完成业务操作,服务端根据这个token是否存在,来判断是否重复请求
# 限流
# 计数器
简单粗暴,比如要限制1s内能通过的请求数。实现思路就是第一个请求进来开始计数,在接下来1s内,每个请求进来请求数+1,超过最大请求数的请求会被拒绝,等到1结束后计数清零,重新开始计数
缺点
比如前10ms已经通过了最大的请求书,那么后面990ms的请求只能拒绝,这种现象叫做“突刺现象”