Untuk beberapa antara muka pelayan permintaan, mungkin terdapat permintaan berulang Jika ia adalah operasi pertanyaan, ia bukan masalah besar, tetapi jika ia melibatkan operasi tulis, apabila ia diulang, ia mungkin mempunyai akibat yang serius logik perniagaan, seperti transaksi Jika antara muka diminta berulang kali, pesanan berulang mungkin dibuat.
Di sini kami menggunakan penapis untuk menapis permintaan yang memasuki pelayan untuk menapis permintaan daripada klien yang sama kepada antara muka yang sama.
@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); } }
Dengan mewarisi penapis OncePerRequestFilter dalam Spring, ia memastikan bahawa hanya satu penapis dihantar dalam permintaan tanpa pelaksanaan berulang
Dengan mendapatkan badan permintaan Data adalah dikira dan nilai MD5 disimpan dalam FastMap berasaskan memori Kunci FastMap ialah nilai MD5. Nilai menunjukkan tempoh permintaan tidak boleh diulang. Dengan memanggil kaedah expired()
FastMap, tetapkan masa tamat tempoh permintaan dan fungsi panggil balik apabila ia tamat tempoh
@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()); } } }
FastMap menggunakan antara muka ScheduledExecutorService
untuk melaksanakan tugas urutan berjadual untuk memadam permintaan secara automatik semasa tamat tempoh masa.
Atas ialah kandungan terperinci Cara SpringBoot melaksanakan pemintasan permintaan berulang berdasarkan penapis dan memori. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!