spring项目使用redis分布式锁解决重复提交问题
場景演示
假設有一個錄入學生信息的功能,為了便于演示,要求不能有重名的學生,并且數據庫對應字段沒有做唯一限制.
@GetMapping("/student/{name}")public Object reSubmitTest(@PathVariable String name){List<Student> allByName = repository.findByName(name);if (allByName != null && allByName.size() > 0) {return "姓名重復";}//便于測試假設都是18歲return repository.save(new Student(name, 18));}學生表
| id | int(11) | 自增 |
| name | varchar(20) | 姓名 |
| age | int(5) | 年齡 |
上面這段代碼,如果什么都不做,100個請求同時進來會發生什么呢
模擬同時100個請求
?發現重復插入了很多條數據
?如何解決
在網上有很多處理重復提交的方案,大部分的邏輯都是利用redis的key的過期時間,讓請求在一點時間內重復進入方法,直達key過期.這種方法有一個缺點,需要固定一個時間,這個時間設置長了浪費性能,設置短了起不到防止重復提交的作用,我覺得有更好的方案
單服務
在請求進入方法前,加鎖,往后的同一個請求(requestId相同)無法獲取鎖,就被判定為重復請求,拋出異常,等第一個請求調用完畢后再釋放鎖,這樣一來,就不需要設定時間來限制訪問
多服務/或redis
上面的aop方法,無法適用于多服務/集群,總體邏輯不變,適用redis來做分布式鎖就行了,改造比較簡單,如下
@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate HttpServletRequest request;@Pointcut("@annotation(Resubmit)")public void needCheckMethod() {}@Before("needCheckMethod()")public void checkMethod() {String requestId = request.getServletPath() + request.getHeader("token");Boolean absent = redisTemplate.opsForValue().setIfAbsent(requestId, "1",60, TimeUnit.SECONDS);Assert.notNull(absent,"");if (!absent) {throw new RuntimeException("重復提交-上一個請求還未處理完");}request.setAttribute("__request_resubmit_need_release","need");}@After("needCheckMethod()")public void release(){if ("need".equals(String.valueOf(request.getAttribute("__request_resubmit_need_release")))) {String requestId = request.getServletPath() + request.getHeader("token");redisTemplate.delete(requestId);}} }上面的 redisTemplate.opsForValue().setIfAbsent(requestId, "1",60, TimeUnit.SECONDS);是原子操作,這也是為什么使用它做分布式鎖的原因, 其次這個方法需要redis版本在2.1以上,否則只能在同步方法中先setIfAbsent 再設置過期時間了. 這里設置過期時間的原因是,有可能第一個請求設置完鎖后,redis出現問題,導致后面的請求一直無法獲取鎖,從而所有請求都被判定為重復請求.
最后 如果發現本文有需要改進的地方,或者你有更好的方案,歡迎留言交流
原文:https://www.jianshu.com/p/bb2a4808c7b9
總結
以上是生活随笔為你收集整理的spring项目使用redis分布式锁解决重复提交问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 优惠券卡包应用数据库字段设计/系统架构设
- 下一篇: Dubbo 优雅停机