Learning summary of three implementations of distributed locks

Three implementations of Java distributed locks

Database : unique constraint
based on cache : redis and other
zookeeper : based on zookeeper

1. Basic principles and basis

Database implementation :
It mainly uses the unique constraint in the database table. The unique constraint itself cannot create the same, which guarantees uniqueness. We can define a name or other identifier for the lock definition and store it in one of the locks we define for managing locks. The table (the table id is self-incrementing, define a field separately as a unique constraint, and set another field to store the locked object, which is used for subsequent unlocking of the same object and excluding other objects from adding the same lock).
It is impossible for the same lock to have two locks with the same data due to the unique constraint.

Cache redis implementation :
Cache redis, etc. are basically memory operations, which can be said to be the fastest in terms of speed.
Redis mainly uses the SETNX key value command of Redis. It will only succeed when the key does not exist. If the key already exists, the command will fail.
That is to say, set the objects of lock and operation lock as key and value to control the joining and unlocking of locks.

Zookeeper implementation :
Zookeeper is generally used in distributed registration and configuration centers. Operating locks through zookeeper actually creates instantaneous nodes in zookeeper. Since the nodes themselves cannot have the same name, this can be used for lock operations.

But the actual issues to be considered are far more than these, such as service downtime, timeout, lock release timing, and so on.

2. Realize individual details

2.1. Database implementation

When building a table, there are two main fields to control the lock. The id is a regular auto-increment primary key and you don’t need to care about it. Then you need to define two additional fields. One field is used to store the lock (that is, the defined name is used to distinguish different lock), and define a unique constraint to physically prevent multiple insertions of the same data.
The other field is used to store the current caller who successfully locks, that is, if there are multiple operators operating the same lock, only one person can lock it successfully each time.

Successful locking : Insert a piece of data into the database, record the lock and operator who have successfully locked it, and when other people want to lock it, they will find that someone has already operated it, so they cannot operate it.

Unlock successful : If the operation needs to be unlocked, the corresponding record will be deleted. Lock it again after other operators preempt it.
(Simply adding additional field management status may still have the problem of being read by two links and then overwritten)

Special case : If a lock is successfully created by a certain operator, but the operator may be abandoned or unable to operate in the middle, then in order to release the lock for other people to use, you can additionally compare whether the lock data in the current library has exceeded After a certain period of time, if the timeout period is exceeded, the lock is also unlocked to delete the data for others to use.

If the same operator operates again within the valid time, it is also considered that the locking is successful.

If the entire database becomes unavailable, then the lock is equivalent to being unavailable. In this way, database high-availability solutions can be considered, such as MySQL's MHA high-availability solution.

2.2.redis implementation

When redis is implemented, the following methods are generally used:

stringRedisTemplate.opsForValue().setIfAbsent(锁关键词key, 操作人id即value, 60, TimeUnit.SECONDS);

The first input parameter is the key of the lock, which is equivalent to the lock name in the database with a unique constraint. The
second input parameter is the value of the lock, that is, the id of the current operator or the competing business id, etc. The
third and the third Four is the expiration time of this value and the unit of the value.

Method meaning :
that is, if there is no corresponding lock key in redis, then add the corresponding key value, otherwise do nothing. And once it times out, the key value will be released automatically.

Actively release the lock :
Here, the redis script is manually written to operate to ensure the atomicity of the operation, that is, all operations are completed at one time, and there is only one object to be operated.

	private static final Long RL_SUCCESS = 1L;

    public static boolean releaseDistributedLock(StringRedisTemplate stringRedisTemplate, String key, String value) {
    
    
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Collections.singletonList(lockKey), requestId);
        return RL_SUCCESS.equals(result);
    }

If the code operation is divided into two steps, it may appear that both operations pass the first key judgment, but the final result is overwritten by the latter operation.

In order to deal with this, in fact, a complete redis lock has been provided to rely on Redisson . The internal implementation
of Redisson actually guarantees atomicity through handwritten scripts.

Redisson implements distributed locks :
Depends on:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>

Initialize the bean:

@Bean
public RedissonClient redisson(){
    
    
    // 单个
    Config config = new Config();
    config.useSingleServer().setAddress("redis://xxx.xxx.xx.xxx:6379").setDatabase(0);
    return Redisson.create(config);
}

lock and unlock

	@Autowired
    private RedissonClient redisson;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

	
		String lockKey = "key_lock001";
        // 获取锁
        RLock redissonLock = redisson.getLock(lockKey);
        try{
    
    
            // 加锁
            redissonLock.lock();  // 等价于 setIfAbsent(key, value, 10,TimeUnit.SECONDS);
            // 从redis 中拿当前值
            int business1 = Integer.parseInt(stringRedisTemplate.opsForValue().get("business1"));
            if(business1 > 0){
    
    
                int realStock = business1 - 1;
                stringRedisTemplate.opsForValue().set("business1",realStock + "");
                System.out.println("减少1,剩余:" + realStock);
            }else{
    
    
                System.out.println("减少失败,已用完");
            }
        }finally {
    
    
            // 释放锁
            redissonLock.unlock();
        }

References https://blog.csdn.net/qq_42764269/article/details/122412431

2.3. zookeeper implementation

Zookeeper's data storage method is similar to a tree, and all nodes are called Znodes.
Znodes are roughly divided into two types: persistent nodes and temporary nodes . Each type is divided into sequential and non-sequential , so there are four types in total .
The zookeeper distributed lock uses temporary sequence nodes . After the temporary nodes are created, they will be deleted if they are no longer used. After adding the sequence, if they are created continuously, the name number will be automatically added for distinction.

In actual implementation , each request to create a lock is to create a temporary sequential node under a specified root node . After creation, if there are more than one, they will be sorted in order. We think that if we are the smallest node, we will create them in sequence. Finally, we have no other nodes before but ourselves, then we think that the node created by the current request has obtained the lock.

After the other temporary nodes are sorted, they will register watchers (monitoring) with their previous nodes in turn. For example, the second one will monitor the first one, and the third one will monitor the second one.

Release lock : When the smallest node actively deletes the lock or times out due to the end of the business, or the link is broken and crashes, the temporary node will be deleted, so that the next queued or all lost will be re-queued to obtain the lock.

In addition, since zookeeper is deployed in a cluster, as long as more than half of the machines survive, normal use can still be guaranteed.

A distributed lock based on zookeeper has been implemented in the zookeeper third-party client curator .

Locking and unlocking are roughly as follows:
curator implementation :

@Autowired
private CuratorFramework curatorFramework;

// 加锁,支持超时,可重入
public boolean setLock(long timeout, TimeUnit unit) throws InterruptedException {
    
    
    //
    InterProcessMutex interProcessMutex= new InterProcessMutex(curatorFramework, "/ParentNodeLock");
    try {
    
    
        return interProcessMutex.acquire(timeout, unit);
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return true;
}
// 解锁
public boolean releaseLock() {
    
    
InterProcessMutex interProcessMutex= new InterProcessMutex(curatorFramework,  "/ParentNodeLock");
    try {
    
    
        interProcessMutex.release();
    } catch (Throwable e) {
    
    
        log.error(e.getMessage(), e);
    } finally {
    
    
        executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS);
    }
    return true;
}

Commonly used locks :
InterProcessMutex: Distributed reentrant exclusive lock
InterProcessSemaphoreMutex: Distributed exclusive lock
InterProcessReadWriteLock: Distributed read-write lock

Reference learning source: https://blog.csdn.net/qq_42764269/article/details/122435977

Guess you like

Origin blog.csdn.net/weixin_44131922/article/details/131727746