SprintBoot2.0中几种redis 分布式锁处理

SpringBoot2.0以后,redis 的库替换为了lettuce 

本文主要转载一些网上比较可靠的实现方式,希望对大家有帮助

实现方式1

定义注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Ide{
    /**
     * 设置请求锁定时间,超时后自动释放锁
     * @return
     */
    int lockTime() default 10;
}

AOP 实现 注解 @Ide 的拦截处理

/**
 * 接口幂等性的 -- 分布式锁实现
 */
@Slf4j
@Aspect
@Component
public class ReqSubmitAspect {

    @Autowired
    private RedisLock redisLock;

    @Pointcut("@annotation(com.laiease.common.annotation.Ide)")
    public void idePointCut() {
    }

    @Around("idePointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 使用分布式锁 机制-实现
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        Ide ide = method.getAnnotation(Ide.class);
        int lockSeconds = ide.lockTime();

        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        AssertUtils.notNull(request, "request can not null");

        // 获取请求的凭证,本项目中使用的JWT,可对应修改
        String token = request.getHeader("Token");
        String requestURI = request.getRequestURI();

        String key = getIdeKey(token, requestURI);
        String clientId = CmUtil.getUUID();
        
        // 获取锁
        boolean lock = redisLock.tryLock(key, clientId, lockSeconds);
        log.info("tryLock key = [{}], clientId = [{}]", key, clientId);

        if (lock) {
            log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
            // 获取锁成功
            Object result;
            try {
                // 执行进程
                result = joinPoint.proceed();
            } finally {
                // 解锁
                redisLock.releaseLock(key, clientId);
                log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
            }
            return result;
        } else {
            // 获取锁失败,认为是重复提交的请求
            log.info("tryLock fail, key = [{}]", key);
            throw  new RuntimeException("重复请求,请稍后再试!");
        }
    }

    private String getIdeKey(String token, String requestURI) {
        return token + requestURI;
    }
}

redis 分布式锁工具类

@Component
public class RedisLock {

  private static final Long RELEASE_SUCCESS = 1L;
  private static final String LOCK_SUCCESS = "OK";
  private static final String SET_IF_NOT_EXIST = "NX";
  // 当前设置 过期时间单位, EX = seconds; PX = milliseconds
  private static final String SET_WITH_EXPIRE_TIME = "EX";
  //lua
  private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

  @Autowired
  private StringRedisTemplate redisTemplate;
  /**
   * 该加锁方法仅针对单实例 Redis 可实现分布式加锁
   * 对于 Redis 集群则无法使用
   * <p>
   * 支持重复,线程安全
   *
   * @param lockKey  加锁键
   * @param clientId 加锁客户端唯一标识(采用UUID)
   * @param seconds  锁过期时间
   * @return
   */
  public boolean tryLock(String lockKey, String clientId, long seconds) {
      return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
//            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
          Object nativeConnection = redisConnection.getNativeConnection();
          RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
          byte[] keyByte = stringRedisSerializer.serialize(lockKey);
          byte[] valueByte = stringRedisSerializer.serialize(clientId);

          // lettuce连接包下 redis 单机模式
          if (nativeConnection instanceof RedisAsyncCommands) {
              RedisAsyncCommands connection = (RedisAsyncCommands) nativeConnection;
              RedisCommands commands = connection.getStatefulConnection().sync();
              String result = commands.set(keyByte, valueByte, SetArgs.Builder.nx().ex(seconds));
              if (LOCK_SUCCESS.equals(result)) {
                  return true;
              }
          }
          // lettuce连接包下 redis 集群模式
          if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
              RedisAdvancedClusterAsyncCommands connection = (RedisAdvancedClusterAsyncCommands) nativeConnection;
              RedisAdvancedClusterCommands commands = connection.getStatefulConnection().sync();
              String result = commands.set(keyByte, valueByte, SetArgs.Builder.nx().ex(seconds));
              if (LOCK_SUCCESS.equals(result)) {
                  return true;
              }
          }

          if (nativeConnection instanceof JedisCommands) {
              JedisCommands jedis = (JedisCommands) nativeConnection;
              String result = jedis.set(lockKey, clientId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, seconds);
              if (LOCK_SUCCESS.equals(result)) {
                  return true;
              }
          }
          return false;
      });
  }

  /**
   * 与 tryLock 相对应,用作释放锁
   *
   * @param lockKey
   * @param clientId
   * @return
   */
  public boolean releaseLock(String lockKey, String clientId) {
      DefaultRedisScript<Integer> redisScript = new DefaultRedisScript<>();
      redisScript.setScriptText(RELEASE_LOCK_SCRIPT);
      redisScript.setResultType(Integer.class);
//        Integer execute = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), clientId);

      Object execute = redisTemplate.execute((RedisConnection connection) -> connection.eval(
              RELEASE_LOCK_SCRIPT.getBytes(),
              ReturnType.INTEGER,
              1,
              lockKey.getBytes(),
              clientId.getBytes()));
      if (RELEASE_SUCCESS.equals(execute)) {
          return true;
      }
      return false;
  }
}

 

实现方式2

为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
1、互斥性。在任意时刻,只有一个客户端能持有锁。
2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
3、具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
4、解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

package com.hz.tgb.data.redis.lock;

import cn.hutool.core.util.IdUtil;
import com.hz.tgb.entity.Book;
import com.hz.tgb.spring.SpringUtils;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.TimeoutUtils;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;

import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Supplier;

/**
 * Redis分布式锁 - 集群版
 *
 * @author hezhao on 2019.11.13
 */
@Component
public class RedisClusterLockUtil {

    /*
    为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
    1、互斥性。在任意时刻,只有一个客户端能持有锁。
    2、不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
    3、具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
    4、解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。
     */

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

    private static RedisTemplate<String, Object> cacheTemplate;

    /** OK: Redis操作是否成功 */
    private static final String REDIS_OK = "OK";
    /** CONN_NOT_FOUND: Redis链接类型不匹配 */
    private static final String REDIS_CONN_NOT_FOUND = "CONN_NOT_FOUND";
    /** 解锁是否成功 */
    private static final Long RELEASE_SUCCESS = 1L;

    /** 解锁Lua脚本 */
    private static final String UNLOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    /**
     * The number of nanoseconds for which it is faster to spin
     * rather than to use timed park. A rough estimate suffices
     * to improve responsiveness with very short timeouts.
     */
    private static final long spinForTimeoutThreshold = 1000000L;

    /**
     * 加锁
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @param expireTime 缓存过期时间
     * @param unit 时间单位
     * @return true: 加锁成功, false: 加锁失败
     */
    @SuppressWarnings("all")
    public static boolean lock(String lockKey, String requestId, long expireTime, TimeUnit unit) {
        // 加锁和设置过期时间必须是原子操作,否则在高并发情况下或者Redis突然崩溃会导致数据错误。
        try {
            // 以毫秒作为过期时间
            long millisecond = TimeoutUtils.toMillis(expireTime, unit);
            String result = execute(connection -> {
                Object nativeConnection = connection.getNativeConnection();
                RedisSerializer<Object> keySerializer = (RedisSerializer<Object>) getRedisTemplate().getKeySerializer();
                RedisSerializer<Object> valueSerializer = (RedisSerializer<Object>) getRedisTemplate().getValueSerializer();
                // springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包
                // lettuce连接包下序列化键值,否知无法用默认的ByteArrayCodec解析
                byte[] keyByte = keySerializer.serialize(lockKey);
                byte[] valueByte = valueSerializer.serialize(requestId);
                //lettuce连接包,单机模式,ex为秒,px为毫秒
                if (nativeConnection instanceof RedisAsyncCommands) {
                    RedisAsyncCommands commands = (RedisAsyncCommands)nativeConnection;
                    // 同步方法执行、setnx禁止异步
                    return commands.getStatefulConnection().sync().set(keyByte, valueByte, SetArgs.Builder.nx().px(millisecond));
                } else if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
                    // lettuce连接包,集群模式,ex为秒,px为毫秒
                    RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
                    return clusterAsyncCommands.getStatefulConnection().sync().set(keyByte, valueByte, SetArgs.Builder.nx().px(millisecond));
                }
                return REDIS_CONN_NOT_FOUND;
            });

            // 如果链接类型匹配不上,使用默认加锁方法
            if (Objects.equals(result, REDIS_CONN_NOT_FOUND)) {
                return getRedisTemplate().opsForValue().setIfAbsent(lockKey, requestId)
                        && getRedisTemplate().expire(lockKey, expireTime, unit);
            }

            return REDIS_OK.equals(result);
        } catch (Exception e) {
            logger.error("RedisLockUtil lock 加锁失败", e);
        }
        return false;
    }

    /**
     * 解锁
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @return true: 解锁成功, false: 解锁失败
     */
    @SuppressWarnings("all")
    public static boolean unLock(String lockKey, String requestId) {
        try {
            // 使用Lua脚本实现解锁的原子性,如果requestId相等则解锁
            Object result = execute(connection -> {
                Object nativeConnection = connection.getNativeConnection();
                RedisSerializer<Object> keySerializer = (RedisSerializer<Object>) getRedisTemplate().getKeySerializer();
                RedisSerializer<Object> valueSerializer = (RedisSerializer<Object>) getRedisTemplate().getValueSerializer();
                // springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包
                // lettuce连接包下序列化键值,否知无法用默认的ByteArrayCodec解析
                byte[] keyByte = keySerializer.serialize(lockKey);
                byte[] valueByte = valueSerializer.serialize(requestId);
                //lettuce连接包,单机模式
                if (nativeConnection instanceof RedisAsyncCommands) {
                    RedisAsyncCommands commands = (RedisAsyncCommands)nativeConnection;
                    // 同步方法执行、setnx禁止异步
                    byte[][] keys = {keyByte};
                    byte[][] values = {valueByte};
                    return commands.getStatefulConnection().sync().eval(UNLOCK_LUA_SCRIPT, ScriptOutputType.INTEGER, keys , values);
                } else if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
                    // lettuce连接包,集群模式
                    RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
                    byte[][] keys = {keyByte};
                    byte[][] values = {valueByte};
                    return clusterAsyncCommands.getStatefulConnection().sync().eval(UNLOCK_LUA_SCRIPT, ScriptOutputType.INTEGER, keys , values);
                }
                return REDIS_CONN_NOT_FOUND;
            });

            // 如果链接类型匹配不上,使用默认解锁方法
            if (Objects.equals(result, REDIS_CONN_NOT_FOUND)) {
                return getRedisTemplate().delete(lockKey);
            }

            return Objects.equals(RELEASE_SUCCESS, result);
        } catch (Exception e) {
            logger.error("RedisLockUtil unLock 解锁失败", e);
        }
        return false;
    }

    /**
     * 阻塞锁,拿到锁后执行业务逻辑。注意:超时返回null,程序会继续往下执行
     * @param callback 业务处理逻辑,入参默认为NULL
     * @param lockKey 锁键
     * @param timeout 超时时长, 缓存过期时间默认等于超时时长
     * @param unit 时间单位
     * @return R
     */
    public static <R> R tryLock(Supplier<R> callback, String lockKey, long timeout, TimeUnit unit) {
        return tryLock(callback, lockKey, IdUtil.fastSimpleUUID(), timeout, timeout, unit, TimeOutProcess.DEFAULT);
    }

    /**
     * 阻塞锁,拿到锁后执行业务逻辑。注意:超时会抛出异常
     * @param callback 业务处理逻辑,入参默认为NULL
     * @param lockKey 锁键
     * @param timeout 超时时长, 缓存过期时间默认等于超时时长
     * @param unit 时间单位
     * @return R
     */
    public static <R> R tryLockTimeout(Supplier<R> callback, String lockKey, long timeout, TimeUnit unit) {
        return tryLock(callback, lockKey, IdUtil.fastSimpleUUID(), timeout, timeout, unit, TimeOutProcess.THROW_EXCEPTION);
    }

    /**
     * 阻塞锁,拿到锁后执行业务逻辑。注意:超时会给予补偿,即处理正常逻辑
     * @param callback 业务处理逻辑,入参默认为NULL
     * @param lockKey 锁键
     * @param timeout 超时时长, 缓存过期时间默认等于超时时长
     * @param unit 时间单位
     * @return R
     */
    public static <R> R tryLockCompensate(Supplier<R> callback, String lockKey, long timeout, TimeUnit unit) {
        return tryLock(callback, lockKey, IdUtil.fastSimpleUUID(), timeout, timeout, unit, TimeOutProcess.CARRY_ON);
    }

    /**
     * 阻塞锁,拿到锁后执行业务逻辑
     * @param callback 业务处理逻辑
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @param timeout 超时时长
     * @param expireTime 缓存过期时间
     * @param unit 时间单位
     * @param timeoutProceed 超时处理逻辑
     * @return R
     */
    public static <R> R tryLock(Supplier<R> callback, String lockKey, String requestId,
                                long timeout, long expireTime, TimeUnit unit, TimeOutProcess timeoutProceed) {
        boolean lockFlag = false;
        try {
            lockFlag = tryLock(lockKey, requestId, timeout, expireTime, unit);
            if(lockFlag){
                return callback.get();
            }
        } finally {
            if (lockFlag){
                unLock(lockKey, requestId);
            }
        }
        if (timeoutProceed == null) {
            return null;
        }
        if (timeoutProceed == TimeOutProcess.THROW_EXCEPTION) {
            throw new RedisLockTimeOutException();
        }
        if (timeoutProceed == TimeOutProcess.CARRY_ON) {
            return callback.get();
        }
        return null;
    }

    /**
     * 阻塞锁
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @param timeout 超时时长, 缓存过期时间默认等于超时时长
     * @param unit 时间单位
     * @return true: 加锁成功, false: 加锁失败
     */
    public static boolean tryLock(String lockKey, String requestId, long timeout, TimeUnit unit) {
        return tryLock(lockKey, requestId, timeout, timeout, unit);
    }

    /**
     * 阻塞锁
     * @param lockKey 锁键
     * @param requestId 请求唯一标识
     * @param timeout 超时时长
     * @param expireTime 缓存过期时间
     * @param unit 时间单位
     * @return true: 加锁成功, false: 加锁失败
     */
    public static boolean tryLock(String lockKey, String requestId, long timeout, long expireTime, TimeUnit unit) {
        long nanosTimeout = unit.toNanos(timeout);
        if (nanosTimeout <= 0L) {
            return false;
        }
        final long deadline = System.nanoTime() + nanosTimeout;
        for (;;) {
            // 获取到锁
            if (lock(lockKey, requestId, expireTime, unit)) {
                return true;
            }
            // 判断是否需要继续阻塞, 如果已超时则返回false
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L) {
                return false;
            }
            // 休眠1毫秒
            if (nanosTimeout > spinForTimeoutThreshold) {
                LockSupport.parkNanos(spinForTimeoutThreshold);
            }
        }
    }

    public static <T> T execute(RedisCallback<T> action) {
        return getRedisTemplate().execute(action);
    }

    public static RedisTemplate<String, Object> getRedisTemplate() {
        if (cacheTemplate == null) {
            cacheTemplate = SpringUtils.getBean("redisTemplate", RedisTemplate.class);
        }
        return cacheTemplate;
    }

    public static void main(String[] args) {
        Book param = new Book();
        param.setBookId(1234);
        param.setName("西游记");

        Boolean flag = tryLock(() -> {
            int bookId = param.getBookId();
            System.out.println(bookId);
            // TODO ...
            return true;
        }, "BOOK-" + param.getBookId(), 3, TimeUnit.SECONDS);

        System.out.println(flag);
    }

    /**
     * 超时处理逻辑
     */
    public enum TimeOutProcess {
        /** 默认,超时返回null,程序会继续往下执行 */
        DEFAULT,
        /** 超时会抛出异常 */
        THROW_EXCEPTION,
        /** 超时会给予补偿,即处理正常逻辑 */
        CARRY_ON,

    }
}

 

实现方式3

分享基于redis一个分布式锁实现,

特点:

1/ 非重入,等待锁时使用线程sleep

2/使用  redis的  SETNX   带过期时间的方法

3/使用ThreadLocal保存锁的值,在锁超时时,防止删除其他线程的锁,使用lua 脚本保证原子性;

实现如下,欢迎提出指正:


import io.lettuce.core.RedisFuture;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import io.lettuce.core.api.async.RedisScriptingAsyncCommands;
import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;

import java.nio.charset.StandardCharsets;
import java.util.UUID;
import java.util.concurrent.ExecutionException;


/**
 * 只支持springboot2 以后的Redis分布式锁(lettuce底层,不支持jedis)
 * 不支持重入
 *
 * 经过测试,在本地redis情况下,一次lock和releaseLock 总花费约3ms
 */
public class RedisLock extends AbstractLock {

    private RedisTemplate<String, Object> redisTemplate;
    private ThreadLocal<String> lockValue = new ThreadLocal<>();
    private final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private static final String REDIS_LIB_MISMATCH = "Failed to convert nativeConnection. " +
            "Is your SpringBoot main version > 2.0 ? Only lib:lettuce is supported.";
    private static final String UNLOCK_LUA;
    static {
        StringBuilder sb = new StringBuilder();
        sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
        sb.append("then ");
        sb.append("    return redis.call(\"del\",KEYS[1]) ");
        sb.append("else ");
        sb.append("    return 0 ");
        sb.append("end ");
        UNLOCK_LUA = sb.toString();
    }

    public RedisLock(RedisTemplate<String, Object> redisTemplate) {
        Assert.notNull(redisTemplate,"redisTemplate should not be null.");
        this.redisTemplate = redisTemplate;
    }

    /**
     * 加锁
     * @param key
     * @param expireSeconds
     * @param retryTimes
     * @param sleepMillis
     * @return
     */
    @Override
    public boolean lock(String key, long expireSeconds, int retryTimes, long sleepMillis) {
        boolean result = tryLock(key, expireSeconds);
        while((!result) && retryTimes-- > 0){
            try {
                logger.debug("Lock failed, retrying..." + retryTimes);
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                return false;
            }
            result = tryLock(key, expireSeconds);
        }
        return result;
    }

    /**
     * 尝试Lock
     * @param key
     * @param expireSeconds
     * @return
     */
    @SuppressWarnings("unchecked")
    private boolean tryLock(String key, long expireSeconds) {

        String uuid = UUID.randomUUID().toString();
        try {
            String result = redisTemplate.execute(new RedisCallback<String>() {
                @Override
                public String doInRedis(RedisConnection connection) throws DataAccessException {
                    try{
                        Object nativeConnection = connection.getNativeConnection();

                        byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
                        byte[] valueByte = uuid.getBytes(StandardCharsets.UTF_8);

                        String resultString = "";
                        if(nativeConnection instanceof RedisAsyncCommands){
                            RedisAsyncCommands command = (RedisAsyncCommands) nativeConnection;
                            resultString = command
                                    .getStatefulConnection()
                                    .sync()
                                    .set(keyByte, valueByte, SetArgs.Builder.nx().ex(expireSeconds));
                        }else if(nativeConnection instanceof RedisAdvancedClusterAsyncCommands){
                            RedisAdvancedClusterAsyncCommands clusterAsyncCommands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
                            resultString = clusterAsyncCommands
                                    .getStatefulConnection()
                                    .sync()
                                    .set(keyByte, keyByte, SetArgs.Builder.nx().ex(expireSeconds));
                        }else{
                            logger.error(REDIS_LIB_MISMATCH);
                        }
                        return resultString;
                    }catch (Exception e){
                        logger.error("Failed to lock, closing connection",e);
                        closeConnection(connection);
                        return "";
                    }
                }
            });
            boolean eq = "OK".equals(result);
            if(eq) {
                lockValue.set(uuid);
            }
            return eq;
        } catch (Exception e) {
            logger.error("Set redis exception", e);
            return false;
        }
    }

    /**
     * 释放锁
     * 有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
     * 使用lua脚本删除redis中匹配value的key
     * @param key
     * @return false:   锁已不属于当前线程  或者 锁已超时
     */
    @SuppressWarnings("unchecked")
    @Override
    public boolean releaseLock(String key) {
        try {
            String lockValue = this.lockValue.get();
            if(lockValue==null){
                return false;
            }
            byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
            byte[] valueBytes = lockValue.getBytes(StandardCharsets.UTF_8);
            Object[] keyParam = new Object[]{keyBytes};

            Long result = redisTemplate.execute(new RedisCallback<Long>() {
                public Long doInRedis(RedisConnection connection) throws DataAccessException {
                    try{
                        Object nativeConnection = connection.getNativeConnection();
                        if (nativeConnection instanceof RedisScriptingAsyncCommands) {
                            /**
                             * 不要问我为什么这里的参数这么奇怪
                             */
                            RedisScriptingAsyncCommands<Object,byte[]> command = (RedisScriptingAsyncCommands<Object,byte[]>) nativeConnection;
                            RedisFuture future = command.eval(UNLOCK_LUA, ScriptOutputType.INTEGER, keyParam, valueBytes);
                            return getEvalResult(future,connection);
                        }else{
                            logger.warn(REDIS_LIB_MISMATCH);
                            return 0L;
                        }
                    }catch (Exception e){
                        logger.error("Failed to releaseLock, closing connection",e);
                        closeConnection(connection);
                        return 0L;
                    }
                }
            });
            return result != null && result > 0;
        } catch (Exception e) {
            logger.error("release lock exception", e);
        }
        return false;
    }

    private Long getEvalResult(RedisFuture future,RedisConnection connection){
        try {
            Object o = future.get();
            return (Long)o;
        } catch (InterruptedException |ExecutionException e) {
            logger.error("Future get failed, trying to close connection.", e);
            closeConnection(connection);
            return 0L;
        }
    }

    private void closeConnection(RedisConnection connection){
        try{
            connection.close();
        }catch (Exception e2){
            logger.error("close connection fail.", e2);
        }
    }

    /**
     * 查看是否加锁
     * @param key
     * @return
     */
    @Override
    public boolean isLocked(String key) {
        Object o = redisTemplate.opsForValue().get(key);
        return o!=null;
    }
}

参考:

https://www.jianshu.com/p/fbedd907a48a

https://blog.csdn.net/sinat_27403673/article/details/105775846

http://www.manongjc.com/article/65529.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值