分布式设计中的幂等性处理
分布式设计中的幂等性处理
在分布式系统设计中,接口幂等性的处理是必须要考虑的重要问题。
很多开发人员对幂等性了解不多,在实际代码开发中也很少关注。
其实幂等性的概念非常简单,它的含义是:一次接口调用与多次相同的接口调用,能够得到与预期相符的结果。
基本架构设计案例
以京东金融为例,京东金融有大量的应用系统,比如审计系统和前端应用。它们需要与后台的数据仓库进行交互,通常选择 RESTful 或 RPC 的方式进行调用。这里以 RESTful 为例进行讲解。
系统之间通过网络传输,网络可能会出现断网的情况。为了保证系统的高可靠性,前端应用系统可能会增加如 Spring Retryable 这样的组件,通过不断重试发送相同调用消息的方式来提高系统的可靠性。
这种方式会带来一个附加问题:后台服务必须考虑幂等性。
案例分析
假设有一个RESTful接口用于将编号为1的员工的工资上调500元。很多新手程序员会这样写代码:
- 从数据库中通过
select by ID
得到当前员工的基本工资信息。 - 用
EMP.setSalary
在原有工资基础上加上参数中的500。 - 执行更新操作。
单看这三行代码没有问题,但忽略了接口的幂等性处理。
如果前端应用为了保证高可用,通过底层组件发送了多次重复请求,每执行一次,员工的工资就会增加500,执行十次则增加5000。预期是在原始工资基础上加500,但实际可能加了5000,这就破坏了幂等性。
解决方案
传统解决方案
在业务代码上进行前置判断,通过数据库中的标识判断员工是否已经调过薪,或者记录上次调薪的时间来进行判断。这种方法有两个问题:
- 需要做前置幂等判断的地方可能太多,容易漏掉。
- 增加程序员的工作量和复杂度,特别是对实习工程师来说,难以保证他们能考虑到幂等性的问题。
无侵入的幂等解决方案
构建一个与业务无关的通用幂等解决方案,称为幂等表。幂等表的设计如下:
- 增加两个组件:应用网关 和 Redis。
- 应用网关职责:对应用发来的请求进行过滤和转发。
- Redis职责:存储最近执行的请求编号。
构建幂等表 in redis 是我们的通用解决方案。
处理过程
- 请求编号生成:前端应用系统在每次发送 RESTful 或 RPC 调用时,在请求头或RPC头部附加一个唯一的
request ID
。 - 网关检查:请求到达网关,网关通过 Nginx 和 Lua 脚本检查 Redis 中的幂等表。如果
request ID
不存在,保存到幂等表中,状态设置为proc
(处理中)。 - 请求处理:业务逻辑处理完成后,更新Redis中的
request ID
状态为OK
(处理成功)。
重复请求处理:如果相同的 request ID
再次发送,网关直接返回错误编码 201,前端应用接收到 201 编码后排除本次请求。
考虑 redis 突然断网怎么办
潜在的影响
- redis 断网
- 新的重试被拒绝,应用系统显示处理失败
- 数据服务的数据变更执行完成
- 无法把 ok 写到 redis
不妨先写 ok 入redis,再执行数据变更业务。
存活时间的设置
Redis中的幂等表数据设置存活时间(如5分钟),作用有两个:
- 防止内存占用过多:及时清除过期数据,释放内存。
- 处理异常情况:数据服务在处理中途崩溃,导致请求一直处于处理中状态,存活时间到期后数据被清除,允许再次处理请求。
代码实现
通过AOP(面向切面编程)实现无侵入的幂等处理:
- 注解定义:在控制器方法上增加自定义注解。
- AOP拦截:AOP在方法执行后,通过后置通知更新Redis中的
request ID
状态。
优缺点分析
优点
- 无代码侵入:无需修改业务逻辑,只需增加注解即可。
- 通用性强:适用于所有系统,保证接口幂等性。
缺点
- 前台改造:要求在请求头中附带唯一的请求编号,并处理自定义错误编码201。
- 架构复杂度增加:增加了Nginx和Redis组件,可能增加运营和开发成本。
总结
幂等表的设计通过Redis和应用网关,确保每个业务请求只被处理一次,避免重复请求带来的问题。
虽然增加了一些架构复杂度,但提供了一个通用的解决方案,适用于多个系统。