Interviewer: How to solve the problem of lockout in Redis distributed locks

2023-03-18  

1, preface

Regarding Redis distributed locks, I checked a lot of information, and found that many of them have only achieved the most basic functions, but it has not solved the problem that the business logic is not completed when the lock is overtime. Set to 10S (in order to solve the problem of dead locks), but the code execution time may require 30S, and then the Redis server is deleted after 10s. At this time, thread B just apply for lock. The redis server does not exist. After the code is executed, the problem comes, and the A and B threads have obtained the lock and execution business logic at the same time. Enjoy.

In order to solve this problem, this article will be verified with a complete code and test cases. I hope to bring a little help to the friends

2, preparation work

pressure testing tool jmeter

https://pan.baidu.com/share/init?surl=NN0c0tDYQjBTTPA-WTT3yg
extract code: 8F2A

Redis-Desktop-Manager client

https://pan.baidu.com/share/init?surl=NoJtZZZOXsk45aQYtveWbQ
extract code: 9BHF

postman

https://pan.baidu.com/share/init?surl=28sGJk4zxoOknAd-47hE7w
extract code: vfu7

can also be downloaded directly on the official website.

Need to postman because I haven’t found a way to open a multi -window of JMETER, haha

3. Explanation

1, SpringMVC project

2, maven dependence

 

        <!–redis–>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>1.6.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.7.3</version>
        </dependency>

3, core category

  • distributed lock tool class: DistributedLock

  • Test interface class: PCINFORMATIONSERVIMPL

  • Lock latency Guardian thread class: Postponetask

Four, realize ideas

First test without opening the lock -delay thread. After the time time of the A thread is set to 10s, the business logic time is set to 30s, 10s, and the interface is called to see if the lock can be obtained. Thread security issue

Same as above, while locking, turn on the lock -delay thread, call the interface, and check if you can get the lock.

5. Implementation

1, version 01 code

1)、DistributedLock

 

package com.cn.pinliang.common.util;

import com.cn.pinliang.common.thread.PostponeTask;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import java.io.Serializable;
import java.util.Collections;

@Component
public class DistributedLock {

    @Autowired
    private RedisTemplate<Serializable, Object> redisTemplate;

    private static final Long RELEASE_SUCCESS = 1L;

    private static final String LOCK_SUCCESS = “OK”;
    private static final String SET_IF_NOT_EXIST = “NX”;
    private static final String SET_WITH_EXPIRE_TIME = “EX”;
// Unlock script (LUA)
    private static final String RELEASE_LOCK_SCRIPT = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;

    /**
*distributed lock
     * @param key
     * @param value
*@param Expiretime unit: second
     * @return
     */
    public boolean lock(String key, String value, long expireTime) {

        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {

            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {

                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

    /**
*Unlock
     * @param key
     * @param value
     * @return
     */
    public Boolean unLock(String key, String value) {

        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {

            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
            if (RELEASE_SUCCESS.equals(result)) {

                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

}

Instructions: Just 2 methods, unlock unlocking, lock the lock using the JEDIS SETNX method, unlock the execution of the LUA script, all atomic operation

2)、PcInformationServiceImpl

 

    public JsonResult add() throws Exception {

        String key = “add_information_lock”;
        String value = RandomUtil.produceStringAndNumber(10);
        long expireTime = 10L;

        boolean lock = distributedLock.lock(key, value, expireTime);
        String threadName = Thread.currentThread().getName();
        if (lock) {

system.out.println (ThreadName+”get locks ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………….ize
            Thread.sleep(30000);
            distributedLock.unLock(key, value);

        } else {

System.out.println (ThreadName+”unlocked …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………..ize
Return jsonresult.fail (“Unlocked”);
        }

        return JsonResult.succeed();
    }

Instructions: The test class is simple, and the value is randomly generated to ensure that the uniqueness will not unlock the locks held by other clients under timeout.

3). Open the Redis-Desktop-Manager client, refresh the cache, you can see that at this time, there is no KEY of the ADD_INFORMATION_LOCK

4), start jmeter, call the interface test

Set 5 threads to access at the same time, and check the redis, add_information_lock during the 10S timeout.

redis

1-4 requests, no locks are obtained

5th request, get the lock

OK, so far, everything is normal. After testing 10s, A is still performing business logic. See if other threads can get the lock

图片It can be seen that the operation is successful, indicating that A and B have performed this code that should have been exclusive at the same time, which needs to be optimized.

2, version 02 code

1)、DistributedLock

 

package com.cn.pinliang.common.util;

import com.cn.pinliang.common.thread.PostponeTask;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import java.io.Serializable;
import java.util.Collections;

@Component
public class DistributedLock {

    @Autowired
    private RedisTemplate<Serializable, Object> redisTemplate;

    private static final Long RELEASE_SUCCESS = 1L;
    private static final Long POSTPONE_SUCCESS = 1L;

    private static final String LOCK_SUCCESS = “OK”;
    private static final String SET_IF_NOT_EXIST = “NX”;
    private static final String SET_WITH_EXPIRE_TIME = “EX”;
// Unlock script (LUA)
    private static final String RELEASE_LOCK_SCRIPT = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
// Delay script
    private static final String POSTPONE_LOCK_SCRIPT = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘expire’, KEYS[1], ARGV[2]) else return ‘0’ end”;

    /**
*distributed lock
     * @param key
     * @param value
*@param Expiretime unit: second
     * @return
     */
    public boolean lock(String key, String value, long expireTime) {

// Lock
        Boolean locked = redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {

            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            if (LOCK_SUCCESS.equals(result)) {

                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });

        if (locked) {

// Successful locks, start a delay thread to prevent the lock release of the lock due to the lockout of the business logic
            PostponeTask postponeTask = new PostponeTask(key, value, expireTime, this);
            Thread thread = new Thread(postponeTask);
            thread.setDaemon(Boolean.TRUE);
            thread.start();
        }

        return locked;
    }

    /**
*Unlock
     * @param key
     * @param value
     * @return
     */
    public Boolean unLock(String key, String value) {

        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {

            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
            if (RELEASE_SUCCESS.equals(result)) {

                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

    /**
*Lock latter
     * @param key
     * @param value
     * @param expireTime
     * @return
     */
    public Boolean postpone(String key, String value, long expireTime) {

        return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {

            Jedis jedis = (Jedis) redisConnection.getNativeConnection();
            Object result = jedis.eval(POSTPONE_LOCK_SCRIPT, Lists.newArrayList(key), Lists.newArrayList(value, String.valueOf(expireTime)));
            if (POSTPONE_SUCCESS.equals(result)) {

                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }

}

Description: Added latter latter method, Lua script, self -brain replenishment related grammar

2), PCINFORMATIONSERVIMPL does not need to be changed

3)、PostponeTask

 

package com.cn.pinliang.common.thread;

import com.cn.pinliang.common.util.DistributedLock;

public class PostponeTask implements Runnable {

    private String key;
    private String value;
    private long expireTime;
    private boolean isRunning;
    private DistributedLock distributedLock;

    public PostponeTask() {

    }

    public PostponeTask(String key, String value, long expireTime, DistributedLock distributedLock) {

        this.key = key;
        this.value = value;
        this.expireTime = expireTime;
        this.isRunning = Boolean.TRUE;
        this.distributedLock = distributedLock;
    }

    @Override
    public void run() {

Long Waittime = Expiretime*1000*2/3; // How long does the thread wait for

……………………….;
        while (isRunning) {

            try {

                Thread.sleep(waitTime);
                if (distributedLock.postpone(key, value, expireTime)) {

                } else {

                    this.stop();
                }
            } catch (Exception e) {

                e.printStackTrace();
            }
        }
    }

    private void stop() {

        this.isRunning = Boolean.FALSE;
    }

}

Instructions: At the same time, call the lock, immediately turn on the postponetask thread. After the thread waits for 2/3 time, start to execute the extension of the passing code. After the implementation, in the process, other threads cannot get the lock, which ensures that thread security

The test results below

After

10s, check the redis server, add_information_lock still exists, indicating that the delay is successful

At this time, use postman to ask again, and find that you can’t get the lock

Look at the console printing

The

A thread gets the lock at 19:09:11, and delay the delay after 10 * 2 /3 = 6s.

A thread is executed, the lock is released, and other threads can compete in the lock

OK, so far, the problem of locking conflict that is still executed by the lockout time and the business logic is still very simple. The most rigorous way is to implement the official Redlock algorithm. They are renewed at the time of timeout to ensure that the business logic is not executed.

source

Random Posts

DWR3 Implementation Message Precise Push Detailed Step

TREEMAP is sorted according to Key and Value

F1 score, Micro F1Score, Macro F1Score

COCOS2DX3.15 New Project Import Android Studio to report an error

CodeForcess 653B Digital DPCSDN