狀态管了解決了什麼
分布式應用程式中的狀态可能很有挑戰性。 例如:
- 應用程式可能需要不同類型的資料存儲。
- 通路和更新資料可能需要不同的一緻性級别。
- 多個使用者可以同時更新資料,這需要解決沖突。
- 服務必須重試 與資料存儲互動 時發生的任何短期暫時性錯誤。
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 偵聽的 HTTP 端口。<dapr-port>
-
:使用的狀态存儲元件的名稱。<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了