使用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沒有成功獲取到鎖");}}} }
轉載于:https://www.cnblogs.com/mantu/p/6197638.html
總結
以上是生活随笔為你收集整理的使用redis来实现分布式锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 登录注册的页面制作
- 下一篇: SQL Server表结构和数据导入到M