Common distributed lock implementation

1. Redis

Because Redis is single-threaded, commands are executed in a serial manner and provide instructions such as SETNX, which are mutually exclusive.

 @Override
    public PmsSkuInfo getSkuInfoById(String skuId) {
        PmsSkuInfo pmsSkuInfo;

        // Connection cache
        Jedis jedis = redisUtil.getJedis();
        // Query the cache
        String skuKey = "sku:" + skuId + ":info";
        String skuJson= jedis.get(skuKey);

        if (StringUtils.isNotBlank(skuJson)) {
            pmsSkuInfo = JSON.parseObject(skuJson, PmsSkuInfo.class);
        }else {
            // Set a distributed lock
            // The thread that holds the lock has 10 seconds to expire
            String token = UUID.randomUUID().toString();
            //setnx is an atomic operation that also sets the expiration time
            String OK = jedis.set("sku:" + skuId + ":lock", token, "nx"."px".10*1000);
            if (StringUtils.isNotBlank(OK) && "OK".equals(OK)) {
                // The setting succeeded. Have access to the database within the 10 second expiration period
                pmsSkuInfo = getSkuByIdFromDb(skuId);
                if(pmsSkuInfo ! =null) {
                    jedis.set(skuKey, JSON.toJSONString(pmsSkuInfo));
                } else {
                    // If the Sku does not exist in the database
                    // To prevent cache penetration, set null to redis
                    jedis.setex(skuKey,60*3, JSON.toJSONString(""));
                }

                String lockToken = jedis.get("sku:" + skuId + ":lock");
                // This judgment is not atomic, use lua script
                // The value of the lock is being returned to us when we acquire it. Lock expired. Redis removes the lock.
                // But we get the value, and the comparison is successful (at this moment someone just got it), will delete the other lock
// if (StringUtils.isNotBlank(lockToken) && lockToken.equals(token)) {
// // releases the lock after accessing mysql
// jedis.del("sku:" + skuId + ":lock");
/ /}

                // Use the lua script to delete the key when it is found.
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Long eval = (Long) jedis.eval(script, Collections.singletonList(skuKey), Collections.singletonList(lockToken));
                if(eval! =null&&eval! =0) {
                    log.info("lua script delete the key success");
                }else {
                    log.error("lua script delete the key failed"); }}else {
                // Setup failed, spin (the thread tries to access this method again after sleeping for a few seconds
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                }

                // return important!! The same thread
                return getSkuInfoById(skuId);
            }
        }
        jedis.close();
        return pmsSkuInfo;
    }
Copy the code

If the current key does not exist, return an “OK” and the set is successful. The current thread takes hold of the lock. If the set is set to an expiration time of 10 seconds. The thread then has the right to access the database within 10 seconds, rebuild the cache, and then delete the key, thus implementing a distributed lock for Redis.

1. Set an expiration date for the key to prevent deadlocks. 2. Lock timeout problem. If the service logic between lock and unlock takes a long time to execute, which exceeds the lock expiration time, and the lock is deleted after execution, the lock of others will be deleted. Therefore, set the value of the lock to a random value. When the lock is deleted, get the key to determine whether the key value is its own. 3, but the above judgment would exist problems, cannot use ordinary if judge, because if we at the time of acquiring a lock, lock the value is returned to us, and then the lock is out of date, redis removed the lock, but we got the value, and contrast the success, someone just in acquiring a lock, then can give others lock to deleted, So we’re going to use lua scripts to ensure atomicity and delete the key when we get the key.

However, there are some drawbacks. If client 1 gets the lock on the master node, and then the master goes down, and the key of the stored lock is not synchronized to the slave, there is a failover, the slave becomes the master, and then client 2 gets the lock from the new master, The security of the lock is broken.

The author of redis proposed an algorithm of RedLock, and redission also has encapsulation of RedLock algorithm. The implementation of distributed lock with setnx instruction is cumbersome. Redission also has perfect implementation of distributed lock, including reentrant lock, read/write lock, fair lock, RedLock, CountDownLatch caters to all levels of our needs.

2. Zookeeper

Using ZooKeeper to implement distributed lock, the general idea is,

1. Create temporary order nodes for each client.

2. Obtain the node list and determine whether you are the first node in the list. If so, acquire the lock; otherwise, watch the node in front of you and wait for it to be deleted.

3. After obtaining the lock, execute the normal service logic and release the lock after deleting the node.

Zookeeper and Redis implement distributed lock differentiation

Redis data is stored in memory, performance is definitely better than ZooKeeper. Zookeeper is more reliable than Redis