Dapr + .NET Core實戰(三)狀態管理

狀態管理解決了什麼

分佈式應用程序中的狀態可能很有挑戰性。 例如:

  • 應用程序可能需要不同類型的數據存儲
  • 訪問和更新數據可能需要不同的一致性級別。
  • 多個用戶可以同時更新數據,這需要解決衝突
  • 服務必須重試 與數據存儲交互 時發生的任何短期暫時性錯誤。

Dapr 狀態管理解決了這些難題。 它簡化了跟蹤狀態,而無需依賴關係或第三方存儲 SDK 上的學習曲線。

 

工作原理

 

 

 

應用程序與 Dapr sidecar 交互,以存儲和檢索鍵/值數據。 在底層,sidecar API 使用可配置的狀態存儲組件來保存數據。 開發人員可以從不斷增長的受支持狀態存儲集合中選擇,其中包括 Azure Cosmos DB、SQL Server 和 Cassandra。

可以使用 HTTP 或 gRPC 調用 API。 使用以下 URL 調用 HTTP API:

http://localhost:<dapr-port>/v1.0/state/<store-name>/
  • <dapr-port>:Dapr 偵聽的 HTTP 端口。
  • <store-name>:使用的狀態存儲組件的名稱。

 

狀態組件

Dapr支持的組件

爲本地自承載開發初始化時,Dapr 將 Redis 註冊爲默認狀態存儲。 下面是默認狀態存儲配置的示例,配置文件位置爲C:\Users\<username>\.dapr\components。 記下默認名稱 statestore :

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"

 

項目演示

仍然使用 上一篇服務調用 的FrontEnd項目,新建StateController

using Dapr;
using Dapr.Client;

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace FrontEnd.Controllers
{
    [Route("[controller]")]
    [ApiController]
    public class StateController : ControllerBase
    {
        private readonly ILogger<StateController> _logger;
        private readonly DaprClient _daprClient;
        public StateController(ILogger<StateController> logger, DaprClient daprClient)
        {
            _logger = logger;
            _daprClient = daprClient;
        }

        // 獲取一個值
        [HttpGet]
        public async Task<ActionResult> GetAsync()
        {
            var result = await _daprClient.GetStateAsync<string>("statestore", "guid");
            return Ok(result);
        }

        //保存一個值
        [HttpPost]
        public async Task<ActionResult> PostAsync()
        {
            await _daprClient.SaveStateAsync<string>("statestore", "guid", Guid.NewGuid().ToString(), new StateOptions() { Consistency = ConsistencyMode.Strong });
            return Ok("done");
        }

        //刪除一個值
        [HttpDelete]
        public async Task<ActionResult> DeleteAsync()
        {
            await _daprClient.DeleteStateAsync("statestore", "guid");
            return Ok("done");
        }

        //通過tag防止併發衝突,保存一個值
        [HttpPost("withtag")]
        public async Task<ActionResult> PostWithTagAsync()
        {
            var (value, etag) = await _daprClient.GetStateAndETagAsync<string>("statestore", "guid");
            await _daprClient.TrySaveStateAsync<string>("statestore", "guid", Guid.NewGuid().ToString(), etag);
            return Ok("done");
        }

        //通過tag防止併發衝突,刪除一個值
        [HttpDelete("withtag")]
        public async Task<ActionResult> DeleteWithTagAsync()
        {
            var (value, etag) = await _daprClient.GetStateAndETagAsync<string>("statestore", "guid");
            return Ok(await _daprClient.TryDeleteStateAsync("statestore", "guid", etag));
        }


        // 從綁定獲取一個值,健值name從路由模板獲取
        [HttpGet("frombinding/{name}")]
        public async Task<ActionResult> GetFromBindingAsync([FromState("statestore", "name")] StateEntry<string> state)
        {
            return Ok(state.Value);
        }


        // 根據綁定獲取並修改值,健值name從路由模板獲取
        [HttpPost("withbinding/{name}")]
        public async Task<ActionResult> PostWithBindingAsync([FromState("statestore", "name")] StateEntry<string> state)
        {
            state.Value = Guid.NewGuid().ToString();
            return Ok(await state.TrySaveAsync());
        }


        // 獲取多個個值
        [HttpGet("list")]
        public async Task<ActionResult> GetListAsync()
        {
            var result = await _daprClient.GetBulkStateAsync("statestore", new List<string> { "guid" }, 10);
            return Ok(result);
        }

        // 刪除多個個值
        [HttpDelete("list")]
        public async Task<ActionResult> DeleteListAsync()
        {
            var data = await _daprClient.GetBulkStateAsync("statestore", new List<string> { "guid" }, 10);
            var removeList = new List<BulkDeleteStateItem>();
            foreach (var item in data)
            {
                removeList.Add(new BulkDeleteStateItem(item.Key, item.ETag));
            }
            await _daprClient.DeleteBulkStateAsync("statestore", removeList);
            return Ok("done");
        }
    }
}

cmd運行

dapr run --dapr-http-port 3501 --app-port 5001  --app-id frontend dotnet  .\FrontEnd\bin\Debug\net5.0\FrontEnd.dll

可通過postman調用sidecar的endpoint

 

 

 查看store存儲中的內容

進入容器內部

docker exec -it dapr_redis /bin/sh

調用redis-cli

redis-cli

查看所有key

keys *

可以看到有"frontend||guid"這個key,所以狀態在redis中存儲中Name的規則是appName||keyName,這樣可以防止不同app的鍵衝突

我們通過type key查看下這個鍵的類型,可以發現他是一個hash

127.0.0.1:6379> type frontend||guid
hash

再通過hgetall key查看他的數據,發現有兩個鍵,一個data,一個version

127.0.0.1:6379> hgetall  frontend||guid
1) "data"
2) "\"e17b3e06-ba30-42c5-8960-48511c70b496\""
3) "version"
4) "1"

data很明顯是存入的數據,version呢?現在猜測是防止併發衝突的etag,我們下面來驗證一下

在StateController中新增接口

        // 獲取一個值和etag
        [HttpGet("withetag")]
        public async Task<ActionResult> GetWithEtagAsync()
        {
            var (value,etag) = await _daprClient.GetStateAndETagAsync<string>("statestore", "guid");
            return Ok($"value is {value}, etag is {etag}");
        }

通過dapr重啓這個app,並調用withetag api,很明顯redis中version與etag相等,初步印證我們的猜測

 

 我們可以通過post方法修改一下guid這個key,修改後etag會變更,再來看一下redis中version和etag是不是一個東西

首先調用POST方法修改值

 

 再調用withetag方法,看下etag,發現etag變成了2

 

 在比較一下redis中的version

127.0.0.1:6379> hgetall  frontend||guid
1) "data"
2) "\"36a55558-35c3-402c-ac9e-615014eb6904\""
3) "version"
4) "2"

現在可以確定etag就是redis中的version

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章