日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

spring项目使用redis分布式锁解决重复提交问题

發布時間:2025/1/21 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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));}

學生表

FieldTypeComment
idint(11)自增
namevarchar(20)姓名
ageint(5)年齡

上面這段代碼,如果什么都不做,100個請求同時進來會發生什么呢

  • 模擬同時100個請求

    ?

  • 發現重復插入了很多條數據

    ?

  • 如何解決

    在網上有很多處理重復提交的方案,大部分的邏輯都是利用redis的key的過期時間,讓請求在一點時間內重復進入方法,直達key過期.這種方法有一個缺點,需要固定一個時間,這個時間設置長了浪費性能,設置短了起不到防止重復提交的作用,我覺得有更好的方案

    單服務

  • 定義一個注解,攔截需要處理的方法
  • @Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Resubmit {}
  • 攔截處理
  • @Aspect @Component public class SubmissionAop {@Autowiredprivate HttpServletRequest request;private static ConcurrentHashMap<String,Integer> concurrentHashMap = new ConcurrentHashMap<>();@Pointcut("@annotation(Resubmit)")public void needCheckMethod() {}@Before("needCheckMethod()")public void checkMethod() {//requestId 的獲取,確保同一個用戶,同一個urlString requestId = request.getMethod() + request.getServletPath() + request.getHeader("token");try {check(requestId);request.setAttribute("__request_resubmit_need_release","need");} catch (Exception e) {//拋出異常后被統一異常處理,轉化為返回信息返回給前端throw new RuntimeException("重復提交-上一個請求還未處理完");}}@After("needCheckMethod()")public void release(){if ("need".equals(String.valueOf(request.getAttribute("__request_resubmit_need_release")))) {String requestId = request.getMethod() + request.getServletPath() + request.getHeader("token");concurrentHashMap.remove(requestId);}}/**同步保證查詢和設置是原子操作* @param requestId* @throws Exception*/private static synchronized void check(String requestId) throws Exception {if (concurrentHashMap.get(requestId) != null) {throw new Exception();}concurrentHashMap.put(requestId, 1);} }

    在請求進入方法前,加鎖,往后的同一個請求(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分布式锁解决重复提交问题的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。