天天看點

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

前言

在分布式開發中,我認為具備CAP理論與了解Raft、Zab等分布式一緻性協定是十分有必要的,例如分布式鎖的選擇,你是選擇Redis的主備叢集(AP模型)還是選擇ZK、etcd(CP模型)呢?

關于分布式鎖的文章

從分布式理論到如何做一個生産級别的分布式鎖_|-| [- |_ |_ ()-CSDN部落格_如何做一個分布式鎖​blog.csdn.net

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

如果不具備這些理論知識,我覺得是無法靈活選擇且用好分布式鎖的,不同業務場景有不同AP、CP模型的需求,例如為什麼Nacos的配置中心使用CP模型,但注冊中心卻使用AP模型呢?這都是不同的場景有着不同的考量。說了那麼多,那麼為什麼CAP總是在CP與AP之間讨論?接着往下看。

CAP理論

CAP理論是在分布式叢集環境下讨論的,為什麼分布式叢集環境下會存在CAP問題呢?舉個例子,假設我們後端存儲服務使用Redis中間件,如果隻部署一台Redis伺服器,那麼這台Redis如果挂了,整個存儲服務都挂了,那麼我們就想要提高它的可用性,怎麼辦呢?最簡單的方法就是

加機器

  • 可用性:我部署兩台Redis機器在一個叢集中(主備叢集),如果此時有一台Redis挂了我用戶端照樣可以通路另一台Redis,保證了一定程度的 可用性 (保證允許一台Redis當機)

同時這兩台主備Redis需要時刻保持

資料同步

,這樣在一台當機之後另一台也能保持原有的查詢服務:

  • 一緻性:如果我用戶端在A節點的Redis設定了一個M值,然後A節點當機,此時換B節點的Redis上,如果B節點正好同步了這個M值,就保證了 一緻性 ,但是有可能B節點的Redis還沒來得及同步這個M值,我用戶端在B節點讀不到M值,這就存在了 一緻性 問題

那什麼是分區容錯性呢(P)?

  • 分區容錯性:這兩台主備Redis發生了分區(A、B節點互相連接配接不上),那就做不了資料同步了,但此時叢集依然向外開放服務,此時叢集具有 分區容錯性 ,如果發生分區,叢集就不能對外服務,則叢集不具有 分區容錯性 (P特性)
分區容錯性,代表當分布式節點發生分區(A、B節點互相連接配接不上)此時的分布式系統是否還提供服務(是否容錯),如果沒有了P,代表發生分區之後整個分布式叢集不能使用,這顯然是不行的。

下面看看保證P與不保證P的叢集是什麼樣的:

  • 如果沒了P,理論上叢集不容許任何一個節點發生分區,當 沒有分區 發生時确實可以保證AC(誰能保證叢集系統的節點百分百不會出問題呢?能保證也不需要讨論AC問題了),當發生分區時整個叢集不可用,沒有現實意義(如圖1)
  • 如果保證P,說明叢集就隻能從A和C選擇其一(如圖2)

綜上所述,是以做一個分布式系統,P一定需要保證,那麼我們的焦點就在AP與CP的模型去選擇

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...
player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

如果保證P,說明叢集就隻能從A和C選擇其一,為什麼呢?我們重點看圖2中的第四步,在B節點read M值的操作。

  • CP:若此時不讓用戶端讀取B節點的M值,那麼此時不可用,隻能讓用戶端讀取A節點,在A節點中M值确實是剛剛用戶端Set M = 1值,M值一緻,此時是CP模型,可用性被舍棄。
  • AP:若此時可以讓用戶端讀取B節點的M值,那麼此時節點可用,但是讀取到的值M=0,如果下次又來一個用戶端讀取A節點的M值,卻讀到了M=1,M值不一緻,此時是AP模型,一緻性被舍棄

分布式一緻性協定

當發生網絡分區或是叢集某個節點挂掉了,我們是否隻能從一緻性與可用性做抉擇呢?不,作為成年人,我們兩者都要(狗頭),但兩者我們都各自隻拿一半,也就是基本可用與最終一緻性(類似BASE理論),這裡我将業界公認的分布式一緻性協定Raft、Zab抽象出來講,看看他們是如果保證CA兩個屬性的。

其實可以Raft、Zab協定可以做到基本可用+強一緻性(線性一緻性),基本可以算是分布式一緻性算法,非要歸類的話我認為其屬于CP模型

Raft和Zab在分布式一緻性角度上來說,主要思想是差不多的,是以可以一起抽象來講,一些協定細節建議檢視相關論文,這裡隻讨論如何保證資料一緻與基本可用特性

協定的幾個規則

在叢集中節點有Leader、Follower兩種角色,隻有Leader節點才負責處理所有的事務操作,Follower節點隻需要同步Leader處理的事務操作的資料,有時候也可以提供讀服務。此算法是通過一切以上司者為準的方式,實作一系列值的共識和各節點日志的一緻。

資料複制

假設此時用戶端請求資料Set M = 0

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...
  • 半數複制原則:隻有Leader節點接受事務請求,Leader節點會通知其他節點儲存資料,當有半數節點傳回OK,Leader節點也儲存資料,傳回給用戶端儲存成功資訊(保證資料在超過半數的節點上)

上司者選舉

  • 獲得叢集内大多數節點的選票的節點才能當一個Leader
  • 每一個節點都可以發起選舉讓别人投自己一票,每一個節點在同一個任期号内隻能投一票
  • 投給誰?
    • 參照Raft算法,按照先來先服務的标準,誰先來要票,誰就能獲得本節點的選票,但是有一個關鍵是來要票的節點的資料至少不能比本節點舊
      • 資料怎麼算新:任期号最大的最新,任期号一樣的話那麼資料越多的越新,每一屆上司者都有一個epoch任期号的值概念,其為遞增的值,重新選舉時任期号會進行自增

先來先服務,若資料都一樣新,且選票被瓜分,選不出上司人怎麼辦?

      • 在Raft算法中使用一個随機的選舉逾時時間,使得每個節點去要票的時間點是随機的,一群節點同時交錯去要票轉化為了一個個節點去要票,Raft在很多地方都做的簡單易懂,還不乏實用性。一個好的算法不僅能work,還能讓人們簡單易懂為什麼能work,這很關鍵。
      • 在Zab協定中每一個節點都有一個myid,如果節點資料都一樣新,選不出上司人就會比較myid,myid大的即可當選

如何保證一緻性

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

假設此時用戶端在寫入一個值M=1:

根據資料複制規則,Leader隻需要保證資料在多數節點上儲存下來就可以傳回成功

各個節點在各個時期挂了,是否會導緻資料的不一緻?

  • 若此時Leader在收到半數節點OK之前就挂了,那麼用戶端也會傳回一個設定M值錯誤,顯然此時的叢集資料是一緻的(從未儲存過值)
  • Leader在收到半數節點OK,證明資料在大部分節點上之後,傳回給用戶端設定值成功之後Leader節點就挂了,正如圖4那樣,第四步傳回用戶端OK資訊後當機,沒來得及複制給S3和S4節點,此時各節點資料如下:
player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

群龍無首,此時必須選舉出一個新的Leader

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

可以看出,資料比較舊的S4,S5隻能要到兩票,得不到半數的票(半數=(節點/2) + 1,這裡5個節點就是3票),但是資料比較新的S2、S3卻可以得到半數以上的票(任何的節點都會給他們投票,因為他們的資料至少都不比其他節點舊),根據上面的上司者選舉規則,隻有S2、S3有機會能當選Leader,當選後,Leader會通過heartbeat或其他同步資料方式同步Set M = 1 這條日志,此時叢集資料一緻

  • 那既然資料最新的才可以當選Leader,那如果最新的S2、S3也挂了呢?
player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...

由圖可以看出,如果最新的S2、S3都挂了,那舊的S4、S5也是隻能拿到兩票(對方和自己總共兩票),此時選不出一個Leader,整個叢集不可用,資料依然可以看作是一緻的

由此可以看出,此分布式協定容許半數以下的節點當機,如果半數節點當機,叢集不可用,是以稱為基本可用

總結

從上面的讨論上來看,半數很關鍵(半數送出、半數選舉),相信看到這裡你會懂得這半數的魔力,接下來總結一下此分布式一緻性協定由于”半數”會有哪些問題:

  • 事務操作的性能瓶頸:由于需要保證半數節點儲存了資料,則增删改資料的性能取決于半數節點的性能(5個節點的寫入性能則取決于3個節點(包括自己)的寫入速度)和與半數節點通信的網絡開銷。這意味着,叢集節點越多(如果想要高可用,那就上多節點),寫入性能有可能會越差(試想,上100個節點,每次寫入都要發給50個節點確定寫入成功,有50次網絡開銷,不過資料複制過程是并行的,木桶效應總耗時取決于半數中最慢的那個節點,不過節點越多,木桶短闆出現的機率也就越大,是以這裡說寫入性能是可能變差的)
  • 基本可用:叢集由于需要半數送出才能成功寫入資料,是以如果5個節點當機2個叢集是可用的,若當機3個,由于得不到半數的送出,叢集就會不可用,從這點上來看達到了基本可用的特性
  • 最終一緻性 or 強一緻性(線性一緻性):

    如果我們僅僅隻是寫入一個值,從寫入角度來說那就是強一緻性(因為都在Leader處理,Raft與Zab都支援順序一緻性,即為你剛剛Set M = 1,又Set M = 2,順序如果反了那結果會變成 M = 1,由于都在同一個Leader節點做,節點維護一個隊列即可保持順序),是以下面更多的是對于讀資料的一緻性讨論

    • 最終一緻性:這取決于具體的實作,若是最終一緻性,那麼Follower就開放讀能力,這麼做有個優點,整個叢集的讀能力會随着節點個數得到提升,但有個缺點,由于半數送出的規則,有可能上一秒你寫入成功了一個值,下一秒在叢集另外某個節點中讀不到這個值(可能還沒同步到),但最終會在一個時間點此值被同步,也就是最終一定會達到一緻性
    • 強一緻性(線性一緻性):如果讀能力隻在Leader節點開放,也就是讀我也隻能在Leader上讀,那麼此時的一緻性是極強的,随着上一秒的設定值,下一秒一定可以查到剛剛設定的值,缺點就是讀性能得不到擴充,局限于Leader單點性能的問題(讀寫都在一台節點上操作)

以上讨論了CP模型,對一緻性要求比較高的系統比較适用,可以看出來,這種分布式協定有性能的局限性,因為他犧牲了一定的用戶端響應延遲來確定一緻性,而且僅僅保證了半數的基本可用性。在延時要求高、高并發場景下這種模型或許并不适用,此時可以考慮AP模型提高響應延遲。

如果是AP模型,相比于一緻性協定會簡單一些,因為他隻需要保證可用性,加叢集節點即可,而資料丢失、不一緻的情況隻會在當機的那一時刻發生,丢失、不一緻的也隻是那一時刻的資料,例如Redis的主從叢集架構,主Redis節點隻需要異步、定時去同步資料,在寫入時隻需要一個節點确認寫入即可傳回,延時比一緻性協定低,由于Redis在使用上大部分場景都用在緩存,快是他的設計目标,偶爾丢幾條或者不一緻幾條緩存資料并不影響場景(關于Redis主從的AP模型的讨論更詳細的在這裡)。

回答開篇

為什麼Nacos配置中心要使用CP模型?

  • 答:CP模型犧牲了一定的延時去換取資料的強一緻,但應用的外部化配置并不要求配置成功的延遲要多低,更多的是要保證配置資訊的一緻性(我配置什麼資訊A=1,不要給我弄丢了或者等下查 A 不等于 1,這些都是資料不一緻,這肯定是不行的),是以在這種配置場景下是十分适合做CP模型的

為什麼Nacos注冊中心要使用AP模型?

  • 答:這個問題也可以轉換成為什麼注冊中心要使用AP模型。因為可用性比一緻性更加重要,可用性展現在兩個方面,第一是容許叢集當機數量,AP模型中叢集全部機器當機才會造成不可用,CP模型隻容許一半的機器當機。第二是分區是否可寫,例如A機房和B機房發生分區,無法進行網絡調用,在CP模型下部署在A機房的服務就無法部署,因為A機房無法與B機房通訊,是以無法寫入IP位址,在AP模型下,可以去中心化,就像Eureka那樣就算發生分區每台機器還都能寫入,叢集間無Master,而是互相複制各自的服務IP資訊,這樣,在分區時依然是支援寫入的。不可用會造成微服務間調用拿不到IP位址,進而業務崩盤,是以不可用是不可接受的,不一緻會造成不同微服務拿到的IP位址不一樣,會造成負載不均衡,這是可以接受的。
更多深度技術文章,可以瞅瞅本人的部落格

|-| [- |_ |_ ()_Static_lin_CSDN部落格-spring,設計模式,分布式領域部落客​blog.csdn.net

player.getcurrentposition() 與暫停時設定的值不一緻_從CAP理論到分布式一緻性協定...