閱讀626 返回首頁    go 阿裏雲 go 技術社區[雲棲]


采用zookeeper的EPHEMERAL節點機製實現服務集群的陷阱

在apush的集群管理中使用了zk的EPHEMERAL節點機製。
在編碼過程中發現很多可能存在的陷阱,毛估估,第一次使用zk來實現集群管理的人應該有80%以上會掉坑,有些坑比較隱蔽,在網絡問題或者異常的場景時才會出現,可能很長一段時間才會暴露出來。

1、不處理zk的連接狀態變化事件導致zk客戶端斷開後與zk服務器集群沒有重連。後果:連接丟失後EPHEMERAL節點會刪除並且客戶端watch丟失。

此坑不深,稍微注意一下還是容易發現的,並且采用Curator會減少此類問題的發生,不是完全避免,具體見第6個坑。

zk客戶端如果和某台zk服務器斷開,會主動嚐試與zk集群中其他服務器重新連接,直到sessiontimeout,需要考慮極端的情況下出現sessiontimeout的處理。
zk客戶端和zk服務器斷開時會收到state為Disconnected的連接事件,此事件一般可以不處理,此事件後續會跟Expired狀態的連接事件或者synconnected狀態的連接事件。
zk客戶端連接重試失敗並且達到sessiontimeout時間則會收到Expired狀態的連接事件,在此事件中應該由應用程序重試建立zk客戶端。

2、在synconnected事件中創建EPHEMERAL節點沒有判斷此節點是否已經存在,在已經存在的情況下沒有判斷是否應該刪除重建,後果:EPHEMERAL節點丟失導致可用的服務器不在可用服務器列表中。

此坑是個深坑,很隱蔽,而且沒看到文章來提醒此坑。一般也不會出現問題,除非服務異常終止後立即重啟。

一般我們會synconnected狀態的連接事件中創建EPHEMERAL節點,注冊watch。
synconnected狀態的連接事件中處理EPHEMERAL節點可以分三種場景:
1、在第一次連接建立時
2、在斷開連接後,sessiontimeout以前客戶端自動重連成功
3、老的客戶端沒有正常調用close進行關閉,並且在此客戶端sessiontimeout以前,創建了一個新的客戶端
先說明一下第3種場景,session是否過期是由server判斷的,如果客戶不是調用close來和服務器主動斷開,服務端會等客戶端重連,直到session timeout。因此可能出現老session未過期,新客戶端來建新session的情況。

在第2和第3種場景下,EPHEMERAL節點都會在服務端存在。
第3種場景下,隨著殘留在zk服務端session的timeout,老的EPHEMERAL節點會被自動刪除。
由於zk的每個session都產生一個新的sessionId,為了區分第2、3種場景,必須在每次synconnected狀態的連接事件中比較當前sessionId和上次sessionId。
在synconnected狀態的連接事件中要同時判斷sessionId是否變化以及EPHEMERAL節點是否已經存在。
對sessionId發生了變化且EPHEMERAL節點已經存在的情況要先刪除後重建,這個是使用Curator也避免不了的。

3、應用程序關閉時不主動關閉zk客戶端,後果:導致可用服務器列表包含已經失效的服務器。

原因同第2條,會導致EPHEMERAL節點在sessiontimeout之前都存在。
如果sessiontimeout時間很長的話,會導致整個集群的可用服務列表長時間包含已關閉的服務器。

4、創建一個zk客戶端時,zk客戶端連接zk服務器是異步的,如果在連接還沒有建立時就調用zk客戶端會拋異常。

正確的做法是在synconnected狀態的連接事件中進行連接後的處理或者阻塞線程在連接事件中通知取消阻塞。
Curator提供了連接時同步阻塞的功能,可以避免此問題。

5、在zk的事件中執行長時間的業務

所有的zk事件都在EventThread中按順序執行,如果一個事件長時間執行會導致其他事件無法及時響應。

6、使用2.X版本的Curator時,ExponentialBackoffRetry的maxRetries參數設置的再大都會被限製到29:MAX_RETRIES_LIMIT。

這個坑真不知道Curator是怎麼想的,文檔裏一般也沒提到這個坑。重試次數不夠導致機房斷網測試時zk的客戶端可能永久丟失連接。不知道3.X的版本裏是不是還是如此。最後我放棄了Curator回到原生zk客戶端。

關鍵概念:

  1. zk內部兩個後台線程:IO和心跳線程(SendThread),事件處理線程(EventThread),均為單線程,且互相獨立。所以eventthread堵塞,不會導致心跳超時;另外由於event thread單線程,如果在process過程中堵塞,其他事件即使發生了,也隻會放到本地隊列中,暫時不會執行。

  2. 如果client與zkserver連接中斷,client的sendthread會使用原來的sessionid一直嚐試重連,連上後server判斷該sessionid是否已經過期,如果未過期,則SyncConnected會通知給client,同時期間的watcher事件也會通知給client,不會丟失;如果已過期,則client會收到到Expired狀態的連接事件,sendthread, eventthread都退出,當前client失效。

  3. session是否過期是由server判斷的;如果過期了則client使用原來的sessionid連接進來時,會收到expired狀態的連接事件。由server來判斷session是否過期主要是因為server需要清理該session相關的emphemeral節點並且通知其他客戶端;如果由client判斷再通知server,在client被直接kill掉的情況下,client創建的臨時節點就清理不掉了;如果client和server各自判斷,會有同步問題。

  4. 一個zk客戶端連接斷開後隻要在session超時期限內重連成功,session會保持。

  5. 注冊的watch事件和EPHEMERAL臨時節點和session關聯和連接沒有關係。

  6. 客戶端連接沒有close就斷開,服務端session仍然存活直到session超時。

原生zk客戶端的連接事件裏麵幾個關鍵狀態

  • SyncConnected 連接成功和重連成功時觸發
  • Disconnected 連接斷開時觸發
  • Expired session過期時觸發

Curator的連接事件裏麵幾個關鍵狀態

  • CONNECTED 第一次連接
  • SUSPENDED 對應原生的Disconnected
  • LOST 對應原生的Expired
  • RECONNECTED 包括sessionid不變的重連和sessionid變化的重連,如果客戶端建立了EPHEMERAL節點,必須在此事件中判斷sessionId。 對應sessionId不變的情況,連接斷開期間watch的事件不會丟失,如果sessionId變化,則期間watch的事件會丟失。

最後更新:2017-10-26 13:34:23

  上一篇:go  表格存儲在QCon2017的分享
  下一篇:go  技術的背後是思想 (轉載一篇好文)