1. 程式人生 > >分布式一致性思想描述及Paxos算法學習

分布式一致性思想描述及Paxos算法學習

clas 需要 讀取數據 但是 進行 一個 body 故障 不成功

在分布式的情況下,出於可用性(單點問題導致全部不可用)和規模性(單點支撐能力有限)的考慮,通過使用多個參與者提供服務。

如何保證通過多個參與者寫入和讀取的值相同,即分布式中的數據一致性,是一個復雜的問題。

為了保持一致性,一般是兩種方案,一種是所有節點每個事務裏都一致(強一致);另一種不是所有節點在每個事物裏都完全一致,比如采用主節點先更新,其他節點追加式更新的模式。

強一致模式下,服務可能會因為等待所有節點的同步更新和反饋,服務變得非常慢或者中斷。節點間不完全一致的模式,如果僅看一個節點,可能在主節點崩潰後,其他節點沒有最新數據。

受分布式系統不可靠特點的影響,多個參與者在一個事物裏不一致將是常態,因此使用強一致模式會容易出現不能達到或代價太高的問題。

兩階段和三階段提交的本質都是想要達到強一致,即通過先做準備工作,參與者在準備工作做完後進入準備狀態的一致,然後再一起做提交操作,達到參與者都一致的目的。但很明顯,提交操作過程中還可能有新的不一致情況,所以不能保證達到強一致效果。

另外一條道路就是節點狀態不一定完全一致,但是還可以達到寫入和讀取數據的值相同的效果。

從直觀感覺上想到的是,要麽取一個維度的最值情況來確定一個值,比如取一個最新時間等;要麽取大家投票的方式,取多數的原則來確定一個值。

按照一個維度取最值的方法,困難在於不穩定性和誤差。取多個節點(提供服務的參與者)中最新時間更新的值,取數指令發出後,可能記錄最新時間更新值的節點宕機或網絡不可達,那麽就會出現讀寫不一致的問題。寫進去的最新值是100,但讀出來可能是50。即使節點和網絡都正常,也還會存在授時誤差的問題,節點1比節點2絕對時間新,但節點2的誤差原因造成節點2記錄的是最新的,也會導致讀寫不一致的問題。

基於上述情況,只能考慮基於大家投票的方式,取投票占到一半以上的值作為最終值(用一半以上是為了保證唯一性)。paxos算法就是此類算法。

因為paxos是理論性的研究,具體的工程實踐差別較大,下面從我想到的兩個經典例子進行說明。在例子中,使用3個請求者,5個分布式服務提供者。

例如,5個服務提供者最後更新的值,分別是:1,2,3,2,2那麽投票結果就是2為最終值。如果沒有一半以上的要求,那麽1,2,2,3,3,3的情況就會沒法得到最終狀態值了。

1. 更新操作

最簡單的情況是<key, old_value>更新成<key, new_value>的過程。

3個請求者分別發送一個或多個請求,將一個key的value進行更新。因為沒有時間戳,服務提供者只能按照到達的時間先後順序來更新值。

這種情況沒有嚴格的時間順序,不同服務提供者的更新情況可能差距很多,然後給最後更新值對應的請求者發送更新成功,給其他請求者發送更新失敗。當然從理論上講也是可以的,但是想達到一半以上很困難。

為了提升效率,可以在請求中加入時間戳,也就是<key, new_value, timestamp>的格式,這樣服務提供者在接收到後可以根據時間來判斷是否要更新,如果更大的時間值(更大意味著更晚的時間)那麽就更新值,否則就不更新值,5個服務者更容易達成一致。

這樣還不夠,因為可能請求者1讀取現在key對應的值是3,想更新成5,但是它的請求達到服務提供者時,該key對應的值可能已經被更新成8了,這時再更新成5不見得是預期效果,所以最好還要加上原值,如果原值不匹配的話更新失敗,這樣確保可以達到預期的更新效果。也就是<key, old_value, new_value, timestamp>的格式。

在更新操作中,使用這樣的請求格式,如果一半以上的服務提供者返回了更新成功的結果,那麽更新就認為是成功了。如果有服務提供者出現故障,通過多數的數量優勢,恢復最後的值。如果3個更新成功,2個更新不成功,然後有1個更新成功的節點宕掉,這樣應該是依賴於宕掉節點是否在日誌中記錄了成功的狀態。如果記錄了,恢復後認為更新成功,否則認為更新失敗(當然這是工程手段,可能根據場景不同而不同)。

2. 插入操作

插入操作跟更新操作的不同之處在於其key值是不確定的,因此多了一個先要生成唯一key值的過程。

從理論上講,可以每個請求者隨機生成一個key值,但在工程上,為了符合key值的規則和後續查詢的效率,通常是先去取一個當前最大的key值,然後做+1操作。

從理論上講,有了這個key值以後,就可以加上value值發起請求進行更新操作;服務提供者接收到請求後,如果key值已經被占用那麽直接返回失敗,否則就進行插入操作,如果一半以上服務提供者都返回了成功,那麽插入就成功了。

但在工程上看,多個請求者高並發進行訪問時,可能會出現拿著同樣的key值進行更新的情況,這樣服務提供者會跟第一種更新的情況一樣,插入完成具有隨機性,會因為多個插入的碰撞導致效率比較差。

因此還是需要加上時間這個附加因素,又變成了<key, value, time_stamp>的組成結構,當然也可以把time_stamp和key組合在一起,這樣可以進一步減少key值相同的可能,並把time_stamp放在高位上,確保時間新舊的可以通過比較大小獲得。

這樣就可以使用<key, value, time_stamp>或<key+time_stamp, value>格式進行插入操作了,當請求者把該請求發送給所有的服務提供者時,服務提供者按照時間是否更大的原則進行插入操作,如果一半以上的服務提供者返回了成功,那麽這個操作就完成了。當然也可能出現下面的情況:

1)可能會出現key值被占用的返回,因為可能多個請求者都讀取了最大key值然後進行了+1操作。

  為了減少這個環節的沖突,可以先進行一次預請求,請求者先拿著key值進行請求,如果服務提供者插入過該key值,那麽就反饋一個已經占用,否則就反饋沒有占用。當然服務提供者此時並不會停止接受新的請求,可能下一時間就有其他請求者使用了這個key值,所以key值反饋不一定是對的。這是從工程上減少概率而已。我感覺這種情況適用於要插入的value值特別長的情況,這樣因為沖突而造成的網絡和時間浪費較大,所以通過預請求盡量避免一下。

2)可能出現一半或以下服務提供者返回成功的情況。存在另外一個請求者也使用該key值,不同的服務請求者插入了不同的value值。

以上基本上把原理說明了一下,但在工程實踐中實現可能差距很大,可以根據具體的場景具體設計。

分布式一致性思想描述及Paxos算法學習