您好,登錄后才能下訂單哦!
在大型分布式系統(tǒng)中,定會(huì)存在大量并發(fā)寫入的場(chǎng)景。在這種場(chǎng)景下如何進(jìn)行更好的并發(fā)控制,即在多個(gè)任務(wù)同時(shí)存取數(shù)據(jù)時(shí)保證數(shù)據(jù)的一致性,成為分布式系統(tǒng)必須解決的問題。
悲觀并發(fā)控制和樂觀并發(fā)控制是并發(fā)控制中采用的主要技術(shù)手段,對(duì)于不同的業(yè)務(wù)場(chǎng)景,應(yīng)該選擇不同的控制方法。
悲觀鎖
悲觀并發(fā)控制(又名“悲觀鎖”,Pessimistic Concurrency Control,縮寫“PCC”)是一種并發(fā)控制的方法。它可以阻止一個(gè)事務(wù)以影響其他用戶的方式來修改數(shù)據(jù)。如果一個(gè)事務(wù)執(zhí)行的操作讀某行數(shù)據(jù)應(yīng)用了鎖,那只有當(dāng)這個(gè)事務(wù)把鎖釋放,其他事務(wù)才能夠執(zhí)行與該鎖沖突的操作。
在悲觀鎖的場(chǎng)景下,假設(shè)用戶A和B要修改同一個(gè)文件,A在鎖定文件并且修改的過程中,B是無法修改這個(gè)文件的,只有等到A修改完成,并且釋放鎖以后,B才可以獲取鎖,然后修改文件。由此可以看出,悲觀鎖對(duì)并發(fā)的控制持悲觀態(tài)度,它在進(jìn)行任何修改前,首先會(huì)為其加鎖,確保整個(gè)修改過程中不會(huì)出現(xiàn)沖突,從而有效的保證數(shù)據(jù)一致性。但這樣的機(jī)制同時(shí)降低了系統(tǒng)的并發(fā)性,尤其是兩個(gè)同時(shí)修改的對(duì)象本身不存在沖突的情況。同時(shí)也可能在競(jìng)爭(zhēng)鎖的時(shí)候出現(xiàn)死鎖,所以現(xiàn)在很多的系統(tǒng)例如Kubernetes采用了樂觀并發(fā)的控制方法。
樂觀鎖
樂觀并發(fā)控制(又名“樂觀鎖”,Optimistic Concurrency Control,縮寫“OCC”)是一種并發(fā)控制的方法。它假設(shè)多用戶并發(fā)的事務(wù)在處理時(shí)不會(huì)彼此影響,各事務(wù)能夠在不請(qǐng)求鎖的情況下處理各自的數(shù)據(jù)。在提交數(shù)據(jù)更新之前,每個(gè)事務(wù)會(huì)先檢查在該事務(wù)讀取數(shù)據(jù)后,有沒有其他事務(wù)又修改了該數(shù)據(jù)。如果其他事務(wù)有更新的話,正在提交的事務(wù)會(huì)進(jìn)行回滾。
相對(duì)于悲觀鎖對(duì)鎖的提前控制,樂觀鎖相信請(qǐng)求之間出現(xiàn)沖突的概率是比較小的,在讀取及更改的過程中都是不加鎖的,只有在最后提交更新時(shí)才會(huì)檢測(cè)沖突,因此在高并發(fā)量的系統(tǒng)中占有絕對(duì)優(yōu)勢(shì)。同樣假設(shè)用戶A和B要修改同一個(gè)文件,A和B會(huì)先將文件獲取到本地,然后進(jìn)行修改。如果A已經(jīng)修改好并且將數(shù)據(jù)提交,此時(shí)B再提交,服務(wù)器端會(huì)告知B文件已經(jīng)被修改,返回沖突錯(cuò)誤。此時(shí)沖突必須由B來解決,可以將文件重新獲取回來,再一次修改后提交。
樂觀鎖通常通過增加一個(gè)資源版本字段,來判斷請(qǐng)求是否沖突。初始化時(shí)指定一個(gè)版本值,每次讀取數(shù)據(jù)時(shí)將版本號(hào)一同讀出,每次更新數(shù)據(jù),同時(shí)也對(duì)版本號(hào)進(jìn)行更新。當(dāng)服務(wù)器端收到數(shù)據(jù)時(shí),將數(shù)據(jù)中的版本號(hào)與服務(wù)器端的做對(duì)比,如果不一致,則說明數(shù)據(jù)已經(jīng)被修改,返回沖突錯(cuò)誤。
Kubernetes中的并發(fā)控制
在Kubernetes集群中,外部用戶及內(nèi)部組件頻繁的數(shù)據(jù)更新操作,導(dǎo)致系統(tǒng)的數(shù)據(jù)并發(fā)讀寫量非常大。假設(shè)采用悲觀并行的控制方法,將嚴(yán)重?fù)p耗集群性能,因此Kubernetes采用樂觀并行的控制方法。Kubernetes通過定義資源版本字段實(shí)現(xiàn)了樂觀并發(fā)控制,資源版本(ResourceVersion)字段包含在Kubernetes對(duì)象的元數(shù)據(jù)(Metadata)中。這個(gè)字符串格式的字段標(biāo)識(shí)了對(duì)象的內(nèi)部版本號(hào),其取值來自etcd的modifiedindex,且當(dāng)對(duì)象被修改時(shí),該字段將隨之被修改。值得注意的是該字段由服務(wù)端維護(hù),不建議在客戶端進(jìn)行修改。
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可以通過該字段判斷對(duì)象是否已經(jīng)被修改。當(dāng)包含ResourceVersion的更新請(qǐng)求到達(dá)Apiserver,服務(wù)器端將對(duì)比請(qǐng)求數(shù)據(jù)與服務(wù)器中數(shù)據(jù)的資源版本號(hào),如果不一致,則表明在本次更新提交時(shí),服務(wù)端對(duì)象已被修改,此時(shí)Apiserver將返回沖突錯(cuò)誤(409),客戶端需重新獲取服務(wù)端數(shù)據(jù),重新修改后再次提交到服務(wù)器端。上述并行控制方法可防止如下的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
當(dāng)未采用并發(fā)控制時(shí),假設(shè)發(fā)生如上請(qǐng)求序列,兩個(gè)客戶端同時(shí)從服務(wù)端獲取同一對(duì)象Foo(含有Bar、Baz兩個(gè)字段),Client#1先將Bar字段置成one,其后Client#2對(duì)Baz字段賦值的更新請(qǐng)求到服務(wù)端時(shí),將覆蓋Client#1對(duì)Bar的修改。反之在對(duì)象中添加資源版本字段,同樣的請(qǐng)求序列將如下:
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更新對(duì)象后資源版本號(hào)將改變,Client#2在更新提交時(shí)將返回沖突錯(cuò)誤(409),此時(shí)Client#2必須在本地重新獲取數(shù)據(jù),更新后再提交到服務(wù)端。
假設(shè)更新請(qǐng)求的對(duì)象中未設(shè)置ResourceVersion值,Kubernetes將會(huì)根據(jù)硬改寫策略(可配置)決定是否進(jìn)行硬更新。如果配置為可硬改寫,則數(shù)據(jù)將直接更新并存入Etcd,反之則返回錯(cuò)誤,提示用戶必須指定ResourceVersion。
Kubernetes中的Update和Patch
Kubernetes實(shí)現(xiàn)了Update和Patch兩個(gè)對(duì)象更新的方法,兩者提供不同的更新操作方式,但沖突判斷機(jī)制是相同的。
Update
對(duì)于Update,客戶端更新請(qǐng)求中包含的是整個(gè)obj對(duì)象,服務(wù)器端將對(duì)比該請(qǐng)求中的obj對(duì)象和服務(wù)器端最新obj對(duì)象的ResourceVersion值。如果相等,則表明未發(fā)生沖突,將成功更新整個(gè)對(duì)象。反之若不相等則返回409沖突錯(cuò)誤,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
}
基本流程為:
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。