
搞了个线上故障,被老板骂了....
大家好,我是Tom哥。
前几天跟一位小伙伴聊天,心情特别沮丧,刚被老板骂完.....
差点丢了饭碗,还好老板没说 “滚”
就今年这就业行情,满眼都是泪哇
小伙伴在一家初创公司,团队规模很小,老板为了节省成本,也没配置什么豪华阵容
他的工作时间也不长,负责交易订单,前几天接到用户投诉,「我的订单列表」有多条一模一样的订单
虽没造成什么资损,但严重影响用户体验
看到这里,有经验的同学可能猜到,应该是接口没做防重控制
日常开发中,重复提交也是蛮常见问题
比如:用户提交一个表单,鼠标点的太快,正好前端又是个新兵蛋子,没做任何控制,瞬间就会有多个请求发到后端系统
如果后端同学也没做兜底方案的话,悲剧就发生了
常见的解决方案是借助数据库自身的「唯一索引约束」,来保证数据的准确性,这种方案一般在插入场景用的多些。
变种方案可以考虑单独创建一个防重表
本文的案例有点特殊,订单号是后端系统生成的,前后两次请求无法区分重复状态,所以系统会创建两条不同订单 ID 记录,绕过了「唯一索引约束」这个限制,这.....
另外,MySQL 性能也单薄了点,单机 QPS 在「千」维度,如果是面对一个高并发接口,性能也有点吃紧
接下来,我们就来讲下,借助 Redis 来实现接口防重复提交
技术方案
首先,我们来看下整理的流程,如下图所示
大致步骤:
1、客户端发送请求到服务端
2、服务端接收请求,然后从请求参数中提取唯一标识。这个标识可以没有什么特殊业务含义,client 端随机生成即可
3、服务端系统将唯一标识先尝试写入 Redis 缓存中,可以认为是加锁操作
4、加锁失败,说明请求还在处理,此次是重复请求,可以丢弃
5、加锁成功,继续后面正常业务逻辑处理
6、业务逻辑处理完成后,删除加锁的标记
7、最后,将处理成功的结果返回给客户端
注意事项:
- 重复提交场景一般都是在极短时间内,同时发送了多次请求(比如:页面表单重复提交),我们只认第一次请求为有效请求
- 锁用完后,要记得手动删除。为了防止锁没有正常释放,我们可以为锁设置一个极短的过期时间(比如 10 秒)
项目实战
1、引入 redis 组件
实战的项目采用 Spring Boot 搭建,这里需要引入 Redis 相关依赖
2、redis 变量配置
application.properties 配置文件中,添加redis相关服务配置
3、定义注解类
定义一个注解,配置在需要防重复的接口方法上,提高开发效率,同时降低代码的耦合度
4、接口拦截器
上面定义了IdempotentRule
注解,需要通过拦截器
对正常的业务方法做拦截,增加一些特殊逻辑处理
这里,比较特殊的是提取请求的唯一标识,由于不同的业务请求唯一标识不一样。
所以,这里采用 SPEL
表达式,将规则设置能力开放出去,由业务方自己定义,比如:
@IdempotentRule(key = "#userParam.cardNumber", prefix = "repeat_")
拦截器根据 SPEL 表达式( 如 "#userParam.cardNumber")以及请求参数对象,计算当前请求唯一标识的值,
然后将值写入 Redis 中,并设置过时间。
如果设置成功,说明是第一次请求,继续下面的业务逻辑处理;否则,判定为重复请求,直接丢弃。
5、上层业务接口
测试结果
1、构造客户端请求,第一次处理成功
2、 Redis 缓存中,能查到请求设置的锁标记
3、模拟重复,连续多次快速提交请求,请求会被拦截,并抛出异常
代码地址:
https://github.com/aalansehaiyang/redis-limit-demo
··············
关于我:Tom哥,前阿里技术专家,拿过 鹅厂、百度、华为 等6家大厂offer,CSDN 博客专家,面试过 500+ 候选人,职场经验丰富。
文章转载自公众号: 微观技术
