In one work, there was a small accident. The results obtained from redis write and read methods were always different from what was expected. After careful reflection, I found that the reason was that all nodes in the cluster used shared cache and queue, and there might be resource competition among nodes in some scenarios. May send each node between the “thread insecurity problem”, in a single machine, can use the lock to solve, in a distributed environment, will use the distributed lock, when the online check, found a lot of ideas and code given by the gods, try to achieve it, in this to share the idea to everyone.
Redis can do distributed lock conditions
Because Redis is single-threaded and the instructions for parallel access are executed serially within it, Redis can be used as a technique for implementing distributed locks.
Command used
In the previous several articles, we have introduced the basic types of Redis and their corresponding operations. If you do not know much about Redis, you can go to “Redis common types know how much? Go and see. In the implementation of distributed lock, mainly used redis string type data structure, and the following operations:
Set key value [ex seconds] [px milliseconds] [nx/xx] : save key-value
Ex and PX refer to the validity period of the key. Nx: creates a key and saves the value if the key does not exist. Xx: saves the value if the key does exist
Localhost :0>set Hello world OK localhost:0>set Hello world ex 100 OK localhost:0>set Hello world px 1000 OK Localhost :0>set hello worldxx xx NULL localhost:0>set hello worldxx xx OK The operation failed. Procedure With xx, the value of the hello key has now been replaced with worldxxCopy the code
If the key does not exist, create the key and save the value. If the key does exist, return NULL. In Java code, this command is encapsulated as a Jedis method. I made another wrapper myself in the code:
/** * return 1 if the key does not exist Return 0 * @param key * @param value * @return */ public Long setNx(final String key,final String value){return this.execute(new Function<Long, Jedis>() {@override public Long callback(Jedis Jedis) {return jedis.setnx(key, value); }}); }Copy the code
Setnx returns 1 to prove that the lock was acquired, and 0 to prove that the lock already exists.
Expire Key integer value: Sets the lifetime of the key in seconds
Expire: specifies the expiration time of the lock.
/** * Set key expiration * @param key * @param exp * @return */ public Long expire(final String key,final int exp){return this.execute(new Function<Long, Jedis>() { @Override public Long callback(Jedis jedis) { return jedis.expire(key, exp); }}); }Copy the code
Get key: retrieves the value of the key
Get (lock, lock, lock);
Public String get(final String key) {return this.execute(new) Function<String, Jedis>() { @Override public String callback(Jedis jedis) { return jedis.get(key); }}); }Copy the code
Getset key newvalue: returns the old value of the key and stores the newvalue newvalue
localhost:0>getset hello wolrd
wogldxx
localhost:0>get hello
wolrd
Copy the code
Getset = getset; getSet = getset; getSet = getset;
/** * get and return the old value, Public String getSet(final String key, final String value){ return this.execute(new Function<String, Jedis>() { @Override public String callback(Jedis jedis) { return jedis.getSet(key, value); }}); }Copy the code
Ideas of implementation
1. By using the current timestamp after expiration time together with the lock lock will be expired, and the time as the value, call setnx method as a key lock, if return 1, prove that the original key, also is the lock does not exist, acquiring a lock is successful, then call the expire method set the timeout of lock, return to acquiring a lock is successful.
2. If setnx returns 0, it proves that the original lock exists and the lock is not acquired, and then blocks the lock acquisition. (I originally consider their own ideas on the second step on the end, after the realization of the idea, found that my idea is not proper, the next to say about the third step of the god).
3. If setnx returns 0, the lock exists, but the lock is not obtained, then call getSet to obtain the value stored in step 1, which is the lock expiration time, compared with the current timestamp, if the current timestamp is longer than the lock expiration time, prove the lock has expired, call getSet. Save the new time, return lock success. This is mainly done in case of an EXPIRE execution failure, or if the lock cannot be released in the case of a server restart.
The following code is posted in accordance with the great god idea, which implements the blocking mechanism of the lock, but also implements an exclusive lock:
import com.eduapi.common.component.RedisComponent; import com.eduapi.common.util.BeanUtils; import org.apache.commons.lang3.StringUtils; * @author: ZhaoWeiNan. * @createdTime: 2017/3/20. * @version: */ public class RedisLock {private RedisComponent RedisComponent; private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100; private String lockKey; /** * ^ [^ # ^ # ^ # ^ # ^ # ^ # ^ #]; /** * ^ [^ # ^ # ^ # ^ #] / private int egex = 10 * egex; private volatile boolean isLocked = false; public RedisLock(RedisComponent redisComponent, String lockKey) { this.redisComponent = redisComponent; this.lockKey = lockKey; } public RedisLock(RedisComponent redisComponent, String lockKey, int timeoutMillisCond) { this(redisComponent, lockKey); this.timeoutMillisCond = timeoutMillisCond; } public RedisLock(RedisComponent redisComponent, String lockKey, int timeoutMillisCond, int expireMillisCond) { this(redisComponent, lockKey, timeoutMillisCond); this.expireMillisCond = expireMillisCond; } public RedisLock(RedisComponent redisComponent, int expireMillisCond, String lockKey) { this(redisComponent, lockKey); this.expireMillisCond = expireMillisCond; } public String getLockKey() { return lockKey; } /** * get lock. Reids cache key is the key of the lock, all shares, value is the expiration time of the lock (note: the expiration time is set in value) * 1 Setnx attempts to set the value of a key. Success (there is no lock currently) returns the lock * 2. * * @return true if the lock is acquired, false acquire timeouted * @throws InterruptedException in case of thread interruption */ public synchronized boolean lock() throws InterruptedException { int timeout = timeoutMillisCond; boolean flag = false; While (timeout > 0) {/ / set the expiration time Long expires = System. CurrentTimeMillis () + expireMillisCond; String expiresStr = BeanUtils.convertObject2String(expires); // There is no lock in redis. Acquiring a lock success if (this. RedisComponent. SetNx (lockKey expiresStr) > 0) {/ / set the expiration date of the lock this.redisComponent.expire(lockKey,expireMillisCond); isLocked = true; return true; } flag = compareLock(expiresStr); if (flag){ return flag; } timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS; Using random time delay / * 100 milliseconds, here may be a little bit better, can prevent the occurrence of hunger process, i.e., when arrived at multiple processes at the same time, there will be only one process gets the lock, other all use the same frequency to try, to behind some of the process, also with the same frequency application locks, this will probably lead to front to lock are not being met. */ thread. sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS); } return false; } /** * exclusive lock. Synchronization * @return * @throws InterruptedException */ public synchronized Boolean excludeLock() { [zipzipzipzipzipzipzipzipzipzipzipzipzipzipzipzipd]; String expiresStr = BeanUtils.convertObject2String(expires); // There is no lock in redis. Acquiring a lock success if (this. RedisComponent. SetNx (lockKey expiresStr) > 0) {/ / set the expiration date of the lock this.redisComponent.expire(lockKey,expireMillisCond); isLocked = true; return true; } return compareLock(expiresStr); } @param expiresStr * @return */ private Boolean compareLock(String expiresStr){ Thread A gets currentValueStr = 1 thread B gets currentValueStr = 1 String currentValueStr = 1 thread this.redisComponent.get(lockKey); // Lock if (stringutils.isnotempty (currentValueStr) &&long.parselong (currentValueStr) < System.currentTimemillis ()){ Jedis.getset = jedis.getSet = jedis.getSet = jedis.getSet = jedis. // thread B put the 2 in. (2). Compare the lock is not obtained, the String oldValue = this. RedisComponent. GetSet (lockKey expiresStr); If (stringutils.isnotempty (oldValue) &&stringutils.equals (oldValue,currentValueStr)){ //[distributed case]: if multiple threads happen to be here, but only one thread is set to the same value as the current value, IsLocked = true; return true; } } return false; } / releases the lock * * * * / public synchronized void unlock () {if (isLocked) {this. RedisComponent. Delete (lockKey); isLocked = false; }}}Copy the code
In the code, the great god wrote the idea of standing in, the great God is still very clear, look at the code to call:
// Create lock object, RedisComponent Key for RedisLock RedisLock = new RedisLock(redisComponent,1000 *) 60,RedisCacheKey.REDIS_LOCK_KEY + now_mm); If (redislock. excludeLock()){try { Read messages regularly ordered set set = this. RedisComponent. ZRangeByScore (RedisCacheKey MSG_TIME_LIST, 0, end); if (set ! = null && set.size() > 0){ flag = true; }} catch (Exception e) {logger. error(" get timed SMS ordered set, Exception is {}", e.tostring ()); }...Copy the code
The use of Redis to achieve a distributed lock is introduced here, welcome everyone to exchange, point out some of the wrong places, let me deepen my understanding.
Thank you!