Introduction to the

Redis is often used as a distributed lock in addition to being used as a cache. Redis distributed lock principle basis: Redis is a single thread, all client requests will be executed in a serial manner; Redis operates atomically to ensure data consistency.

Lock timeout problem

If service A and service B both request A lock, service A acquires the lock. Without A timeout mechanism, the lock cannot be released and service B will never be able to acquire the lock. In this case, you can add a timeout period to lock. When the lock exceeds the timeout period, the lock is automatically released. As in the above case, service A is down and the lock is not released immediately. The lock is automatically released when the timeout occurs. Here’s a question. How do YOU set this timeout? If service A holds the lock and does A long job, the lock is automatically released when the job lock times out, and service B takes possession of the lock. Wait until service A completes the job to release the lock, but the lock is held by service B instead of Service A. If another service C happens to be waiting for the lock, service C will have acquired the lock before service B releases it normally. This can lead to unpredictable errors. Solution: set a random number when adding the lock, release the lock need to compare the random number is consistent, match consistent to release the lock. Redis distributed locks are not suitable for long duration tasks.

Redis distributed lock implementation

A distributed lock is similar to a trap, and the SETNX(SET If Not eXists) directive is an atomic operation that allows only one client to possess it. SETNX source code is as follows:

Void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { long long milliseconds = 0; /* Initialized to avoid any harmness warning */ / If the expiration time of the key is defined, it is saved to the variable defined above. // If the expiration time is set incorrectly, error message if (expire) {if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) ! = C_OK) return; if (milliseconds <= 0) { addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); return; } if (unit == UNIT_SECONDS) milliseconds *= 1000; } // lookupKeyWrite is a function that fetches the value object of the key for the write operation. If NX is set (not present) and the key value // 2 is found in the database. Abort_reply if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key)! = NULL) || (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL)) { addReply(c, abort_reply ? abort_reply : shared.null[c->resp]); return; GenericSetKey (c->db,key,val,flags & OBJ_SET_KEEPTTL); genericSetKey(c->db,key,val,flags & OBJ_SET_KEEPTTL); // The server changes the dirty value server.dirty++ after each key change. if (expire) setExpire(c,c->db,key,mstime()+milliseconds); notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id); if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC, "expire",key,c->db->id); addReply(c, ok_reply ? ok_reply : shared.ok); }Copy the code

Based on the principle of Redis SETNX instruction, the following is a Redis distributed lock with timeout mechanism and random value matching implemented in Java:

private static final String LOCK_SUCCESS = "OK"; private static final Long RELEASE_SUCCESS = 1L; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; @override public String acquire() {try { Long end = System.currentTimemillis () + acquireTimeout; // Generate a value String requireToken = uuid.randomuuid ().toString(); while (System.currentTimeMillis() < end) { String result = jedis .set(lockKey, requireToken, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime); if (LOCK_SUCCESS.equals(result)) { return requireToken; } try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } catch (Exception e) { log.error("acquire lock due to error", e); } return null; } @Override public boolean release(String identify) { if (identify == null) { return false; } String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; Object result = new Object(); try { result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(identify)); if (RELEASE_SUCCESS.equals(result)) { log.info("release lock success, requestToken:{}", identify); return true; } } catch (Exception e) { log.error("release lock due to error", e); } finally { if (jedis ! = null) { jedis.close(); } } log.info("release lock failed, requestToken:{}, result:{}", identify, result); return false; }Copy the code

Is redis distributed lock with timeout mechanism and random value matching foolproof? Apparently not. The official solution is called Redlock: Redlock. Mark will explore the principles of Redlock later