I've seen all kinds of codes that use redis to implement distributed locks on the network. They are either wrong or fragmented. There is no complete example. Let me summarize two mechanisms of redis to implement distributed locks this weekend
Spin lock and exclusive lock
In view of the different ways of implementing locks, the policy pattern is used to organize the code
1, Spin lock
Distributed lock abstraction policy interface
package com.srr.lock; /** * @Description Interface of distributed lock */ abstract public interface DistributedLock { /** * Acquire lock */ boolean lock(); /** * Unlock */ void unlock(); }
Abstract class of spin lock strategy, built with template method pattern
package com.srr.lock; /** * Spin lock strategy template */ public abstract class SpinRedisLockStrategy implements DistributedLock { private static final Integer retry = 50; //5 retries by default private static final Long sleeptime = 100L; protected String lockKey; protected String requestId; protected int expireTime; private SpinRedisLockStrategy(){} public SpinRedisLockStrategy(String lockKey, String requestId, int expireTime){ this.lockKey=lockKey; this.requestId=requestId; this.expireTime=expireTime; } /** * Template method, the framework of obtaining lock, and the specific logic are implemented by subclasses */ @Override public boolean lock() { Boolean flag = false; try { for (int i=0;i<retry;i++){ flag = tryLock(); if(flag){ System.out.println(Thread.currentThread().getName()+"Lock acquired successfully"); break; } Thread.sleep(sleeptime); } }catch (Exception e){ e.printStackTrace(); } return flag; } /** * Try to get lock, subclass implementation */ protected abstract boolean tryLock() ; /** * Unlock: delete key */ @Override public abstract void unlock(); }
Spin lock implementation subclass
package com.srr.lock; import redis.clients.jedis.Jedis; import java.util.Collections; /** * Spin lock */ public class SpinRedisLock extends SpinRedisLockStrategy{ private static final Long RELEASE_SUCCESS = 1L; 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"; public SpinRedisLock(String lockKey, String requestId, int expireTime) { super(lockKey,requestId, expireTime); } @Override protected boolean tryLock() { Jedis jedis = new Jedis("localhost", 6379); //Create client, 1 p And port number String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } @Override public void unlock() { Jedis jedis = new Jedis("localhost", 6379); //Create client, 1 p And port number 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)) { System.out.println("lock is unlock"); } } }
At this point, the implementation of distributed lock by spin lock is completed. Let's see the implementation of exclusive lock blocking
2, Exclusive lock
Before implementation, you need to understand a concept, that is, redis event notification:
/**
*Keyspace notification, all notifications are prefixed with keyspace @
*Key event notification, all notifications are prefixed with keyevent @
*All commands will be notified only after the key has been changed. For example, deleting foo will result in
*Key space notification
* "pmessage","__ key*__ : * ","__ keyspace@0__:foo","set"
*And key event notification
* "pmessage","__ key*__ : *","__ keyevent@0__:set","foo"
*/
After understanding the concept, you need to "Kea" the notify keyspace events in redis configuration file redis.conf, which defaults to "notify keyspace events", so as to start the event listening mechanism of redis.
Exclusive lock policy abstract class
package com.srr.lock; import redis.clients.jedis.Jedis; /** * @Description Block get lock, template class */ public abstract class BlockingRedisLockStrategy implements DistributedLock { protected String lockKey; protected String requestId; protected int expireTime; private BlockingRedisLockStrategy(){} public BlockingRedisLockStrategy(String lockKey, String requestId,int expireTime){ this.lockKey=lockKey; this.requestId=requestId; this.expireTime=expireTime; } /** * Template method, the framework of obtaining lock, and the specific logic are implemented by subclasses * @throws Exception */ @Override public final boolean lock() { //Lock acquired successfully if (tryLock()){ System.out.println(Thread.currentThread().getName()+"Lock acquired successfully"); return true; }else{ //Failed to acquire lock //Blocking waiting waitLock(); //Recursion, get lock again return lock(); } } /** * Try to get lock, subclass implementation */ protected abstract boolean tryLock() ; /** * Waiting to acquire lock, subclass implementation */ protected abstract void waitLock(); /** * Unlock: delete key */ @Override public abstract void unlock(); }
Exclusive lock implementation subclass
package com.srr.lock; import redis.clients.jedis.Jedis; import java.util.Collections; /** * Lock, block */ public class BlockingRedisLock extends BlockingRedisLockStrategy { private static final Long RELEASE_SUCCESS = 1L; 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"; public BlockingRedisLock(String lockKey, String requestId, int expireTime) { super(lockKey,requestId, expireTime); } /** * Try to get distributed lock * @return Success or not */ @Override public boolean tryLock() { Jedis jedis = new Jedis("localhost", 6379); //Create client, 1 p And port number String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return true; } return false; } @Override public void waitLock() { //judge key Does it exist Jedis jedis = new Jedis("localhost", 6379); //Create client, 1 p And port number KeyExpiredListener keyExpiredListener = new KeyExpiredListener(); /** * Keyspace notification, all notifications are prefixed with keyspace @ * Key event notification, all notifications are prefixed with keyevent @ * All commands will be notified only after the key has been changed. For example, deleting foo will result in * Key space notification * "pmessage","__ key*__ : * ","__ keyspace@0__:foo","set" * And key event notification * "pmessage","__ key*__ : *","__ keyevent@0__:set","foo" */ //If you want to monitor a key You can subscribe to the__ keyspace@0__,Monitor what's going on key,Just subscribe__ keyevent@0__ //Here we need to monitor that the key of the distributed lock has been deleted, so we need to monitor the deletion action"__keyspace@0__:"+key jedis.psubscribe(keyExpiredListener, "__keyspace@0__:"+lockKey); System.out.println("over"); } /** * Release distributed lock * @return Whether to release successfully */ @Override public void unlock() { Jedis jedis = new Jedis("localhost", 6379); //Create client, 1 p And port number 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)) { System.out.println("lock is unlock"); } } }
redis event listening class
package com.srr.lock; import redis.clients.jedis.JedisPubSub; /** * redis Event listener */ public class KeyDelListener extends JedisPubSub { public KeyDelListener(){ } // Handling when initializing subscription @Override public void onPSubscribe(String pattern, int subscribedChannels) { } // Processing after getting the subscribed message @Override public void onPMessage(String pattern, String channel, String message) { System.out.println("message == "+message); this.punsubscribe(); System.out.println("unsubscribe == "+message); } }
The complete code of exclusive lock is finished here. In fact, the difference between the two is that the implementation of lock is different. In order to ensure the code integrity, the author pasted it all.
After the code is written, give a scenario to test whether our code has any problems. Please see the following test code:
Here we build a Lock tool class:
package com.srr.lock; /** * Lock tools */ public class Lock { /** * Acquire lock */ boolean lock(DistributedLock lock) { return lock.lock(); }; /** * Release lock */ void unlock(DistributedLock lock) { lock.unlock(); }; }
Test class:
package com.srr.lock; import redis.clients.jedis.Jedis; /** * Test scenario * count From 1 to 101 * Using redis distributed lock to ensure correct result in distributed environment */ public class T { volatile int count = 1; public void inc(){ for(int i = 0;i<100;i++){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } count++; System.out.println("count == "+count); } } public int getCount(){ return count; } public static void main(String[] args) { final T t = new T(); final Lock lock = new Lock(); //final RedisLock redisLock = new BlockingRedisLock("","1",100000,jedis); final DistributedLock distributedLock = new SpinRedisLock("test","1",100000); Thread t1 = new Thread(new Runnable() { @Override public void run() { if(lock.lock(distributedLock)){ t.inc(); System.out.println("t1 running"); System.out.println("t1 == count == "+ t.getCount()); lock.unlock(distributedLock); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { if(lock.lock(distributedLock)) { t.inc(); System.out.println("t2 running"); System.out.println("t2 == count == " + t.getCount()); lock.unlock(distributedLock); } } }); t1.start(); t2.start(); } }
Test results:
At this point, all the code is completed. If you want to use zookeeper to implement the distributed lock, you only need to abstract a policy class to implement the DistributedLock interface. Is it convenient.
Original is not easy, pay more attention!