编程知识 cdmana.com

Distributed lock and redis implementation

Many novices will Distributed lock and Distributed transactions confusion , Personal understanding : lock It is used to solve the problem of multi program concurrent contention for a shared resource ; Business It is used to ensure the consistency of a series of operations . I've had a few previous articles on distributed transactions , About 2PC、TCC And asynchronous ensure the implementation of the scheme , This time, I'm going to talk about several distributed lock schemes .

1. Definition

In a traditional monomer architecture , Our most common lock is lock jdk Lock of . Because thread is the smallest unit that the operating system can run scheduling , stay java In multithreading development , It is inevitable that different threads compete for resources under the same process .jdk The library provides us with synchronized、Lock And contract out java.util.concurrent.* etc. . But they all have uniform limits , Threads competing for resources , It's all running on the same Jvm Under process , In a distributed architecture , Different Jvm The process cannot use the lock .

In order to prevent multiple processes from interfering with each other in a distributed system , We need a distributed coordination technology to schedule these processes . And the core of this distributed coordination technology is to achieve this Distributed lock .

Take a classic “ Oversold ” Example , In an e-commerce project 100 Items in stock , The logic of the flash purchase interface can be simply divided into :1、 Check whether the inventory is greater than zero ;2、 When inventory is greater than zero , Purchase goods . When only 1 When the parts are in stock ,A Users and B Users all perform the first step at the same time , Query inventory is 1 Pieces of , And then they all buy . When they buy , Found that the inventory is -1 Pieces . We can do it in java The code will “ Check stock ” and “ Reduce inventory ” Lock the operation of , guarantee A Users and B The user's request cannot be executed concurrently . But in case our interface service is a cluster service ,A Users and B The user's request is forwarded to different by load balancing Jvm In progress , That still doesn't solve the problem .

2. Distributed lock contrast

From the previous example we can see that , Coordinate resources to resolve distributed locks , It can't be Jvm Process level resources , It should be an external resource that can be shared .

Three ways of implementation

There are three ways to implement common distributed locks :1. The database lock ;2. be based on ZooKeeper Distributed locks for ;3. be based on Redis Distributed locks for .

  1. The database lock : It's easy to think of this way , Put competing resources in the database , Using database lock to realize resource competition , Refer to previous articles 《 Database transactions and locks 》. for example :(1) Pessimistic lock realization : Check the inventory of goods sql You can add "FOR UPDATE" To achieve exclusive lock , And will “ Check stock ” and “ Reduce inventory ” Package it as a transaction COMMIT, stay A Before the user queries and purchases are completed ,B Users' requests will be blocked .(2) Optimistic lock implementation : Add a version number field to the inventory table to control . Or a simpler implementation is , When each purchase is completed, it is found that the inventory is less than zero , Just roll back the transaction .
  2. zookeeper Distributed locks for : Implement distributed locks ,ZooKeeper It's professional . It's like a file system , Competing for file resources on a file system through multiple systems , It acts as a distributed lock . Specific implementation mode , Please refer to the previous article 《zookeeper Development and application of 》.
  3. redis Distributed locks for : The previous article said redis Development applications and transactions of , I haven't talked about it all the time redis Distributed locks for , This is also the core of this paper . Simply put, it's through setnx The value of the competitive key .

“ The database lock ” It's a competitive table level resource or a row level resource ,“zookeeper lock ” It's a competitive file resource ,“redis lock ” To compete for key value resources . They all compete for shared resources outside of the program , To implement distributed locking .

contrast

But in the field of distributed locks , still zookeeper More professional .redis In essence, it's also a database , All the other two options are “ Part-time job ” Implementation of distributed locks , There's no effect zookeeper good .

  1. Low performance consumption : When concurrent lock contention really occurs , A database or redis The implementation of is basically through blocking , Or keep trying to get the lock again and again , Some performance consumption . and zookeeper The lock is by registering the listener , When a program releases a lock, it is , The next program listens to the message and acquires the lock .
  2. The lock release mechanism is perfect : If it is redis The client that got the lock bug Or hang up , Then the lock can only be released after the timeout period ; and zk Words , Because it's temporary znode, As long as the client hangs up ,znode It's gone , The lock will be released automatically .
  3. Strong consistency of clusters : as everyone knows ,zookeeper It's a typical realization CP The case of business , In a cluster, there will always be Leader To request the node to process the transaction . and redis In fact, it is the realization of AP The transaction , If master Node failure , Master slave switch occurs , At this point, it is possible that the lock will be lost .
The necessary condition of lock

In addition, to ensure that distributed locks are available , We should at least make sure that the implementation of the lock satisfies the following conditions at the same time :

  1. Mutual exclusivity . At any moment , Only one client can hold the lock .
  2. A deadlock will not occur . Even if a client crashes while holding a lock without actively unlocking it , It can also ensure that other clients can be locked later .
  3. He who breaks the bell must tie the bell . Lock and unlock must be the same client , The client can't unlock the lock added by others .

3. Redis Implement distributed locks

3.1. Lock

The right lock
public class RedisTool {

    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";

    /**
     *  Try to get distributed lock 
     * @param jedis Redis client 
     * @param lockKey  lock 
     * @param requestId  The request id 
     * @param expireTime  Beyond the time 
     * @return  Success or failure 
     */
    public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {

        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

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

To see , We lock on a line of code :jedis.set(String key, String value, String nxxx, String expx, int time), This set() Method has five parameters :

  1. key: We use key To be a lock , because key Is the only one. .
  2. value: What we pass is requestId, Many children's shoes may not understand , Yes key As a lock is not enough , Why use value? The reason is that when we talk about reliability above , In order to solve the fourth condition of the distributed lock, the ringer is needed , By giving value The assignment is requestId, We will know which request this lock was added , When unlocking, it can be based on .requestId have access to UUID.randomUUID().toString() Method generation .
  3. Nxxx: This parameter we fill in is NX, intend SET IF NOT EXIST, When key When there is no , We carry out set operation ; if key Already exist , Then do nothing ;
  4. EXPX: This parameter we pass is PX, It means we're going to give this key Add an expired setting , The specific time is determined by the fifth parameter .
  5. time: Corresponding to the fourth parameter , representative key The expiration time of .

in general , Execute the above set() Methods lead to only two results :

  • No current locks (key non-existent ), Then do the lock operation , And set an expiration date for the lock , meanwhile value Represents the locked client .
  • Existing lock , Do nothing .
Not recommended lock mode ( Not recommended !!!)

I've seen a lot of blogs , Lock them in the following way , namely setnx and getset With , Manually maintain the expiration time of the key .

public static boolean wrongGetLock2(Jedis jedis, String lockKey, int expireTime) {

    long expires = System.currentTimeMillis() + expireTime;
    String expiresStr = String.valueOf(expires);

    //  If the current lock does not exist , Returns lock successfully 
    if (jedis.setnx(lockKey, expiresStr) == 1) {
        return true;
    }

    //  If the lock exists , The expiration time of the lock acquired 
    String currentValueStr = jedis.get(lockKey);
    if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
        //  Lock expired , Gets the expiration time of the last lock , And set the expiration time of the current lock 
        String oldValueStr = jedis.getSet(lockKey, expiresStr);
        if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
            //  Consider the case of multi-threaded concurrency , Only one thread has the same set value as the current value , It has the right to lock it 
            return true;
        }
    }
    //  Other situations , All return locks failed 
    return false;
}

On the surface , This code also implements distributed locks , And the code logic is similar to the above , But there are a few questions :

  1. Due to the client's own generation expiration time , So we need to force the time synchronization of each client under the distributed environment .
  2. When the lock expires , If multiple clients execute at the same time jedis.getSet() Method , So, although only one client can lock in the end , However, the expiration time of this client's lock may be overridden by other clients .
  3. Locks do not have owner identity , That is, any client can unlock .

This kind of code on the Internet may be based on early jedis Version of , There were great limitations .Redis 2.6.12 The above version is set The command adds optional parameters , Like I said before jedis.set(String key, String value, String nxxx, String expx, int time) Of api, You can put SETNX and EXPIRE It's packaged and executed together , And give the unlock of the expired key to redis Server to manage . So in the actual development process , Don't lock in this more primitive way .

3.2. Unlock

The right lock
public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     *  Release distributed lock 
     * @param jedis Redis client 
     * @param lockKey  lock 
     * @param requestId  The request id 
     * @return  Whether to release successfully 
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

        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(requestId));

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

First get the corresponding lock value value , Check with requestId equal , If equal, the lock is removed ( Unlock ). So why use it Lua language ? Because to make sure that the above operations are atomic . Before 《Redis Thread model and transaction of 》 In the article , We guarantee the atomicity of a series of operation instructions by means of transaction , Use Lua Scripts can do the same thing .

Why guarantee atomicity ? If A The request is to obtain the lock corresponding to value Value validation requestId After equality , Give the order to delete . But because of the Internet and other reasons , The delete command is blocked . At this time, the lock is automatically unlocked due to timeout , also B The request got the lock , Re lock . Now A Request to delete command executed , The result is B The lock you requested was deleted .

3.3. lua

Redis The computing power of commands is not very powerful , Use Lua Language can make up for it to a great extent Redis That's not enough . stay Redis in , perform Lua Language is atomic , in other words Redis perform Lua It won't be interrupted , Having atomicity , This feature helps Redis Support for concurrent data consistency .

Redis Two methods are supported to run scripts , One is to input some directly Lua Language program code , The other is to make Lua Language is written as a document . in application , Some simple scripts can take the first way , For those with certain logic, the second one is generally adopted . And for simple scripts ,Redis Support for caching scripts , It just uses SHA-1 The algorithm signs the script , And then put SHA-1 Identification returns , Just run through this logo .

redis In the implementation of lua

Here is a brief introduction , Just type in some Lua Language program code way , Can be found in redis-cli Execute the following :

eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]

-- Example 1 
eval "return 'Hello World'" 0
-- Example 2
eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
  • eval On behalf of the executive Lua The command of language .
  • lua-script representative Lua Language script .
  • key-num Indicates how many of the parameters key, It should be noted that Redis in key It's from 1 At the beginning , without key Parameters of , Then write 0.
  • [key1 key2 key3…] yes key Pass as parameter to Lua Language , It can also be left blank , But need and key-num The number of corresponds to .
  • [value1 value2 value3 …] These parameters are passed to Lua Language , They can fill in or not .
lua Call in redis

stay Lua Use... In language redis.call Perform the operation :

redis.call(command,key[param1, param2…])

-- Example 1
eval "return redis.call('set','foo','bar')" 0
-- Example 2
eval "return redis.call('set',KEYS[1],'bar')" 1 foo
  • command Is the command , Include set、get、del etc. .
  • key Is the key to be operated .
  • param1,param2… For and on behalf of key Parameters of .

for example , Achieve one getset Of lua Script
getset.lua

local key = KEYS[1]
local newValue = ARGV[1]
local oldValue = redis.call('get', key)
redis.call('set', key, newValue)
return oldValue

3.4. Limitations and improvements

We said earlier , stay Redis In the cluster , There are some limitations in the implementation of distributed locks , When the master-slave is replaced, it is difficult to guarantee consistency .

The phenomenon

stay redis sentinel In the cluster , We have multiple redis, There is a master-slave relationship between them , For example, one master and two slaves . our set The data corresponding to the command is written to the main database , And then synchronize to the slave Library . When we apply for a lock , Correspondence is a command setnx mykey myvalue , stay redis sentinel In the cluster , This command first fell to the main library . Let's assume that the main database down 了 , This data has not yet been synchronized to the slave database ,sentinel Will be elected from one of the libraries to be the master library . At this time , We don't have mykey This data , If at this point the other one client perform setnx mykey hisvalue , It will also succeed , Even if you can get a lock . That means , There are two client For the lock . This is not what we want to see , Although the record of this happening is very small , Only in the master-slave failover It will happen when , Most of the time 、 Most systems can tolerate , But not all systems can tolerate this flaw .

solve

To address defects in the case of a fail over ,Antirez Invented Redlock Algorithm . Use redlock Algorithm , Multiple required redis example , When it's locked , It will send to more than half of the nodes setex mykey myvalue command , as long as More than half of the nodes are successful , So even if the lock is successful . This sum zookeeper The implementation of is very similar to ,zookeeper Clustered leader When you broadcast a command , More than half of them are required follower towards leader feedback ACK To take effect .

When used in actual work , We can choose existing open source implementations ,python Yes redlock-py,java There is Redisson redlock.

redlock It does solve the problem mentioned above “ Unreliable situations ”. however , It solves problems at the same time , And it comes with a price . You need more than one redis example , You need to introduce new libraries The code has to be adjusted , It's also bad for performance . therefore , It doesn't exist “ The perfect solution ”, What we need more is to be able to solve the problem according to the actual situation and conditions .

版权声明
本文为[KerryWu.]所创,转载请带上原文链接,感谢

Scroll to Top