接口幂等性
https://www.bilibili.com/video/BV1J44y1k7Se
什么是幂等性
发一次接口调用与发多次相同的接口消息都能得到与预期相符的结果
作为前端的应用,应用系统往往需要和后台的数据服务进行交互,在这个交互的过程中通常会选择 RESTful 或者 RPC 的方式进行调用。
在这个过程中,以目前主流的 RESTful 为例:
在实际的开发过程中会遇到这样的情况——系统和系统之间(这里指的就是应用系统和数据服务)是通过网络来进行数据传输的,只要是网络就可能出现网络抖动的情况,那么为了保证系统的高可靠性,作为前端的调用系统,可能会增加像 Spring Retryable 这样的组件,通过不断重试发送相同的调用来尽可能地提高系统的可靠性,这是日常开发中非常常见的一种使用方式。
但是这种使用方式会带来一种附加的问题:作为后台服务,必须要考虑幂等性。
具体案例
PUT https://xxxxx.com/employee/salary {"id":"1","incr_salary":500}
这是一个 RESTful 接口,接口的作用是对 id=1 的员工工资上调 500
//查询1号员工数据
Employee employee = employeeService.selectById(1);
//更新工资
employee.setSalary(employee.getSalary() + incrSalary);
//执行更新语句
employeeService.update(employee);
这样实现看上去没有问题,但如果放在前面描述过的应用 Spring Retryable 这样的组件的系统和系统之间进行调用的环境中就没有考虑到幂等性的处理。
引出问题
如果真的像上面代码中那样的简单实现,前端调用系统每重发一次请求,1号员工的工资就会上调500,这种情况就是一般说的幂等性被破坏了。
传统解决方案
传统办法是代码增加前置判断
if (!员工已调薪) {
进行调薪
}
比如通过一些数据库中的标识发现这个员工是否在最近已经调过新了,基于这种业务上的考量限制再次进行这个员工的调薪操作;
或者在这个员工的记录上记录上一次调薪的时间做基于业务逻辑的判断
传统解决方案带来的问题
需要前置判断的地方太多了,容易遗漏,和业务代码耦合在一起,影响开发效率,这种公共功能的需求实现做统一的抽象处理更好
通用解决方案
需要一种无侵入的幂等解决方案,构建幂等表是通用解决方案,让开发人员专心业务逻辑实现
幂等表的含义是在原有的数据存储之外,额外构建出一个专门用于检查接口幂等性的一张数据表,通过幂等表来保证每一个业务请求只能被后台服务的方法真正处理一次
使用幂等表,最大的好处就是可以让开发人员专心业务逻辑实现,幂等问题得到统一解决
使用幂等表的流程设计
但应用网关调用数据服务也有可能失败吧?(设置缓存存活时间可以一定程度上解决这个问题)
为什么要设置存活时间?
不设存活时间数据会一直被保存在 Redis 中,会有数据积压的情况,也会触发 LRU 淘汰策略使得系统变慢,直到内存不够用;
另外数据服务也有可能回写 Redis 调用成功失败,导致 Nginx+lua 的实现不放过之后应用系统的重试调用;
统一实现数据服务对 Redis 的回写
@GetMapping("/abc/bcd")
//利用 AOP After 通知,更新 Redis 状态
@Idempotent
public Object 接口方法() {
//处理方法
}
@Idempotent
为开发人员自定义注解,注解在方法上,注解实现在方法调用后的数据服务回写 Redis
总结
优点:后台服务无代码侵入,无需修改业务逻辑
缺点:前台应用要针对幂等进行改造
架构复杂度增加,需要额外部署 Nginx、Redis
其他的一些思考
幂等性只能保证自己的服务调用自己的服务的幂等性吗?
幂等性在客户端(手机、浏览器)对服务端的调用上可以保证吗?