手把手教你学Dapr - 5. 状态管理
介紹
使用狀態(tài)管理,您的應(yīng)用程序可以將數(shù)據(jù)作為鍵/值對(duì)存儲(chǔ)在支持的狀態(tài)存儲(chǔ)中。
您的應(yīng)用程序可以使用 Dapr 的狀態(tài)管理 API 使用狀態(tài)存儲(chǔ)組件來(lái)保存和讀取鍵/值對(duì),如下圖所示。例如,通過(guò)使用 HTTP POST,您可以保存鍵/值對(duì),通過(guò)使用 HTTP GET,您可以讀取鍵并返回其值。
特性
可插拔狀態(tài)存儲(chǔ)
Dapr 數(shù)據(jù)存儲(chǔ)被建模為組件,可以在不更改代碼的情況下更換它。例如:MySQL、Redis、Azure CosmosDB等。
可配置的狀態(tài)存儲(chǔ)行為
Dapr 允許開發(fā)人員將額外的元數(shù)據(jù)附加到狀態(tài)操作請(qǐng)求中,用以描述請(qǐng)求的處理方式。如:
并發(fā)要求
一致性要求
默認(rèn)情況下,您的應(yīng)用程序應(yīng)假定數(shù)據(jù)存儲(chǔ)最終一致并使用最后寫入獲勝的并發(fā)模式
并發(fā)
Dapr 支持使用 ETags 的樂(lè)觀并發(fā)控制 (OCC)。當(dāng)請(qǐng)求狀態(tài)時(shí),Dapr 總是將 ETag 屬性附加到返回的狀態(tài)。當(dāng)用戶代碼嘗試更新或刪除狀態(tài)時(shí),應(yīng)該通過(guò)請(qǐng)求正文附加 ETag 以進(jìn)行更新或通過(guò) If-Match 標(biāo)頭進(jìn)行刪除。只有當(dāng)提供的 ETag 與狀態(tài)存儲(chǔ)中的 ETag 匹配時(shí),寫操作才能成功。建議您在使用 ETag 時(shí)使用重試策略來(lái)補(bǔ)償此類沖突。
如果您的應(yīng)用程序在寫入請(qǐng)求時(shí)省略 ETag,則 Dapr 在處理請(qǐng)求時(shí)會(huì)跳過(guò) ETag 檢查。與使用 ETag 的先寫贏模式相比,這實(shí)質(zhì)上啟用了最后寫贏模式。
自動(dòng)加密
Dapr 支持應(yīng)用程序狀態(tài)的自動(dòng)客戶端加密,并支持密鑰輪換。這是一項(xiàng)預(yù)覽功能,所有 Dapr 狀態(tài)存儲(chǔ)都支持。
一致性
Dapr 支持強(qiáng)一致性和最終一致性,最終一致性作為默認(rèn)行為。
當(dāng)使用強(qiáng)一致性時(shí),Dapr 在確認(rèn)寫入請(qǐng)求之前等待所有副本(或指定的仲裁)確認(rèn)。
當(dāng)使用最終一致性時(shí),一旦底層數(shù)據(jù)存儲(chǔ)接受寫入請(qǐng)求,Dapr 就會(huì)立即返回,即使這是單個(gè)副本。
批量操作
Dapr 支持兩種類型的批量操作 - 批量(bulk)或多(multi)。
注:bulk與multi的區(qū)別在于bulk不是事務(wù)性的,multi是事務(wù)處理。
Actor狀態(tài)
事務(wù)狀態(tài)存儲(chǔ)可用于存儲(chǔ)Actor狀態(tài)。要指定用于Actor的狀態(tài)存儲(chǔ),請(qǐng)?jiān)跔顟B(tài)存儲(chǔ)組件的元數(shù)據(jù)部分中將屬性 actorStateStore的值指定為 true。
注:Actors 狀態(tài)以特定方案存儲(chǔ)在事務(wù)狀態(tài)存儲(chǔ)中允許一致的查詢。所以只能有一個(gè)狀態(tài)存儲(chǔ)組件被用于所有的Actor。
直接查詢狀態(tài)存儲(chǔ)
Dapr 無(wú)需任何轉(zhuǎn)換即可保存和檢索狀態(tài)值。您可以直接從底層狀態(tài)存儲(chǔ)查詢和聚合狀態(tài)。
例如,要在 Redis 中獲取與應(yīng)用程序 ID “myApp” 關(guān)聯(lián)的所有狀態(tài)鍵,請(qǐng)使用:
KEYS "myApp*"查詢Actor狀態(tài)
如果數(shù)據(jù)存儲(chǔ)支持 SQL 查詢,您可以使用 SQL 查詢查詢參與者的狀態(tài)。例如使用:
SELECT * FROM StateTable WHERE Id='<app-id>||<actor-type>||<actor-id>||<key>'您還可以跨Actor實(shí)例執(zhí)行聚合查詢,避免Actor 框架常見的基于回合的并發(fā)限制。例如,要計(jì)算所有溫度計(jì)Actor的平均溫度,請(qǐng)使用:
保存并獲取狀態(tài)
狀態(tài)管理是任何應(yīng)用程序最常見的需求之一:新的或遺留的、單體或微服務(wù)。處理不同的數(shù)據(jù)庫(kù)、測(cè)試、處理重試和故障可能既費(fèi)時(shí)又費(fèi)力。
先決條件
準(zhǔn)備好Dapr運(yùn)行環(huán)境可以看之前的文章
手把手教你學(xué)Dapr - 3. 使用Dapr運(yùn)行第一個(gè).Net程序
設(shè)置狀態(tài)存儲(chǔ)
Windows打開目錄%USERPROFILE%\.dapr\components
創(chuàng)建文件statestore.yaml
使用redis作為狀態(tài)存儲(chǔ)的數(shù)據(jù)庫(kù)
apiVersion: dapr.io/v1alpha1 kind: Component metadata:name: statestore spec:type: state.redisversion: v1metadata:- name: redisHostvalue: localhost:6379- name: redisPasswordvalue: ""- name: actorStateStorevalue: "true"注:這個(gè)yaml已經(jīng)通過(guò)actorStateStore開啟了Actor狀態(tài)
保存和檢索單個(gè)狀態(tài)
注:設(shè)置 app-id 很重要,因?yàn)闋顟B(tài)鍵以該值作為前綴。如果您不設(shè)置它,則在運(yùn)行時(shí)為您生成一個(gè),下次運(yùn)行該命令時(shí)將生成一個(gè)新的,您將無(wú)法再訪問(wèn)以前保存的狀態(tài)。換句話說(shuō),如果你要共享狀態(tài)可以自定義一個(gè)保留app-id作為共享狀態(tài)而不是留空。
運(yùn)行Dapr Sidecar
運(yùn)行一個(gè)空的Sidecar,因?yàn)槲覀冎挥盟鼇?lái)幫助訪問(wèn)狀態(tài)存儲(chǔ),所以與之前不同的是,dapr run后面沒(méi)有接dotnet run去作為某一個(gè)程序的Sidecar
dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001創(chuàng)建客戶端
創(chuàng)建控制臺(tái)程序,添加Dapr.Client NuGet包引用。
修改Program.cs
using Dapr.Client;var storeName = "statestore"; var key = "myFirstKey"; var value = "myFirstValue";var client = new DaprClientBuilder().Build(); await client.SaveStateAsync(storeName, key, value); Console.WriteLine("State has been stored");var data = await client.GetStateAsync<string>(storeName, key); Console.WriteLine($"Got value: {data}");Console.ReadKey();刪除單個(gè)狀態(tài)
await client.DeleteStateAsync(storeName, key);通過(guò)事務(wù)保存和檢索多個(gè)狀態(tài)
Dapr 還允許您在同一個(gè)調(diào)用中保存和檢索多個(gè)狀態(tài)。
var lst = new List<StateTransactionRequest>() {new StateTransactionRequest("test1", System.Text.Encoding.UTF8.GetBytes("value1"), StateOperationType.Upsert),new StateTransactionRequest("test2", System.Text.Encoding.UTF8.GetBytes("value2"), StateOperationType.Upsert), }; await client.ExecuteStateTransactionAsync(storeName, lst);var datas = await client.GetBulkStateAsync(storeName, lst.Select(r => r.Key).ToList(), 0); Console.WriteLine($"Got items: {string.Join(",", datas.Select(d => $"{d.Key}={d.Value}"))}");強(qiáng)一致性
使用強(qiáng)一致性時(shí),Dapr將確保底層狀態(tài)存儲(chǔ)在寫入或刪除狀態(tài)之前,一旦數(shù)據(jù)被寫入到所有副本或收到來(lái)自quorum的ack,就會(huì)返回響應(yīng)。
對(duì)于GET請(qǐng)求,Dapr 將確保存儲(chǔ)在副本之間一致地返回最新數(shù)據(jù)。默認(rèn)為最終一致性,除非在對(duì)狀態(tài) API 的請(qǐng)求中另有說(shuō)明。
await client.SaveStateAsync(storeName, key, value, new StateOptions() { Consistency = ConsistencyMode.Strong });var etagData = await client.GetStateAndETagAsync<string>(storeName, key, ConsistencyMode.Strong); Console.WriteLine($"ETag:{etagData.etag}");await client.DeleteStateAsync(storeName, key, new StateOptions() { Consistency = ConsistencyMode.Strong });先寫贏和最后寫贏
Dapr 允許開發(fā)人員在使用數(shù)據(jù)存儲(chǔ)時(shí)選擇兩種常見的并發(fā)模式:首先寫入獲勝和`最后寫入獲勝。First-Write-Wins 在您有多個(gè)應(yīng)用程序?qū)嵗那闆r下很有用,所有實(shí)例都同時(shí)寫入同一個(gè)鍵。
Dapr 的默認(rèn)模式是最后寫入獲勝。
下面的例子展示了如何獲取一個(gè) ETag,然后使用它來(lái)保存狀態(tài),然后刪除狀態(tài):
await client.SaveStateAsync(storeName, key, value, new StateOptions() { Concurrency = ConcurrencyMode.FirstWrite }); var firstWriteWinData = await client.GetStateAndETagAsync<string>(storeName, key); var etag = firstWriteWinData.etag;await client.TrySaveStateAsync(storeName, key, DateTime.Now.Ticks.ToString(), etag, new StateOptions() { Concurrency = ConcurrencyMode.FirstWrite }); var firstWriteWinDeleteSucceeded = await client.TryDeleteStateAsync(storeName, key, etag); Console.WriteLine($"First write wins delete:{firstWriteWinDeleteSucceeded}");firstWriteWinData = await client.GetStateAndETagAsync<string>(storeName, key); firstWriteWinDeleteSucceeded = await client.TryDeleteStateAsync(storeName, key, firstWriteWinData.etag); Console.WriteLine($"First write wins delete:{firstWriteWinDeleteSucceeded}");注:這里演示了ETag在更新后嘗試刪除失敗的例子,最后再重新獲取新的狀態(tài)以修正ETag再刪除
在不同的應(yīng)用程序之間共享狀態(tài)
為了實(shí)現(xiàn)狀態(tài)共享,Dapr 支持以下鍵前綴策略
appid - 這是默認(rèn)策略。appid 前綴允許狀態(tài)只能由具有指定 appid 的應(yīng)用程序管理。所有狀態(tài)鍵都將以 appid 為前綴,并以應(yīng)用程序?yàn)榉秶?/p>
name - 此設(shè)置使用狀態(tài)存儲(chǔ)組件的名稱作為前綴。對(duì)于給定的狀態(tài)存儲(chǔ),多個(gè)應(yīng)用程序可以共享相同的狀態(tài)。
none - 此設(shè)置不使用前綴。多個(gè)應(yīng)用程序在不同的狀態(tài)存儲(chǔ)之間共享狀態(tài)
舉個(gè)例子:要指定前綴策略,請(qǐng)?jiān)跔顟B(tài)組件上添加名為 keyPrefix 的元數(shù)據(jù)鍵
apiVersion: dapr.io/v1alpha1 kind: Component metadata:name: statestorenamespace: production spec:type: state.redisversion: v1metadata:- name: keyPrefixvalue: <key-prefix-strategy>注:此示例演示相對(duì)較復(fù)雜,思路大概是使用多個(gè)statestore.yaml,然后根據(jù)不同的storename切換不同策略即可。感興趣的小伙伴可以自行嘗試。
自動(dòng)加密狀態(tài)并管理密鑰輪換
注:截止目前,這個(gè)功能是個(gè)預(yù)覽版,感興趣的小伙伴可以自行嘗試
應(yīng)用程序狀態(tài)通常需要靜態(tài)加密,以在企業(yè)工作負(fù)載或受監(jiān)管環(huán)境中提供更強(qiáng)的安全性。Dapr 提供基于 AES256 的自動(dòng)客戶端加密。
狀態(tài)的生存時(shí)間(TTL)
Dapr 為每個(gè)狀態(tài)在請(qǐng)求時(shí)設(shè)置生存時(shí)間 (TTL)。這意味著應(yīng)用程序可以為每個(gè)存儲(chǔ)的狀態(tài)設(shè)置生存時(shí)間,并且這些狀態(tài)在到期后無(wú)法檢索。
注:只有一部分 Dapr 狀態(tài)存儲(chǔ)組件與狀態(tài) TTL 兼容。對(duì)于支持的狀態(tài)存儲(chǔ),只需在發(fā)布消息時(shí)設(shè)置 ttlInSeconds 元數(shù)據(jù)。其他狀態(tài)存儲(chǔ)將忽略此值。
await client.SaveStateAsync(storeName, key, value, metadata: new Dictionary<string, string>() { { "ttlInSeconds", "3" } }); var ttlData = await client.GetStateAsync<string>(storeName, key); Console.WriteLine($"TTL Data:{ttlData}");Thread.Sleep(5000); ttlData = await client.GetStateAsync<string>(storeName, key); Console.WriteLine($"TTL Data:{ttlData}");持久化狀態(tài)
要顯式設(shè)置持久化狀態(tài)(忽略為鍵設(shè)置的任何 TTL),請(qǐng)將 ttlInSeconds 值指定為 -1。
本章源碼
Assignment05
https://github.com/doddgu/dapr-study-room
我們正在行動(dòng),新的框架、新的生態(tài)
我們的目標(biāo)是自由的、易用的、可塑性強(qiáng)的、功能豐富的、健壯的。
所以我們借鑒Building blocks的設(shè)計(jì)理念,正在做一個(gè)新的框架MASA Framework,它有哪些特點(diǎn)呢?
原生支持Dapr,且允許將Dapr替換成傳統(tǒng)通信方式
架構(gòu)不限,單體應(yīng)用、SOA、微服務(wù)都支持
支持.Net原生框架,降低學(xué)習(xí)負(fù)擔(dān),除特定領(lǐng)域必須引入的概念,堅(jiān)持不造新輪子
豐富的生態(tài)支持,除了框架以外還有組件庫(kù)、權(quán)限中心、配置中心、故障排查中心、報(bào)警中心等一系列產(chǎn)品
核心代碼庫(kù)的單元測(cè)試覆蓋率90%+
開源、免費(fèi)、社區(qū)驅(qū)動(dòng)
還有什么?我們?cè)诘饶?#xff0c;一起來(lái)討論
經(jīng)過(guò)幾個(gè)月的生產(chǎn)項(xiàng)目實(shí)踐,已完成POC,目前正在把之前的積累重構(gòu)到新的開源項(xiàng)目中
目前源碼已開始同步到Github(文檔站點(diǎn)在規(guī)劃中,會(huì)慢慢完善起來(lái)):
MASA.BuildingBlocks
MASA.Contrib
MASA.Utils
MASA.EShop
BlazorComponent
MASA.Blazor
QQ群:7424099
微信群:加技術(shù)運(yùn)營(yíng)微信(MasaStackTechOps),備注來(lái)意,邀請(qǐng)進(jìn)群
總結(jié)
以上是生活随笔為你收集整理的手把手教你学Dapr - 5. 状态管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何阻止给 一个程序 开启多个实例 ?
- 下一篇: 第六届中国开源年会(COSCon'21)