Redis分布式锁实现

Redis实现分布锁

命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。

客户端执行以上的命令:

如果服务器返回 OK ,那么这个客户端获得锁。 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。 设置的过期时间到达之后,锁将自动释放。

从2.6.12版本开始,redis为SET命令增加了一系列选项:

  • EX seconds – Set the specified expire time, in seconds.
  • PX milliseconds – Set the specified expire time, in milliseconds.
  • NX – Only set the key if it does not already exist.
  • XX – Only set the key if it already exist.

  • EX seconds – 设置键key的过期时间,单位时秒
  • PX milliseconds – 设置键key的过期时间,单位时毫秒
  • NX – 只有键key不存在的时候才会设置key的值
  • XX – 只有键key存在的时候才会设置key的值

注意: 由于SET命令加上选项已经可以完全取代SETNXSETEXPSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

代码:

 1 package cn.wywk.yac.comm.redis;
 2 
 3 
 4 import redis.clients.jedis.Jedis;
 5 
 6 /**
 7  * ClassName: redis分布式锁实现 <br/>
 8  * date: 2017年2月17日 上午10:23:24 <br/>
 9  * 
10  * @author 13414wuxinyu
11  */
12 public class RedisLock {
13 
14     private  final static String lua = //
15             "if redis.call("get",KEYS[1]) == ARGV[1]
" //
16                     + "then
" //
17                     + "  return redis.call("del",KEYS[1])
" //
18                     + "else
" //
19                     + "  return 0
" //
20                     + "end
";
21     
22     /**
23      * acquire:分布式加锁
24      * @param key 锁的key
25      * @param value value
26      * @param expires 持有锁的时间-秒
27      * @param timeout 获取锁的最大等待时间_毫秒
28      * @return
29      * @author 13414
30      * 2017年3月2日
31      */
32     public boolean acquire(Jedis jedis,String key,String value,long expires,int timeout) throws InterruptedException {
33         while (timeout >= 0) {
34             if("OK".equals(jedis.set(key,value, "NX","EX",expires))){
35                 return true;
36             }
37             timeout -= 100;
38             Thread.sleep(100);
39         }
40         return false;
41     }
42 
43     /**
44      * 解锁并释放redis连接
45      */
46     public boolean release(Jedis jedis,String key,String value) {
47         try {
48             String[] params1 = new String[] { key, value };
49             if("1".equals(jedis.eval(lua, 1, params1))){
50                 jedis.close();
51                 return true;
52             }
53             return false;
54         } catch (Exception e) {
55             return false;
56         }
57     }
58 }

测试代码:

package cn.wywk.yac.redistest;
import cn.wywk.yac.comm.redis.RedisLock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class RedisLockTest implements Runnable{
    
     private static JedisPool pool = null;  
     
     private String xianName="";
         
     public RedisLockTest(){}
     
     public RedisLockTest(String name){
         xianName=name;
     }
     
    /** 
     * 构建redis连接池 
     *  
     * @param ip 
     * @param port 
     * @return JedisPool 
     */  
    public static JedisPool getPool() {  
        if (pool == null) {  
            JedisPoolConfig config = new JedisPoolConfig();  
            //控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;  
            //如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。  
            config.setMaxIdle(500);  
            //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。  
            config.setMaxIdle(5);  
            //表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;  
            config.setMaxWaitMillis(1000 * 100);  
            //在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;  
            config.setTestOnBorrow(true);  
            pool = new JedisPool(config, "127.0.0.1",6379);  
        }  
        return pool;  
    }  
    
    @Override
    public void run() {
        RedisLock jedisLock = new RedisLock();  
        try {  
            if (jedisLock.acquire(jedis,"aa","123231",10,6000)) { // 启用锁  
                System.out.println("线程名:"+xianName+"获得锁");
                //执行业务逻辑
                //....
                //释放锁
                Thread.sleep(5000);
                System.out.println("---------------线程名:"+xianName+"开始释放锁");
                jedisLock.release(jedis,"aa","123231");
            } else {  
                System.out.println("ERROR-线程名:"+xianName+"获取锁失败");
                //返回给连接池
                jedis.close();
            }  
        } catch (Exception e) {  
            // 分布式锁异常  
            e.printStackTrace();
        } finally {  
//            if (jedis != null) {  
//                try {
//                    
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }  
//            }  
        }
    }
        
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i <40; i++) {
            RedisLockTest test=new RedisLockTest("A线程"+i);
            new Thread(test).start();
//            RedisLockTest test2=new RedisLockTest("B线程"+i);
//            new Thread(test2).start();
        }
    }
}

释放锁的时候:

不使用 DEL 命令来释放锁,而是发送一个 Lua 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。 这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。

原文地址:https://www.cnblogs.com/wuxinyu/p/6410396.html