Kubernetes并發(fā)控制與數(shù)據(jù)一致性的實現(xiàn)原理

在大型分布式系統(tǒng)中,定會存在大量并發(fā)寫入的場景。在這種場景下如何進行更好的并發(fā)控制,即在多個任務同時存取數(shù)據(jù)時保證數(shù)據(jù)的一致性,成為分布式系統(tǒng)必須解決的問題。
悲觀并發(fā)控制和樂觀并發(fā)控制是并發(fā)控制中采用的主要技術手段,對于不同的業(yè)務場景,應該選擇不同的控制方法。
悲觀鎖
悲觀并發(fā)控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)是一種并發(fā)控制的方法。它可以阻止一個事務以影響其他用戶的方式來修改數(shù)據(jù)。如果一個事務執(zhí)行的操作讀某行數(shù)據(jù)應用了鎖,那只有當這個事務把鎖釋放,其他事務才能夠執(zhí)行與該鎖沖突的操作。
在悲觀鎖的場景下,假設用戶A和B要修改同一個文件,A在鎖定文件并且修改的過程中,B是無法修改這個文件的,只有等到A修改完成,并且釋放鎖以后,B才可以獲取鎖,然后修改文件。由此可以看出,悲觀鎖對并發(fā)的控制持悲觀態(tài)度,它在進行任何修改前,首先會為其加鎖,確保整個修改過程中不會出現(xiàn)沖突,從而有效的保證數(shù)據(jù)一致性。但這樣的機制同時降低了系統(tǒng)的并發(fā)性,尤其是兩個同時修改的對象本身不存在沖突的情況。同時也可能在競爭鎖的時候出現(xiàn)死鎖,所以現(xiàn)在很多的系統(tǒng)例如Kubernetes采用了樂觀并發(fā)的控制方法。
樂觀鎖
樂觀并發(fā)控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種并發(fā)控制的方法。它假設多用戶并發(fā)的事務在處理時不會彼此影響,各事務能夠在不請求鎖的情況下處理各自的數(shù)據(jù)。在提交數(shù)據(jù)更新之前,每個事務會先檢查在該事務讀取數(shù)據(jù)后,有沒有其他事務又修改了該數(shù)據(jù)。如果其他事務有更新的話,正在提交的事務會進行回滾。
相對于悲觀鎖對鎖的提前控制,樂觀鎖相信請求之間出現(xiàn)沖突的概率是比較小的,在讀取及更改的過程中都是不加鎖的,只有在最后提交更新時才會檢測沖突,因此在高并發(fā)量的系統(tǒng)中占有絕對優(yōu)勢。同樣假設用戶A和B要修改同一個文件,A和B會先將文件獲取到本地,然后進行修改。如果A已經(jīng)修改好并且將數(shù)據(jù)提交,此時B再提交,服務器端會告知B文件已經(jīng)被修改,返回沖突錯誤。此時沖突必須由B來解決,可以將文件重新獲取回來,再一次修改后提交。
樂觀鎖通常通過增加一個資源版本字段,來判斷請求是否沖突。初始化時指定一個版本值,每次讀取數(shù)據(jù)時將版本號一同讀出,每次更新數(shù)據(jù),同時也對版本號進行更新。當服務器端收到數(shù)據(jù)時,將數(shù)據(jù)中的版本號與服務器端的做對比,如果不一致,則說明數(shù)據(jù)已經(jīng)被修改,返回沖突錯誤。
Kubernetes中的并發(fā)控制
在Kubernetes集群中,外部用戶及內(nèi)部組件頻繁的數(shù)據(jù)更新操作,導致系統(tǒng)的數(shù)據(jù)并發(fā)讀寫量非常大。假設采用悲觀并行的控制方法,將嚴重損耗集群性能,因此Kubernetes采用樂觀并行的控制方法。Kubernetes通過定義資源版本字段實現(xiàn)了樂觀并發(fā)控制,資源版本(ResourceVersion)字段包含在Kubernetes對象的元數(shù)據(jù)(Metadata)中。這個字符串格式的字段標識了對象的內(nèi)部版本號,其取值來自etcd的modifiedindex,且當對象被修改時,該字段將隨之被修改。值得注意的是該字段由服務端維護,不建議在客戶端進行修改。
type ObjectMeta struct {
......
// An opaque value that represents the internal version of this object that can
// be used by clients to determine when objects have changed. May be used for optimistic
// concurrency, change detection, and the watch operation on a resource or set of resources.
// Clients must treat these values as opaque and passed unmodified back to the server.
// They may only be valid for a particular resource or set of resources.
//
// Populated by the system.
// Read-only.
// Value must be treated as opaque by clients and .
// More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency
// +optional
ResourceVersion string
......
}
Kube-Apiserver可以通過該字段判斷對象是否已經(jīng)被修改。當包含ResourceVersion的更新請求到達Apiserver,服務器端將對比請求數(shù)據(jù)與服務器中數(shù)據(jù)的資源版本號,如果不一致,則表明在本次更新提交時,服務端對象已被修改,此時Apiserver將返回沖突錯誤(409),客戶端需重新獲取服務端數(shù)據(jù),重新修改后再次提交到服務器端。上述并行控制方法可防止如下的data race:
Client #1: GET Foo
Client #2: GET Foo
Client #1: Set Foo.Bar = "one"
Client #1: PUT Foo
Client #2: Set Foo.Baz = "two"
Client #2: PUT Foo
當未采用并發(fā)控制時,假設發(fā)生如上請求序列,兩個客戶端同時從服務端獲取同一對象Foo(含有Bar、Baz兩個字段),Client#1先將Bar字段置成one,其后Client#2對Baz字段賦值的更新請求到服務端時,將覆蓋Client#1對Bar的修改。反之在對象中添加資源版本字段,同樣的請求序列將如下:
Client #1: GET Foo //初始Foo.ResourceVersion=1
Client #2: GET Foo //初始Foo.ResourceVersion=1
Client #1: Set Foo.Bar = "one"
Client #1: PUT Foo //更新Foo.ResourceVersion=2
Client #2: Set Foo.Baz = "two"
Client #2: PUT Foo //返回409沖突
Client#1更新對象后資源版本號將改變,Client#2在更新提交時將返回沖突錯誤(409),此時Client#2必須在本地重新獲取數(shù)據(jù),更新后再提交到服務端。
假設更新請求的對象中未設置ResourceVersion值,Kubernetes將會根據(jù)硬改寫策略(可配置)決定是否進行硬更新。如果配置為可硬改寫,則數(shù)據(jù)將直接更新并存入Etcd,反之則返回錯誤,提示用戶必須指定ResourceVersion。
Kubernetes中的Update和Patch
Kubernetes實現(xiàn)了Update和Patch兩個對象更新的方法,兩者提供不同的更新操作方式,但沖突判斷機制是相同的。
Update
對于Update,客戶端更新請求中包含的是整個obj對象,服務器端將對比該請求中的obj對象和服務器端最新obj對象的ResourceVersion值。如果相等,則表明未發(fā)生沖突,將成功更新整個對象。反之若不相等則返回409沖突錯誤,Kube-Apiserver中沖突判斷的代碼片段如下。
e.Storage.GuaranteedUpdate(ctx, key...) (runtime.Object, *uint64, error) {
// If AllowUnconditionalUpdate() is true and the object specified by
// the user does not have a resource version, then we populate it with
// the latest version. Else, we check that the version specified by
// the user matches the version of latest storage object.
resourceVersion, err := e.Storage.Versioner().ObjectResourceVersion(obj)
if err != nil {
return nil, nil, err
}
version, err := e.Storage.Versioner().ObjectResourceVersion(existing)
doUnconditionalUpdate := resourceVersion == 0 && e.UpdateStrategy.AllowUnconditionalUpdate()
if doUnconditionalUpdate {
// Update the object's resource version to match the latest
// storage object's resource version.
err = e.Storage.Versioner().UpdateObject(obj, res.ResourceVersion)
if err != nil {
return nil, nil, err
}
} else {
// Check if the object's resource version matches the latest
// resource version.
......
if resourceVersion != version {
return nil, nil, kubeerr.NewConflict(qualifiedResource, name, fmt.Errorf(OptimisticLockErrorMsg))
}
}
......
return out, creating, nil
}
基本流程為:

成都創(chuàng)新互聯(lián)從2013年開始,是專業(yè)互聯(lián)網(wǎng)技術服務公司,擁有項目網(wǎng)站設計、成都做網(wǎng)站網(wǎng)站策劃,項目實施與項目整合能力。我們以讓每一個夢想脫穎而出為使命,1280元沈陽做網(wǎng)站,已為上家服務,為沈陽各地企業(yè)和個人服務,聯(lián)系電話:13518219792

  1. 獲取當前更新請求中obj對象的ResourceVersion值,及服務器端最新obj對象(existing)的ResourceVersion值
  2. 如果當前更新請求中obj對象的ResourceVersion值等于0,即客戶端未設置該值,則判斷是否要硬改寫(AllowUnconditionalUpdate),如配置為硬改寫策略,將直接更新obj對象
  3. 如果當前更新請求中obj對象的ResourceVersion值不等于0,則判斷兩個ResourceVersion值是否一致,不一致返回沖突錯誤(OptimisticLockErrorMsg)
    Patch
    相比Update請求包含整個obj對象,Patch請求實現(xiàn)了更細粒度的對象更新操作,其請求中只包含需要更新的字段。例如要更新pod中container的鏡像,可使用如下命令:
    kubectl patch pod my-pod -p '{"spec":{"containers":[{"name":"my-container","image":"new-image"}]}}'
    服務器端只收到以上的patch信息,然后通過如下代碼將該patch更新到Etcd中。
    func (p *patcher) patchResource(ctx context.Context) (runtime.Object, error) {
    p.namespace = request.NamespaceValue(ctx)
    switch p.patchType {
    case types.JSONPatchType, types.MergePatchType:
    p.mechanism = &jsonPatcher{patcher: p}
    case types.StrategicMergePatchType:
    schemaReferenceObj, err := p.unsafeConvertor.ConvertToVersion(p.restPatcher.New(), p.kind.GroupVersion())
    if err != nil {
    return nil, err
    }
    p.mechanism = &smpPatcher{patcher: p, schemaReferenceObj: schemaReferenceObj}
    default:
    return nil, fmt.Errorf("%v: unimplemented patch type", p.patchType)
    }
    p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
    return finishRequest(p.timeout, func() (runtime.Object, error) {
    updateObject, _, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, false, p.options)
    return updateObject, updateErr
    })
    }
    基本流程為:
    1.首先判斷patch的類型,根據(jù)類型選擇相應的mechanism
    2.利用DefaultUpdatedObjectInfo方法將applyPatch(應用Patch的方法)添加到admission chain的頭部
    3.最終還是調(diào)用上述Update方法執(zhí)行更新操作
    在步驟2中將applyPatch方法掛到admission chain的頭部,與admission行為相似,applyPatch方法會將patch應用到最新獲取的服務器端obj上,生成一個已更新的obj,再對該obj繼續(xù)執(zhí)行admission chain中的Admit與Validate。最終調(diào)用的還是update方法,因此沖突檢測的機制與上述Update方法完全一致。
    相比Update,Patch的主要優(yōu)勢在于客戶端不必提供全量的obj對象信息??蛻舳酥恍枰詐atch的方式提交要修改的字段信息,服務器端會將該patch數(shù)據(jù)應用到最新獲取的obj中。省略了Client端獲取、修改再提交全量obj的步驟,降低了數(shù)據(jù)被修改的風險,更大大減小了沖突概率。 由于Patch方法在傳輸效率及沖突概率上都占有絕對優(yōu)勢,目前Kubernetes中幾乎所有更新操作都采用了Patch方法,我們在編寫代碼時也應該注意使用Patch方法。
    附:
    ResourceVersion字段在Kubernetes中除了用在上述并發(fā)控制機制外,還用在Kubernetes的list-watch機制中。Client端的list-watch分為兩個步驟,先list取回所有對象,再以增量的方式watch后續(xù)對象。Client端在list取回所有對象后,將會把最新對象的ResourceVersion作為下一步watch操作的起點參數(shù),也即Kube-Apiserver以收到的ResourceVersion為起始點返回后續(xù)數(shù)據(jù),保證了list-watch中數(shù)據(jù)的連續(xù)性與完整性。

分享名稱:Kubernetes并發(fā)控制與數(shù)據(jù)一致性的實現(xiàn)原理
網(wǎng)頁地址:http://www.muchs.cn/article46/ijsehg.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供營銷型網(wǎng)站建設、建站公司、移動網(wǎng)站建設、用戶體驗、面包屑導航、App開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站制作