您好,登錄后才能下訂單哦!
本篇內容介紹了“ZooKeeper會話的原理是什么”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
會話,即 session,這個詞語或者說概念很多地方都有用到,在 ZK 中會話指的是兩個不同的機器建立了網絡連接后,就可以說他們之間創建了一個會話。 ZK 的會話是有超時的概念的,當會話超時后,會由服務端主動關閉,當然客戶端也可以主動請求服務端想要關閉會話。你可能會問,為什么要搞這個麻煩,直接兩邊連上一直用不就好了嗎?有了會話這個概念就是為了防止,在建立連接后,有些客戶端不常使用,早點關閉連接可以節省資源。
我發現我好久沒有 cue 雞太美了,這次就讓他再 C 位出道一次吧。
我們的雞太美每天起床后,日常發微博、直播、跳舞、打籃球,很多事務都需要去辦事處辦理。
所以第一件事情就是去辦事處找馬果果(現在就假設馬果果一個辦事處)申請使用辦事處(建立連接,創建會話)
而馬果果會為雞太美創建一個 ID,就是會話 ID,這個 ID (我這里假設是 19980802) 和雞太美會進行綁定,而雞太美在申請的同時還需要告訴馬果果自己最長的超時時間是多久,我這里假設是 6000 毫秒。
而馬果果這邊會記錄下來:
在馬果果開張的時候自己本身也有一個會話的檢查間隔,就是配置在 zoo.cfg
中的 tickTime
選項,我這里假設是 3000 毫秒。馬果果在開張的時候會計算出一個時間軸,這個時間軸的間隔是固定的,并且不會改變。
然后馬果果會通過雞太美的 6000 以及當前的時間戳結合時間軸,計算出一個雞太美會話超時時間點
然后會記錄下來:
記錄完,就算雞太美會話創建成功了。
而馬果果這邊會遵循這個時間軸的節點定期對會話進行檢查,假設現在的時間進行到雞太美的時間點了
馬果果會把在這個時間點的會話全部取出(記得我們上面說過,可以是多個嗎?)
然后會根據 ID 信息找到對應的村民,一個個通知他們會話關閉了。
你可能會問現在因為雞太美超時時間是 6000,而馬果果超時檢查是 3000,正好是整數倍,如果超時時間不是整數倍呢?要不說我們的馬果果同志好學上進呢,他早就想到啦,所以設計了一個算法,無論村民的超時時間是怎么樣,都會向下取整找到馬果果設置的檢查點。
假設雞太美的超時間是 5900
再比如雞太美的超時時間是 6500
所以看到了吧,以馬果果的 3000 為例,只要小于 3000 的都按照 0 來算,小于 6000 的按照 3000 來算,小于 9000 的按照 6000 來算,以此類推,所以只要馬果果自己的檢查時間間隔確定了之后,無論是哪個村民設置了什么樣的超時時間都能被向下取整至最近的統一檢查點。這樣馬果果檢查的時候就不會有太大的負擔,可以統一對村民的超時時間進行檢查。
但是這么做一定會造成客戶端的超時時間是有誤差的(通常是比設置的要短一點),減少這個誤差的方式就是減小馬果果的檢查間隔,也就是 tickTime
參數(默認是 2000,已經夠用了我覺得)。
而馬果果的會話管理不會只有雞太美一個人,我們來看看有多個村民的會話管理頁長什么樣吧
可以看到使用了三個哈希表去記錄這些映射關系,畫到時間軸是這樣的
所以當時間進行到 25317000 的時候,對應三個村民就超時了,25320000 時另外兩個村民就超時了。
這里我還得說下其實會話 ID 在馬果果這邊辦事處開張后就會根據當前時間戳和 myid 初始化出一個基數,舉個例子可能是 987434245 類似這種數字,之后每一個村民過來分配會話 ID 的時候,只是對這個數字不停的加 1,所以不會出現亂七八糟無序的數字,圖中的數字舉例僅僅是我個人的玩梗癖好,和實際情況不符~
但是這樣的話,雞太美豈不是每次 6000 毫秒就超時了嗎?這當然不可能,因為村民的每一次任意的操作(增刪改查)都會刷新該超時時間戳,具體怎么做的呢?我們一起來看下,假設紅色箭頭是會話剛創建時馬果果替雞太美計算出來的超時時間,假設在綠色箭頭時間戳的地方,雞太美執行了任意操作。
馬果果會根據當前時間戳(綠色箭頭處)加上雞太美之前設置的超時時間(6000),重新計算出新的超時時間:
然后對會話管理頁的數據進行修改,我仍然以多個村民的例子講解
更新前:
更新后:
這個更新的過程可以被稱為會話激活。
猿話一下,除了客戶端每次的正常操作會刷新超時時間以外,客戶端仍然需要一個機制去保持住這個會話,這個機制就是我們平時聽到過的心跳檢測,原理是每次客戶端啟動的時候也會設置一個心跳檢測的間隔時間,在后臺一直會去判斷最后一次發送的時間戳和當前時間是否超過了該心跳檢測的間隔,如果超過了就會發送一個名為 PING 的請求,由于剛剛我們說了客戶端的任意操作都會刷新該超時時間,PING 也不例外,有了這個心跳機制就可以讓客戶端保持住和服務端的會話狀態。而服務端收到 PING,除了刷新超時時間會簡單的回復一個 PING 給客戶端,而客戶端收到服務端的 PING 會直接丟棄不需要任何其他操作。
我們以 Java 客戶端為例
ZooKeeper client = new ZooKeeper("127.0.0.1:2181", 12000, null);
假設超時時間設置 12000 毫秒,那么客戶端的心跳間隔就是 4000 毫秒,計算過程如下
12000 * 2 / 3 / 2 = 4000 // 這個公式是代碼中的寫死邏輯,其實就是 / 3
所以只要客戶端空閑時間超過 4000 毫秒,就會發送一個 PING 給服務端,如果客戶端的超時時間設置的非常大的話,比如半小時,那每隔 10 秒也會強制發送一個 PING(這個 10 秒是 Java 客戶端寫死的邏輯)。
客戶端和服務端之間的會話先講到這里,接下來我們聊聊服務端之間的會話。
如果村里是同時有多個辦事處的時候(我這里先假設兩個),情況就不太一樣了。
假設雞太美第一次連接的時候找到的作為 Follower 的馬小云:
而 Follower 是不能獨自處理非讀請求的,所以此次馬小云會為雞太美分配好 ID 之后,將創建會話操作轉發給馬果果,這樣就好像是雞太美找到馬果果一樣,流程和上面是一樣的,在會話管理頁中記錄下來。
而馬小云自己也會簡單的維護一個會話 ID 和超時時間的映射關系,以多個村民為例,每次收到請求都會對其進行記錄
現在雞太美是連接的馬小云辦事處(包括每次心跳發送),但是全局的會話管理數據在馬果果這里,這樣是怎么維持住會話狀態的呢?
這里我們就得先聊聊服務端之間是怎么進行心跳的。
服務端有一個重要的配置 tickTime
(默認是 2000),還有另一個重要的配置 syncLimit
(默認是 5),我就以這兩個默認值來舉例:
首先 Leader 會以 1000 (tickTime / 2
) 毫秒的頻率去對各個 Follower 發起 PING 的請求
每次檢查 Follower 返回的 PING 的超時時間是否超過 10000 (tickTime * syncLimit
),超過這個時間沒有收到該 Follower 的 ACK 響應就關閉和該 Follower 的 socket 連接
那 Follower 收到 PING 的消息后會回復一個 PING 給 Leader 并且會把自己記錄的會話映射關系一起發過去
還會立即清空自己本地的映射關系!
然后 Leader 收到 Follower 的這個 PING 響應后,因為之前所有客戶端的會話管理數據其實都在 Leader 這里,所以 Leader 可以對發過來的會話 ID 和超時時間進行會話激活,具體方法和之前的例子中是一樣的,通過服務端之間的 PING,既可以完成服務端之間的心跳檢測,又可以對客戶端的會話進行激活,又是一次一魚兩吃。
小結一下:
會話是 ZK 中的重要概念,會話的狀態會影響,服務端對客戶端請求的處理
客戶端的每次操作都會延長會話的超時時間,并且客戶端會主動發起 PING 請求來保持住會話,以免在空閑時會話超時被服務端關閉
客戶端的會話數據是保存在 Leader 端的,Follower 只是在每次操作的時候簡單的記錄下會話 ID 和超時時間的映射關系
服務端之間的心跳 PING 是由 Leader 主動向 Follower 發起的
Follower 收到 PING 后會將自己保存的會話映射數據發送給 Leader
Leader 收到 Follower 的 PING 響應后會對發送過來的會話數據進行激活
我們現在已經知道了會話的概念,就可以聊聊臨時節點了。
我們先來看下臨時節點的創建代碼
client.create("/HelloZooKeeper/niubi", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
這次的創建操作和其他的持久節點創建并無區別,需要在小紅本上寫下記錄,而這個記錄中有一個字段是 ephemeralOwner
當節點是持久節點這個字段值是 0,但當節點是臨時節點時這個字段記錄的就是持有該節點的會話 ID。
除了在小紅本上創建記錄以外,由于是臨時節點,還需要額外在一個專門的地方也記錄一下,假設還是雞太美創建了 3 個臨時節點:
19980802 => ["/雞太美/我真美", "/雞太美/我真帥", "/雞太美/我真秀"]
在雞太美會話超時的時候,可能是會話真超時了(由于有心跳機制,所以這個可能性其實不大),也可能是雞太美主動關閉的會話。
馬果果就會從這個記錄臨時節點的地方根據雞太美的會話 ID 取出對應的臨時節點的路徑,然后根據路徑刪除即可,效果和雞太美主動刪除是一樣的,這樣就達到了,當客戶端關閉之后,對應的臨時節點會自動清除的特點。這個臨時節點的特性就會被用在 ZK 實現分布式鎖的時候,防止了客戶端因意外退出沒法執行釋放鎖的邏輯!
還有一個東西我一直就沒提過,就是 ZK 的協議。
眾所周知,ZK 是一個 CS 架構的應用,有客戶端和服務端之分,那既然這樣就免不了需要進行網絡通信,而且不光是客戶端和服務端之間,服務端和服務端之間也需要通信,有了網絡通信就離不開協議,但是協議既是最重要的東西,也是最不重要的東西。
最重要是因為,ZK 本身就是基于該協議去通信的,無論是客戶端還是服務端之間,我之前提到的各種暗號,如:REQUEST、ACK、COMMIT、PING 等。都屬于協議中的一個字段,用來區分不同的消息。協議構成了整個 ZK 通信的基礎,能夠通信了才能完成整個組件的功能。
最不重要是因為,除非你想開發 ZK 的客戶端,主動去請求 ZK 服務端,不然即使你完全不知道協議的具體格式,也不會影響你理解整個 ZK 的原理,而且協議的介紹非常的枯燥和無用,容易勸退。
所以我把這個概念留到了最后才提起,并且我也不打算去講解 ZK 中不同請求的協議具體長什么樣。這次我就換一個角度簡單的介紹下協議。
首先,我介紹的 ZK 都是 Java 程序,無論客戶端還是服務端,所以協議的本質是規定如何把 Java 對象轉成字節流,方便在網絡中傳輸,以及拿到字節流的那一方,如何再把這個字節流轉換回 Java 對象,這其實就是序列化和反序列化的過程。而為了方便序列化,ZK 中定義的各種對象,如 XxxRequest 、 XxxResponse、XxxPacket 等,它們的字段類型通常就幾種:int
、long
、String
、byte[]
、List
、boolean
以及其他嵌套的類型。
對于這三種類型來說最簡單,直接用輸出流寫即可,區別就是一個是 4 字節,一個是 8 字節,一個是 1 字節
這兩種是類似,如果字段為空,則就寫入一個 -1,不為空就先寫一個 int
表示長度,之后緊跟 byte[]
表示具體數據即可
碰到 List
和 4.2 是一樣,如果為空就寫 -1,不為空就先寫 List
長度,之后遍歷 List
根據泛型(也只可能是上面這幾種)決定如何繼續寫入,嵌套對象的話就把這個寫入操作委托給它就行了,因為它的字段也只可能是上面這幾種。
ZK 的序列化協議采用的緊湊書寫的方式,根據不同的字段類型依次寫入最終的字節流即可。
“ZooKeeper會話的原理是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。