欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Redis实现分布式锁

程序员文章站 2022-04-09 18:17:46
1.setnx锁在redis中最简单的数据结构就是string。最早的时候,上锁的操作一般使用setnx,这个命令是当:lock不存在的时候set一个val,或许你还会记得使用expire来增加锁的过期,解锁操作就是使用del命令,伪代码如下:if (Redis::setnx("my:lock", ......

1.setnx
锁在redis中最简单的数据结构就是string。最早的时候,上锁的操作一般使用setnx,这个命令是当:lock不存在的时候set一个val,或许你还会记得使用expire来增加锁的过期,解锁操作就是使用del命令,伪代码如下:
if (redis::setnx("my:lock", 1)) {
  redis::expire("my:lock", 10);
  // ... do something

  redis::del("my:lock")
}
这里其实是有问题的,问题就在于setnx和expire中间如果遇到crash等行为,可能这个lock就不会被释放了。

2.set
现在官方建议直接使用set来实现锁。我们可以使用set命令来替代setnx,就是下面这个样子
if (redis::set("my:lock", 1, "nx", "ex", 10)) {
  ... do something

  redis::del("my:lock")
}
上面的代码把my:lock设置为1,当且仅当这个lock不存在的时候,设置完成之后设置过期时间为10。
获取锁的机制是对了,但是删除锁的机制直接使用del是不对的。因为有可能导致误删别人的锁的情况。比如,这个锁我上了10s,但是我处理的时间比10s更长,到了10s,这个锁自动过期了,被别人取走了,并且对它重新上锁了。那么这个时候,我再调用redis::del就是删除别人建立的锁了。
官方对解锁的命令也有建议,建议使用lua脚本,先进行get,再进行del

主控:
$token = rand(1, 100000);

function lock() {
  return redis::set("my:lock", $token, "nx", "ex", 10);
}

function unlock() {
  $script = `
  if redis.call("get",keys[1]) == argv[1]
  then
    return redis.call("del",keys[1])
  else
    return 0
  end 
  `
  return redis::eval($script, "my:lock", $token)
}

if (lock()) {
  // do something

  unlock();
}

redis工具类:

基于Redis实现分布式锁
public class redistool {

    private static final string lock_success = "ok";
    private static final string set_if_not_exist = "nx";
    private static final string set_with_expire_time = "px";

    private static final long release_success = 1l;

    /**
     * 尝试获取分布式锁
     * @param jedis redis客户端
     * @param lockkey 锁
     * @param requestid 请求标识
     * @param expiretime 超期时间
     * @return 是否获取成功
     */
    public static boolean trygetdistributedlock(jedis jedis, string lockkey, string requestid, int expiretime) {

        string result = jedis.set(lockkey, requestid, set_if_not_exist, set_with_expire_time, expiretime);

        if (lock_success.equals(result)) {
            return true;
        }
        return false;

    }


    /**
     * 释放分布式锁
     * @param jedis redis客户端
     * @param lockkey 锁
     * @param requestid 请求标识
     * @return 是否释放成功
     */
    public static boolean releasedistributedlock(jedis jedis, string lockkey, string requestid) {

        string script = "if redis.call('get', keys[1]) == argv[1] then return redis.call('del', keys[1]) else return 0 end";
        object result = jedis.eval(script, collections.singletonlist(lockkey), collections.singletonlist(requestid));

        if (release_success.equals(result)) {
            return true;
        }
        return false;

    }


}
基于Redis实现分布式锁

 

其他:lua脚本的原子性:
redis 使用单个 lua 解释器去运行所有脚本,并且, redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 redis 命令被执行。这和使用 multi / exec 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。