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); }