Several implementations of distributed lock

In distributed systems, the systems synchronize access to shared resources is very common. Therefore, we often need to coordinate their actions. If different systems or shared with one or a set of resources between different hosts a system, then when accessing these resources, often require mutually exclusive to prevent interference with each other to ensure consistency, in this case, we need to use the distributed lock.

A good distributed lock often requires the following characteristics:

  • Reentrant
  • The same point in time, only one thread holds a lock
  • Fault tolerance, when the lock node goes down, the timely release lock
  • high performance
  • No single point of issue

A. Distributed Lock database

Distributed Lock flowchart - based database Distributed Lock .jpg
Distributed database locks, commonly used way is to use a unique constraint characteristic table. When a successfully inserted into the database data, representing only get to lock. Put this data is deleted from the database, then release to send.

So we need to create a lock table

CREATE TABLE `methodLock` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `method_name` varchar(64) NOT NULL DEFAULT '' COMMENT '锁定的方法名',
  `cust_id` varchar(1024) NOT NULL DEFAULT '客户端唯一编码',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '保存数据时间,自动生成',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uidx_method_name` (`method_name `) USING BTREE
)
 ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='锁定中的方法';
复制代码

Add lock

insert into methodLock(method_name,cust_id) values (‘method_name’,‘cust_id’)
复制代码

Here cust_id can be machine mac address + thread number, make sure there is only one thread of a number. With this number, you can effectively determine whether the creator of the lock, which locks to be released and re-entry lock judge

Release the lock

delete from methodLock where method_name ='method_name' and cust_id = 'cust_id'
复制代码

Analyzing lock reentrant

select 1 from methodLock where method_name ='method_name' and cust_id = 'cust_id'
复制代码

Code Example locking and releasing the lock

/**
* 获取锁
*/
public boolean lock(String methodName){
    boolean success = false;
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    try{
        //添加锁
       success = insertLock(methodName, custId);
    } catch(Exception e) {
        //如添加失败
    }
    return success;
}

/**
* 释放锁
*/
public boolean unlock(String methodName) {
    boolean success = false;
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    try{
        //添加锁
       success = deleteLock(methodName, custId);
    } catch(Exception e) {
        //如添加失败
    }
    return success;
}
复制代码

Complete the process

public void test() {
    String methodName = "methodName";
    //判断是否重入锁
    if (!checkReentrantLock(methodName)) {
        //非重入锁
        while (!lock(methodName)) {
            //获取锁失败, 则阻塞至获取锁
            try{
                Thread.sleep(100)
            } catch(Exception e) {
            }
        }
    }
    //TODO 业务处理
    
    //释放锁
    unlock(methodName);
}
复制代码

This code has some problems:

  • No dead time. Solution: Set a timer processing, regular cleaning expired lock
  • Single-point problem. Solution: get a few back up the database, the database before the two-way synchronization, once hang up quickly switch to the backup database

II. Redis-based distributed lock

Distributed Lock flowchart - based Distributed Lock .jpg redis
Redis use of set (String key, String value, String nxxx, String expx, int time) command

  • The first is the key, when we use the key to lock because the key is unique.
  • The second is the value, we preach is custId, where cust_id can be machine mac address + thread number, make sure there is only one thread of a number. With this number, you can effectively determine whether the creator of the lock, which locks to be released and re-entry lock judge
  • The third is nxxx, we fill this parameter is NX, meaning that SET IF NOT EXIST, that is, when the key is not present, we set operations; if the key already exists, do nothing
  • The fourth is expx, we pass this parameter is PX, meaning that we give this key plus an expired set the specific time decided by the fifth parameter.
  • The fifth is the time, and the fourth argument echoes, representatives of key expiration time.

The sample code

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";
private static final Long RELEASE_SUCCESS = 1L;

// Redis客户端
private Jedis jedis;

/**
 * 尝试获取分布式锁
 * @param lockKey 锁
 * @param expireTime 超期时间
 * @return 是否获取成功
 */
public boolean lock(String lockKey, int expireTime) {
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    String result = jedis.set(lockKey, custId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

    if (LOCK_SUCCESS.equals(result)) {
        return true;
    }
    
    return false;
}

/**
 * 释放分布式锁
 * @param lockKey 锁
 * @param requestId 请求标识
 * @return 是否释放成功
 */
public boolean unlock(String lockKey,) {
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    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(custId));

    if (RELEASE_SUCCESS.equals(result)) {
        return true;
    }
    return false;
}

/**
 * 获取锁信息
 * @param lockKey 锁
 * @return 是否重入锁
 */
public boolean checkReentrantLock(String lockKey){
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    
    //获取当前锁的客户唯一表示码
    String currentCustId = redis.get(lockKey);
    if (custId.equals(currentCustId)) {
        return true;
    }
    return false;
}
复制代码

Complete the process

public void test() {
    String lockKey = "lockKey";
    //判断是否重入锁
    if (!checkReentrantLock(lockKey)) {
        //非重入锁
        while (!lock(lockKey)) {
            //获取锁失败, 则阻塞至获取锁
            try{
                Thread.sleep(100)
            } catch(Exception e) {
            }
        }
    }
    //TODO 业务处理
    
    //释放锁
    unlock(lockKey);
}
复制代码

III. Based on memcached distributed lock

Distributed Lock flowchart - based Distributed Lock .jpg memcached

memcached and redis similar implementations, using the command add (key, value, expireDate), Note: only if the key does not exist in the cache, will add success

  • The first is the key, when we use the key to lock because the key is unique.
  • The second is the value, we preach is custId, where cust_id
  • The third is expireDate, set an expiration date, such as: new Date (1000 * 10), it said after ten seconds removed from the Memcached memory cache).

The sample code

// Redis客户端
private MemCachedClient memCachedClient;

/**
 * 尝试获取分布式锁
 * @param lockKey 锁
 * @param expireTime 超期时间
 * @return 是否获取成功
 */
public boolean lock(String lockKey, Date expireDate) {
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    Boolean result = false;
    try {
        result = memCachedClient.add(lockKey, custId,expireDate);
    } catch(Excetion e) {
    }
    return result;
}

/**
 * 释放分布式锁
 * @param lockKey 锁
 * @param requestId 请求标识
 * @return 是否释放成功
 */
public boolean unlock(String lockKey,) {
    //获取客户唯一识别码,例如:mac+线程信息
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    Boolean result = false;
    try {
        String currentCustId = memCachedClient.get(lockKey);
        if (custId.equals(currentCustId)) {
            result = memCachedClient.delete(lockKey, custId,expireDate);
        }
    } catch(Excetion e) {
    }
    return result;
}

/**
 * 获取锁信息
 * @param lockKey 锁
 * @return 是否重入锁
 */
public boolean checkReentrantLock(String lockKey){
    //获取客户唯一识别码,例如:mac+线程信息
    String custId = getCustId();
    //获取当前锁的客户唯一表示码
    try {
         String currentCustId = memCachedClient.get(lockKey);
        if (custId.equals(currentCustId)) {
            return true;
        }
    } catch(Excetion e) {
    }
   
    return false;
}
复制代码

Complete the process

public void test() {
    String lockKey = "lockKey";
    //判断是否重入锁
    if (!checkReentrantLock(lockKey)) {
        //非重入锁
        while (!lock(lockKey)) {
            //获取锁失败, 则阻塞至获取锁
            try{
                Thread.sleep(100)
            } catch(Exception e) {
            }
        }
    }
    //TODO 业务处理
    
    //释放锁
    unlock(lockKey);
}
复制代码

IV. Zookeeper based distributed lock

Distributed Lock flowchart - based Distributed Lock .jpg zookeeper

Distributed Lock zookeeper temporary order node can be achieved. Is the general idea: each client to lock a particular method, the specified directory corresponding to the node on zookeeper method generates a unique node instantaneously ordered. Determining whether to acquire the lock is very simple, requires only a minimum determination number of ordered nodes. When the lock is released, simply delete this node can be instantaneous. At the same time, it avoids service downtime caused by lock can not be released, and the deadlock created.

May be used as a third-party library Curator zookeeper client, the client encapsulates a reentrant lock service.

Complete the process

public void test() {
    //Curator提供的InterProcessMutex是分布式锁的实现。通过acquire获得锁,并提供超时机制,release方法用于释放锁。
    InterProcessMutex lock = new InterProcessMutex(client, ZK_LOCK_PATH);
    try {
        //获取锁
        if (lock.acquire(10 * 1000, TimeUnit.SECONDS)) {
            //TODO 业务处理
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            //释放锁
            lock.release();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
复制代码

Guess you like

Origin juejin.im/post/5cff593c6fb9a07ec56e6ed4