<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Kubernetes 鎖機(jī)制設(shè)計(jì)與實(shí)現(xiàn)

          共 4549字,需瀏覽 10分鐘

           ·

          2020-10-20 14:20

          資源鎖是通過一個(gè)資源的CRUD操作,然后配合分布式鎖的一些機(jī)制來完成,分布式環(huán)境中Leader節(jié)點(diǎn)的選舉,今天我們來臆測下k8s里面是如何基于configMap來實(shí)現(xiàn)的吧

          1. 面向終態(tài)的鎖基礎(chǔ)篇

          在分布式系統(tǒng)中通常由各種各樣的鎖,我們先來看下,主流的鎖里面有哪些共性,以及是如何進(jìn)行設(shè)計(jì)的。

          1.1 分布式系統(tǒng)中的鎖

          在分布式系統(tǒng)中鎖有很多種實(shí)現(xiàn)方式:基于CP模型的、基于AP模型的,但是這些鎖機(jī)制都有一些通用的設(shè)計(jì)原則,接下來我們先看下這部分

          1.1.1 鎖憑證

          鎖憑證主要來證明誰持有鎖,不同系統(tǒng)里面的實(shí)現(xiàn)各不相同,比如在zookeeper中是臨時(shí)順序節(jié)點(diǎn),而在redission中則是通過uuid+threadID組成,而k8s中則是LeaderElectionRecord, 通過該憑證來識別當(dāng)前是哪個(gè)客戶端加的鎖

          1.1.2 鎖超時(shí)

          當(dāng)有l(wèi)eader節(jié)點(diǎn)持有鎖之后,其余的節(jié)點(diǎn)就需要嘗試競爭鎖,在CP系統(tǒng)中通常會(huì)由服務(wù)端進(jìn)行維護(hù),即如果發(fā)現(xiàn)對應(yīng)的節(jié)點(diǎn)沒有心跳,則會(huì)進(jìn)行節(jié)點(diǎn)的踢出,并且通過watch這種機(jī)制進(jìn)行回調(diào),而在AP系統(tǒng)中則需要客戶端自己維護(hù),比如redission里面的時(shí)間戳

          ?

          1.1.3 時(shí)鐘

          在分布式系統(tǒng)中通常我們無法保證各個(gè)節(jié)點(diǎn)的物理時(shí)鐘完全一致,通常就會(huì)有一個(gè)邏輯時(shí)鐘的概念,在很多系統(tǒng)中比如raft和zab中其實(shí)就是一個(gè)遞增的全局計(jì)數(shù)器,但是在redission中則是通過物理時(shí)鐘,即需要保證大家的物理時(shí)鐘盡可能同步,不能超過鎖超時(shí)的時(shí)間

          1.2 網(wǎng)絡(luò)分區(qū)問題

          無論是CP還是AP,在分布式系統(tǒng)中通常我們都要保證P即分區(qū)可用性,那如果持有鎖的Leader節(jié)點(diǎn)發(fā)生網(wǎng)絡(luò)分區(qū)的情況,則需要一種保護(hù)機(jī)制,即Leader節(jié)點(diǎn)需要主動(dòng)退出

          在zookeeper中因?yàn)閘eader節(jié)點(diǎn)需要通過session來進(jìn)行心跳的維護(hù),如果說對應(yīng)的leader節(jié)點(diǎn)發(fā)生分區(qū),則session就無法進(jìn)行心跳的發(fā)生,就會(huì)退出,就需要通知我們的主流程來進(jìn)行退出清理工作

          1.3 資源鎖的實(shí)現(xiàn)機(jī)制

          資源鎖其實(shí)就是可以通過操作一個(gè)資源(順序一致性),借助前面說的鎖的思想來實(shí)現(xiàn)分布式鎖,其首先核心流程如下:

          通過資源對象來存儲(chǔ)鎖憑證信息

          即將標(biāo)識當(dāng)前Leader節(jié)點(diǎn)的信息放入到對應(yīng)的憑證里面,并嘗試進(jìn)行鎖競爭,進(jìn)行鎖的獲取的嘗試

          鎖超時(shí)

          k8s的鎖超時(shí)的機(jī)制比較有趣,即他并不關(guān)心你的邏輯時(shí)鐘,而是以本地時(shí)鐘為準(zhǔn),即每個(gè)節(jié)點(diǎn)會(huì)存儲(chǔ)觀測到leader節(jié)點(diǎn)變更的時(shí)間,然后根據(jù)本地的鎖超時(shí)時(shí)間來檢測,是否重新發(fā)起leader的競爭

          2. 核心源碼剖析

          因?yàn)槠蜻@里只介紹基于configMap的resourceLock, 其他的都大同小異

          2.1 LeaderElectionRecord

          在我的理解上這個(gè)數(shù)結(jié)構(gòu)的設(shè)計(jì),才是真正的那把鎖(就好像生活中我們可以隨便買把鎖,鎖各種門)。通過這個(gè)鎖屏蔽底層的各種鎖實(shí)現(xiàn)系統(tǒng)的實(shí)現(xiàn)細(xì)節(jié),但注意這把鎖并不是嚴(yán)格的分布式互斥鎖

          2.1.1 數(shù)據(jù)結(jié)構(gòu)

          在鎖的實(shí)現(xiàn)中,數(shù)據(jù)主要分為三類:身份憑證、時(shí)間戳、全局計(jì)數(shù)器,然后我們依次來看猜下對應(yīng)的設(shè)計(jì)思路

          type LeaderElectionRecord struct {
          HolderIdentity string `json:"holderIdentity"`
          LeaseDurationSeconds int `json:"leaseDurationSeconds"`
          AcquireTime metav1.Time `json:"acquireTime"`
          RenewTime metav1.Time `json:"renewTime"`
          LeaderTransitions int `json:"leaderTransitions"`
          }

          身份憑證:HolderIdentity

          身份憑證主要是用于標(biāo)識一個(gè)節(jié)點(diǎn)信息,在一些分布式協(xié)調(diào)系統(tǒng)中通常都是系統(tǒng)自帶的機(jī)制,比如zookeeper中的session, 在此處資源鎖的場景下,主要是為了用于后續(xù)流程里驗(yàn)證當(dāng)前節(jié)點(diǎn)是否獲取到鎖

          時(shí)間戳:LeaseDurationSeconds、AcquireTime、RenewTime

          因?yàn)橹罢f的時(shí)間同步的問題,這里的時(shí)間相關(guān)的主要是用于leader節(jié)點(diǎn)觸發(fā)節(jié)點(diǎn)變更來使用(Lease類型也在使用),非Leader節(jié)點(diǎn)則根據(jù)當(dāng)前記錄是否變更來檢測leader節(jié)點(diǎn)是否存活

          LeaderTransitions

          計(jì)數(shù)器主要就是通過計(jì)數(shù)來記錄leader節(jié)點(diǎn)切換的次數(shù)

          2.2 ConfigMapLock

          所謂的資源鎖其實(shí)就是通過創(chuàng)建一個(gè)ConfigMap實(shí)例來保存我們的鎖信息,并通過這個(gè)實(shí)例信息的維護(hù),來實(shí)現(xiàn)鎖的競爭和釋放

          2.2.1 創(chuàng)建鎖

          通過利用etcd的冪等性操作,可以保證同時(shí)只會(huì)有一個(gè)leader節(jié)點(diǎn)進(jìn)行鎖創(chuàng)建成功,并且通過Annotations來提交上面說的LeaderElectionRecord來進(jìn)行鎖的提交

          func (cml *ConfigMapLock) Create(ler LeaderElectionRecord) error {
          cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Create(&v1.ConfigMap{
          ObjectMeta: metav1.ObjectMeta{
          Name: cml.ConfigMapMeta.Name,
          Namespace: cml.ConfigMapMeta.Namespace,
          Annotations: map[string]string{
          LeaderElectionRecordAnnotationKey: string(recordBytes),
          },
          },
          })
          return err
          }

          2.2.2 獲取鎖

          func (cml *ConfigMapLock) Get() (*LeaderElectionRecord, []byte, error) {
          cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Get(cml.ConfigMapMeta.Name, metav1.GetOptions{})
          recordBytes, found := cml.cm.Annotations[LeaderElectionRecordAnnotationKey]
          if found {
          if err := json.Unmarshal([]byte(recordBytes), &record); err != nil {
          return nil, nil, err
          }
          }
          return &record, []byte(recordBytes), nil
          }

          2.2.3 更新鎖

          func (cml *ConfigMapLock) Update(ler LeaderElectionRecord) error {
          cml.cm.Annotations[LeaderElectionRecordAnnotationKey] = string(recordBytes)
          cml.cm, err = cml.Client.ConfigMaps(cml.ConfigMapMeta.Namespace).Update(cml.cm)
          return err
          }

          2.3 LeaderElector

          LeaderElector的核心流程分為三部分:競爭鎖、超時(shí)檢測、心跳維護(hù),首先所有節(jié)點(diǎn)都會(huì)進(jìn)行資源鎖的競爭,但是最終只會(huì)有一個(gè)節(jié)點(diǎn)成為Leader節(jié)點(diǎn), 然后核心流程就會(huì)按照角色分成兩個(gè)主流程, 讓我們一起來看下其實(shí)現(xiàn)

          2.3.1 核心流程

          如果節(jié)點(diǎn)沒有acquire成功則會(huì)一直進(jìn)行嘗試,直至取消或者競選成功,而leader節(jié)點(diǎn)則會(huì)執(zhí)行成為leader節(jié)點(diǎn)的回調(diào)(補(bǔ)充基于leader的zookeeper的實(shí)現(xiàn)機(jī)制)

          func (le *LeaderElector) Run(ctx context.Context) {
          defer func() {
          runtime.HandleCrash()
          le.config.Callbacks.OnStoppedLeading()
          }()
          if !le.acquire(ctx) { // 精選鎖
          return // ctx signalled done
          }
          // 如果鎖競選成功,則leader節(jié)點(diǎn)會(huì)執(zhí)行剩余流程,而非leader節(jié)點(diǎn)則繼續(xù)嘗試acquire
          ctx, cancel := context.WithCancel(ctx)
          defer cancel()
          go le.config.Callbacks.OnStartedLeading(ctx)
          le.renew(ctx)
          }

          2.3.2 鎖的續(xù)約

          如果競選為leader節(jié)點(diǎn),則就需要進(jìn)行鎖的續(xù)約操作,就是通過調(diào)用上面提到的更新鎖的操作來,周期性的更新鎖記錄信息即LeaderElectionRecord,從而達(dá)到續(xù)約的目標(biāo)

          func (le *LeaderElector) renew(ctx context.Context) {
          ctx, cancel := context.WithCancel(ctx)
          defer cancel()
          wait.Until(func() {
          timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
          defer timeoutCancel()
          err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
          done := make(chan bool, 1)
          go func() {
          defer close(done)
          // 鎖的續(xù)約
          done <- le.tryAcquireOrRenew()
          }()

          select {
          case <-timeoutCtx.Done():
          return false, fmt.Errorf("failed to tryAcquireOrRenew %s", timeoutCtx.Err())
          case result := <-done:
          return result, nil
          }
          }, timeoutCtx.Done())
          cancel()
          }, le.config.RetryPeriod, ctx.Done())

          // if we hold the lease, give it up
          if le.config.ReleaseOnCancel {
          // 釋放鎖
          le.release()
          }
          }

          2.3.3 鎖的釋放

          鎖的釋放則比較好玩,就是更新對應(yīng)的資源,去掉annotations里面的信息,這樣在獲取鎖的時(shí)候,因?yàn)闄z測到當(dāng)前資源沒有被任何憑證信息,就會(huì)嘗試進(jìn)行競選

          func (le *LeaderElector) release() bool {
          if !le.IsLeader() {
          return true
          }
          leaderElectionRecord := rl.LeaderElectionRecord{
          LeaderTransitions: le.observedRecord.LeaderTransitions,
          }
          if err := le.config.Lock.Update(leaderElectionRecord); err != nil {
          klog.Errorf("Failed to release lock: %v", err)
          return false
          }
          le.observedRecord = leaderElectionRecord
          le.observedTime = le.clock.Now()
          return true
          }

          2.3.4 鎖的競爭

          鎖的競爭整體分為四個(gè)部分: 1)獲取鎖 2)創(chuàng)建鎖 3)檢測鎖 4)更新鎖,下面來依次看下對應(yīng)的實(shí)現(xiàn)

          獲取鎖

          首先會(huì)嘗試獲取對應(yīng)的鎖,在獲取鎖中會(huì)檢測對應(yīng)的annotations中是否存在,如果不存在則oldLeaderElectionRecord就為空,即當(dāng)前資源鎖沒有被人持有

          oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get()

          創(chuàng)建鎖

          如果檢測到對應(yīng)的鎖不存在,則就會(huì)直接進(jìn)行鎖的創(chuàng)建,如果創(chuàng)建成功則表明當(dāng)前節(jié)點(diǎn)獲取鎖,則就成為leader,執(zhí)行l(wèi)eader的回調(diào)邏輯

              if err != nil {
          if !errors.IsNotFound(err) {
          klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
          return false
          }
          // 創(chuàng)建鎖
          if err = le.config.Lock.Create(leaderElectionRecord); err != nil {
          klog.Errorf("error initially creating leader election record: %v", err)
          return false
          }
          // 記錄當(dāng)前的選舉記錄,還有時(shí)鐘
          le.observedRecord = leaderElectionRecord
          le.observedTime = le.clock.Now()
          return true
          }

          檢查鎖

          在k8s里面并沒有使用邏輯時(shí)鐘而是使用本地時(shí)間,通過對比每次鎖憑證是否更新,來進(jìn)行本地observedTime的更新,如果leader沒有在LeaseDuration內(nèi)來更新對應(yīng)的鎖憑證信息,則當(dāng)前節(jié)點(diǎn)就會(huì)嘗試成為leader

          同時(shí)這里還會(huì)保障最終的一致性鎖,因?yàn)楹罄m(xù)的renew其實(shí)也是走的這個(gè)邏輯,如果說當(dāng)前節(jié)點(diǎn)最開始持有鎖,但是被別的節(jié)點(diǎn)搶占,則當(dāng)前節(jié)點(diǎn)會(huì)主動(dòng)讓出鎖

              if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
          le.observedRecord = *oldLeaderElectionRecord
          le.observedRawRecord = oldLeaderElectionRawRecord
          le.observedTime = le.clock.Now() // 此處更新的是本地的時(shí)鐘
          }
          if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
          le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
          !le.IsLeader() {
          // 如果當(dāng)前Leader任期沒有超時(shí),則當(dāng)前競選鎖失敗
          klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
          return false
          }

          更新鎖

          核心邏輯其實(shí)就是Lock.Update這個(gè)地方,設(shè)計(jì)的比較有意思,不同于強(qiáng)一致性的鎖,在k8s中我們可以同時(shí)有多個(gè)節(jié)點(diǎn)都走到這里,但是因?yàn)楦耬tcd是一個(gè)原子的操作,最終只會(huì)有一個(gè)節(jié)點(diǎn)更新成功,那如何保證最終的鎖的語義呢,其實(shí)就要配合上面的檢測鎖,這樣就可以實(shí)現(xiàn)一個(gè)面向終態(tài)的最終的鎖機(jī)制

              if le.IsLeader() {
          leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
          leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
          } else {
          leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
          }

          // update the lock itself
          if err = le.config.Lock.Update(leaderElectionRecord); err != nil {
          klog.Errorf("Failed to update lock: %v", err)
          return false
          }

          le.observedRecord = leaderElectionRecord
          le.observedTime = le.clock.Now()
          return true

          3. 疑問

          回過來看鎖是因?yàn)樽罱谧鱿到y(tǒng)設(shè)計(jì)的時(shí)候,想到的一個(gè)問題。在PAAS系統(tǒng)中通常會(huì)有N多的Operator,那在一些沖突的場景該如何解決呢?比如擴(kuò)縮容、發(fā)布、容災(zāi)這幾個(gè)控制器,如果要操作同一個(gè)app下面的pod該如何被調(diào)度呢?

          其實(shí)我理解這個(gè)流程中是無法做到各種完美cover各種異常沖突的,但是我們可以玩另外一種有意思的事情,比如我們可以加一個(gè)保護(hù)狀態(tài),因?yàn)閷ιa(chǎn)穩(wěn)定壓倒一起。即對應(yīng)的控制器,關(guān)注當(dāng)前的狀態(tài)是否處于穩(wěn)定狀態(tài),如果是非穩(wěn)定狀態(tài),則就應(yīng)該自身凍結(jié),等當(dāng)前應(yīng)用處于非保護(hù)狀態(tài)再進(jìn)行操作,保證SLA的同時(shí)也不影響各種好玩的操作

          云原生學(xué)習(xí)筆記地址:??https://www.yuque.com/baxiaoshi/tyado3


          ?點(diǎn)擊屏末?|??|?學(xué)習(xí)云原生

          瀏覽 104
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  九九成人电影www | ZZjiZZji亚洲日本少妇 | 免费看一级a片一级a片不人片 | 北条麻妃在线一区二区三区免费 | 免费亚洲婷婷 |