Redis实现分布式锁

Redis实现分布式锁

Redis实现分布式锁利用 SETNX 和 SETEX

基本命令主要有:

  • SETNX(SET If Not Exists):

当且仅当 Key 不存在时,则可以设置,否则不做任何动作。

当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

  • SETEX:

基于SETNX功能外,还可以设置超时时间,防止死锁。

分布式锁其实大白话,本质上要实现的目标(客户端)在redis中占一个位置,等到这个客户试用,别的人进来就必须得等着,等我试用完了,走了,你再来。感觉跟多线程锁一样,意思大致是一样的,多线程是针对单机的,在同一个Jvm中,但是分布式石锁,是跨机器的,多个进程不同机器上发来得请求,去对同一个数据进行操作。

比如,分布式架构下的秒杀系统,几万人对10个商品进行抢购,10个商品存在redis中,就是表示10个位置,第一个人进来了,商品就剩9个了,第二个人进来就剩8个,在第一个人进来的时候,其他人必须等到10个商品数量成功减去1之后你才能进来。

这个过程中第一个人进来的时候还没操作减1然后异常了,没有释放锁,然后后面人一直等待着,这就是死锁。真对这种情况可以设置超时时间,如果超过10s中还是没出来,就让他超时失效。


/**
 * 获取锁
 * @param lockKey 锁
 * @param identity 身份标识(保证锁不会被其他人释放)
 * @param expireTime 锁的过期时间(单位:秒)
 * @return
 */
public boolean lock(String lockKey, String identity, long expireTime){
  boolean lockResult = redisTemplate.opsForValue().setIfAbsent(lockKey, identity, expireTime, TimeUnit.SECONDS);
  return opsForValue;
}

/**
 * 释放锁
 * @param lockKey 锁
 * @param identity 身份标识(保证锁不会被其他人释放)
 * @return
 */
public boolean releaseLock(String lockKey, String identity){
  String luaScript =
    "if " +
    "  redis.call('get', KEYS[1]) == ARGV[1] " +
    "then " +
    "  return redis.call('del', KEYS[1]) " +
    "else " +
    "  return 0 " +
    "end";
  DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
  redisScript.setResultType(Boolean.class);
  redisScript.setScriptText(luaScript);
  List<String> keys = new ArrayList<>();
  keys.add(lockKey);
  boolean result = redisTemplate.execute(redisScript, keys, identity);
  return result;
}

解锁的方法只需两个参数:lockKey、identity。

  • 第一个参数lockKey为key,一个资源对应一个唯一的key。
  • 第二个参数identity为身份标识,作为此key对应的value存储,为了判断在释放锁时是不是和加锁的身份相同,防止别人释放锁。

此处使用Lua脚本来判断身份,身份相同就删除,身份不同就不对数据做操作并返回失败。为什么要使用Lua脚本呢?这是为了要保证操作的原子性,redis在执行Lua脚本的时候是把脚本当作一个命令来执行的,我们都知道redis的命令是都是原子操作,这样就保证了操作的原子性。


文章作者: 凌云
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 凌云 !
  目录