请求幂等性总结:
1.是否需要幂等。比如查询,insert含唯一索引的表,update set数据,delete by id 等简单场景是天然幂等。不需要额外做幂等操作。无法这样的比如要做数据的累加,就要做幂等。
2.如何判断重复。
业务唯一键,查记录表或流水表,根据记录的有无和状态来判断。
3.实现。
- 简单的话接口直接实现, 但通常幂等逻辑是有通用性的
- 如果服务多接口使用写幂等工具类
- 如果多服务使用一套幂等逻辑,写sdk
- 最复杂的多服务间幂等,还无法都获取到记录结果,就要在sdk统一读写幂等表,读写相同的幂等记录做幂等
4.并发处理
- 单机情况下,使用java锁synchronized
- 使用数据库锁,悲观锁:select for update,乐观锁:update set XXX and version = 2 where version = 1
- 使用分布式锁:
redis锁:1.过期时间导致并发问题,2.主从切换时锁丢失
zookeeper锁:写能力不如redis
原文:
维度1 : 是否需要幂等处理?
查询场景
select 字段 from 表 where 条件 ,这种是不会对数据产生任何变化,所以查询场景具备天然幂等性;注意这里的幂等是对系统资源的改变,而不是返回数据的结果,即使返回结果不相同但是该操作本身没有副作用,所以幂等
新增场景
insert into 表 (order_id,字段) value(1,字段) :
1)如果order_id为唯一键,重复操作上面的业务,只会插入一条数据,具备天然幂等性。
2)如order_id不是唯一键,那上面业务多次操作,数据都会新增多条,不具备幂等性。
修改场景
1)直接赋值场景:update 表 set price = 20 where id=1 ;分析: 这种场景不管执行多少次,price都一样,具备天然幂等性;
2)计算赋值场景:update 表 set price = price + 20 where id=1,每次操作price数据都不一样,不具备幂等性;
删除场景
1)delete from 表 where id=1 ,多次操作,结果一样,具备天然幂等性
2)delete from 表 where price > 20,多次操作,结果一样,具备天然幂等性
总结:单纯的查询接口,删除接口,没有计算的更新接口,每次执行结果是天然幂等的,其他情况则可能需要做幂等
维度2 : 是否需要并发控制?
场景1: 唯一键数据的重复插入
此场景不需要并发控制,天然并发安全,但需要业务方处理DuplicateKeyException异常
场景2:转账,若 a >= 100, a -100, b+100
此场景为了保证数据一致性,事务原子性,需要顺序执行
总结:除了极其简单的场景外,大部分幂等场景都需要并发控制,需要强锁还是弱锁
维度3 : 如何判断是重复请求?
场景1: 无业务主键的新增
此场景下需要服务端颁发token,通过token判断是否重复,做防重复提交
场景2: 有业务唯一键的新增
此场景下可以使用业务唯一键判断是否重复
场景3: 有业务唯一键的更新
此场景下可以使用业务数据对应的状态判断是否重复
维度4 : 重复请求处理方式?
场景1:消息重复消费
此场景下,不需要给上游一个返回,重复执行只需要丢弃
场景2:前端重复提交
此场景下,需要给用户一个提示,那么重复请求可以直接抛出一个DuplicateReqeustException异常,由上层捕获,前端展示“请不要重复请求”
场景3: 底层的转账接口
此场景下,同一笔转账,重复调用第一次若成功了,第二次应该返回相同“成功”,若第一次失败了,第二次返回业务执行结果
结论:根据不同场景,应该有不同的结束方式,应该由业务方自己决定是丢弃,抛异常,直接返回,还是执行业务逻辑
维度5 : 重复请求返回是否相同?
场景1:第一次执行成功,第二次返回应该与第一次相同
场景2:第一次执行因为代码原因导致的异常而失败,第二次返回应该与第一次相同
场景3:第一次执行因为网络原因失败,第二次应该执行业务逻辑,返回结果可能不一致
总结:根据第一次执行的成功和失败,判断第二次应该执行业务逻辑还是直接返回相同结果
维度6 : 重复请求调用下游如何处理?
场景1: 业务逻辑中,有调用下游rpc接口,第一次执行失败,第二次重复执行业务逻辑再次调用下游接口
总结:此场景本质上是一个分布式一致性问题,需要业务方自己解决,或者保证下游接口也是幂等的
识别相同请求
- token机制:每次提交上来的请求,都带上一个token标识,同一个token只处理一次,例如:新增场景的防止重复提交
- 业务唯一标识:使用id或者unique key,例如:支付后,支付凭证落库
- 业务唯一标识 + 状态:支付后,通过支付凭证号和当前订单状态,更新订单状态
- 在业务侧建立同库幂等数据表,记录请求唯一键和执行状态,执行成功后更新状态为成功
并发处理方式
单机情况下,使用java锁synchronized
使用数据库锁,悲观锁:select for update,乐观锁:update set XXX and version = 2 where version = 1
使用分布式锁:
redis锁:1.过期时间导致并发问题,2.主从切换时锁丢失
zookeeper锁:写能力不如redis
一致性保证
- 业务方需保证原子性,本地更新都在一个事务里
- 若无法保证分布式事务,下游接口需是幂等的,且本地事务必须重试达到最终一致
SDK设计#
- 并发控制
- 提供注解,按业务自动划分,提供指定幂等唯一键
- 提供并发控制,分布式锁服务(redis或zk),指定分布式锁时间
- 提供并发时幂等处理方式,丢弃/抛异常/等待锁
- 提供降级选择,不强依赖于锁服务
- 提供token注解,防止重复提交
- 判断重复请求
处理方式 优点 缺点
方案一 业务方自己判断 业务方需要自己考虑判重
方案二 sdk提供判重接口,由业务方实现 可以实现重复请求的通用处理
方案三 幂等表:在业务侧建立同库幂等数据表,记录请求唯一键和执行状态,执行成功后更新状态为成功 业务方无需再关心判重问题 数据量大,开发量大,复杂度较高