幂等设计

请求幂等性总结:

1.是否需要幂等。比如查询,insert含唯一索引的表,update set数据,delete by id 等简单场景是天然幂等。不需要额外做幂等操作。无法这样的比如要做数据的累加,就要做幂等。

2.如何判断重复。

业务唯一键,查记录表或流水表,根据记录的有无和状态来判断。

3.实现。

  1. 简单的话接口直接实现, 但通常幂等逻辑是有通用性的
  2. 如果服务多接口使用写幂等工具类
  3. 如果多服务使用一套幂等逻辑,写sdk
  4. 最复杂的多服务间幂等,还无法都获取到记录结果,就要在sdk统一读写幂等表,读写相同的幂等记录做幂等

4.并发处理

  1. 单机情况下,使用java锁synchronized
  2. 使用数据库锁,悲观锁:select for update,乐观锁:update set XXX and version = 2 where version = 1
  3. 使用分布式锁:
    redis锁:1.过期时间导致并发问题,2.主从切换时锁丢失
    zookeeper锁:写能力不如redis

原文:

  1. 维度1 : 是否需要幂等处理?

    1. 查询场景

      select 字段 from 表 where 条件 ,这种是不会对数据产生任何变化,所以查询场景具备天然幂等性;注意这里的幂等是对系统资源的改变,而不是返回数据的结果,即使返回结果不相同但是该操作本身没有副作用,所以幂等

    2. 新增场景

      insert into 表 (order_id,字段) value(1,字段) :

      1)如果order_id为唯一键,重复操作上面的业务,只会插入一条数据,具备天然幂等性。

      2)如order_id不是唯一键,那上面业务多次操作,数据都会新增多条,不具备幂等性。

    3. 修改场景

      1)直接赋值场景:update 表 set price = 20 where id=1 ;分析: 这种场景不管执行多少次,price都一样,具备天然幂等性;

      2)计算赋值场景:update 表 set price = price + 20 where id=1,每次操作price数据都不一样,不具备幂等性;

    4. 删除场景

      1)delete from 表 where id=1 ,多次操作,结果一样,具备天然幂等性

      2)delete from 表 where price > 20,多次操作,结果一样,具备天然幂等性

      总结:单纯的查询接口,删除接口,没有计算的更新接口,每次执行结果是天然幂等的,其他情况则可能需要做幂等

  2. 维度2 : 是否需要并发控制?

    1. 场景1: 唯一键数据的重复插入

      此场景不需要并发控制,天然并发安全,但需要业务方处理DuplicateKeyException异常

    2. 场景2:转账,若 a >= 100, a -100, b+100

      此场景为了保证数据一致性,事务原子性,需要顺序执行

      总结:除了极其简单的场景外,大部分幂等场景都需要并发控制,需要强锁还是弱锁

  3. 维度3 : 如何判断是重复请求?

    1. 场景1: 无业务主键的新增

      此场景下需要服务端颁发token,通过token判断是否重复,做防重复提交

    2. 场景2: 有业务唯一键的新增

      此场景下可以使用业务唯一键判断是否重复

    3. 场景3: 有业务唯一键的更新

      此场景下可以使用业务数据对应的状态判断是否重复

  4. 维度4 : 重复请求处理方式?

    1. 场景1:消息重复消费

      此场景下,不需要给上游一个返回,重复执行只需要丢弃

    2. 场景2:前端重复提交

      此场景下,需要给用户一个提示,那么重复请求可以直接抛出一个DuplicateReqeustException异常,由上层捕获,前端展示“请不要重复请求”

    3. 场景3: 底层的转账接口

      此场景下,同一笔转账,重复调用第一次若成功了,第二次应该返回相同“成功”,若第一次失败了,第二次返回业务执行结果

      结论:根据不同场景,应该有不同的结束方式,应该由业务方自己决定是丢弃,抛异常,直接返回,还是执行业务逻辑

  5. 维度5 : 重复请求返回是否相同?

    1. 场景1:第一次执行成功,第二次返回应该与第一次相同

    2. 场景2:第一次执行因为代码原因导致的异常而失败,第二次返回应该与第一次相同

    3. 场景3:第一次执行因为网络原因失败,第二次应该执行业务逻辑,返回结果可能不一致

      总结:根据第一次执行的成功和失败,判断第二次应该执行业务逻辑还是直接返回相同结果

  6. 维度6 : 重复请求调用下游如何处理?

    1. 场景1: 业务逻辑中,有调用下游rpc接口,第一次执行失败,第二次重复执行业务逻辑再次调用下游接口

      总结:此场景本质上是一个分布式一致性问题,需要业务方自己解决,或者保证下游接口也是幂等的

  7. 识别相同请求

    1. token机制:每次提交上来的请求,都带上一个token标识,同一个token只处理一次,例如:新增场景的防止重复提交
    2. 业务唯一标识:使用id或者unique key,例如:支付后,支付凭证落库
    3. 业务唯一标识 + 状态:支付后,通过支付凭证号和当前订单状态,更新订单状态
    4. 在业务侧建立同库幂等数据表,记录请求唯一键和执行状态,执行成功后更新状态为成功
  8. 并发处理方式

    1. 单机情况下,使用java锁synchronized

    2. 使用数据库锁,悲观锁:select for update,乐观锁:update set XXX and version = 2 where version = 1

    3. 使用分布式锁:

      redis锁:1.过期时间导致并发问题,2.主从切换时锁丢失

      zookeeper锁:写能力不如redis

  9. 一致性保证

    1. 业务方需保证原子性,本地更新都在一个事务里
    2. 若无法保证分布式事务,下游接口需是幂等的,且本地事务必须重试达到最终一致

SDK设计#

  1. 并发控制
    1. 提供注解,按业务自动划分,提供指定幂等唯一键
    2. 提供并发控制,分布式锁服务(redis或zk),指定分布式锁时间
    3. 提供并发时幂等处理方式,丢弃/抛异常/等待锁
    4. 提供降级选择,不强依赖于锁服务
    5. 提供token注解,防止重复提交
  2. 判断重复请求

处理方式 优点 缺点

方案一 业务方自己判断 业务方需要自己考虑判重

方案二 sdk提供判重接口,由业务方实现 可以实现重复请求的通用处理

方案三 幂等表:在业务侧建立同库幂等数据表,记录请求唯一键和执行状态,执行成功后更新状态为成功 业务方无需再关心判重问题 数据量大,开发量大,复杂度较高

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×