使用redis來實現分佈式鎖

在實際的工做中,有部分的特定場景須要使用到分佈式鎖來進行跨服務器資源的統一調配。以前在一家醫療互聯網公司,由於黃牛搶號等緣由,形成同一個患者同一時段在同一個醫生處,掛到了兩個及以上的號,我對以前我司實現的代碼進行了封裝和改進,在github上提供了源碼,有須要的朋友,能夠下載代碼,並用maven打包成jar包,使用起來比較方便。java

源碼地址https://github.com/mantuliu/distributedgit

核心feature:github

   redis的setnx()方法,此方法提供了一個原子操做,能夠保證有且只有一個分佈式的調用返回值爲1,在分佈式鎖的概念裏,則表明此鎖被這次調用的線程佔用;redis

   redis的expire()方法,經過此方法來設置此鎖的過時時間;服務器

   對於死鎖的狀況,封裝的分佈式鎖包能夠自動解鎖;maven

   每次獲取鎖的線程,都會被標識,當此線程尚未釋放鎖時,此線程繼續調用trylock方法,還能夠得到該鎖,只有獲取該鎖的線程纔有unlock()釋放鎖的權利;分佈式

   支持trylock()和trylock(long timeout, TimeUnit unit)兩種方式;this

   使用應用服務器提供的jedis實例;spa

   對於當前服務器試圖獲取該鎖的線程數量進行監控,當數量大於閥值時,後續線程在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沒有成功獲取到鎖");
            }
            
        }
    }
}
相關文章
相關標籤/搜索