Distributed Lock Implemented by Single-machine redis in springboot

Distributed locks are often required in microservices to perform some tasks.For example, deleting expired data periodically requires only one to execute in multiple services.

The following describes distributed locks in a less rigorous sense, because redis implements distributed locks in a rigorous sense or is more complex, using the following simple methods for everyday simple use.That is, an occasional failure to perform a task does not affect the business.

Implementing Essentials

1) Acquiring and releasing locks requires atomic operations.Either succeed or fail.Release either succeeds or fails

2) To complete the task, you need to release your own locks, not others'locks.

3) Locks must have an expiration time limit to prevent the task from crashing without releasing the lock, which prevents other nodes from acquiring the lock.

4) Execution node timed out for a long time without releasing the lock until the next task starts executing in parallel

 

Risk Points to Consider

1) Failure to acquire locks and occasional failure to perform tasks without affecting business or warning manual intervention

2) redis is down, preventing lock acquisition

 

Scenario 1: Low versions are implemented using jedis

1 Add dependencies, high or low versions Some methods may not

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.10.2</version>
</dependency>

2 Implementation Method

@Slf4j
public abstract class AbsDistributeLock {

    private Jedis jedis;

    public void initDistributeLock(String ip, int port, Integer database, String password) {
        jedis = new Jedis(ip, port);
        if (password != null && !password.isEmpty()) {
            jedis.auth(password.trim());
        }
        if (database == null || database < 0 || database > 15) {
            database = 0;
        }
        jedis.select(database);
    }

    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;

    /**
     * Specific tasks require subclasses to implement
     *
     * @throws RTException Distributed Lock Exception
     */
    public abstract void taskService() throws RTException;

    /**
     * Any execution, ANY OF
     * Any one of the nodes can execute the task taskService. Nodes without a lock will not execute the task
     *
     * @param lockKey    lockKey
     * @param keyValue   keyValue
     * @param expireTime Expiration time ms
     */
    public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) {
        boolean getLock = false;
        try {
            if ((getLock = getDistributedLock(jedis, lockKey, keyValue, expireTime))) {
                taskService();
            }
        } finally {
            if (getLock) {
                releaseDistributedLock(jedis, lockKey, keyValue);
            }
        }
    }

    /**
     * All Serial Execution, ALL IN LINE
     * All nodes must perform this task, one at a time.
     *
     * @param lockKey    lockKey
     * @param keyValue   keyValue
     * @param expireTime Expiration time ms
     */
    public void allNodeExecute(String lockKey, String keyValue, int expireTime) {
        try {
            while (!(getDistributedLock(jedis, lockKey, keyValue, expireTime))) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    log.info(e.getMessage());
                }
            }
            taskService();
        } finally {
            releaseDistributedLock(jedis, lockKey, keyValue);
        }
    }

    /**
     * @param jedis      Client
     * @param lockKey    key
     * @param keyValue   key value
     * @param expireTime Expiration time, ms
     */
    public static boolean getDistributedLock(Jedis jedis, String lockKey, String keyValue, int expireTime) {
        String result = jedis.set(lockKey, keyValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            log.info("ip:[{}] get lock:[{}], value:[{}], getLock result:[{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result);
            return true;
        } else {
            return false;
        }
    }

    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String keyValue) {
        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(keyValue));
        log.info("ip:[{}] release lock:[{}], value:[{}], release result: [{}]", IpUtil.getLocalIpAddr(), lockKey, keyValue, result);
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

}

 

Scenario 2: Higher version of springboot, executed using lua script

1 Add Dependency

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <version>2.2.0.RELEASE</version>
      </dependency>

2 Code implementation

@Slf4j
public abstract class AbsDistributeLockLua {

    private RedisTemplate<String, String> redisTemplate;

    public AbsDistributeLockLua(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * Specific tasks require subclasses to implement
     *
     * @throws RTException Distributed Lock Exception
     */
    public abstract void taskService() throws RTException;

    /**
     * Any execution, ANY OF
     * Any one of the nodes can execute the task taskService. Nodes without a lock will not execute the task
     *
     * @param lockKey    lockKey
     * @param keyValue   keyValue
     * @param expireTime Expiration time ms
     */
    public void onlyOneNodeExecute(String lockKey, String keyValue, int expireTime) {
        boolean getLock = false;
        try {
            if ((getLock = getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) {
                taskService();
            }
        } finally {
            if (getLock) {
                releaseDistributeLock(redisTemplate, lockKey, keyValue);
            }
        }
    }

    /**
     * All Serial Execution, ALL IN LINE
     * All nodes must perform this task, one at a time.
     *
     * @param lockKey    lockKey
     * @param keyValue   keyValue
     * @param expireTime Expiration time ms
     */
    public void allNodeExecute(String lockKey, String keyValue, int expireTime) {
        try {
            while (!(getDistributeLock(redisTemplate, lockKey, keyValue, expireTime))) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    log.info(e.getMessage());
                }
            }
            taskService();
        } finally {
            releaseDistributeLock(redisTemplate, lockKey, keyValue);
        }
    }

    /**
     * Lock and set expiration time through lua scripts
     *
     * @param key    Lock key value
     * @param value  Lock value
     * @param expire Expiration time, in milliseconds
     * @return true: Lock succeeded, false: Lock failed
     */
    public boolean getDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value, int expire) {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<String>();
        redisScript.setResultType(String.class);
        String strScript = "if redis.call('setNx',KEYS[1],ARGV[1])==1 then return redis.call('pexpire',KEYS[1],ARGV[2]) else return 0 end";
        redisScript.setScriptText(strScript);
        try {
            Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value, expire);
            System.out.println("redis Return:" + result);
            return "1".equals("" + result);
        } catch (Exception e) {
            //You can do exception handling yourself
            return false;
        }
    }

    /**
     * Release locks through lua scripts
     *
     * @param key   Lock key value
     * @param value Lock value (only released if the value in redis is the same as the value passed in, avoiding releasing locks from other threads)
     * @return true: Lock release succeeded, false: Lock release failed (may have expired or been released)
     */
    public boolean releaseDistributeLock(RedisTemplate<String, String> redisTemplate, String key, String value) {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(String.class);
        String strScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        redisScript.setScriptText(strScript);
        try {
            Object result = redisTemplate.execute(redisScript, redisTemplate.getStringSerializer(), redisTemplate.getStringSerializer(), Collections.singletonList(key), value);
            return "1".equals("" + result);
        } catch (Exception e) {
            //You can do exception handling yourself
            return false;
        }
    }
}

 

 

Code address: https://github.com/crazyCodeLove/distribute-lock

 

Reference:

https://www.cnblogs.com/bcde/p/11132479.html

https://blog.csdn.net/u013985664/article/details/94459529

Keywords: Java Jedis Redis Database less

Added by RickyF on Fri, 03 Jan 2020 09:02:48 +0200