java在后端处理请求幂等性问题(重复提交相同请求问题)

代码:

package com.boot.base.common.util.cache;

import org.apache.commons.collections.map.LRUMap;

import java.util.Collections;
import java.util.Map;
import java.util.Optional;

/**
 * @Description 处理请求幂等性
 * @Author admin
 * @Date 2021/3/3
 */
public class IdempotentUtils {

    /**
     * 默认的防抖动时间:3分钟(假设最佳情况为:每次所有请求对应的业务处理数据小于3分钟,不用默认则调用时单独指定!)
     * 注意:
     * 防抖动时间不能太长(因为有些请求正常流程中可以重复,如:补卡审批未通的情况下),
     * 也不能太短(如:最后的结算结果入库前的业务逻辑比较长时!)
     */
    public static final int DEFAULT_ANTI_JITTER_TIME = 3 * 60 * 1000;

    /**
     * 默认的缓存过期时间:10分钟
     */
    public static final int DEFAULT_EXPIRE_TIME = 10 * 60 * 1000;

    /**
     * 定期清理缓存过期数据的周期:60分钟
     */
    private static final int CLEAN_UP_PERIOD = 60 * 60 * 1000;

    /**
     * 缓存
     */
    private static final Map<Object, CacheObject> cache = Collections.synchronizedMap(new LRUMap(512));

    // 类加载时启动定期清理缓存的守护线程
    static {
        Thread cleanerThread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    Thread.sleep(CLEAN_UP_PERIOD);
                    // 清理过期的缓存
                    cache.entrySet().removeIf(e -> Optional.ofNullable(e.getValue()).map(CacheObject::isExpired).orElse(false));
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        cleanerThread.setName("IdempotentUtils-cleanerThread");
        cleanerThread.setDaemon(true);
        cleanerThread.start();
    }

    public static void main(String[] args) throws InterruptedException {
//        for (int i = 0; i < 3; i++) {
//            IdempotentUtils.check(i + "i", IdempotentUtils.class, System.currentTimeMillis());
//        }
//        System.out.println("从缓存中取出值:");
//        IdempotentUtils.cache.entrySet().forEach(System.out::println);
//        Thread.sleep(3000L);
//        System.out.println("3秒钟过后");
//        System.out.println("从缓存中取出值:");
//        IdempotentUtils.cache.entrySet().forEach(System.out::println);

//        Thread t = new Thread(() -> {
//            while (!Thread.currentThread().isInterrupted()) {
//                try {
//                    Thread.sleep(CLEAN_UP_PERIOD);
//                    // 清理过期的缓存
//                    System.out.println(Thread.currentThread().getName() + " sleep " + CLEAN_UP_PERIOD + " le");
//                } catch (InterruptedException e) {
//                    Thread.currentThread().interrupt();
//                }
//            }
//        });
//
//        t.setName("t");
//        t.setDaemon(true);
//        t.start();
//        Thread.sleep(7000L);
//        System.out.println("main ready to exit");
//        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
//            System.out.println("jvm exit");
//        }));

    }

    /**
     * 校验是否为连续地重复请求,若相邻的2次请求时间间隔小于默认防抖时间,则视为连续的重复请求
     *
     * @param req           请求数据
     * @param lock
     * @return true-重复, false-不重复
     */
    public static boolean check(Object req, Object lock) {
        return check(req, lock, DEFAULT_ANTI_JITTER_TIME, DEFAULT_EXPIRE_TIME);
    }

    /**
     * 校验是否为连续地重复请求,若相邻的2次请求时间间隔小于防抖时间antiJitterTime,则视为连续的重复请求
     *
     * @param req              请求数据
     * @param lock
     * @param antiJitterTime 防抖动时间,单位:毫秒
     * @return true-重复, false-不重复
     */
    public static boolean check(Object req, Object lock, int antiJitterTime) {
        return check(req, lock, antiJitterTime, DEFAULT_EXPIRE_TIME);
    }

    /**
     * 校验是否为连续地重复请求,若相邻的2次请求时间间隔小于防抖时间antiJitterTime,则视为连续的重复请求
     *
     * @param req              请求数据
     * @param lock
     * @param antiJitterTime   防抖动时间,单位:毫秒
     * @param expireTimeMillis 过期时间,单位:毫秒
     * @return true-重复, false-不重复
     */
    public static boolean check(Object req, Object lock, int antiJitterTime, int expireTimeMillis) {
        final long reqTimeMillis = System.currentTimeMillis();
        synchronized (lock) {
            if (cache.containsKey(req) && (reqTimeMillis - (long) cache.get(req).value <= antiJitterTime)) {
                final long expireTime = reqTimeMillis + expireTimeMillis;
                cache.put(req, new CacheObject(reqTimeMillis, expireTime));
                return true;
            }
            final long expireTime = reqTimeMillis + expireTimeMillis;
            cache.put(req, new CacheObject(reqTimeMillis, expireTime));
        }
        return false;
    }

    private static class CacheObject {

        private Object value;
        private long expireTime;

        public CacheObject(Object value, long expireTime) {
            this.value = value;
            this.expireTime = expireTime;
        }

        public boolean isExpired() {
            return System.currentTimeMillis() > this.expireTime;
        }

        public Object getValue() {
            return value;
        }

        public void setValue(Object value) {
            this.value = value;
        }
    }

}

参考:

参考1.:最简单的6种防止数据重复提交的方法!(干货)

参考2:Java内存缓存-通过Map定制简单缓存

参考3:Java 内存缓存工具类

参考4:谈谈什么是守护线程以及作用 ?

参考5:分布式场景中的幂等redis实现

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值