Fine product redis distributed lock

background

I have written two sections of the high-performance data structure of redis. Click to view it . Today, let’s change my taste. Today, let’s take a look at the application of redis in distributed systems. Using redis for distributed locks can be said to be a commonplace question.

redis distributed lock

Problems solved by distributed locks

When it comes to locks, the first response is thread blocking. What needs to be noted here is that the dimension here will rise to a level, not only between threads of a service (process), but also between multiple services. It can be said about the concurrency problem between multiple processes (the two processes are on the two services respectively). Therefore, the use of inter-thread locks here cannot solve problems such as (reentrantLock, Sychronized, CyclicBarrier, CountDownlotch, Semaphore, volatile) these JUC packages and the locking mechanism provided by JDK. They can only deal with concurrency issues between different threads of the same process. Therefore, in order to solve the concurrency security problem of different processes and different server times, redis distributed locks are created. So much to say here is to distinguish between distributed locks and thread locks (I don't know if it is appropriate to call them, or object locks).

Understand according to the diagram. Two clients manipulate the same data in the database for modification.
Insert picture description here

achieve

  1. The essence of distributed locks is that threads between different services or the same service compete for the hole in redis. When a thread occupies the hole and the door is locked, other threads have to give up or wait again. When a thread finishes occupying the pit, use the del instruction to release the pit position, that is, the toilet door is open. The actual method of use is to set a name for the pit, and this value indicates that it is occupied. The
    pit is usually occupied by the setnx (set if not exists) command, and only one client is allowed to occupy the pit. First come first, use up first
    , then call the del command to release the pit.
// 这里的冒号:就是一个普通的字符,没特别含义,它可以是任意其它字符,不要误解
> setnx lock:codehole true
OK
... do something critical ...
> del lock:codehole
(integer) 1
  1. But the above scheme is problematic, that is, when this thread executes abnormally, causing the del release command to not take effect, there is a problem. But some people will say that I use the finally block in java to release the pit. This is indeed a way, but what if the service used directly hangs up?
//伪代码
try{
            
        }catch (Exception e){
            e.printStackTrace();
        }finally {
        //就算异常了也会执行,删除操作
            delKey;
        }
    }
  1. That is to give this lock (pit position) a time limit, you have to release it automatically when the time comes. Then the implementation is to acquire the lock first, and then set the timing time:
> setnx lock:codehole true
OK
> expire lock:codehole 5
... do something critical ...
> del lock:codehole
(integer) 1
  1. Since the method mentioned above is not that the operation process is not atomic (to be executed all, or not executed at all. There will be no half of the execution), it will also appear after the lock is obtained. Then there is the problem of abnormal lock timeout time or service hanging. This returns to the original problem.

  2. Then we think about what good way to deal with this? Speaking of atomicity, isn't this one of the four characteristics of things? Then we will use redis to deal with this problem. Put the instructions setnx and expire in the same thing for execution. This problem was dealt with in redis 2.8, that is, these two commands can be executed together. This is why we use distributed locks.

> set lock:codehole true ex 5 nx OK ... do something critical ... 
> del lock:codehole
  1. What should I do if the lock timeout occurs because the thread execution time is too long?

Distributed lock expired

  1. Redis distributed locks cannot solve the timeout problem. If the logic between locking and releasing the lock is executed too long, and
    the lock timeout limit is exceeded, problems will occur. Because the lock expires at this time, the second thread re-holds the lock,
    but immediately after the first thread has executed the business logic, the lock is released, and the third thread will be in the second thread logic
    I got the lock after the edit was executed.
  2. First of all, we have to avoid this kind of problem. The reason is that the execution time of the business logic is too long, so we should try to use it for a short time when using it, try to avoid the business exaggerated service call, and set the expiration time as reasonable as possible Try to keep the business execution time as large as possible.
  3. Another safer solution is to put a value for each lock. When doing del, we can first compare whether the value was previously put. This is a bit of optimistic locking. compare and swap Replace after comparison. But matching and key deletion are not an atomic operation, so worry. Fortunately, redis provides a script execution method, and the script is atomic, and the lua script is used to achieve the goal of atomicity. (Pay attention to the redis sharding mode of Alibaba Cloud. Their sharding mode has problems with the support of lua scripts. The pit I stepped on. When using redission's RmapCache, its source code has a lua script, which causes the final run to fail
# delifequals
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end

Reentrancy

  1. Reentrancy means that if you request the lock again while holding the lock, you can still request the lock again, then the lock is reentrant. For example, reentrantLock and sychronized in java are reentrant locks.
  2. The general principle of their implementation is to modify their lock status through optimistic locking. If it is the same thread ID, it will be changed successfully and the lock can be acquired. If it is not the same thread ID, the lock will be upgraded. How do we use redis? The same reasoning. We can refer to this thread ID or this task ID, which can be thread dimension or task dimension. Make reentrant.

Use of middleware

  1. jedis (when used, there is a problem with the connection pool recycling, it should be a problem with my own code, which was not resolved in the end)
  2. redission (The problem of reclaiming the connection pool after replacing redssion is solved, but there is a lua script problem when upgrading the original redis stand-alone version to sharding mode)

The two redis middleware above are very comfortable to use. Inside the brackets are the pits I stepped on.

Real case sharing

  • Requirements
    Calling between microservices, exposing the interface to the outside, in order to prevent the interface from being repeatedly called by the same task ID in a short time (to ensure the completion of the previous task execution). Prevent simultaneous operation of a piece of data.
  • Solution: Use redis distributed lock and taskId as the lock. Set the expiration time to 2s and the waiting time to 2 seconds.
  • 2s means that the longest time for business execution is 2s. Release if not finished.
  • 3s means that the task I am waiting for will be executed when the lock timeout expires.
    Used: redssion packaged:
    @Override
    public Response discernCommon(DiscernCallBackResultTo discernCallBackResultTo) {
        //防止并发,通过同一个TSKID做锁 重复新增发票,锁过期时间3秒,等待时间为2秒
        boolean lock = RedissonLockUtils.tryLock(discernCallBackResultTo.getTaskId(), 2, 3);
        if (lock) {
            try {
                dealDiscernResult(discernCallBackResultTo);
            } catch (Exception e) {
                LOGGER.error(e.getMessage(), Coder.of(ErrorCode.DISCERN_DEAL_EXCEPTION), e);
            } finally {
             RedissonLockUtils.unlock(discernCallBackResultTo.getTaskId());
            }
        } else {
            EventMessage confilct = new EventMessage(EventEnum.EVENT_CONFILC_TASKID.getEventId())
                    .addExtValue("taskId", discernCallBackResultTo.getTaskId());
            Tracker.getInstance().record(eventMessage);
        }
        return Response.ok("回调成功!");
    }

to sum up

  • Implementation and evolution of redis distributed lock
  • The redis lock expires and causes the lock to be released incorrectly. By adding tasks, first compare and delete
  • The realization of the reentrant lock of redis distributed lock, through the optimistic locking method, the thread iD or task ID is designed to be reentrant.

reference

  • "Redis Extreme Cold"
  • http://ifeve.com/?x=34&y=9&s=%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81

Guess you like

Origin blog.csdn.net/weixin_40413961/article/details/107995385