Redis realizes distributed lock (design mode application practice)

Keywords: Jedis Redis Java network

I've seen all kinds of codes that use redis to implement distributed locks on the network. They are either wrong or fragmented. There is no complete example. Let me summarize two mechanisms of redis to implement distributed locks this weekend

Spin lock and exclusive lock

In view of the different ways of implementing locks, the policy pattern is used to organize the code

1, Spin lock

Distributed lock abstraction policy interface

package com.srr.lock;

/**
 * @Description Interface of distributed lock
 */
abstract  public interface DistributedLock {
    /**
     * Acquire lock
     */
    boolean lock();
    /**
     * Unlock
     */
    void unlock();
}

Abstract class of spin lock strategy, built with template method pattern

package com.srr.lock;

/**
 * Spin lock strategy template
 */
public abstract class SpinRedisLockStrategy implements DistributedLock {

    private static final Integer retry = 50; //5 retries by default
    private static final Long sleeptime = 100L;
    protected String lockKey;
    protected String requestId;
    protected int expireTime;

    private SpinRedisLockStrategy(){}
    public SpinRedisLockStrategy(String lockKey, String requestId, int expireTime){
        this.lockKey=lockKey;
        this.requestId=requestId;
        this.expireTime=expireTime;
    }
    /**
     * Template method, the framework of obtaining lock, and the specific logic are implemented by subclasses
     */
    @Override
    public boolean lock() {
        Boolean flag = false;
        try {
            for (int i=0;i<retry;i++){
                flag = tryLock();
                if(flag){
                    System.out.println(Thread.currentThread().getName()+"Lock acquired successfully");
                    break;
                }
                Thread.sleep(sleeptime);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
         return flag;
    }
    /**
     * Try to get lock, subclass implementation
     */
    protected abstract boolean tryLock() ;

    /**
     * Unlock: delete key
     */
    @Override
    public  abstract void unlock();
}

Spin lock implementation subclass

package com.srr.lock;

import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 * Spin lock
 */
public class SpinRedisLock extends SpinRedisLockStrategy{

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

    public SpinRedisLock(String lockKey, String requestId, int expireTime) {
        super(lockKey,requestId, expireTime);
    }

    @Override
    protected boolean tryLock() {
        Jedis jedis = new Jedis("localhost", 6379);  //Create client, 1 p And port number
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public void unlock() {
        Jedis jedis = new Jedis("localhost", 6379);  //Create client, 1 p And port number
        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)) {
            System.out.println("lock is unlock");
        }
    }
}

At this point, the implementation of distributed lock by spin lock is completed. Let's see the implementation of exclusive lock blocking

2, Exclusive lock

Before implementation, you need to understand a concept, that is, redis event notification:

/**
*Keyspace notification, all notifications are prefixed with keyspace @
*Key event notification, all notifications are prefixed with keyevent @
*All commands will be notified only after the key has been changed. For example, deleting foo will result in
*Key space notification
* "pmessage","__ key*__ : * ","__ keyspace@0__:foo","set"
*And key event notification
* "pmessage","__ key*__ : *","__ keyevent@0__:set","foo"
*/







After understanding the concept, you need to "Kea" the notify keyspace events in redis configuration file redis.conf, which defaults to "notify keyspace events", so as to start the event listening mechanism of redis.

Exclusive lock policy abstract class

   

package com.srr.lock;

import redis.clients.jedis.Jedis;

/**
 * @Description  Block get lock, template class
 */
public abstract class BlockingRedisLockStrategy implements DistributedLock {

    protected String lockKey;
    protected String requestId;
    protected int expireTime;

    private BlockingRedisLockStrategy(){}
    public BlockingRedisLockStrategy(String lockKey, String requestId,int expireTime){
        this.lockKey=lockKey;
        this.requestId=requestId;
        this.expireTime=expireTime;
    }
    /**
     * Template method, the framework of obtaining lock, and the specific logic are implemented by subclasses
     * @throws Exception
     */
    @Override
    public final boolean lock() {
        //Lock acquired successfully
        if (tryLock()){
            System.out.println(Thread.currentThread().getName()+"Lock acquired successfully");
            return true;
        }else{  //Failed to acquire lock
            //Blocking waiting
            waitLock();
            //Recursion, get lock again
            return lock();
        }
    }
    /**
     * Try to get lock, subclass implementation
     */
    protected abstract boolean tryLock() ;
    /**
     * Waiting to acquire lock, subclass implementation
     */
    protected abstract void waitLock();
    /**
     * Unlock: delete key
     */
    @Override
    public  abstract void unlock();
}

Exclusive lock implementation subclass

package com.srr.lock;

import redis.clients.jedis.Jedis;

import java.util.Collections;

/**
 * Lock, block
 */
public class BlockingRedisLock extends BlockingRedisLockStrategy {

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

    public BlockingRedisLock(String lockKey, String requestId, int expireTime) {
        super(lockKey,requestId, expireTime);
    }


    /**
     * Try to get distributed lock
     * @return Success or not
     */
    @Override
    public boolean tryLock() {
        Jedis jedis = new Jedis("localhost", 6379);  //Create client, 1 p And port number
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    }

    @Override
    public void waitLock() {
        //judge key Does it exist
        Jedis jedis = new Jedis("localhost", 6379);  //Create client, 1 p And port number
        KeyExpiredListener keyExpiredListener = new KeyExpiredListener();
        /**
         * Keyspace notification, all notifications are prefixed with keyspace @
         * Key event notification, all notifications are prefixed with keyevent @
         * All commands will be notified only after the key has been changed. For example, deleting foo will result in
         * Key space notification
         * "pmessage","__ key*__ : * ","__ keyspace@0__:foo","set"
         * And key event notification
         * "pmessage","__ key*__ : *","__ keyevent@0__:set","foo"
         */
        //If you want to monitor a key You can subscribe to the__ keyspace@0__,Monitor what's going on key,Just subscribe__ keyevent@0__
        //Here we need to monitor that the key of the distributed lock has been deleted, so we need to monitor the deletion action"__keyspace@0__:"+key
        jedis.psubscribe(keyExpiredListener, "__keyspace@0__:"+lockKey);
        System.out.println("over");
    }

    /**
     * Release distributed lock
     * @return Whether to release successfully
     */
    @Override
    public void unlock() {
        Jedis jedis = new Jedis("localhost", 6379);  //Create client, 1 p And port number
        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)) {
            System.out.println("lock is unlock");
        }
    }
}

redis event listening class

package com.srr.lock;


import redis.clients.jedis.JedisPubSub;

/**
 * redis Event listener
 */
public class KeyDelListener extends JedisPubSub {
    public KeyDelListener(){

    }
    // Handling when initializing subscription
    @Override
    public void onPSubscribe(String pattern, int subscribedChannels) {
    }

    // Processing after getting the subscribed message
    @Override
    public void onPMessage(String pattern, String channel, String message) {
        System.out.println("message == "+message);
        this.punsubscribe();
        System.out.println("unsubscribe == "+message);
    }
}

The complete code of exclusive lock is finished here. In fact, the difference between the two is that the implementation of lock is different. In order to ensure the code integrity, the author pasted it all.

After the code is written, give a scenario to test whether our code has any problems. Please see the following test code:

Here we build a Lock tool class:

package com.srr.lock;

/**
 * Lock tools
 */
public class Lock {
    /**
     * Acquire lock
     */
    boolean lock(DistributedLock lock) {
        return lock.lock();
    };

    /**
     * Release lock
     */
    void unlock(DistributedLock lock) {
        lock.unlock();
    };
}

Test class:

package com.srr.lock;

import redis.clients.jedis.Jedis;

/**
 *  Test scenario
 *  count From 1 to 101
 *  Using redis distributed lock to ensure correct result in distributed environment
 */
public class T {

    volatile int  count = 1;

    public void inc(){
        for(int i = 0;i<100;i++){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println("count == "+count);
        }
    }

    public int getCount(){
       return count;
    }

    public static void main(String[] args) {
        final T t = new T();
        final Lock lock = new Lock();
        //final RedisLock redisLock = new BlockingRedisLock("","1",100000,jedis);
        final DistributedLock distributedLock = new SpinRedisLock("test","1",100000);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                if(lock.lock(distributedLock)){
                    t.inc();
                    System.out.println("t1 running");
                    System.out.println("t1 == count == "+ t.getCount());
                    lock.unlock(distributedLock);
                }

            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                if(lock.lock(distributedLock)) {
                    t.inc();
                    System.out.println("t2 running");
                    System.out.println("t2 == count == " + t.getCount());
                    lock.unlock(distributedLock);
                }

            }
        });

        t1.start();
        t2.start();
    }
}

Test results:

At this point, all the code is completed. If you want to use zookeeper to implement the distributed lock, you only need to abstract a policy class to implement the DistributedLock interface. Is it convenient.

Original is not easy, pay more attention!

Posted by Shiki on Sun, 10 May 2020 20:10:38 -0700