Java+Redisson封装高效的幂等性组件,简单易用

Java基于Redisson实现幂等性组件

何为幂等?

**幂等(Idempotent)**是一个数学和计算机科学中的概念,主要描述的是一种属性,即一个操作可以被多次应用,但结果仍然保持不变。在数学中,幂等通常用于描述某些运算或函数的特性。例如,对于单目运算,如果一个运算对于在范围内的所有数,多次进行该运算所得的结果和进行一次该运算所得的结果相同,那么该运算就是幂等的。在双目运算中,如果当参与运算的两个值是等值的情况下,运算结果与参与运算的两个值相等,那么该运算也是幂等的。

实现幂等性的方式

主要有三种:

  1. 基于分布式锁
  2. 利用MySQL的去重性,做去重表
  3. 前端请求带Token,后端根据Token做幂等校验

基于Redisson分布式锁实现幂等

幂等注解:

@Target(value = {ElementType.TYPE, ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface RepeatExecuteLimit {

    String name() default "";

    String[] keys();
	// 保持幂等的时间
    long durationTime() default 0L;

    String message() default "提交频繁,请稍后重试";
}

切面类:

@Slf4j
@Aspect
@Order(-11)
public class RepeatExecuteLimitAspect {

    private LocalLockCache localLockCache;

    private RedissonClient redissonClient;

    private ExpressionParser parser;

    public RepeatExecuteLimitAspect(LocalLockCache localLockCache, RedissonClient redissonClient) {
        this.localLockCache = localLockCache;
        this.redissonClient = redissonClient;
        this.parser = new SpelExpressionParser();
    }

    @Around("@annotation(repeatLimit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatExecuteLimit repeatLimit) throws Throwable {
        long durationTime = repeatLimit.durationTime();
        String message = repeatLimit.message();
        Object obj;
        String lockName = getLockName(joinPoint, repeatLimit);
        String repatFlageName = "repeat_flag" + lockName;

        // 判断幂等标识是否存在
        String flag = (String) redissonClient.getBucket(repatFlageName).get();
        if ("success".equals(flag)) {
            // 触发了幂等
            throw new RuntimeException(message);
        }
        // 没有触发幂等,先获取本地锁
        ReentrantLock localLock = localLockCache.getLock(lockName, true);
        if (!localLock.tryLock()) {
            // 触发了幂等
            throw new RuntimeException(message);
        }
        try {
            // 获取分布式锁
            RLock lock = redissonClient.getLock(lockName);
            if (!lock.tryLock()) {
                // 触发幂等
                throw new RuntimeException(message);
            }
            try {
                obj = joinPoint.proceed();
                if (durationTime > 0) {
                    // 设置幂等标志,同时设置过期时间
                    redissonClient.getBucket(repatFlageName).set("success", durationTime, TimeUnit.SECONDS);
                }
                return obj;
            } finally {
                // 分布式锁解锁
                lock.unlock();
            }
        } finally {
            // 释放本地锁
            localLock.unlock();
        }
    }

    private String getLockName(ProceedingJoinPoint joinPoint, RepeatExecuteLimit repeatLimit) {
        String name = repeatLimit.name();
        String[] keys = repeatLimit.keys();
        StringBuilder sb = new StringBuilder(name);
        for (String key : keys) {
            EvaluationContext context = new StandardEvaluationContext();
            Object[] args = joinPoint.getArgs();
            String[] paramNames = new DefaultParameterNameDiscoverer().getParameterNames(((MethodSignature) joinPoint.getSignature()).getMethod());
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
            String value = parser.parseExpression(key).getValue(context, String.class);
            sb.append("_").append(value);
        }
        return sb.toString();
    }

}

本地锁使用Caffine做实现,Caffine是线程安全的,加上缓存过期,清理不常用的锁:

public class LocalLockCache {

    private Cache<String, ReentrantLock> localLockCache;

    @Value("${durationTime:48}")
    private Integer durationTime;

    @PostConstruct
    public void init() {
        this.localLockCache = Caffeine.newBuilder()
                .expireAfterWrite(durationTime, TimeUnit.HOURS)
                .build();
    }

    public ReentrantLock getLock(String lockKey, boolean fair) {
        return localLockCache.get(lockKey, key -> new ReentrantLock(fair));
    }
}

自动装配类:

public class RepateAutoConfig {

    @Bean
    public LocalLockCache localLockCache() {
        return new LocalLockCache();
    }

    @Bean
    public RepeatExecuteLimitAspect repeatExecuteLimitAspect(LocalLockCache localLockCache, RedissonClient redissonClient) {
        return new RepeatExecuteLimitAspect(localLockCache, redissonClient);
    }

}

在resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中声明自动装配类信息:

com.jie.config.RepateAutoConfig

至此,一个幂等组件就封装好了。

测试

编写测试类:

@RestController
public class TestController {

    @GetMapping("/test")
    @RepeatExecuteLimit(name = "test", keys = {"#test"}, durationTime = 10L, message = "提交频繁,请稍后重试")
    public String test() {
        return "hello world";
    }
}

浏览器调用方法,多次刷新会看到”提交频繁,请稍后重试“的信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值