Maison > Java > javaDidacticiel > le corps du texte

Comment SpringBoot implémente l'interception de requêtes répétées basée sur des filtres et de la mémoire

PHPz
Libérer: 2023-05-17 20:25:11
avant
1377 Les gens l'ont consulté

Pour certaines interfaces qui sollicitent le serveur, il peut y avoir des requêtes répétées. S'il s'agit d'une opération de requête, ce n'est pas grave. Par contre, s'il s'agit d'une opération d'écriture, une fois répétée, cela peut avoir de graves conséquences sur le serveur. logique métier. Par exemple, si l'interface de transaction, les demandes répétées peuvent entraîner la passation de commandes en double.

Ici, nous utilisons des filtres pour filtrer les requêtes entrant sur le serveur afin de filtrer les requêtes du même client vers la même interface.

 @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("请求的MD5值为:{}", hex);
             if (fastMap.containsKey(hex)) {
                 throw new IllegalStateException("请求重复,请稍后重试!");
             }
             fastMap.put(hex, 10 * 1000L);
             fastMap.expired(hex, 10 * 1000L, (key, val) -> System.out.println("map:" + fastMap + ",删除的key:" + key + ",线程名:" + Thread.currentThread().getName()));
         }
         log.info("请求的 address:{}", address);
         chain.doFilter(request, response);
     }
 }
Copier après la connexion

En héritant du filtre OncePerRequestFilter au Spring, il garantit qu'un seul filtre est transmis dans une requête sans exécution répétée

En obtenant les données dans le corps de la requête, la valeur MD5 est calculée et stockée dans la mémoire implémentation Dans FastMap, la clé de FastMap est la valeur MD5, et la valeur indique la durée pendant laquelle la demande ne peut pas être répétée. La configuration ici est que la demande ne peut pas être répétée dans les 10 secondes. En appelant la méthode expired() de FastMap, définissez le délai d'expiration de la requête et la fonction de rappel à son expirationexpired()方法,设置该请求的过期时间和过期时的回调函数

 @Component
 public class FastMap {
     /**
      * 按照时间顺序保存了会过期key集合,为了实现快速删除,结构:时间戳 -> key 列表
      */
     private final TreeMap<Long, List<String>> expireKeysMap = new TreeMap<>();
     /**
      * 保存会过期key的过期时间
      */
     private final Map<String, Long> keyExpireMap = new ConcurrentHashMap<>();
     /**
      * 保存键过期的回调函数
      */
     private final HashMap<String, ExpireCallback<String, Long>> keyExpireCallbackMap = new HashMap<>();
     private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
     /**
      * 数据写锁
      */
     private final Lock dataWriteLock = readWriteLock.writeLock();
     /**
      * 数据读锁
      */
     private final Lock dataReadLock = readWriteLock.readLock();
     private final ReentrantReadWriteLock expireKeysReadWriteLock = new ReentrantReadWriteLock();
     /**
      * 过期key写锁
      */
     private final Lock expireKeysWriteLock = expireKeysReadWriteLock.writeLock();
     /**
      * 过期key读锁
      */
     private final Lock expireKeysReadLock = expireKeysReadWriteLock.readLock();
     /**
      * 定时执行服务(全局共享线程池)
      */
     private volatile ScheduledExecutorService scheduledExecutorService;
     /**
      * 100万,1毫秒=100万纳秒
      */
     private static final int ONE_MILLION = 100_0000;
     /**
      * 构造器,enableExpire配置是否启用过期,不启用排序
      */
     public FastMap() {
         this.init();
     }
     /**
      * 初始化
      */
     private void init() {
         // 双重校验构造一个单例的scheduledExecutorService
         if (scheduledExecutorService == null) {
             synchronized (FastMap.class) {
                 if (scheduledExecutorService == null) {
                     // 启用定时器,定时删除过期key,1秒后启动,定时1秒, 因为时间间隔计算基于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) {
         // 对过期数据写上锁
         expireKeysWriteLock.lock();
         try {
             // 使用nanoTime消除系统时间的影响,转成毫秒存储降低timeKey数量,过期时间精确到毫秒级别
             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) {
                 // 设置的过期回调函数
                 this.keyExpireCallbackMap.put(key, callback);
             }
             // 使用延时服务调用清理key的函数,可以及时调用过期回调函数
             // 同key重复调用,会产生多个延时任务,就是多次调用清理函数,但是不会产生多次回调,因为回调取决于过期时间和回调函数)
             scheduledExecutorService.schedule(this::clearExpireData, ms, TimeUnit.MILLISECONDS);
 
             //假定系统时间不修改前提下的过期时间
             return System.currentTimeMillis() + ms;
         } finally {
             expireKeysWriteLock.unlock();
         }
     }
     /**
      * 清理过期的数据
      * 调用时机:设置了过期回调函数的key的延时任务调用
      */
     private void clearExpireData() {
         // 查找过期key
         Long curTimestamp = System.nanoTime() / ONE_MILLION;
         Map<Long, List<String>> expiredKeysMap = new LinkedHashMap<>();
         expireKeysReadLock.lock();
         try {
             // 过期时间在【从前至此刻】区间内的都为过期的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()) {
                 // 删除数据
                 Long val = this.remove(key);
                 // 首次调用删除(val!=null,前提:val存储值都不为null)
                 if (val != null) {
                     // 如果存在过期回调函数,则执行回调
                     ExpireCallback<String, Long> callback;
                     expireKeysReadLock.lock();
                     try {
                         callback = this.keyExpireCallbackMap.get(key);
                     } finally {
                         expireKeysReadLock.unlock();
                     }
                     if (callback != null) {
                         // 回调函数创建新线程调用,防止因为耗时太久影响线程池的清理工作
                         // 这里为什么不用线程池调用,因为ScheduledThreadPoolExecutor线程池仅支持核心线程数设置,不支持非核心线程的添加
                         // 核心线程数用一个就可以完成清理工作,添加额外的核心线程数浪费了
                         new Thread(() -> callback.onExpire(key, val), "callback-thread-" + UUID.randomUUID()).start();
                     }
                 }
                 this.keyExpireCallbackMap.remove(key);
             }
             this.expireKeysMap.remove(entry.getKey());
         }
     }
 }
Copier après la connexion

FastMap通过ScheduledExecutorServicerrreee

FastMap implémente les tâches de thread planifiées via l'interface ScheduledExecutorService pour mettre en œuvre la demande Suppression automatique après le délai d'expiration. 🎜

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:yisu.com
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!