Redis实现简单的分布式锁 (RedisTemplate实现)

概述

分布式锁有很多种实现方式,比如数据库,zk,Redis等。本文主要讲解Redis方式。

实现

Redis分布式锁使用 SET k v NX EX max-lock-time 实现
该方案在 Redis 官方 SET 命令页有详细介绍。
http://doc.redisfans.com/string/set.html
在介绍该分布式锁设计之前,我们先来看一下在从 Redis 2.6.12 开始 SET 提供的新特性,
命令 SET key value [EX seconds] [PX milliseconds] [NX|XX],其中:

  • EX seconds — 以秒为单位设置 key 的过期时间;
  • PX milliseconds — 以毫秒为单位设置 key 的过期时间;
  • NX — set key value,当且仅当key不存在才set成功,等效于 SETNX。
  • XX — set key value,当且仅当key存在才set成功,等效于 SETEX。

命令 SET k v NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
客户端执行以上的命令:
如果服务器返回 OK ,那么这个客户端获得锁。(RedisTemplate封装为 true)
如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。(RedisTemplate封装为 false)


@Slf4j
@Component
public class RedisLock {

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

    @Autowired
    private StringRedisTemplate redisTemplate;


    /**
     * 解锁的lua脚本
     */
    public 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();
    }

    final Random random = new Random();

    /**
     * @param millis 毫秒
     * @param nanos  纳秒
     * @Title: seleep
     * @Description: 线程等待时间
     * @author yuhao.wang
     */
    private void sleep(long millis, int nanos) {
        try {
            Thread.sleep(millis, random.nextInt(nanos));
        } catch (InterruptedException e) {
            logger.info("获取分布式锁休眠被中断:", e);
        }
    }

	/**
	* 尝试加锁,非阻塞
	*/
    public boolean tryLock(String key, String value, long expireSeconds) {
        final String lockKey = new StringBuilder(RedisConsts.LOCK_KEY_PRE).append(key).toString();
        Boolean setBool = redisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
                byte[] keyByte = stringRedisSerializer.serialize(lockKey);
                //springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包
                RedisSerializer<String> valueSerializer = (RedisSerializer<String>) redisTemplate.getValueSerializer();
                byte[] valueBytes = valueSerializer.serialize(value);
                Boolean setBool = connection.set(keyByte, valueBytes, Expiration.seconds(expireSeconds), RedisStringCommands.SetOption.SET_IF_ABSENT);
                return setBool;
            }
        });
        if (setBool == null) {
            return false;
        }
        return setBool;
    }

    /**
     * 解锁
     *
     * @param lockKey
     * @param lockValue
     * @return
     */
    public Boolean unlock(String lockKey, String lockValue) {
        lockKey = new StringBuilder(RedisConsts.LOCK_KEY_PRE).append(lockKey).toString();
        //注意是Long类型,而不是Integer
        RedisScript redisScript = RedisScript.of(UNLOCK_LUA, Long.class);
        Object exeResult = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
        if (exeResult == null) {
            return false;
        }
        return NumberUtils.LONG_ONE.equals(exeResult);
    }
}

猜你喜欢

转载自blog.csdn.net/wandou9527/article/details/99627601