记一次限制接口重复请求的解决方法
由于今年经济形势不好,个人财务也比较紧张,所以接了不少私活。这就造成没有时间写技术博客,再加上前段时间写了个可加微信群和发布群等功能的小程序(人脉扩列群多多),为了给这个小程序提供更多的微信群二维码,就研究了一段时间的Hook技术。好在最后通过Golang成功实现了自动化发布微信群,彻底解放了双手。
闲暇时间解决了一堆公司项目上的老Bug,借这个时间将其中比较难搞的接口重复请求问题记录一下。
一直以来,我对一些敏感的接口,比如支付、下单、记录等会创建数据记录的接口都会使用常规的限制重复请求。基操就是通过使用的框架去记录一个缓存,限制未来几秒内同一源头的请求只有一次。
因为我司框架中的缓存是用的Redis,并且一开始业务量也不大,Redis与后端服务代码都是在一台服务器上运行的。但去年底开始业务量上来后通过均衡负载将代码分发到了两个服务器,而Redis也相应的使用了独立的服务器。
也就是在这种环境下,随着业务量的激增,问题逐渐的暴露了出来。近期客服经常反馈说用户那边会重复生成一些订单,我查看订单后,发现确实每天都有不少的重复订单,都是在同一秒生成。加上日志后发现虽然大部分重复请求已被拦截,但是还是有不少漏网之鱼。
我通过在测试环境中不断并发请求接口,切换本地Redis与独立服务器的Redis发现,虽然独立服务器是通过内网连接,但是还是会出现更多的漏网之鱼。
当然了即使是本地Redis做缓存,也出现了少量的未拦截的情况。
虽然通过让前端小程序、APP都增加了请求限制,但是因为业务逻辑的关系,并不能做到完全拦截。甚至在小程序端出现了很频繁的重复请求,一直都没找到好的解决方案。(多入口的扫描设备开启使用功能)
或许要彻底解决此类问题,只有通过读写内存的方式吧!比如将框架换成Workerman,通过常驻内存,直接读写内存或许可以解决。
因为考虑到我们的业务对效率要求不是很高,所以我就想到了一个比较笨的方法:
缓存拦截之所以失败,还是因为两次请求时间相差很小,都是毫秒级别。而缓存写入和读取同样需要时间,这其中的时间差,就会造成该问题。既然两次请求时间差小,那可不可以人为干预一下,让其拉大距离内?
在业务效率允许的情况下,我在接口开端增加Sleep毫秒时间,并且使用随机睡眠时间(比如0-200毫秒)。这样能大大增加两次请求的时间差,配合后面的缓存拦截,就能将成功率提高很多。
当然这种杀敌一千自损八百的招式不是通用的,在各方面都允许的情况下,还是建议使用常驻内存方式。甚至在秒杀类似场景中,最好直接截断业务,将请求依次写入缓存,另开进程读取缓存列表去处理保证业务的正常允许。