分享一個redis分佈式鎖的demo

首先說一下需要使用分佈式鎖的場景

  • 多實例部署,分佈式系統中經常會遇到,基於jvm的鎖無法滿足多實例中鎖的需求, synchronized,Lock只能控制在當前JVM中的資源競爭。基於數據庫實現的話肯能會造成IO壓力,甚至死鎖,其本身的效率也比較低。zk的實現方式也是可以的。

 

redis作爲分佈式鎖要注意的事項,還有用到的一些功能

注意事項:

  • 互斥性,同一時刻,智能有一個客戶端持有鎖。
  • 防止死鎖發生,如果持有鎖的客戶端崩潰沒有主動釋放鎖,也要保證鎖可以正常釋放及其他客戶端可以正常加鎖。
  • 防止誤刪,加鎖和釋放鎖必須是同一個客戶端。
  • 高可用,如果只有Redis服務端負責加鎖,這個Redis掛了怎麼辦?

用到的功能:

  • setnx(key, value): set if not exits,若該key-value不存在,則成功加入緩存並且返回1,否則返回0。
  • expire(key, seconds):設置key-value的有效期爲seconds秒。
  • watch(key):用於監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那麼事務將被打斷
  • unwatch:取消 WATCH 命令對所有 key 的監視
  • multi:標記一個事務塊的開始。多條命令會按照先後順序被放進一個隊列當中,最後由 EXEC 命令原子性(atomic)地執行。
  • del(key):在 key 存在時刪除 key。

代碼:

註釋TODO的地方需要自行修改處理,日誌也自行替換吧。思路解釋寫在註釋中了,方便理解。

redis客戶端:

/**
 * reids 客戶端
 */
public class RedisManager {

    private static JedisPool jedisPool;
    static{
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(20);
        jedisPoolConfig.setMaxIdle(10);
        jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379);
    }

    public static Jedis getJedis() throws Exception {
        if(null != jedisPool){
            return jedisPool.getResource();
        }
        throw  new Exception("Jedispool was not init");
    }
}

獲取鎖:

/**
     * 獲取分佈式鎖
      * @param key
     * @param timeout 超時時限
     * @return
     */
    public String getLock(String key,int timeout){
         //TODO 過期時間從緩存中取 配置化
        try {
            //獲得客戶端實例
            Jedis jedis = RedisManager.getJedis();
            //生成隨機鎖號,解鎖時用於判斷,是否爲當前線程持有鎖
            String value = UUID.randomUUID().toString();
            //不能無限等待,超出時間放棄自旋
            long end = System.currentTimeMillis() + timeout;
            //判斷是否超出時限  並且阻塞
           while(System.currentTimeMillis()<end){
               //設置  setnx 成功後 返回1,否則爲0
               if(jedis.setnx(key,value)==1){
                   jedis.expire(key,timeout);
                   return value;
               }
               //如果鎖獲取成功 reids掛掉,判斷鎖永久有效,再次設置失效時間
               if(jedis.ttl(key)==-1){
                   jedis.expire(key,timeout);
               }
               //不能無限佔用資源 設置休眠,然後再次獲取鎖
               Thread.sleep(1000);
           }
        } catch (Exception e) {
            //TODO 日誌
            e.printStackTrace();
        }

        return null;
    }

釋放鎖:

 /**
     * 釋放鎖(判斷是否爲當前線程持有)
     * @param key
     * @param value
     * @return
     */
    public boolean releaseLock(String key,String value){
        try {
            Jedis jedis = RedisManager.getJedis();
            while (true) {
            //監控一個或多個KEY 一旦KEY 被刪除 ,後面事務的代碼不會被執行
            jedis.watch(key);
            //判斷是否爲當前線程持有鎖
            if(value.equals(jedis.get(key))) {
                //開啓事務
                Transaction transaction = jedis.multi();
                transaction.del(key);
                List<Object> list = transaction.exec();
                //沒有指令集執行成功 解鎖失敗
                //TODO 關注事務執行過程(監視解鎖前 人爲變動鎖可能會出現的問題)
                if (list == null) {
                    continue;
                }
                System.out.println("鎖釋放成功,key【"+key+"】,value【"+value+"】");
                return true;
            }
            jedis.unwatch();
                break;
            }
        } catch (Exception e) {
            //TODO 添加日誌
            System.out.println("進程名稱【"+Thread.currentThread().getName()+"】"+"進程ID【"+Thread.currentThread().getId()+"】解鎖失敗"+e.getCause());
            e.printStackTrace();
        }
        return false;
    }

測試:

    public static void main(String... args) throws Exception {
        RedisLock redisLock = new RedisLock();
//        Jedis jedis = RedisManager.getJedis();
//        String curLock = jedis.get("lock:testLock");
//        System.out.println(curLock);
//        //強制解鎖 清空
//        jedis.del("lock:testLock");
        String key = "lock:testLock";
        String lockId = redisLock.getLock(key,10000);
        if(null != lockId){
            System.out.println("進程名稱【"+Thread.currentThread().getName()+"】"+"進程ID【"+Thread.currentThread().getId()+"】獲得鎖成功,"+"鎖KEY【"+key+"】"+"鎖序號:【"+lockId+"】");
        }else{
            System.out.println("進程名稱【"+Thread.currentThread().getName()+"】"+"進程ID【"+Thread.currentThread().getId()+"】獲得鎖失敗");
        }
        redisLock.releaseLock("lock:testLock",lockId);
        System.out.println("完成");
}

入門級demo 主要用於理解思路,demo未經過生產考驗!!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章