使用redis来实现分布式锁
在实际的工作中,有部分的特定场景需要使用到分布式锁来进行跨服务器资源的统一调配。之前在一家医疗互联网公司,因为黄牛抢号等原因,造成同一个患者同一时段在同一个医生处,挂到了两个及以上的号,我对之前我司实现的代码进行了封装和改进,在github上提供了源码,有需要的朋友,可以下载代码,并用maven打包成jar包,使用起来比较方便。
源码地址:https://github.com/mantuliu/distributed
核心feature:
redis的setnx()方法,此方法提供了一个原子操作,可以保证有且只有一个分布式的调用返回值为1,在分布式锁的概念里,则代表此锁被此次调用的线程占用;
redis的expire()方法,通过此方法来设置此锁的过期时间;
对于死锁的情况,封装的分布式锁包可以自动解锁;
每次获取锁的线程,都会被标识,当此线程还没有释放锁时,此线程继续调用trylock方法,还可以获得该锁,只有获取该锁的线程才有unlock()释放锁的权利;
支持trylock()和trylock(long timeout, TimeUnit unit)两种方式;
使用应用服务器提供的jedis实例;
对于当前服务器试图获取该锁的线程数量进行监控,当数量大于阀值时,后续线程在trylock(long timeout, TimeUnit unit)及trylock()时,直接返回失败,阀值可以设置;
锁的过期时间默认值是5秒,可以根据实际情况进行设置;
锁的键值前缀默认值是mantu:dislock:,可以根据实际情况进行设置;
通过LockSupport类来获取锁,使得试图获取同一把锁的线程得到的对象是同一个。
源码解析:
CommonType类
public class CommonType { public static int WAITLOCKERS = 2;//当前服务器等待锁的线程数量,如果超过或等于此值,当前线程直接返回,不再等待锁 public static String REDISKEY="mantu:dislock:";//redis下key前缀 public static int LOCKEXPIRETIME = 5;//锁的过期时间,单位秒,默认5秒过期 }
DisLock接口
public interface DisLock{ boolean tryLock(Jedis jedis); boolean tryLock(long time, TimeUnit unit,Jedis jedis) throws InterruptedException; void unlock(Jedis jedis); }
RedisDisLock类,实际的管理锁的类
public class RedisDisLock implements DisLock{ private static final Logger LOG = LoggerFactory.getLogger(RedisDisLock.class); private transient Thread exclusiveOwnerThread; String lockKey=""; AtomicInteger waitToLock=new AtomicInteger(0); public RedisDisLock(String lockKey){ this.lockKey=CommonType.REDISKEY+lockKey; } public boolean tryLock(Jedis jedis) { Thread thread = Thread.currentThread(); if(thread==this.getExclusiveOwnerThread()){ return true; } Long i = jedis.setnx(lockKey, System.currentTimeMillis()+""); if(i.intValue()==1){ jedis.expire(lockKey, CommonType.LOCKEXPIRETIME); setExclusiveOwnerThread(thread); return true; } else{//对于可能性非常低的死锁情况进行解锁 String initTime = jedis.get(lockKey); if(initTime==null){ LOG.debug("initTime's value is null"); return false; } long iniTime=0L; try{ iniTime = Long.parseLong(initTime); } catch(NumberFormatException nfex){ LOG.warn(nfex.getMessage()); jedis.expire(lockKey, 1); return false; } if(((System.currentTimeMillis()-iniTime)/1000-CommonType.LOCKEXPIRETIME-1)>0){ String oldTime = jedis.getSet(lockKey, System.currentTimeMillis()+"");//对于及其极端的情况,lock被线程1处理掉了,但是又被线程2getset新的值了,通过下一次调用trylock()方法处理 if(oldTime==null){ LOG.info("oldTime is null"); return false; } if(initTime.equals(oldTime)){ release(jedis); } } } return false; } public boolean tryLock(long timeout, TimeUnit unit,Jedis jedis) throws InterruptedException { long nanosTimeout = unit.toNanos(timeout); long lastTime = System.nanoTime(); if(tryLock(jedis)){ return true; } try{ int waitLockers = waitToLock.getAndIncrement(); if(waitLockers>=CommonType.WAITLOCKERS){ LOG.debug("wait the lock' thread num is much,so return flase"); return false; } for(;;){ if(tryLock(jedis)){ return true; } if (nanosTimeout <= 0){ LOG.debug("getlock timeout"); return false; } if(nanosTimeout>100000){ LockSupport.parkNanos(100000);//中断100毫秒 } long now = System.nanoTime(); nanosTimeout -= now - lastTime; lastTime = now; if (nanosTimeout <= 0){ LOG.debug("getlock timeout"); return false; } if (Thread.interrupted()){ throw new InterruptedException(); } } } finally{ waitToLock.decrementAndGet(); } } public void unlock(Jedis jedis) { Thread thread = Thread.currentThread(); if(thread==this.getExclusiveOwnerThread()){ LOG.debug("unlock the thread {}",thread.getId()); release(jedis); } } private void release(Jedis jedis){ setExclusiveOwnerThread(null); jedis.del(lockKey); } /** * Sets the thread that currently owns exclusive access. A * <tt>null</tt> argument indicates that no thread owns access. * This method does not otherwise impose any synchronization or * <tt>volatile</tt> field accesses. */ protected final void setExclusiveOwnerThread(Thread t) { exclusiveOwnerThread = t; } /** * Returns the thread last set by * <tt>setExclusiveOwnerThread</tt>, or <tt>null</tt> if never * set. This method does not otherwise impose any synchronization * or <tt>volatile</tt> field accesses. * @return the owner thread */ protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; } }
LockSupport类,实际的业务代码首先要通过LockSupport来获取redis锁的对象,再使用
public class LockSupport { static ConcurrentHashMap <String,RedisDisLock>lockMap = new ConcurrentHashMap<String,RedisDisLock>(); public static DisLock getRedisLock(String lockKey){ RedisDisLock lock=null; if(lockMap.contains(lockKey)){ lock = lockMap.get(lockKey); } else{ RedisDisLock lockN = new RedisDisLock(lockKey); lock = lockMap.putIfAbsent(lockKey, lockN); if(lock==null){ lock=lockN; } } return lock; } }
RedisDisLockTest类是使用此jar的demo代码
public class RedisDisLockTest { public static void main(String [] args){ RedisDisLockTest test = new RedisDisLockTest(); //test.testOrder(); //test.testOrder2(); //test.testNOUnlock(); test.testOtherUnlock(); } public void testOrder(){ JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){ Jedis jedis = jp.getResource(); OrderThread th = new OrderThread("123456",jedis); th.start(); } } public void testOrder2(){ JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){ Jedis jedis = jp.getResource(); OrderThread th = new OrderThread("1234567",jedis); th.start(); } } public void testNOUnlock(){ JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){ Jedis jedis = jp.getResource(); TestNOUnlock th = new TestNOUnlock("12345678",jedis); th.start(); } } public void testOtherUnlock(){ JedisPool jp = new JedisPool("127.0.0.1",6379); for(int i=0;i<5;i++){ Jedis jedis = jp.getResource(); TestOtherUnlock th = new TestOtherUnlock("unlock",jedis); th.start(); } } class OrderThread extends Thread{ String lockKey=""; Jedis jedis; public OrderThread(String lockKey,Jedis jedis){ this.lockKey=lockKey; this.jedis=jedis; } public void run(){ DisLock lock = LockSupport.getRedisLock(lockKey); try { if(lock.tryLock(2,TimeUnit.SECONDS,jedis)){ System.out.println("订单"+lockKey+"创建成功!"); lock.unlock(jedis); } else{ System.out.println("没有成功获取到锁"); } } catch (InterruptedException e) { e.printStackTrace(); } } } class TestNOUnlock extends Thread{ String lockKey=""; Jedis jedis; public TestNOUnlock(String lockKey,Jedis jedis){ this.lockKey=lockKey; this.jedis=jedis; } public void run(){ DisLock lock = LockSupport.getRedisLock(lockKey); try { if(lock.tryLock(2,TimeUnit.SECONDS,jedis)){ System.out.println("订单"+lockKey+"创建成功!"); //lock.unlock(jedis);//no unlock } else{ System.out.println("没有成功获取到锁"); } } catch (InterruptedException e) { e.printStackTrace(); } } } class TestOtherUnlock extends Thread{ String lockKey=""; Jedis jedis; public TestOtherUnlock(String lockKey,Jedis jedis){ this.lockKey=lockKey; this.jedis=jedis; } public void run(){ DisLock lock = LockSupport.getRedisLock(lockKey); if(lock.tryLock(jedis)){ System.out.println("订单"+lockKey+"创建成功!"); //lock.unlock(jedis);//no unlock } else{ lock.unlock(jedis); System.out.println("TestOtherUnlock没有成功获取到锁"); } } } }