Dapr微服务应用开发系列4:状态管理构件块
Dapr微服務(wù)應(yīng)用開發(fā)系列0:概述
Dapr微服務(wù)應(yīng)用開發(fā)系列1:環(huán)境配置
Dapr微服務(wù)應(yīng)用開發(fā)系列2:Hello World與SDK初接觸
Dapr微服務(wù)應(yīng)用開發(fā)系列3:服務(wù)調(diào)用構(gòu)件塊
題記:這篇介紹狀態(tài)管理構(gòu)件塊,這個概念相對于微服務(wù)框架而言是比較特殊的。
注:本文僅針對非Actor狀態(tài)存儲的情況進(jìn)行說明,對于Actor狀態(tài)存儲會在講述Actor的時候一并說明。
原理
要用好這個構(gòu)件塊,首先需要正確理解狀態(tài)管理的概念。
大部分微服務(wù)開發(fā)框架或者說指導(dǎo),都提倡微服務(wù)以無狀態(tài)類型的方式來運行,這種無狀態(tài)微服務(wù)當(dāng)然更容易進(jìn)行伸縮,但是在遇到需要處理一些類似Session這樣的數(shù)據(jù)的時候,為了應(yīng)對分布式的環(huán)境往往要借助于外部存儲(一般是數(shù)據(jù)庫或者緩存中間件)。但是這樣做不可避免引入了對外部服務(wù)以及特定協(xié)議的依賴。
如果對Service Fabric熟悉的同學(xué),可能對有狀態(tài)服務(wù)這個概念有所了解,這種SF中特有的微服務(wù)類型,直接通過運行時和SDK給微服務(wù)提供了一種開箱即用的狀態(tài)管理機(jī)制——即可靠集合(Reliable Collections)。這種機(jī)制讓你可以通過Key/Value的方式來存取相關(guān)狀態(tài)值(業(yè)務(wù)數(shù)據(jù)),在某些情況下甚至可以當(dāng)作一個NoSQL來使用。有狀態(tài)服務(wù)的優(yōu)勢是把數(shù)據(jù)和業(yè)務(wù)邏輯作為一個整體來處理,特別適合領(lǐng)域驅(qū)動設(shè)計為指導(dǎo)的每個微服務(wù)對應(yīng)獨立的數(shù)據(jù)源的原則。
由于Dapr和Service Fabric有一些淵源,所以“有狀態(tài)”的這個概念也被引入到了Dapr當(dāng)中,但是這個時候的微服務(wù)類型其實還是依舊保持著無狀態(tài)。因此狀態(tài)管理構(gòu)件塊本質(zhì)上是給開發(fā)人員提供了一種狀態(tài)值存取的機(jī)制和API,并把狀態(tài)存儲的存儲源(也即狀態(tài)存儲組件)和訪問協(xié)議進(jìn)行了抽象和屏蔽。原理圖如下(其中使用了Redis作為狀態(tài)存儲組件,這也是開發(fā)環(huán)境默認(rèn)的組件):
從上圖所知,微服務(wù)只需要對自己的Dapr邊車進(jìn)行訪問,即可完成狀態(tài)的保存和獲取(可批量)。而存取的方式遵循了標(biāo)準(zhǔn)了HTTP規(guī)范的謂詞。
因而,有了狀態(tài)管理構(gòu)件塊,微服務(wù)輕可以利用其完成臨時狀態(tài)的持久化和微服務(wù)間共享,重可以利用其實現(xiàn)有狀態(tài)服務(wù)來保存業(yè)務(wù)模型。
能力
Dapr的狀態(tài)管理構(gòu)件塊并非是簡簡單單為大家提供了一種狀態(tài)存取的機(jī)制(可以從原理圖直觀的看出),更為重要的是提供了如下額外能力:
隱藏在運行微服務(wù)的時候配置存儲組件:開發(fā)的時候只需要關(guān)心Dapr的規(guī)范接口,并使用某些簡單易得的存儲組件來進(jìn)行調(diào)試,比如默認(rèn)的Redis存儲組件;運行的時候可以引入(替換)為其他存儲組件,比如Azure CosmosDB,而無需改變業(yè)務(wù)代碼。
內(nèi)置重試機(jī)制:和服務(wù)調(diào)用構(gòu)件塊一樣,狀態(tài)管理的API提供了內(nèi)置的重試能力,并可以用同樣的語義配置重試策略。這樣的重試能力也為并發(fā)和一致性等能力提供了基礎(chǔ)。
內(nèi)置并發(fā)控制機(jī)制:Dapr依賴ETag這一特殊狀態(tài)屬性來保證樂觀并發(fā)控制的處理。也即在更新或者刪除狀態(tài)的時候,會檢查ETag是否匹配,從而決定是否完成數(shù)據(jù)操作。眾所周知,樂觀并發(fā)這種模式是比較適合數(shù)據(jù)沖突很少的情況,也即數(shù)據(jù)的更新主要由不同的業(yè)務(wù)數(shù)據(jù)操作而導(dǎo)致。注意:某些狀態(tài)存儲中間件是不支持ETag的,所以Dapr進(jìn)行了額外的處理模擬了這一機(jī)制。
內(nèi)置一致性處理機(jī)制:Dapr支持兩種一致性處理——強(qiáng)一致性和最終一致性。對于強(qiáng)一致性,Dapr會等待所有底層請求返回確認(rèn)信息才最終完成操作;最終一致性不會等待底層請求確認(rèn)。
批量處理:Dapr的狀態(tài)管理提供了兩種模式的批量處理。Bulk模式用于把一種同類型的請求合并,這種時候不會保證事務(wù)性;Multi模式可以把不同類型的請求一起發(fā)送,可以保證事務(wù)性。
需要注意的是由于Dapr需要支持盡量多的狀態(tài)存儲源,所以必然有一些存儲源是無法支持以上所有的能力的(主要是事務(wù)能力),可以通過瀏覽這個列表[1]來確認(rèn)存儲源的支持情況,還算我們常用的Redis、SQL Server、MySQL、PostgreSQL和CosmosDB是可以完整支持。
規(guī)范
Dapr狀態(tài)管理構(gòu)件塊由于提供了這種特定的能力給你的微服務(wù)使用,所以給使用的方式制訂了如下規(guī)范:
由于狀態(tài)管理依賴于狀態(tài)組件,所以首先規(guī)定了應(yīng)用狀態(tài)組件的聲明格式
從概念所知,狀態(tài)的存儲需要依賴狀態(tài)鍵,所以接著規(guī)定了鍵的構(gòu)成方式
最為重要的規(guī)范是規(guī)定了狀態(tài)存取的HTTP/gRPC的地址格式
狀態(tài)組件聲明
通過如下yaml文件來聲明對狀態(tài)組件的引用(在本地開發(fā)環(huán)境可以不聲明,使用默認(rèn)的狀態(tài)存儲源):
apiVersion: dapr.io/v1alpha1 kind: Component metadata:name: <NAME>namespace: <NAMESPACE> spec:type: state.<TYPE>metadata:- name:<KEY>value:<VALUE>- name: <KEY>value: <VALUE>由于整個聲明的解釋,會在后續(xù)單獨的組件文章中詳細(xì)展開。我們只要記住其中的 metadata.name 代表了存儲源的名稱,對于應(yīng)用程序而言需要匹配的就是這個名稱。另外 spec.type 代表了存儲源所使用的存儲類型,這個對于應(yīng)用開發(fā)者而言可以了解到存儲源是否具備完整的狀態(tài)存儲能力。
鍵的組成模式
從上所知,狀態(tài)管理構(gòu)件塊是以鍵值的方式保存數(shù)據(jù)的,為了保證和存儲源的兼容,那么就需要按照一定的模式來定義鍵的組成。
普通的(非Actor)狀態(tài)的鍵為:<App ID>||<state key>
對于應(yīng)用開發(fā)者而言,其實只需要關(guān)心 <state key> 即可。
請求地址
要對狀態(tài)進(jìn)行操作,需要對如下地址進(jìn)行HTTP/gRPC請求:http://localhost:<daprPort>/v1.0/state/<storename>
其中daprPort代表了Dapr邊車的特定協(xié)議端口,HTTP默認(rèn)50001或者gRPC默認(rèn)3500;storename即是在組件聲明中的 metadata.name。
保存狀態(tài)
對上述地址進(jìn)行POST請求,并傳遞一個鍵值對(外加可選的etag)的數(shù)組作為請求體,比如:
[{"key": "weapon","value": "DeathStar","etag": "1234"},{"key": "planet","value": {"name": "Tatooine"}} ]獲取狀態(tài)
對上述地址進(jìn)行GET請求,并傳遞狀態(tài)鍵作為路由參數(shù):
GET http://localhost:<daprPort>/v1.0/state/<storename>/<key>返回結(jié)果是一個json的對象,具體格式是由你確定(即你保存狀態(tài)的時候傳入什么格式);另外etag會附加在響應(yīng)頭 ETag 當(dāng)中。
以bulk的方式獲取狀態(tài)
對上述地址進(jìn)行POST/PUT請求,并傳遞 bulk 作為路由參數(shù):
POST/PUT http://localhost:<daprPort>/v1.0/state/<storename>/bulk同時再構(gòu)建一個如下格式的請求體,把需要獲取的狀態(tài)鍵放到 keys 數(shù)組當(dāng)中,同時設(shè)定 parallelism 的值來確定在存儲源中執(zhí)行查找操作的并行度(如果狀態(tài)是以分區(qū)的方式保存在存儲源中的話):
{"keys": [ "key1", "key2" ],"parallelism": 10 }請求后,響應(yīng)體是一個包含了鍵值對數(shù)組的json對象:
[{"key": "key1","data": "value1","etag": "1"},{"key": "key2","data": "value2","etag": "1"} ]刪除狀態(tài)
對上述地址進(jìn)行GET請求,并傳遞狀態(tài)鍵作為路由參數(shù):
DELETE http://localhost:<daprPort>/v1.0/state/<storename>/<key>刪除狀態(tài)請求沒有響應(yīng)體,通過響應(yīng)狀態(tài)碼204來確認(rèn)刪除成功。
事務(wù)操作
如果你希望一次請求執(zhí)行多步操作的話,可以使用這種請求方式。這種請求由于是支持事務(wù)的,所以并非所有存儲源都支持。
對上述地址進(jìn)行POST/PUT請求,并傳遞 transaction 作為路由參數(shù):
POST/PUT http://localhost:<daprPort>/v1.0/state/<storename>/transaction請求體是一個操作的數(shù)組,標(biāo)明了各個操作要完成的操作類型和狀態(tài)內(nèi)容,如:
{"operations": [{"operation": "upsert","request": {"key": "key1","value": "myData"}},{"operation": "delete","request": {"key": "key2"}}],"metadata": {"partitionKey": "planet"} }其中 operation 有兩種類型:upsert (更新或插入)和 delete(刪除)。
目前支持事務(wù)操作的存儲源有:
Redis
MongoDB
MySQL
RethinkDB
PostgreSQL
SQL Server
Azure CosmosDB
DOTNET SDK
由于狀態(tài)管理構(gòu)件塊為你的應(yīng)用程序提供了一些和狀態(tài)相關(guān)的操作接口,SDK除了提供 DaprClient 這個客戶端封裝類方便你使用.NET函數(shù)庫來操作狀態(tài)以外,也為ASP.NET Core提供了更加便捷的模型綁定屬性標(biāo)記類 FromStateAttribute 方便你在Controller中通過屬性綁定的方式來獲取狀態(tài)。
DaprClient中和狀態(tài)相關(guān)的方法有:
GetStateAsync:基于storeName和key獲取狀態(tài)值
GetBulkStateAsync:基于storeName和keys列表獲取多個狀態(tài)值
GetStateAndETagAsync:基于storeName和key獲取狀態(tài)值和etag
GetStateEntryAsync:基于storeName和key獲取StateEntry封裝類,此類包含了狀態(tài)的更詳細(xì)信息
SaveStateAsync:基于storeName和key保存狀態(tài)值
ExecuteStateTransactionAsync:執(zhí)行狀態(tài)事務(wù)操作
DeleteStateAsync:基于storeName和key刪除狀態(tài)值
FromStateAttribute可以在Controller的Action中直接獲取StateEntry,如:
[HttpGet("{account}")] public ActionResult<Account> Get([FromState(StoreName)] StateEntry<Account> account) {if (account.Value is null){return this.NotFound();}return account.Value; }用法與例子
其實通過上面對規(guī)范的講解,對狀態(tài)管理的基本用法應(yīng)該有一定的理解了。官方文檔給出了如下文章來分別講述了狀態(tài)管理構(gòu)件塊的3種使用場景:
如何保存和獲取狀態(tài)[2]:基本的HTTP使用方式,如果希望看到DOTNET SDK的使用方式,需要參考(https://github.com/dapr/dotnet-sdk)中的例子,其中包含了Client的使用和ASP.NET Core中的使用
如何構(gòu)建有狀態(tài)服務(wù)[3],其依賴了狀態(tài)管理構(gòu)件塊提供的并發(fā)和一致性特性
如何在服務(wù)之間共享狀態(tài)[4],通過給狀態(tài)存儲源設(shè)置不同的keyPrefix策略讓不同的服務(wù)之間可以以特定的鍵組成格式來讀取同一個存儲源
另外,我的dapr-dotnet-quickstarts開源項目(https://github.com/heavenwing/dapr-dotnet-quickstarts)也包含了狀態(tài)管理構(gòu)件塊的基本用法的例子:
StateManagement:使用原生的HTTP請求來保存、獲取和刪除狀態(tài)
StateManagementWithSdk:使用SDK的DaprClient來保存、獲取和刪除狀態(tài)
參考資料
[1]
列表: https://docs.dapr.io/operations/components/setup-state-store/supported-state-stores/
[2]如何保存和獲取狀態(tài): https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-get-save-state/
[3]如何構(gòu)建有狀態(tài)服務(wù): https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-stateful-service/
[4]如何在服務(wù)之間共享狀態(tài): https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-share-state/
總結(jié)
以上是生活随笔為你收集整理的Dapr微服务应用开发系列4:状态管理构件块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 70%以上程序员,不懂数据结构和算法!
- 下一篇: .NET 云原生架构师训练营(模块二 基