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
命令加上选项已经可以完全取代SETNX, SETEX, PSETEX的功能,所以在将来的版本中,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 脚本,这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除。 这两个改动可以防止持有过期锁的客户端误删现有锁的情况出现。