手把手教你學Dapr - 5. 狀態管理


目錄

手把手教你學Dapr - 1. .Net開發者的大時代

手把手教你學Dapr - 2. 必須知道的概念

手把手教你學Dapr - 3. 使用Dapr運行第一個.Net程序

手把手教你學Dapr - 4. 服務調用

手把手教你學Dapr - 5. 狀態管理

手把手教你學Dapr - 6. 發布訂閱

手把手教你學Dapr - 7. Actors

手把手教你學Dapr - 8. 綁定

手把手教你學Dapr - 9. 可觀測性

介紹

使用狀態管理,您的應用程序可以將數據作為鍵/值對存儲在支持的狀態存儲中。

您的應用程序可以使用 Dapr 的狀態管理 API 使用狀態存儲組件來保存和讀取鍵/值對,如下圖所示。例如,通過使用 HTTP POST,您可以保存鍵/值對,通過使用 HTTP GET,您可以讀取鍵並返回其值。

state-management-overview.png

特性

可插拔狀態存儲

Dapr 數據存儲被建模為組件,可以在不更改代碼的情況下更換它。例如:MySQL、Redis、Azure CosmosDB等。

可配置的狀態存儲行為

Dapr 允許開發人員將額外的元數據附加到狀態操作請求中,用以描述請求的處理方式。如:

  • 並發要求
  • 一致性要求

默認情況下,您的應用程序應假定數據存儲最終一致並使用最后寫入獲勝的並發模式

並發

Dapr 支持使用 ETags 的樂觀並發控制 (OCC)。當請求狀態時,Dapr 總是將 ETag 屬性附加到返回的狀態。當用戶代碼嘗試更新或刪除狀態時,應該通過請求正文附加 ETag 以進行更新或通過 If-Match 標頭進行刪除。只有當提供的 ETag 與狀態存儲中的 ETag 匹配時,寫操作才能成功。建議您在使用 ETag 時使用重試策略來補償此類沖突。

如果您的應用程序在寫入請求時省略 ETag,則 Dapr 在處理請求時會跳過 ETag 檢查。與使用 ETag 的先寫贏模式相比,這實質上啟用了最后寫贏模式。

自動加密

Dapr 支持應用程序狀態的自動客戶端加密,並支持密鑰輪換。這是一項預覽功能,所有 Dapr 狀態存儲都支持。

一致性

Dapr 支持強一致性和最終一致性,最終一致性作為默認行為。

  • 當使用強一致性時,Dapr 在確認寫入請求之前等待所有副本(或指定的仲裁)確認。

  • 當使用最終一致性時,一旦底層數據存儲接受寫入請求,Dapr 就會立即返回,即使這是單個副本。

批量操作

Dapr 支持兩種類型的批量操作 - 批量(bulk)或多(multi)。

:bulk與multi的區別在於bulk不是事務性的,multi是事務處理。

Actor狀態

事務狀態存儲可用於存儲Actor狀態。要指定用於Actor的狀態存儲,請在狀態存儲組件的元數據部分中將屬性 actorStateStore 的值指定為 true

:Actors 狀態以特定方案存儲在事務狀態存儲中允許一致的查詢。所以只能有一個狀態存儲組件被用於所有的Actor。

直接查詢狀態存儲

Dapr 無需任何轉換即可保存和檢索狀態值。您可以直接從底層狀態存儲查詢和聚合狀態。

例如,要在 Redis 中獲取與應用程序 ID “myApp” 關聯的所有狀態鍵,請使用:

KEYS "myApp*"

查詢Actor狀態

如果數據存儲支持 SQL 查詢,您可以使用 SQL 查詢查詢參與者的狀態。例如使用:

SELECT * FROM StateTable WHERE Id='<app-id>||<actor-type>||<actor-id>||<key>'

您還可以跨Actor實例執行聚合查詢,避免Actor 框架常見的基於回合的並發限制。例如,要計算所有溫度計Actor的平均溫度,請使用:

SELECT AVG(value) FROM StateTable WHERE Id LIKE '<app-id>||<thermometer>||*||temperature'

保存並獲取狀態

狀態管理是任何應用程序最常見的需求之一:新的或遺留的、單體或微服務。處理不同的數據庫、測試、處理重試和故障可能既費時又費力。

先決條件

准備好Dapr運行環境可以看之前的文章

手把手教你學Dapr - 3. 使用Dapr運行第一個.Net程序

設置狀態存儲

Windows打開目錄%USERPROFILE%\.dapr\components

  1. 創建文件statestore.yaml

  2. 使用redis作為狀態存儲的數據庫

    apiVersion: dapr.io/v1alpha1
    kind: Component
    metadata:
      name: statestore
    spec:
      type: state.redis
      version: v1
      metadata:
      - name: redisHost
        value: localhost:6379
      - name: redisPassword
        value: ""
      - name: actorStateStore
        value: "true"
    
    

    :這個yaml已經通過actorStateStore開啟了Actor狀態

保存和檢索單個狀態

:設置 app-id 很重要,因為狀態鍵以該值作為前綴。如果您不設置它,則在運行時為您生成一個,下次運行該命令時將生成一個新的,您將無法再訪問以前保存的狀態。換句話說,如果你要共享狀態可以自定義一個保留app-id作為共享狀態而不是留空。

運行Dapr Sidecar

運行一個空的Sidecar,因為我們只用它來幫助訪問狀態存儲,所以與之前不同的是,dapr run后面沒有接dotnet run去作為某一個程序的Sidecar

dapr run --app-id myapp --dapr-http-port 3500 --dapr-grpc-port 50001

創建客戶端

創建控制台程序,添加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();

刪除單個狀態

await client.DeleteStateAsync(storeName, key);

通過事務保存和檢索多個狀態

Dapr 還允許您在同一個調用中保存和檢索多個狀態。

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}"))}");

強一致性

使用強一致性時,Dapr將確保底層狀態存儲在寫入或刪除狀態之前,一旦數據被寫入到所有副本或收到來自quorum的ack,就會返回響應。

對於GET請求,Dapr 將確保存儲在副本之間一致地返回最新數據。默認為最終一致性,除非在對狀態 API 的請求中另有說明。

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 允許開發人員在使用數據存儲時選擇兩種常見的並發模式:首先寫入獲勝和``最后寫入獲勝`。 First-Write-Wins 在您有多個應用程序實例的情況下很有用,所有實例都同時寫入同一個鍵。

Dapr 的默認模式是最后寫入獲勝

下面的例子展示了如何獲取一個 ETag,然后使用它來保存狀態,然后刪除狀態:

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在更新后嘗試刪除失敗的例子,最后再重新獲取新的狀態以修正ETag再刪除

在不同的應用程序之間共享狀態

為了實現狀態共享,Dapr 支持以下鍵前綴策略

  • appid - 這是默認策略。 appid 前綴允許狀態只能由具有指定 appid 的應用程序管理。所有狀態鍵都將以 appid 為前綴,並以應用程序為范圍。
  • name - 此設置使用狀態存儲組件的名稱作為前綴。對於給定的狀態存儲,多個應用程序可以共享相同的狀態。
  • none - 此設置不使用前綴。多個應用程序在不同的狀態存儲之間共享狀態

舉個例子:要指定前綴策略,請在狀態組件上添加名為 keyPrefix 的元數據鍵

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: statestore
  namespace: production
spec:
  type: state.redis
  version: v1
  metadata:
  - name: keyPrefix
    value: <key-prefix-strategy>

:此示例演示相對較復雜,思路大概是使用多個statestore.yaml,然后根據不同的storename切換不同策略即可。感興趣的小伙伴可以自行嘗試。

自動加密狀態並管理密鑰輪換

:截止目前,這個功能是個預覽版,感興趣的小伙伴可以自行嘗試

應用程序狀態通常需要靜態加密,以在企業工作負載或受監管環境中提供更強的安全性。 Dapr 提供基於 AES256 的自動客戶端加密。

狀態的生存時間(TTL)

Dapr 為每個狀態在請求時設置生存時間 (TTL)。這意味着應用程序可以為每個存儲的狀態設置生存時間,並且這些狀態在到期后無法檢索。

:只有一部分 Dapr 狀態存儲組件與狀態 TTL 兼容。對於支持的狀態存儲,只需在發布消息時設置 ttlInSeconds 元數據。其他狀態存儲將忽略此值。

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}");

持久化狀態

要顯式設置持久化狀態(忽略為鍵設置的任何 TTL),請將 ttlInSeconds 值指定為 -1

本章源碼

Assignment05

https://github.com/doddgu/dapr-study-room

我們正在行動,新的框架、新的生態

我們的目標是自由的易用的可塑性強的功能豐富的健壯的

所以我們借鑒Building blocks的設計理念,正在做一個新的框架MASA Framework,它有哪些特點呢?

  • 原生支持Dapr,且允許將Dapr替換成傳統通信方式
  • 架構不限,單體應用、SOA、微服務都支持
  • 支持.Net原生框架,降低學習負擔,除特定領域必須引入的概念,堅持不造新輪子
  • 豐富的生態支持,除了框架以外還有組件庫、權限中心、配置中心、故障排查中心、報警中心等一系列產品
  • 核心代碼庫的單元測試覆蓋率90%+
  • 開源、免費、社區驅動
  • 還有什么?我們在等你,一起來討論

經過幾個月的生產項目實踐,已完成POC,目前正在把之前的積累重構到新的開源項目中

目前源碼已開始同步到Github(文檔站點在規划中,會慢慢完善起來):

MASA.BuildingBlocks

MASA.Contrib

MASA.Utils

MASA.EShop

BlazorComponent

MASA.Blazor

QQ群:7424099

微信群:加技術運營微信(MasaStackTechOps),備注來意,邀請進群

masa_stack_tech_ops.png


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM