• Dapr + .NET Core实战(三)状态管理


    4小时Dapr+.NET 5+K8S 的实战  https://ke.qq.com/course/4000292?tuin=1271860f

    Dapr进阶虚拟机集群实战(非K8S) https://ke.qq.com/course/4002149?tuin=1271860f

                       

    状态管理解决了什么

    分布式应用程序中的状态可能很有挑战性。 例如:

    • 应用程序可能需要不同类型的数据存储
    • 访问和更新数据可能需要不同的一致性级别。
    • 多个用户可以同时更新数据,这需要解决冲突
    • 服务必须重试 与数据存储交互 时发生的任何短期暂时性错误。

    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>.daprcomponents。 记下默认名称 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  .FrontEndinDebug
    et5.0FrontEnd.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

  • 相关阅读:
    SharePoint远程发布Solution
    SharePoint Web Part Error – The Specified Solution Was Not Found
    Visual Studio 2013启用AnkSVN
    Using LINQ to SharePoint
    SharePoint配置搜索服务和指定搜索范围
    SharePoint 站点集和子站点数据互相读取
    SharePoint Backup
    Bootstrap滚动监听
    探索 SharePoint 2013 Search功能
    C#利用iComparable接口实现List排序
  • 原文地址:https://www.cnblogs.com/chenyishi/p/15325337.html
Copyright © 2020-2023  润新知