一、背景
平台部分API需要保证接口的幂等性,防止业务频繁刷API导致资源浪费,或者不小心重发消息影响业务。
二、解释
接口的幂等,指一个操作重复执行N次得到的结果与执行一次是相等的。比如在HTTP请求中,Get请求,得到的结果是相同的。但POST和PATCH的接口,每次请求得到的结果是不同的。这就是不幂等。
但是实际业务对应部分接口,有幂等要求。比如银行的转账,由于各种原因,可能同一个请求会被多次发送,但是结果只会成功转账一次,其他转账不会生效。
三、解决办法
悲观锁,乐观锁,防重表,Token。
3.1、悲观锁
流程:
1、收到请求时,开启事务,对查询事件(如订单)加锁;
2、判断事件是否符合执行条件;
3、全部执行完毕后,提交或者回滚。
缺点:执行时间长,悲观锁容易锁住整表,导致服务不可用问题。
3.2、乐观锁
实现方式:通过版本号,在更新事件的时候锁表(大部分时间不锁表),比如:
即使多个请求过来,因为进来的version是相同的,但是DB中的版本号已经被更新,修改条件不成立,也就是说不会被多次更新。
缺点:使用主键或者唯一索引来更新,使用行锁而不是表锁。
3.3、防重表
流程:
1、建立一张防重表,使用时间ID(比如订单号)作为唯一索引
2、在发起请求时,根据事件ID在防重表中新增一条记录
因为是唯一索引,重发的请求添加记录是不会成功的,第一个进入的事件可以被执行(事件完成后,可以删除防重表的记录)
缺点:多维护一张数据表,增加业务逻辑复杂度。
3.4、Token
流程:
1、业务方在调用API的时候,在Header里传递一个X-Request-Id的参数(随机id,类似token)
2、平台接收到这次请求,判断在缓存里是否存在对应的X-Request-Id的对应记录
3、如果记录存在,标明这条消息已经被发送过,接口返回HTTP 412 Code
4、如果记录不存在,将该记录刷到缓存里,发送消息
5、Reaponse的Header也返回X-Request-Id
缺点:流程比防重表更加复杂。
四、幂等性的优缺点
优点:
1、实现接口的幂等性
2、不需要关心随机ID的业务逻辑
缺点:
1、增加了实现的复杂度
2、增加运维成本
五、总结
基于我们当前的业务现状,使用 X-Request-Id
+ 缓存来支持消息 API 的幂等操作。虽然方案依赖 Redis, 但是平台本身就有使用缓存, 运维成本这个缺点可忽略不计, 逻辑实现相对简单。 综上可以设施