Redis分布式锁使用与验证-Spring Boot 使用 AOP 防止重复提交的一种思路

本文介绍如何在SpringBoot中使用AOP防止重复提交,通过Redis实现分布式锁,确保同一时间只有一个请求能够执行,避免了多次提交带来的数据错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring Boot 使用 AOP 防止重复提交
https://www.toutiao.com/i6690413456086008331/
https://mp.weixin.qq.com/s?__biz=MzA5MTMyNTI0Nw==&mid=2649789979&idx=1&sn=1d6c053e736fb645305fff45b46ab0fc&chksm=887a204bbf0da95d1df8c5e508ec193211ee9b8d1123440a892628b2e969b569e0e3e09aa2a8&mpshare=1&scene=1&srcid=&pass_ticket=Tmlt2ow7POhLTEni%2F6xk34UgBXtGusNDwDRk7lDResKGN33%2F2bkn2XS39RkCEN%2FJ#rd

https://blog.youkuaiyun.com/u010096717/article/details/84562926 此链接也可参考

以上两个链接是思路的源头:

验证分布式锁是否可用的代码及分布式锁的实现(验证可用的链接,网上大部分代码不可用)
1.网上给的RedisTemplate+lua暂时未验证成

2.以下方式可行https://www.jianshu.com/p/9b5bc060d01b

public String tryLock(String name, long expire) {
        Assert.notNull(name, "Lock name must not be null");
        Assert.isTrue(expire>0, "expire must greater than 0");

        String token = UUID.randomUUID().toString();
        boolean result = redisTemplate.opsForValue().setIfAbsent(name, token);
        if(result) {//成功则设置ttl
            redisTemplate.expire(name, expire, TimeUnit.MILLISECONDS); 
            return token;
        }else {  //失败则检查一下锁有没有设置ttl,没有则设置上
            long milliSec = redisTemplate.getExpire(name, TimeUnit.MILLISECONDS);
            if (milliSec <= 0) {
                redisTemplate.expire(name, expire, TimeUnit.MILLISECONDS);
            }
            return null;
        }
    }

https://www.jianshu.com/p/6dd656e7051f
private static String LOCK_PREFIX = "prefix";

public boolean lock(String key) {

        String lock = LOCK_PREFIX + key;

        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {

            long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
            Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());


            if (acquire) {
                return true;
            } else {

                byte[] value = connection.get(lock.getBytes());

                if (Objects.nonNull(value) && value.length > 0) {

                    long expireTime = Long.parseLong(new String(value));

                    if (expireTime < System.currentTimeMillis()) {
                        byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());

                        return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
    }

验证代码1
package com.yan.test.redisToken;

import com.yan.annotation.NoRepeatSubmit;
import com.yan.utils.ResultUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @Description:
 * @Auther: Administrator
 * @Date: 2019/7/6
 */
@Aspect
@Component
public class RepeatSubmitAspect {

   private final static Logger logger = LoggerFactory.getLogger(RepeatSubmitAspect.class);

   @Autowired
   private RedisTemplate redisTemplate;

   @Pointcut("@annotation(noRepeatSubmit)")
   public void pointCut(NoRepeatSubmit noRepeatSubmit) {
   }

   @Around("pointCut(noRepeatSubmit)")
   public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
      int lockSeconds = noRepeatSubmit.lockTime();
      HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
      Assert.notNull(request, "request can not null");
      // 此处可以用token或者JSessionId
      String token = request.getHeader("Authorization");
      String path = request.getServletPath();
      String key = getKey("token", path);
      String clientId = getClientId();
      boolean isSuccess = tryLock(key, clientId, lockSeconds);
      if (isSuccess) {
         logger.info("tryLock success, key = [{}], clientId = [{}] lockSeconds=[{}]", key, clientId, lockSeconds);
         // 获取锁成功, 执行进程
         Object result;
         try {
            result = pjp.proceed();
         } finally {
            // 解锁
            releaseLock(key, clientId);
            logger.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
         }
         return result;
      } else {
         // 获取锁失败,认为是重复提交的请求
         logger.info("tryLock fail, key = [{}]", key);
         return ResultUtils.error().toString();// ResultBean(ResultBean.FAIL, "重复请求,请稍后再试", null);
      }
   }

   private String getKey(String token, String path) {
      return token + path;
   }

   private String getClientId() {
      return UUID.randomUUID().toString();
   }

   public boolean tryLock(String key, String value, long time) {

//    try {
////         redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
//       if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
//          //这里存在宕机风险,导致设置有效期失败
//          redisTemplate.expire(key, time, TimeUnit.SECONDS);
//          System.out.println("-----------------这里存在宕机风险");
//       }
//       Object obj = redisTemplate.opsForValue().get(key);
//       if (ObjectUtils.isEmpty(obj)) {
//          return false;
//       }
//    } catch (Exception e) {
//       logger.error("tryLock error", e);
//       return false;
//    }
//    return true;

      //https://www.jianshu.com/p/9b5bc060d01b
      boolean result = redisTemplate.opsForValue().setIfAbsent(key, value);
      if (result) {//成功则设置ttl
         redisTemplate.expire(key, time, TimeUnit.SECONDS);
         return true;
      } else {  //失败则检查一下锁有没有设置ttl,没有则设置上
         long milliSec = redisTemplate.getExpire(key, TimeUnit.SECONDS);
         if (milliSec <= 0) {
            redisTemplate.expire(key, time, TimeUnit.MILLISECONDS);
         }
         return false;
      }

      //https://www.jianshu.com/p/6dd656e7051f
//    return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
//       String lock = "redis-lock-1";
//       long expireAt = System.currentTimeMillis() + 50 * 1000 + 1;
//       Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
//
//
//       if (acquire) {
//          return true;
//       } else {
//
//          byte[] value1 = connection.get(lock.getBytes());
//
//          if (Objects.nonNull(value1) && value1.length > 0) {
//
//             long expireTime = Long.parseLong(new String(value1));
//
//             if (expireTime < System.currentTimeMillis()) {
//                byte[] oldValue = connection.getSet(lock.getBytes(), String.valueOf(expireAt).getBytes());
//
//                return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
//             }
//          }
//       }
//       return false;
//    });

   }

   void releaseLock(String key, String value) {
      Object obj = redisTemplate.opsForValue().get(key);
      if (ObjectUtils.isEmpty(obj)) {
         return;
      }
      redisTemplate.delete(key);
   }


// private static final Long SUCCESS = 1L;
//
//
// /**
//  * 获取锁
//  *
//  * @param lockKey
//  * @param value
//  * @param expireTime:单位-秒
//  * @return
//  */
//
// public boolean tryLock(String lockKey, String value, int expireTime) {
//    boolean ret = false;
//    try {
//       String script = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
//       RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
//       Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);
//       if (SUCCESS.equals(result)) {
//          return true;
//       }
//    } catch (Exception e) {
//       return ret;
//    }
//    return ret;
// }
//
//
// /**
//  * 释放锁
//  *
//  * @param lockKey
//  * @param value
//  * @return
//  */
// public boolean releaseLock(String lockKey, String value) {
//    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//    RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
//    Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);
//    if (SUCCESS.equals(result)) {
//       return true;
//    }
//    return false;
// }

}

多线程验证代码2
package com.yan.test.redisToken;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Description:
 * @Auther: Administrator
 * @Date: 2019/7/6
 */
@Component
public class RunTest implements ApplicationRunner {
   private static final Logger logger = LoggerFactory.getLogger(RunTest.class);

   @Autowired
   private RestTemplate restTemplate;

   @Override
   public void run(ApplicationArguments args) throws Exception {
      System.out.println("执行多线程测试");
      String url = "http://localhost:8090/getIndex1";
      CountDownLatch countDownLatch = new CountDownLatch(1);
      ExecutorService executorService = Executors.newFixedThreadPool(10);
      for (int i = 0; i < 10; i++) {
         String userId = "userId" + i;
         HttpEntity request = buildRequest(userId);
         executorService.submit(() -> {
            try {
               //countDownLatch.await();
               System.out.println("Thread:" + Thread.currentThread().getName() + ", time:" + System.currentTimeMillis());
               ResponseEntity<String> response = restTemplate.getForEntity(url, String.class, "");
               System.out.println("Thread:" + Thread.currentThread().getName() + ", body=" + response.getBody());
            } catch (Exception e) {
               logger.error("ApplicationRunner run fail", e);
            }
         });
      }
      countDownLatch.countDown();
   }

   private HttpEntity buildRequest(String userId) {
      HttpHeaders headers = new HttpHeaders();
      headers.setContentType(MediaType.APPLICATION_JSON);
      headers.set("Authorization", "yourToken");
      Map<String, Object> body = new HashMap<>();
      body.put("userId", userId);
      return new HttpEntity<>(body, headers);
   }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值