溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

SpringBoot基于過(guò)濾器和內(nèi)存如何實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能

發(fā)布時(shí)間:2023-02-01 09:20:37 來(lái)源:億速云 閱讀:110 作者:iii 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹了SpringBoot基于過(guò)濾器和內(nèi)存如何實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇SpringBoot基于過(guò)濾器和內(nèi)存如何實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

對(duì)于一些請(qǐng)求服務(wù)器的接口,可能存在重復(fù)發(fā)起請(qǐng)求,如果是查詢操作倒是并無(wú)大礙,但是如果涉及到寫入操作,一旦重復(fù),可能對(duì)業(yè)務(wù)邏輯造成很嚴(yán)重的后果,例如交易的接口如果重復(fù)請(qǐng)求可能會(huì)重復(fù)下單。

這里我們使用過(guò)濾器的方式對(duì)進(jìn)入服務(wù)器的請(qǐng)求進(jìn)行過(guò)濾操作,實(shí)現(xiàn)對(duì)相同客戶端請(qǐng)求同一個(gè)接口的過(guò)濾。

 @Slf4j
 @Component
 public class IRequestFilter extends OncePerRequestFilter {
     @Resource
     private FastMap fastMap;
 
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
         ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
         String address = attributes != null ? attributes.getRequest().getRemoteAddr() : UUID.randomUUID().toString();
         if (Objects.equals(request.getMethod(), "GET")) {
             StringBuilder str = new StringBuilder();
             str.append(request.getRequestURI()).append("|")
                     .append(request.getRemotePort()).append("|")
                     .append(request.getLocalName()).append("|")
                     .append(address);
             String hex = DigestUtil.md5Hex(new String(str));
             log.info("請(qǐng)求的MD5值為:{}", hex);
             if (fastMap.containsKey(hex)) {
                 throw new IllegalStateException("請(qǐng)求重復(fù),請(qǐng)稍后重試!");
             }
             fastMap.put(hex, 10 * 1000L);
             fastMap.expired(hex, 10 * 1000L, (key, val) -> System.out.println("map:" + fastMap + ",刪除的key:" + key + ",線程名:" + Thread.currentThread().getName()));
         }
         log.info("請(qǐng)求的 address:{}", address);
         chain.doFilter(request, response);
     }
 }

通過(guò)繼承Spring中的OncePerRequestFilter過(guò)濾器,確保在一次請(qǐng)求中只通過(guò)一次filter,而不需要重復(fù)的執(zhí)行

通過(guò)獲取請(qǐng)求體中的數(shù)據(jù),計(jì)算出MD5值,存儲(chǔ)在基于內(nèi)存實(shí)現(xiàn)的FastMap中,F(xiàn)astMap的鍵為MD5值,value表示多久以內(nèi)不能重復(fù)請(qǐng)求,這里配置的是10s內(nèi)不能重復(fù)請(qǐng)求。通過(guò)調(diào)用FastMap的expired()方法,設(shè)置該請(qǐng)求的過(guò)期時(shí)間和過(guò)期時(shí)的回調(diào)函數(shù)

 @Component
 public class FastMap {
     /**
      * 按照時(shí)間順序保存了會(huì)過(guò)期key集合,為了實(shí)現(xiàn)快速刪除,結(jié)構(gòu):時(shí)間戳 -> key 列表
      */
     private final TreeMap<Long, List<String>> expireKeysMap = new TreeMap<>();
     /**
      * 保存會(huì)過(guò)期key的過(guò)期時(shí)間
      */
     private final Map<String, Long> keyExpireMap = new ConcurrentHashMap<>();
     /**
      * 保存鍵過(guò)期的回調(diào)函數(shù)
      */
     private final HashMap<String, ExpireCallback<String, Long>> keyExpireCallbackMap = new HashMap<>();
     private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
     /**
      * 數(shù)據(jù)寫鎖
      */
     private final Lock dataWriteLock = readWriteLock.writeLock();
     /**
      * 數(shù)據(jù)讀鎖
      */
     private final Lock dataReadLock = readWriteLock.readLock();
     private final ReentrantReadWriteLock expireKeysReadWriteLock = new ReentrantReadWriteLock();
     /**
      * 過(guò)期key寫鎖
      */
     private final Lock expireKeysWriteLock = expireKeysReadWriteLock.writeLock();
     /**
      * 過(guò)期key讀鎖
      */
     private final Lock expireKeysReadLock = expireKeysReadWriteLock.readLock();
     /**
      * 定時(shí)執(zhí)行服務(wù)(全局共享線程池)
      */
     private volatile ScheduledExecutorService scheduledExecutorService;
     /**
      * 100萬(wàn),1毫秒=100萬(wàn)納秒
      */
     private static final int ONE_MILLION = 100_0000;
     /**
      * 構(gòu)造器,enableExpire配置是否啟用過(guò)期,不啟用排序
      */
     public FastMap() {
         this.init();
     }
     /**
      * 初始化
      */
     private void init() {
         // 雙重校驗(yàn)構(gòu)造一個(gè)單例的scheduledExecutorService
         if (scheduledExecutorService == null) {
             synchronized (FastMap.class) {
                 if (scheduledExecutorService == null) {
                     // 啟用定時(shí)器,定時(shí)刪除過(guò)期key,1秒后啟動(dòng),定時(shí)1秒, 因?yàn)闀r(shí)間間隔計(jì)算基于nanoTime,比timer.schedule更靠譜
                     scheduledExecutorService = new ScheduledThreadPoolExecutor(1, runnable -> {
                         Thread thread = new Thread(runnable, "expireTask-" + UUID.randomUUID());
                         thread.setDaemon(true);
                         return thread;
                     });
                 }
             }
         }
     }
     public boolean containsKey(Object key) {
         dataReadLock.lock();
         try {
             return this.keyExpireMap.containsKey(key);
         } finally {
             dataReadLock.unlock();
         }
     }
     public Long put(String key, Long value) {
         dataWriteLock.lock();
         try {
             return this.keyExpireMap.put(key, value);
         } finally {
             dataWriteLock.unlock();
         }
     }
     public Long remove(Object key) {
         dataWriteLock.lock();
         try {
             return this.keyExpireMap.remove(key);
         } finally {
             dataWriteLock.unlock();
         }
     }
     public Long expired(String key, Long ms, ExpireCallback<String, Long> callback) {
         // 對(duì)過(guò)期數(shù)據(jù)寫上鎖
         expireKeysWriteLock.lock();
         try {
             // 使用nanoTime消除系統(tǒng)時(shí)間的影響,轉(zhuǎn)成毫秒存儲(chǔ)降低timeKey數(shù)量,過(guò)期時(shí)間精確到毫秒級(jí)別
             Long expireTime = (System.nanoTime() / ONE_MILLION + ms);
             this.keyExpireMap.put(key, expireTime);
             List<String> keys = this.expireKeysMap.get(expireTime);
             if (keys == null) {
                 keys = new ArrayList<>();
                 keys.add(key);
                 this.expireKeysMap.put(expireTime, keys);
             } else {
                 keys.add(key);
             }
             if (callback != null) {
                 // 設(shè)置的過(guò)期回調(diào)函數(shù)
                 this.keyExpireCallbackMap.put(key, callback);
             }
             // 使用延時(shí)服務(wù)調(diào)用清理key的函數(shù),可以及時(shí)調(diào)用過(guò)期回調(diào)函數(shù)
             // 同key重復(fù)調(diào)用,會(huì)產(chǎn)生多個(gè)延時(shí)任務(wù),就是多次調(diào)用清理函數(shù),但是不會(huì)產(chǎn)生多次回調(diào),因?yàn)榛卣{(diào)取決于過(guò)期時(shí)間和回調(diào)函數(shù))
             scheduledExecutorService.schedule(this::clearExpireData, ms, TimeUnit.MILLISECONDS);
 
             //假定系統(tǒng)時(shí)間不修改前提下的過(guò)期時(shí)間
             return System.currentTimeMillis() + ms;
         } finally {
             expireKeysWriteLock.unlock();
         }
     }
     /**
      * 清理過(guò)期的數(shù)據(jù)
      * 調(diào)用時(shí)機(jī):設(shè)置了過(guò)期回調(diào)函數(shù)的key的延時(shí)任務(wù)調(diào)用
      */
     private void clearExpireData() {
         // 查找過(guò)期key
         Long curTimestamp = System.nanoTime() / ONE_MILLION;
         Map<Long, List<String>> expiredKeysMap = new LinkedHashMap<>();
         expireKeysReadLock.lock();
         try {
             // 過(guò)期時(shí)間在【從前至此刻】區(qū)間內(nèi)的都為過(guò)期的key
             // headMap():獲取從頭到 curTimestamp 元素的集合:不包含 curTimestamp
             SortedMap<Long, List<String>> sortedMap = this.expireKeysMap.headMap(curTimestamp, true);
             expiredKeysMap.putAll(sortedMap);
         } finally {
             expireKeysReadLock.unlock();
         }
 
         for (Map.Entry<Long, List<String>> entry : expiredKeysMap.entrySet()) {
             for (String key : entry.getValue()) {
                 // 刪除數(shù)據(jù)
                 Long val = this.remove(key);
                 // 首次調(diào)用刪除(val!=null,前提:val存儲(chǔ)值都不為null)
                 if (val != null) {
                     // 如果存在過(guò)期回調(diào)函數(shù),則執(zhí)行回調(diào)
                     ExpireCallback<String, Long> callback;
                     expireKeysReadLock.lock();
                     try {
                         callback = this.keyExpireCallbackMap.get(key);
                     } finally {
                         expireKeysReadLock.unlock();
                     }
                     if (callback != null) {
                         // 回調(diào)函數(shù)創(chuàng)建新線程調(diào)用,防止因?yàn)楹臅r(shí)太久影響線程池的清理工作
                         // 這里為什么不用線程池調(diào)用,因?yàn)镾cheduledThreadPoolExecutor線程池僅支持核心線程數(shù)設(shè)置,不支持非核心線程的添加
                         // 核心線程數(shù)用一個(gè)就可以完成清理工作,添加額外的核心線程數(shù)浪費(fèi)了
                         new Thread(() -> callback.onExpire(key, val), "callback-thread-" + UUID.randomUUID()).start();
                     }
                 }
                 this.keyExpireCallbackMap.remove(key);
             }
             this.expireKeysMap.remove(entry.getKey());
         }
     }
 }

FastMap通過(guò)ScheduledExecutorService接口實(shí)現(xiàn)定時(shí)線程任務(wù)的方式對(duì)請(qǐng)求處于過(guò)期時(shí)間的自動(dòng)刪除。

關(guān)于“SpringBoot基于過(guò)濾器和內(nèi)存如何實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“SpringBoot基于過(guò)濾器和內(nèi)存如何實(shí)現(xiàn)重復(fù)請(qǐng)求攔截功能”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI