Reids实现分布式锁

基于SETNX

如果 key不存在,则SETNX成功返回1,如果这个key已经存在了,则返回0。 

import redis.clients.jedis.Jedis;
public class SetNxExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");

        String key = "my_key";
        String value = "my_value";
        // 使用 SETNX 命令
        if (jedis.setnx(key, value)== 1) {try{
                //处理业务
            }catch(){
            
            }finally{
                jedis.del(key); //释放锁
            }
            
        } 
        // 关闭连接
        jedis.close();
    }
}

这里我们实现了简单的互斥锁,但是有个问题,如果持有锁的线程挂掉,锁将一直存在,导致死锁。

天才的你肯定想到了,只要给这个key设置一个过期时间就可以。我们通过Expire(key,time)命令设置过期时间,在加锁的时候同时设置一个过期时间。改进代码:

import redis.clients.jedis.Jedis;
public class SetNxExample {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("localhost");
       
        String key = "my_key";
        String value = "my_value";
        // 使用 SETNX 命令
        if (jedis.setnx(key, value)== 1) {
            jedis.expire(key , 100);
            try{
                //处理业务
            }catch(){
            
            }finally{
                jedis.del(key); //释放锁
            }
            
        } 
        // 关闭连接
        jedis.close();
    }
}

设置了时间解决了死锁问题,但是这里又出现新问题了,设置key和设置过期时间不是原子操作,如果key设置成功,但是过期时间设置失败了,又出现死锁了。另外如果线程A执行时间过长,key到期了释放锁,这个时候线程B拿到锁,此后线程A执行结束,释放锁,将B的锁释放了。

解决方法:

将当前线程id或者使用uuid设为value值,这样在释放的时候可以判断是否是当前线程持有的锁。使用SET的扩展方式使设置key和设置过期时间的具有原子性。

String uuid = String uuid  = UUID.randomUUID().toString();
if("OK".equals(jedis.set(key, uuid,newSetParams().nx().ex(100)))){ //加锁
    try {
        //业务处理
    }catch(){

     }finally{
       if(uuid.eauls(jedis.get(key))){
           jedis.del(key); //释放锁
       }
    }
}
这里if判断和释放锁不是原子操作,可能会释放其他线程的锁.使用lua脚本解决.
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
    else
    return 0
    end

改写后的代码:

String uuid = String uuid  = UUID.randomUUID().toString();
if("OK".equals(jedis.set(key, uuid,newSetParams().nx().ex(100)))){ //加锁
    try {
        //业务处理
    }catch(){

     }finally{
       String luaSript = "" +
                "if redis.call('get', KEYS[1]) == ARGV[1] then\n" +
                "    return redis.call('del', KEYS[1])\n" +
                "    else\n" +
                "    return 0\n" +
                "    end";
        jedis.eval(luaSript, Collections.singletonList(key),Collections.singletonList(value));
    }
}

 

以上方案可以基于redis的SETNX实现分布式锁。
posted @ 2024-08-05 21:43  马处  阅读(15)  评论(0)    收藏  举报