Using redis to realize distributed lock (2) - specific implementation method 1

The previous article introduced the concept, function, and basic principles of distributed locks (http://guwq2014.iteye.com/blog/2365658),

This article takes a look at how to use redis to implement a distributed lock:

 

Step 1: Distributed lock implementation class: 

import redis.clients.jedis.ShardedJedis;

import com.suning.framework.sedis.ShardedJedisAction;
import com.suning.framework.sedis.impl.ShardedJedisClientImpl;

/**
 * Distributed lock based on redis implementation
 *
 * @author guweiqiang
 */
public class DistributedSedisLock {

	private ShardedJedisClientImpl jedisClient; // jedis client
	private String lockKey; // lock redis key
	private int expireMsecs = 60 * 1000; // The lock times out to prevent the thread from waiting for infinite execution after entering the lock
	private int timeoutMsecs = 10 * 1000; // lock waiting to prevent thread starvation
	private boolean locked = false;// The sign of getting the lock: true means getting the lock
	
	private static final long DEFAULT_SLEEP_TIME = 100; // thread sleep time 100 milliseconds

	/************************Construction method start**************************** ************/
	public DistributedSedisLock(ShardedJedisClientImpl jedisClient,
			String lockKey) {
		this.jedisClient = jedisClient;
		this.lockKey = lockKey;
	}

	public DistributedSedisLock(ShardedJedisClientImpl jedisClient,
			String lockKey, int timeoutMsecs) {
		this(jedisClient, lockKey);
		this.timeoutMsecs = timeoutMsecs;
	}

	public DistributedSedisLock(ShardedJedisClientImpl jedisClient,
			String lockKey, int timeoutMsecs, int expireMsecs) {
		this(jedisClient, lockKey, timeoutMsecs);
		this.expireMsecs = expireMsecs;
	}
	/************************Construction method end**************************** ************/
	
    public String getLockKey() {
        return lockKey;
    }
    
    /**
     * Determine whether the lock is obtained (the method of obtaining the lock provided externally)
     * @return true: got the lock; false: did not get the lock
     * @throws InterruptedException
     */
    public synchronized boolean acquire() throws InterruptedException {
        return acquire(jedisClient);
    }
    
    /**
     * Determine whether the lock is obtained
     * @param redisClient
     * @return true: got the lock; false: did not get the lock
     * @throws InterruptedException
     */
    private synchronized boolean acquire(ShardedJedisClientImpl jedisClient) throws InterruptedException {
        int timeout = timeoutMsecs;
        while (timeout >= 0) {
            long expires = System.currentTimeMillis() + expireMsecs + 1;
            final String expiresStr = String.valueOf(expires); // lock expiration time
            // lock
            Long setnxResult = jedisClient.execute(new ShardedJedisAction<Long>() {
                public Long doAction(ShardedJedis jedis) {
                    return jedis.setnx(lockKey, expiresStr);
                }
            });
            if (setnxResult!=null && setnxResult.intValue()==1) { // setnx returns 1, which means the setting is successful
                // lock acquired success
                locked = true;
                return true;
            }
            
            // Get the time in redis
            String currentValueStr = jedisClient.execute(new ShardedJedisAction<String>() {
                public String doAction(ShardedJedis jedis) {
                    return jedis.get(lockKey);
                }
            });
            if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
                // Judging whether it is empty or not, if it is not empty, if the value is set by other threads, the second conditional judgment will not pass
                // lock is expired
                String oldValueStr = jedisClient.execute(new ShardedJedisAction<String>() {
                    public String doAction(ShardedJedis jedis) {
                        return jedis.getSet(lockKey, expiresStr);
                    }
                });

                // Get the last lock expiration time, and set the current lock expiration time,
                // Only one thread can get the set time of the previous thread, because jedis.getSet is synchronized (atomic)
                if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
                    // If at this time, multiple threads have just arrived here, but only one thread whose set value is the same as the current value has the right to acquire the lock
                    // lock acquired
                    locked = true;
                    return true;
                }
            }
            
            timeout -= DEFAULT_SLEEP_TIME;
            Thread.sleep(DEFAULT_SLEEP_TIME);
        }
        
        return false;
    }

    /**
     * Release the lock (the method of releasing the lock provided externally)
     */
    public synchronized void release() {
        release(jedisClient);
    }
    
    /**
     * release lock
     */
    private synchronized void release(ShardedJedisClientImpl jedisClient) {
        if (locked) {
        	jedisClient.execute(new ShardedJedisAction<Long>() {
                public Long doAction(ShardedJedis jedis) {
                    return jedis.del(lockKey);
                }
            });
            locked = false;
        }
    }
}

 

 Step 2: Use tool classes exposed to the outside world: 

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.suning.framework.sedis.impl.ShardedJedisClientImpl;

/**
 * Distributed lock usage tool class
 *
 * @author guweiqiang
 */
public class DistributedLock {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(DistributedLock.class.getName());

	private DistributedSedisLock distributedSedisLock; // distributed lock
	private static ShardedJedisClientImpl jedisClient; // jedis client
	private String lockKey; // lock redis key
	private int expireMsecs; // The lock times out to prevent the thread from waiting for infinite execution after entering the lock
	private int timeoutMsecs; // lock waiting to prevent thread starvation
	
	public DistributedLock(String lockKey){
		this(lockKey, 3000, 300000);
	}
	
	public DistributedLock(String lockKey, int timeoutMsecs, int expireMsecs){
		this.lockKey = "YEB:BYUTIL:SHP:LOCK:" + lockKey;
		this.timeoutMsecs = timeoutMsecs;
		this.expireMsecs = expireMsecs;
		this.distributedSedisLock = new DistributedSedisLock(jedisClient, this.lockKey.intern(), timeoutMsecs, expireMsecs);
	}
	
	/**
	 * Thread wrapper
	 */
	public void wrap(Runnable runnable){
		long begin = System.currentTimeMillis();
		try {
			//timeout timeout, the time to wait for the lock to be set to 3 seconds; expiration expired, the time of the lock to exist is set to 5 minutes
			LOGGER.info("begin logck,lockKey={},timeoutMsecs={},expireMsecs={}", lockKey, timeoutMsecs, expireMsecs);
            if(distributedSedisLock.acquire()){ // Get the lock and execute the thread task
            	runnable.run();
            } else {
            	LOGGER.info("The time wait for lock more than [{}] ms ", timeoutMsecs);
            }
		} catch(Exception e){
			LOGGER.error("acquire lock Exception ", e);
		} finally {
			LOGGER.info("[{}]cost={}", lockKey, System.currentTimeMillis() - begin);
			// release the lock
			if(distributedSedisLock!=null){
				distributedSedisLock.release();
			}
		}
	}
	
	/**
	 * Initialize jedisClient
	 * @param jedisClient
	 */
    public static synchronized void setShardedJedisClient(ShardedJedisClientImpl jedisClient) {
    	DistributedLock.jedisClient = jedisClient;
    }
}

 

 When configuring a listener to initialize the jedis client (or initialize in other ways): 

import javax.servlet.ServletContextEvent;

import org.springframework.context.ApplicationContext;
import org.springframework.web.context.ContextLoaderListener;

import com.suning.framework.sedis.impl.ShardedJedisClientImpl;
import com.suning.shp.utils.DistributedLock;

/**
 * start the listener
 *
 * @author guweiqiang
 */
public class SystemListener extends ContextLoaderListener {

    private ApplicationContext applicationContext;

    @Override
    public void contextInitialized(ServletContextEvent event) {
        super.contextInitialized(event);
        
        /************* spring *********/
        applicationContext = super.getCurrentWebApplicationContext();

        /**** redis distributed lock *****/
        DistributedLock.setShardedJedisClient(applicationContext.getBean("jedisClient", ShardedJedisClientImpl.class));
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
        super.contextDestroyed(event);
    }

}

 After the listener is written, you need to configure it in web.xml:  

    <listener>
        <listener-class>com.suning.shp.listener.SystemListener</listener-class>
    </listener>

  At this point, a distributed lock based on redis can be used. The usage method is as follows: 

        DistributedLock lock = new DistributedLock(key, 10000, 5000);
        try {
                  lock.wrap(new Runnable(){
                       @Override
                       public void run() {
                           // Write the business code that needs to add distributed locks here
                        }
                  });
        } catch (Exception e){
                 LOGGER.error("Exception occurred: " + e.getMessage(), e);
        }

  

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326416782&siteId=291194637