成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從零搭建開發(fā)腳手架 保證服務(wù)的冪等性和防止重復(fù)請求

開發(fā) 架構(gòu)
前端同步阻塞按鈕置灰,用戶點擊“發(fā)布”按鈕后,在網(wǎng)絡(luò)請求沒有返回,或者超時之前,用戶都不可以繼續(xù)點擊“發(fā)布按鈕”,界面可以將按鈕置灰或者轉(zhuǎn)圈。
本文轉(zhuǎn)載自微信公眾號「Java大廠面試官」,作者laker。轉(zhuǎn)載本文請聯(lián)系Java大廠面試官公眾號。
  •  什么是冪等?
  • 重復(fù)請求原因
  • 解決方案
    • 方案一:前端同步阻塞按鈕置灰
    • 方案二:前后端搭配干活,預(yù)生成訂單號
    • 方案三:通用方案,鎖模式
  • 實現(xiàn)
    • 自定義注解限制重復(fù)提交
    • 自定義切面攔截過濾處理
    • 使用示例

什么是冪等?

多次執(zhí)行的結(jié)果和一次執(zhí)行的結(jié)果相同,例如查詢操作天然就是冪等的。

重復(fù)請求原因

我們以電商場景中的下單來舉例,造成下單重復(fù)一般有以下幾個原因:

  • 用戶手抖點快了,導(dǎo)致多次重復(fù)下單。
  • 網(wǎng)絡(luò)抖動導(dǎo)致失敗或者超時重傳,例如nginx、Fegin、RPC框架等

解決方案

方案一:前端同步阻塞按鈕置灰

前端同步阻塞按鈕置灰,用戶點擊“發(fā)布”按鈕后,在網(wǎng)絡(luò)請求沒有返回,或者超時之前,用戶都不可以繼續(xù)點擊“發(fā)布按鈕”,界面可以將按鈕置灰或者轉(zhuǎn)圈。

優(yōu)點:實現(xiàn)成本極低

缺點:

  1. 只能防御用戶手抖的誤操作。
  2. 確防不住遠(yuǎn)程調(diào)用的重試以及惡意重放。

方案二:前后端搭配干活,預(yù)生成訂單號

可以通過預(yù)先生成訂單號(在進(jìn)入下單頁面的時候生成訂單號),然后利用數(shù)據(jù)庫中訂單號的唯一約束這個特性,避免重復(fù)寫入訂單。

時序圖如下:

細(xì)節(jié)如下:

訂單號生成時機(jī)

是在進(jìn)入訂單頁面,而不是提交訂單的時候 。

訂單號生成規(guī)則

  • 小規(guī)模系統(tǒng)完全可以用MySQL的Sequence或者Redis來生成。大規(guī)模系統(tǒng)也可以采用類似雪花算法之類的方式分布式生成GUID。
  • 訂單號中最好包含一些品類、時間等信息,便于業(yè)務(wù)處理,它不能是一個單純自增的ID,否則別人很容易根據(jù)訂單號計算出你大致的銷量,所以訂單號的生產(chǎn)算法在保證不重復(fù)的前提下,一般都會加入很多業(yè)務(wù)規(guī)則在里面。

訂單號是否是主鍵

方式一:使用訂單號做主鍵

如果訂單號不是遞增的可能造成頻繁頁分裂,導(dǎo)致并發(fā)高的時候性能降低,所以要保證訂單號全局遞增。

方式二:有自增主鍵和訂單號列并設(shè)置唯一索引

因為訂單號不是主鍵,所以根據(jù)訂單號查詢會多一次回表操作,且如果訂單號不遞增二級訂單號索引也會有頁分裂。

訂單號可以由前端生成嗎

不可以,訂單號一定是在后端生成,后端生成可以保證全局唯一,且可以用于做安全認(rèn)證,不是后端頒發(fā)的訂單號不予處理。

提交訂單的時候,一種是先拿著訂單號去查庫,讓業(yè)務(wù)代碼校驗是否存在,另一種是直接利用庫表主鍵唯一約束拋異常,這兩種處理方式哪種性能更好?

選后者,等查完庫確定不存在再插入的時候,可能數(shù)據(jù)已經(jīng)變化了,訂單存在了,還是要拋異常,檢查意義不大。

方案三:通用方案,鎖模式

使用鎖來控制一段時間內(nèi)的重復(fù)請求,注意: 鎖的粒度為用戶+業(yè)務(wù)。

請求流程如下:

  • 1.請求接口時,獲取一個鎖 鎖的粒度 :同一用戶的同一操作邏輯 鎖名稱規(guī)則:業(yè)務(wù)名稱+用戶ID
  • 2.給鎖設(shè)置過期時間10秒,防止業(yè)務(wù)邏輯執(zhí)行錯誤,用戶一直被鎖住
  • 3.如果被鎖了,返回“正在處理,請勿重復(fù)提交”
  • 4.沒有被鎖,執(zhí)行正常邏輯,在邏輯結(jié)束后,刪掉鎖

實現(xiàn)

針對方案三實現(xiàn)如下:

自定義注解限制重復(fù)提交

  1. @Target(ElementType.METHOD) 
  2. @Retention(RetentionPolicy.RUNTIME) 
  3. @Documented 
  4. @Inherited 
  5. public @interface RepeatSubmitLimit { 
  6.     /** 
  7.      * 業(yè)務(wù)key,例如下單業(yè)務(wù) order 
  8.      */ 
  9.     String businessKey(); 
  10.  
  11.     /** 
  12.      * 業(yè)務(wù)參數(shù),用于做更細(xì)粒度鎖,例如鎖到具體 訂單id #orderId 
  13.      */ 
  14.     String businessParam() default ""
  15.  
  16.     /** 
  17.      * 是否用戶隔離,默認(rèn)啟用 
  18.      */ 
  19.     boolean userLimit() default true
  20.  
  21.     /** 
  22.      * 鎖時間 默認(rèn)10s 
  23.      */ 
  24.     int time() default 10; 

自定義切面攔截過濾處理

  1. @Component 
  2. @Aspect 
  3. @Slf4j 
  4. public class LimitSubmitAspect { 
  5.     LFUCache<Object, Object> LFUCACHE = CacheUtil.newLFUCache(100, 60 * 1000); 
  6.  
  7.     @Pointcut("@annotation(RepeatSubmitLimit)"
  8.     private void pointcut() { 
  9.     } 
  10.  
  11.     @Around("pointcut()"
  12.     public Object handleSubmit(ProceedingJoinPoint joinPoint) throws Throwable { 
  13.  
  14.  
  15.         Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); 
  16.         //獲取注解信息 
  17.         RepeatSubmitLimit repeatSubmitLimit = method.getAnnotation(RepeatSubmitLimit.class); 
  18.         int limitTime = repeatSubmitLimit.time(); 
  19.         String key = getLockKey(joinPoint, repeatSubmitLimit); 
  20.         Object result = LFUCACHE.get(keyfalse); 
  21.         if (result != null) { 
  22.             throw new BusinessException("請勿重復(fù)訪問!"); 
  23.         } 
  24.         LFUCACHE.put(key, StpUtil.getLoginId(), limitTime * 1000); 
  25.         try { 
  26.             Object proceed = joinPoint.proceed(); 
  27.             return proceed; 
  28.         } catch (Throwable e) { 
  29.             log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(), 
  30.                     joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL", e.getMessage(), e); 
  31.             throw e; 
  32.         } finally { 
  33.             LFUCACHE.remove(key); 
  34.         } 
  35.     } 
  36.  
  37.     private static final ParameterNameDiscoverer NAME_DISCOVERER = new DefaultParameterNameDiscoverer(); 
  38.  
  39.     private static final ExpressionParser PARSER = new SpelExpressionParser(); 
  40.  
  41.     private String getLockKey(ProceedingJoinPoint joinPoint, RepeatSubmitLimit repeatSubmitLimit) { 
  42.         String businessKey = repeatSubmitLimit.businessKey(); 
  43.         boolean userLimit = repeatSubmitLimit.userLimit(); 
  44.         String businessParam = repeatSubmitLimit.businessParam(); 
  45.         if (userLimit) { 
  46.             businessKey = businessKey + ":" + StpUtil.getLoginId(); 
  47.         } 
  48.  
  49.         if (StrUtil.isNotBlank(businessParam)) { 
  50.             Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); 
  51.             EvaluationContext context = new MethodBasedEvaluationContext(null, method, joinPoint.getArgs(), NAME_DISCOVERER); 
  52.             String key = PARSER.parseExpression(businessParam).getValue(context, String.class); 
  53.             businessKey = businessKey + ":" + key
  54.         } 
  55.         return businessKey; 
  56.     } 

使用示例

  1. @RepeatSubmitLimit(businessKey = "tokenInfo", businessParam = "#name"
  2.   @GetMapping("/api/v1/tokenInfo"
  3.   public Response tokenInfo(String name) { 
  4.   } 

請求示例:http://localhost:8080/api/v1/tokenInfo?name=123

鎖粒度為:taokeninfo:1:123

防重效果:

  1.  code: "500"
  2.  msg: "請勿重復(fù)訪問!" 

參考:

后端存儲實踐課

 

責(zé)任編輯:武曉燕 來源: Java大廠面試官
相關(guān)推薦

2021-04-28 16:10:48

開發(fā)腳手架 Spring

2021-05-13 17:02:38

MDC腳手架日志

2021-04-13 14:47:53

認(rèn)證授權(quán)Java

2021-07-13 18:42:38

Spring Boot腳手架開發(fā)

2021-02-19 22:43:50

開發(fā)腳手架Controller

2021-07-29 18:49:49

Spring開發(fā)腳手架

2020-08-19 08:55:47

Redis緩存數(shù)據(jù)庫

2021-04-20 19:24:16

腳手架 Java微信

2021-09-01 10:07:43

開發(fā)零搭建Groovy

2021-03-09 17:11:09

數(shù)據(jù)庫腳手架開發(fā)

2021-03-11 14:16:47

Spring Boo開發(fā)腳手架

2021-04-14 17:18:27

冪等性數(shù)據(jù)源MySQL

2016-08-10 14:59:41

前端Javascript工具

2021-01-07 05:34:07

腳手架JDK緩存

2023-11-21 17:36:04

OpenFeignSentinel

2023-09-01 15:27:31

2018-06-11 14:39:57

前端腳手架工具node.js

2018-08-30 16:08:37

Node.js腳手架工具

2014-08-15 09:36:06

2025-02-26 08:20:18

點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产成人综合在线 | 欧美视频成人 | 久在线观看 | 久久亚 | 亚洲色欧美另类 | 亚洲欧洲视频 | 久草中文在线 | 日韩尤物视频 | 日韩一区三区 | 国产一区二区在线免费观看 | 黄色片av | 日韩成人免费视频 | 日本特黄a级高清免费大片 特黄色一级毛片 | 操射视频| 日韩免费av | 成人黄色在线观看 | 亚洲最大福利网 | 亚洲一区在线观看视频 | www.狠狠干 | 狠狠的操 | 在线欧美一区 | www.男人天堂.com | 国产在线精品一区二区 | 91精品国产综合久久久久久 | 亚洲精品福利在线 | 在线观看亚洲专区 | 日韩精品一区二区三区视频播放 | 午夜视频网站 | 亚洲网站观看 | 91黄在线观看 | 成人一区二区三区在线观看 | 亚洲精品久久久久久国产精华液 | 91精品国产91久久久久游泳池 | 国产精品成人一区二区三区 | 亚洲黄色一级 | 国产福利在线小视频 | av中文字幕在线观看 | 国产一区中文 | 久久成人精品视频 | 国产二区在线播放 | 韩国电影久久 |