分布式锁(1) ----- 介绍和基于数据库的分布式锁 分布式锁(2) ----- 基于redis的分布式锁 分布式锁(3) ----- 基于zookeeper的分布式锁 代码:https://github.com/shuo123/distributeLockhtml
SET resource_name my_random_value NX PX 30000
NX key不存在时才set PX 设置过时时间 my_random_value 要保证每台客户端的每一个锁请求惟一,可使用UUID+ThreadID 该命令在Redis 2.6.12才有,网上有基于setnx、epire的实现和基于setnx、get、getset的实现,这些多多少少都有点瑕疵,大几率是旧版本的redis实现,建议高版本的redis仍是使用这个实现。java
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
经过上述lua脚本实现,先判断锁是否该请求拥有,防止误解锁。git
public boolean tryLock(long waitTime, long leaseTime) { long end = Calendar.getInstance().getTimeInMillis() + waitTime; Jedis jedis = jedisPool.getResource(); try { do { String result = jedis.set(lockName, getClientId(), "NX", "EX", leaseTime); if ("OK".equals(result)) { return true; } try { Thread.sleep(100); } catch (InterruptedException e) { return false; } } while (Calendar.getInstance().getTimeInMillis() < end); }finally { if(jedis != null) { jedis.close(); } } return false; } public boolean unlock() { String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" + " return redis.call(\"del\",KEYS[1])\n" + "else\n" + " return 0\n" + "end"; Jedis jedis = jedisPool.getResource(); try { Object obj = jedis.eval(lua, Collections.singletonList(lockName), Collections.singletonList(getClientId())); if (obj.equals(1)) { return true; } }finally { if(jedis != null){ jedis.close(); } } return false; }
public void lockTest() { //用线程模拟进程 ExecutorService threadPool = Executors.newFixedThreadPool(20); CyclicBarrier barrier = new CyclicBarrier(20); for (int i = 0; i < 20; i++) { threadPool.execute(new RedisLockTest(barrier)); } threadPool.shutdown(); while (!threadPool.isTerminated()) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } private static class RedisLockTest implements Runnable{ private CyclicBarrier barrier; RedisLockTest(CyclicBarrier barrier){ this.barrier = barrier; } @Override public void run() { //模拟一台客户端一个jedisPool JedisPool jedisPool = new JedisPool("192.168.9.150", 6379); try { DistributeLock lock = new SingletonRedisLock(jedisPool, "lock"); barrier.await(); boolean flag = lock.tryLock(Integer.MAX_VALUE, 300); try { System.out.println(Thread.currentThread().getId() + "get lock:flag=" + flag); Thread.sleep(100); System.out.println(Thread.currentThread().getId() + "get unlock"); } finally { lock.unlock(); } } catch (Exception e) { e.printStackTrace(); } finally { jedisPool.close(); } } }
该实现简单,但存在如下问题: 1.没有实现可重入 2.没有锁续租,若是代码在锁的租期内没有执行完成,那么锁过时会致使另外一个客户端获取锁github
Redisson是redis推荐的分布式锁实现开源项目。Redisson的分布式锁实现可重入,同时有LockWatchDogTimeout来实现锁续约 github:https://github.com/redisson/redissonredis
private static class RedissonLockTest implements Runnable{ private CyclicBarrier barrier; RedissonLockTest(CyclicBarrier barrier){ this.barrier = barrier; } @Override public void run() { Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.9.150:6379"); RedissonClient client = Redisson.create(config); try { RLock lock = client.getLock("lock"); barrier.await(); lock.lock(); try { System.out.println(Thread.currentThread().getName() + "get lock"); Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "get unlock"); } finally { lock.unlock(); } }catch (Exception e){ e.printStackTrace(); }finally { client.shutdown(); } } }
若是Redis实现了集群,因为主从之间时经过异步复制的,假设客户端A在主机上得到锁,这时在未将锁数据复制到从机时,主机挂了,从机切换为主机,那么从机没有这条数据,客户端B一样能够得到锁。算法
使用多个独立(非集群)的实例来实现分布式锁,因为实例独立不需复制同步,因此没有上述问题;而保证可用性的是靠数据冗余,将数据多存放几份在不一样的实例上。算法以下:数据库
public void redLockTest() { Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.9.150:7000"); RedissonClient client = Redisson.create(config); Config config1 = new Config(); config1.useSingleServer().setAddress("redis://192.168.9.150:7001"); RedissonClient client1 = Redisson.create(config1); Config config2 = new Config(); config2.useSingleServer().setAddress("redis://192.168.9.150:7002"); RedissonClient client2 = Redisson.create(config2); try{ RLock lock = client.getLock("lock"); RLock lock1 = client1.getLock("lock"); RLock lock2 = client2.getLock("lock"); RedissonRedLock redLock = new RedissonRedLock(lock, lock1, lock2); redLock.lock(); try{ System.out.println(Thread.currentThread().getName() + "get lock"); Thread.sleep(100); System.out.println(Thread.currentThread().getName() + "get unlock"); } finally { redLock.unlock(); } }catch (Exception e){ e.printStackTrace(); }finally { client.shutdown(); client1.shutdown(); client2.shutdown(); } }
https://redis.io/topics/distlock https://github.com/redisson/redisson/wikidom