= 情境 =
原本有一個3個node (A[127.0.0.1], B[127.0.0.2], C[127.0.0.3])的cluster ring,後來因為一個操作失誤,讓一個非預期的node D[127.0.0.4] 使用跟 C 一樣的token加入了這個ring。因為token 一樣,所以cluster會顯示node D取代node C成為這個token的擁有者,在發現了這個失誤之後,將node D給關掉,然後重開node C,讓node C再把token搶回來。結果之後Node C開始一直出現這樣的log(大約90秒一個循環):
INFO 2014-06-24 14:02:05,778 Node /127.0.0.4 is now part of the cluster
INFO 2014-06-24 14:02:05,779 InetAddress /127.0.0.4 is now UP
INFO 2014-06-24 14:02:05,779 Nodes /127.0.0.4 and /127.0.0.3 have the same token 113427455640312810000000000000000000000. Ignoring /127.0.0.4
INFO 2014-06-24 14:02:15,281 InetAddress /127.0.0.4 is now dead.
INFO 2014-06-24 14:02:36,302 FatClient /127.0.0.4 has been silent for 30000ms, removing from gossip
這些log最讓人不解的是 Node D其實已經關掉了,但是他卻每隔一段時間又像幽靈一般連了上來,然後過沒多久又斷掉。在網路上追查之後,發現有隻issue應該跟這件事有關係: https://issues.apache.org/jira/browse/CASSANDRA-3736 ,以下大至整理一下這個問題相關的資訊。
PS. 以上情境是在Cassandra 1.0.12版發生的
= 解說 =
這件事情跟Gossip的運作息息相關,所以我們先來了解一下Gossip的protocol。 Gossip是cassandra node之間用來同步ring的狀態的一個機制,node之間透過Gossip protocol來同步所知道的node的狀態。每個node啟動之後,就會每秒一次,頻繁地選幾個已知的node做gossip(對一個全新的node來說,最一開始他會跟seed node做gossip),以知道ring的狀態。以下是個資訊簡化的一對一的gossip過程,實際情況每個node會同時跟幾個node做這個互動:
圖1. Gossip 流程
首先我們先解說一下圖例,上圖是兩個node 在交換Gossip資訊的過程,其中node裡面的框代表這個node所知道的 endpoint 的table(gossip 裡的endpoint指的就是一個node),這裡的endpoint的資訊是簡化過的,只寫出最重要的資訊,實際還有更多的資訊,這邊看到的資訊格式被稱作 gossip digest。一個 gossip digest 的格式大至有三欄位,分別是: host、generation、version,這三個資訊用分號( : ) 分隔連成一個字串就是一個 gossip digest,例如 /127.0.0.1:1403669222:9 ,指的是這個endpoint的 host= /127.0.0.1 , generation= 1403669222, version: 9
其中host比較沒什麼疑問,指的就是該node的 hostname;而generation代表這個endpoint的 啟動的狀態,會隨著每次重新啟動而增加;而version則是由這個endpoint的各個資訊計算出來的一個version值,代表這個endpoint這次啟動之後的狀態的版本(隨著狀態的變化,版號會增加,版號越大代表狀態是越新的)。
接著我們分別解說gossip的流程:
第一格
發動的 node 1根據自己所知道的endpoint的table發了一個 GossipDigestSynMessage 的訊息,裡面會帶有所知道的endpoint的 gossip digest。 接收者node 2收到 GossipDigestSynMessage 之後,會跟自己的endpoint table做比較,透過比較generation跟version來了解自己所知的資訊是比送來的資訊新還是舊,以上圖的第一個步驟的狀況,node 2可以知道他所知的/127.0.0.1的資訊跟所收到的一致;而/127.0.0.2則他所知的較新;而送來的 /127.0.0.4 的generation比較大,可知 /127.0.0.4曾經重啟過;最後,/127.0.0.3跟 /127.0.0.5 都是 node 2不知道的node
第二格
node 2 根據比較的結果,發出一個 GossipDigestAckMessage ,若手上的資訊較新,就會把自己所知道的該endpoint的所有資訊付上;若自己不知道、或是手上的資訊較舊的,就把該gossip digest的version改成0送出。而node 1在收到 GossipDigestAckMessage 之後,就會更新手上較舊的 endpoint 的資料,接著看自己手上的endpoint的版本是不是比送過來的 gossip digest還新,而因為送回來的gossip digest的version都是0,所以手上有的資訊都會被認為較新…除了 /127.0.0.5 這個node,這個node自己手上的資訊一樣是 /127.0.0.5:0:0 ,代表我們兩個知道的一樣。
第三格
node 1再根據 GossipDigestAckMessage 的比對結果,將更新的endpoint的資訊用 GossipDigestAck2Message 訊息傳給 node 2,如此一來node 2就可以去更新手上的資訊。其中,而 /127.0.0.3 這個endnode在這個時侯才被加入了node 2所知的 ring,因此在node 2會出現以下的 log:
INFO 20yy-MM-dd hh:mm:ss,SSS Node /127.0.0.3 is now part of the cluster
INFO 20yy-MM-dd hh:mm:ss,SSS InetAddress /127.0.0.3 is now UP
第四格
代表做完一輪 gossip 之後,兩個 node的狀態,對於有generation、version的endpoint,雙方的資訊同步了,而對於 /127.0.0.5,兩個node在這次啟動之後,都沒看過這個node,所以最終這個node的還是只有 node 1知道。
以上是 gossip的流程,這個流程中,跟我們一開始的問題有關的主要是第三格的部份,node 2會印出我們看到的 log,這個log看似好像有個新的 node(/127.0.0.3)跟node 2連上了,但是實際上只是gossip的交換資訊的過程中,node 2知道了一個新的node,它會相信gossip所傳來的訊息,認為 /127.0.0.3 是活著的,接著才會嘗試跟 / 127.0.0.3 做通訊。而假設 /127.0.0.3 實際並沒有開著的話,過一段時間的連線嘗試,node最終會認定 /127.0.0.3 是死的,就會出現以下的log
INFO 20yy-MM-dd hh:mm:ss,SSS InetAddress /127.0.0.3 is now dead.
出現這行log的之後,gossip還是一直持續收發,有關該node還存活的謠言還會繼續流傳,這時侯node 2會暫時不理會任何有關 /127.0.0.3 還活著狀態消息(這個持續的時間被稱為QUARANTINE_DELAY,他的值會等於 cassandra.ring_delay_ms 的兩倍),如此一來,假設 /127.0.0.3 真的死了,那這個機制會讓/127.0.0.3已死的狀態最終在ring裡所有的node間得到一致的結果。但是這時侯 node 2其實還是記著 /127.0.0.3,要再過了一段時間,(例子中是30秒,實際上會是 QUARANTINE_DELAY 除以2),如果 /127.0.0.3 還是一直沒消息,那 node 2就會把他從已知的node 列表刪掉,並印出以下的log:
INFO 20yy-MM-dd hh:mm:ss,SSS FatClient /127.0.0.3 has been silent for 30000ms, removing from gossip
那在我們的情境中,為什麼node C會一直出現同樣的訊息呢? 主要是因為情境中 node D曾經連上過 node A, B,因此在 A跟B的心裡,一直有node D的某個版本的資料(版號大於0),而node C重開後則從來沒連上過 D,所以C會一直重覆這個過程:收到A跟B傳來的D的某版本的資料,然後將他加入心中的ring,接著再發現D其實連不上,所以再把他移除。此時,若將A跟B重啟的話,C就不會再出現那些log。
看起來重啟解決了問題,但是接著我們會發現,從JMX的 org.apache.cassandra.net:type=FailureDetector 的attribute裡, D其實還是被知道的,只是被mark Down而以,如圖2:
圖2. /127.0.0.4 其實還沒完全消失
之所以會這樣是因為D的資訊已經被persistent了,cassandra會對ring的狀態做一定程度的persistent,這樣重啟node可以更快開始服務。這些persistent起來的資料就會存在system table的 LocationInfo裡,每次cassandra initial時,會從LocationInfo裡讀取被存起來的endpoint資訊,不過在initial時讀的endpoint的generation跟version都會是0,以圖1中的例子,/127.0.0.5就是initial時從LocationInfo裡讀到的,而從流程中可以知道,如果它一直沒再連回來的話,這個 endpoint的資訊並不會被散布出去。若要徹底的將這個node從ring中踢除(不讓他連上來的情況),那就要把LocationInfo也清除。
= 總結 =
其實這件事情會這麼麻煩,主要是因為我們沒法用正確的方式將node D從ring中移除,這是因為node D本來不是這個ring的一份子,為了怕資料進一步髒掉,我們不能讓他重新加回來,再用正規的手段去把他從ring中移掉。另一方面,在了解 gossip之後,才發現以前對那些log訊息的認知其實跟實際的情形是有落差的,那些log並不代表兩個node真的已經連線了……不過以我們遇到的情況,至少可以知道那些訊息是無害的。
PS. 在 CASSANDRA-3736 中,他們的解法是在replace node時,去system table將該endpoint移掉,如此一來,重開之後,那個node就會被眾人所遺忘,但是這個移除的的條件在後來的更動又有一些改變了,導至這個問題後來又重現,不過似乎沒發現後來有人再提起這些,不知道是不是後來的cassandra已經不會有問題了。
沒有留言 :
張貼留言