zookeeper對(duì)象超時(shí)管理 ExpiryQueue

背景

在zookeeper服務(wù)端中需要管理session和connection這兩類具有超時(shí)屬性的對(duì)象。zookeeper提供了ExpiryQueue來實(shí)現(xiàn)通用對(duì)象超時(shí)管理容器孕锄。

實(shí)現(xiàn)

我拿connection來說如何管理他的超時(shí)問題,不同的連接超時(shí)的時(shí)間點(diǎn)是不同的

connection_with_different_timeout_point.png

那么zookeeper是如何高效的管理這些不同的超時(shí)時(shí)間點(diǎn)的呢?
在ExpiryQueue有個(gè)expirationInterval屬性真慢,zookeeper會(huì)使用expirationInterval作為基準(zhǔn)把每一個(gè)連接的超時(shí)時(shí)間點(diǎn)歸一化為expirationInterval整數(shù)倍服爷,歸一化的計(jì)算方式為
normalizeTimeout = (timeoutPoint/expirationInterval +1) * expirationInterval

我們拿上一個(gè)圖為例子药薯,通過歸一化處理connection_1,connection_2,connection_3這三個(gè)連接的超時(shí)時(shí)間點(diǎn)可能會(huì)變成同一個(gè)

假定expirationInterval = 10000您朽,
connection_1_timeout_point = 1599715479084
connection_2_timeout_point =  1599715479184 
connection_3_timeout_point =  1599715479384 
那么通過歸一化計(jì)算他們?nèi)齻€(gè)的超時(shí)時(shí)間點(diǎn)都變成了1599715480000

我們現(xiàn)在看下ExpiryQueue兩個(gè)重要的屬性

  //記錄了每一個(gè)對(duì)象的歸一化后的超時(shí)時(shí)間點(diǎn)咪橙,key是被管理的對(duì)象,value是超時(shí)時(shí)間點(diǎn)
  private final ConcurrentHashMap<E, Long> elemMap = new ConcurrentHashMap<E, Long>();

   //存儲(chǔ)了相同超時(shí)時(shí)間點(diǎn)的所有對(duì)象,key是超時(shí)時(shí)間點(diǎn)美侦,value是相同超時(shí)時(shí)間點(diǎn)的對(duì)象集合
  private final ConcurrentHashMap<Long, Set<E>> expiryMap = new ConcurrentHashMap<Long, Set<E>>();

超時(shí)管理線程

有了超時(shí)對(duì)象管理容器,還需要相應(yīng)的超時(shí)管理線程來監(jiān)控容器中對(duì)象的狀態(tài)魂奥,超時(shí)管理線程也會(huì)按照expirationInterval為時(shí)間間隔單位來運(yùn)行菠剩,它每次運(yùn)行的時(shí)間點(diǎn)記錄在ExpiryQueue的nextExpirationTime屬性中,nextExpirationTime初始化值由
nextExpirationTime = (now / expirationInterval + 1) * expirationInterval
now是ExpiryQueue創(chuàng)建時(shí)的系統(tǒng)時(shí)間點(diǎn)
nextExpirationTime之后每次更新的值通過下面公式得到
nextExpirationTime = nextExpirationTime + expirationInterval
可以看出nextExpirationTime和歸一化后的connection超時(shí)時(shí)間點(diǎn)是一致的耻煤,都是expirationInterval的倍數(shù)

超時(shí)管理線程處理超時(shí)對(duì)象

超時(shí)管理線程會(huì)每隔expirationInterval去ExpiryQueue中獲取超時(shí)對(duì)象具壮,具體實(shí)現(xiàn)是ExpiryQueue.poll方法

 public Set<E> poll() {
        long now = Time.currentElapsedTime();
        long expirationTime = nextExpirationTime.get();
        if (now < expirationTime) {
            return Collections.emptySet();
        }

        Set<E> set = null;
        //更新nextExpirationTime = nextExpirationTime + expirationInterval
        long newExpirationTime = expirationTime + expirationInterval;
        if (nextExpirationTime.compareAndSet(expirationTime, newExpirationTime)) {
            //獲取expirationTime超時(shí)時(shí)間點(diǎn)對(duì)應(yīng)的超時(shí)對(duì)象集合
            set = expiryMap.remove(expirationTime);
        }
        if (set == null) {
            return Collections.emptySet();
        }
       //返回在本次expirationTime超時(shí)時(shí)間點(diǎn)超時(shí)的對(duì)象
        return set;
    }

每次在超時(shí)時(shí)間點(diǎn)獲取超時(shí)對(duì)象之后,超時(shí)管理線程可以根據(jù)超時(shí)對(duì)象的不同業(yè)務(wù)特性做不同的業(yè)務(wù)邏輯

超時(shí)對(duì)象的更新

超時(shí)對(duì)象在放入到ExpiryQueue中之后哈蝇,這些對(duì)象會(huì)根據(jù)自己的特性(比如對(duì)于連接對(duì)象來說當(dāng)連接上發(fā)生IO事件棺妓,那么就需要更新連接超時(shí)時(shí)間點(diǎn))更新自己的超時(shí)時(shí)間點(diǎn),具體的實(shí)現(xiàn)邏輯是在ExpiryQueue.update方法中

 //timeout是超時(shí)對(duì)象的超時(shí)時(shí)間(或者說存活時(shí)長(zhǎng))
 public Long update(E elem, int timeout) {
      //通過elemMap獲取超時(shí)對(duì)象之前的超時(shí)時(shí)間點(diǎn)
        Long prevExpiryTime = elemMap.get(elem);
        long now = Time.currentElapsedTime();
      //通過歸一化方法獲取超時(shí)對(duì)象新的超時(shí)時(shí)間點(diǎn)
        Long newExpiryTime = roundToNextInterval(now + timeout);
        
        //如果新超時(shí)時(shí)間點(diǎn)和老超時(shí)時(shí)間點(diǎn)一樣那么不做任何處理
        if (newExpiryTime.equals(prevExpiryTime)) {
            // No change, so nothing to update
            return null;
        }

        // First add the elem to the new expiry time bucket in expiryMap.
        //使用新超時(shí)時(shí)間點(diǎn)從expiryMap獲取所有在新超時(shí)時(shí)間點(diǎn)超時(shí)的對(duì)象集合
        Set<E> set = expiryMap.get(newExpiryTime);
        if (set == null) {
            // Construct a ConcurrentHashSet using a ConcurrentHashMap
            //如果超時(shí)對(duì)象集合為空炮赦,那么創(chuàng)建一個(gè)
            set = Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
            // Put the new set in the map, but only if another thread
            // hasn't beaten us to it
             //并發(fā)的情況下可能會(huì)出現(xiàn)多個(gè)線程同時(shí)創(chuàng)建相同超時(shí)時(shí)間點(diǎn)對(duì)象集合怜跑,所以需要做如下是否存在判斷處理
            Set<E> existingSet = expiryMap.putIfAbsent(newExpiryTime, set);
            if (existingSet != null) {
                set = existingSet;
            }
        }
       //把本超時(shí)對(duì)象加入集合
        set.add(elem);

        // Map the elem to the new expiry time. If a different previous
        // mapping was present, clean up the previous expiry bucket.
        //同時(shí)更新超時(shí)對(duì)象在elemMap中新的超時(shí)時(shí)間點(diǎn)
        prevExpiryTime = elemMap.put(elem, newExpiryTime);
        if (prevExpiryTime != null && !newExpiryTime.equals(prevExpiryTime)) {
            //根據(jù)超時(shí)對(duì)象上一個(gè)超時(shí)時(shí)間點(diǎn)從expiryMap對(duì)應(yīng)的超時(shí)對(duì)象集合中把本超時(shí)對(duì)象刪除
            Set<E> prevSet = expiryMap.get(prevExpiryTime);
            if (prevSet != null) {
                prevSet.remove(elem);
            }
        }
        return newExpiryTime;
    }

上面從源碼的角度分析了zookeeper是如何實(shí)現(xiàn)超時(shí)對(duì)象管理的,關(guān)于這一塊的理解強(qiáng)烈推薦大家看《從 Paxos 到 ZooKeeper:分布式一致性原理與實(shí)踐》這本書吠勘,這本書寫的太棒了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末性芬,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子剧防,更是在濱河造成了極大的恐慌植锉,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件峭拘,死亡現(xiàn)場(chǎng)離奇詭異俊庇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸡挠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門辉饱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人宵凌,你說我怎么就攤上這事鞋囊。” “怎么了瞎惫?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵溜腐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瓜喇,道長(zhǎng)挺益,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任乘寒,我火速辦了婚禮望众,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己烂翰,他們只是感情好夯缺,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著甘耿,像睡著了一般踊兜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上佳恬,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天捏境,我揣著相機(jī)與錄音,去河邊找鬼毁葱。 笑死垫言,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的倾剿。 我是一名探鬼主播筷频,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼柱告!你這毒婦竟也來了截驮?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤际度,失蹤者是張志新(化名)和其女友劉穎葵袭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乖菱,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坡锡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了窒所。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鹉勒。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖吵取,靈堂內(nèi)的尸體忽然破棺而出禽额,到底是詐尸還是另有隱情,我是刑警寧澤皮官,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布脯倒,位于F島的核電站,受9級(jí)特大地震影響捺氢,放射性物質(zhì)發(fā)生泄漏藻丢。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一摄乒、第九天 我趴在偏房一處隱蔽的房頂上張望悠反。 院中可真熱鬧残黑,春花似錦、人聲如沸斋否。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)茵臭。三九已至冰木,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笼恰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工歇终, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留社证,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓评凝,卻偏偏與公主長(zhǎng)得像追葡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奕短,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349