参考:https://docs.dapr.io/zh-hans/concepts
安装脚手架
# Set-ExecutionPolicy
Set-ExecutionPolicy RemoteSigned
# 安装choco包管理工具
iwr https://chocolatey.org/install.ps1 -UseBasicParsing | iex
# 查看choco版本
choco -v
# 安装helm命令
choco install kubernetes-helm
# 查看helm帮助
helm -h
# 查看helm版本
helm version
# windows
powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
# linux
wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash
# 混合集群参考:https://docs.dapr.io/zh-hans/operations/hosting/kubernetes/kubernetes-hybrid-clusters/
# 验证
dapr --version
# 初始化
dapr init
# 初始化(k8s环境)
dapr init -k
# 查看dapr状态
dapr status -k
# 查看已注册的应用
dapr list
dapr list -k
# 启动仪表盘
dapr dashboard
dapr dashboard -k
# 运行空 sidecar
dapr run --app-id myapp
# 运行空 dotnet程序
dapr run --app-id myapp --app-port 5000 -- dotnet run
# 卸载
dapr uninstall
dapr uninstall --all #卸载并删除 .dapr 目录、Redis、Placement 和 Zipkin 容器
dapr uninstall -k #卸载k8s集群dapr
# 帮助手册
dapr help {command}
# 更多命令:https://docs.dapr.io/zh-hans/reference/cli/
组件
组件schema
格式
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: [COMPONENT-NAME]
namespace: [COMPONENT-NAMESPACE]
spec:
type: [COMPONENT-TYPE]
version: v1
initTimeout: [TIMEOUT-DURATION]
ignoreErrors: [BOOLEAN]
metadata:
- name: [METADATA-NAME]
value: [METADATA-VALUE]
字段说明
存储密钥(示例)
# 创建目录
mkdir my-components
# 创建json文件 mysecrets.json
{
"my-secret" : "I'm Batman"
}
# 在此目录内创建yml文件 localSecretStore.yaml
## type: secretstores.local.file 字段值,其告诉Dapr使用本地文件组件作为密钥存储
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: my-secret-store
namespace: default
spec:
type: secretstores.local.file
version: v1
metadata:
- name: secretsFile
value: <PATH TO SECRETS FILE>/mysecrets.json
- name: nestedSeparator
value: ":"
kubectl apply -f localSecretStore.yaml
# 运行Dapr sidecar
dapr run --app-id myapp --dapr-http-port 3500 --components-path ./my-components
# 获取密钥
curl http://localhost:3500/v1.0/secrets/my-secret-store/my-secret
Redis(示例)
# 参考:https://zhuanlan.zhihu.com/p/611915800
# 部署redis
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install redis bitnami/redis --namespace store --create-namespace --set replica.replicaCount=0 --set auth.password=******
# 创建yml文件 redis-state.yaml
# 纯文本密钥,不建议用于生产
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"
# 在密钥存储中创建密钥,并在组件定义中引用它
## 创建密钥
kubectl create secret generic redis-secret --from-literal=redis-password='******'
## 使用密钥
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: statestore
spec:
type: state.redis
version: v1
metadata:
# These settings will work out of the box if you use `helm install
# bitnami/redis`. If you have your own setup, replace
# `redis-master:6379` with your own Redis master address, and the
# Redis password with your own Secret's name. For more information,
# see https://docs.dapr.io/operations/components/component-secrets .
- name: redisHost
value: redis-master:6379
- name: redisPassword
secretKeyRef:
name: redis
key: redis-password
- name: actorStateStore
value: "true"
auth:
secretStore: kubernetes # 指定秘钥存储的类型是kubernetes
# 创建Component
kubectl apply -f redis-state.yaml
状态管理
# 监听空应用程序 myapp
dapr run --app-id myapp --dapr-http-port 3500
# 保存状态
curl -X POST -H "Content-Type: application/json" -d '[{ "key": "name", "value": "Bruce Wayne"}]' http://localhost:3500/v1.0/state/statestore
# 获取状态
curl http://localhost:3500/v1.0/state/statestore/name
"Bruce Wayne"
# 查看redis数据
docker exec -it dapr_redis redis-cli
hgetall "myapp||name"
# dapr-dotnet-sdk
## 保存状态
public abstract Task<bool> TrySaveStateAsync(string storeName, string key, TValue value, string etag, StateOptions stateOptions = null, IReadOnlyDictionary<string, string> metadata = null, CancellationToken cancellationToken = default(CancellationToken));
## 获取状态
public abstract Task<TValue> GetStateAsync<TValue>(string storeName, string key, ConsistencyMode? consistencyMode = default, IReadOnlyDictionary<string, string> metadata = default, CancellationToken cancellationToken = default)
发布订阅
参考:https://blog.csdn.net/ChaITSimpleLove/article/details/123791744
// 发布
[HttpPost("DemoDaprEventPub")]
public async Task DemoDaprEventPubAsync()
{
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken cancellationToken = source.Token;
using var client = new DaprClientBuilder().Build();
//Using Dapr SDK to publish a topic
var data = new
{
...
};
await client.PublishEventAsync("pubsub", "DemoDaprEvent", data, cancellationToken);
}
// 订阅
app.MapSubscribeHandler(); //Program.cs添加
// 订阅代码(编码式订阅)
[Topic("pubsub", "DemoDaprEvent")]
[HttpPost("DemoDaprEvent")]
public async Task<IActionResult> DemoDaprEventAsync([FromBody] JsonObject data)
{
...
}
//dapr-cli发布消息
dapr publish --publish-app-id b-api --topic DemoDaprEvent --pubsub pubsub --data '{"msg":"hello"}'
//消息格式如下(发布的消息在data字段)
{
"data": {
"msg": "hello"
},
"datacontenttype": "application/json",
"id": "982954ae-295b-4365-8b61-93eba80aed3b",
"pubsubname": "pubsub",
"source": "a-api",
"specversion": "1.0",
"time": "2023-08-02T10:28:44+08:00",
"topic": "DemoDaprEvent",
"traceid": "00-f8a9e5b07502462d63448e23d8309269-e14b2b2721ded571-01",
"traceparent": "00-f8a9e5b07502462d63448e23d8309269-e14b2b2721ded571-01",
"tracestate": "",
"type": "com.dapr.event.sent"
}
注意:多个实例订阅时,同一条消息只会发送某一个,而不是所有的实例(亲测), redis:publish之后所有客户端都会收到同一条消息
分布式锁
参考:http://img.tnblog.net/hb/article/details/7647
// components 目录下创建 lockstore.yaml
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: lockstore
spec:
type: lock.redis
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
// 创建锁
abstract Task<TryLockResponse> Lock(string storeName,string resourceId,string lockOwner,Int32 expiryInSeconds,CancellationToken cancellationToken = default);
// 释放锁
abstract Task<UnlockResponse> Unlock(string storeName,string resourceId,string lockOwner,CancellationToken cancellationToken = default);
// 启动项目(指定components目录)
dapr run --app-id locker --dapr-grpc-port 50001 --components-path ./components -- dotnet run
Actor
// 定义一个actor
public class UserActor : Dapr.Actors.Runtime.Actor, IDemoActor
{
public UserActor(ActorHost host) : base(host){}
...
}
// 使用actor
public class Demo
{
private readonly IActorProxyFactory _actorProxyFactory;
public Demo(IActorProxyFactory actorProxyFactory)
{
_actorProxyFactory = actorProxyFactory;
}
var actorId = new ActorId(userId);
var actor = _actorProxyFactory.CreateActorProxy<IUserActor>(actorId, UserActor);
actor.XXX();
}
// 注册Timer(用于当前actor的job任务)
async Task<ActorTimer> RegisterTimerAsync(string timerName,string callback,byte[] callbackParams,TimeSpan dueTime,TimeSpan period)
async Task<ActorTimer> RegisterTimerAsync(string timerName,string callback,byte[] callbackParams,TimeSpan dueTime,TimeSpan period,TimeSpan ttl)
// 取消Timer
async Task UnregisterTimerAsync(string timerName)
// Reminders 是一种在指定时间内触发 persistent 回调的机制。
// 它们的功能类似于 timer。
// 但与 timer 不同,在所有情况下 reminders 都会触发,
// 直到 actor 显式取消注册 reminders 或删除 actor 。
// 具体而言, reminders 会在所有 actor 失活和故障时也会触发触发,
// 因为Dapr Actors 运行时会将 reminders 信息持久化到 Dapr Actors 状态提供者中。
interface IRemindable
Task ReceiveReminderAsync(string reminderName, byte[] state, TimeSpan dueTime, TimeSpan period);
// 只有当actor被激活时,计时器(timer)才会保持活动状态。 计时器不会重置空闲计时器[2],因此它们不能让参与者自己处于活动状态。
// 提醒器比actor活得长[3]。 如果actor被禁用,一个提醒器可以重新激活该actor。 提醒器将重置空闲计时器。
服务间通信
# 启动服务A(调用方)
dapr run --app-id a-api --app-port 5084 --app-protocol http --log-level debug -- dotnet run --no-build
# 启动服务B(被调用方)
dapr run --app-id b-api --app-port 5264 --app-protocol http --log-level debug -- dotnet run --no-build
# 服务B暴露的方法
[HttpGet("/User/{userId}/balance")]
public async Task<IActionResult> Index(string userId)
{
var actorId = new ActorId(userId);
var actor = _actorProxyFactory.CreateActorProxy<IUserActor>(actorId, nameof(UserActor));//_actorProxyFactory为ActorProxyFactory注入的私有变量
var result = await actor.GetBalance();
return Ok(result);
}
# dapr-cli调用
dapr invoke --app-id b-api --method /User/1/balance --verb GET
# 服务A请求服务B的方法
[HttpGet("{userId}")]
public async Task<IActionResult> Index(string userId)
{
var result = await _daprClient.InvokeMethodAsync<decimal>(HttpMethod.Get, "b-api", $"/User/{userId}/balance");
return Ok(result);
}
# dapr-cli调用
dapr invoke --app-id a-api --method /User/1/balance --verb GET
调试
VS调试
参考:https://www.cnblogs.com/bhfdz/p/16649283.html
安装扩展
命令: dotnet tool install --global PowerShell
Microsoft Child Process Debugging Power Tool 或 Microsoft Child Process Debugging Power Tool 2022
修改launchSettings.json
"dapr": {
"commandName": "Executable",
"executablePath": "pwsh",
"commandLineArgs": "-Command \"dapr run --app-id a-api --app-port 5084 --app-protocol http --log-level debug -- dotnet run --no-build\"",
"workingDirectory": ".",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"nativeDebugging": true,
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5084"
}
注意:app-protocol 要设置成 http,否则调试dapr通信失败
配置子进程调试
断点调试
VS Code调试
本人没有实际操作,请参考:https://docs.dapr.io/zh-hans/developing-applications/ides/vscode/
idea调试
本人没有实际操作,请参考:https://docs.dapr.io/zh-hans/developing-applications/ides/intellij/
Kubernetes 发布Dapr应用
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Values.serviceName }}
namespace: {{ .Values.nameSpace }}
labels:
app: {{ .Values.appLabel }}
service: {{ .Values.serviceName }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
service: {{ .Values.serviceName }}
template:
metadata:
labels:
app: {{ .Values.appLabel }}
service: {{ .Values.serviceName }}
logging: "true" # 一定要具有该标签才会被采集日志
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: {{ .Values.serviceName }}
dapr.io/app-port: "80"
dapr.io/config: "daprConfig"
spec:
containers:
- ...
注意:annotations的配置是关键
Kubernetes annotations规范
日志(Fluentd、Elastic、Kibana)
参考:https://docs.dapr.io/zh-hans/operations/monitoring/logging/fluentd/
SDK支持
tye启动
说明:tye工具主要是为了方便一次性启动多个微服务,提升工作效率
参考:
# 安装tye
dotnet tool install -g Microsoft.Tye --version "0.11.0-alpha.22111.1"
# 解决方案目录创建 tye.yaml
name: test
extensions:
- name: dapr
services:
- name: a-api
project: test.dapr.WebApiA/test.dapr.WebApiA.csproj
replicas: 1
bindings:
- port: 5084
- name: b-api
project: test.dapr.WebApiB/test.dapr.WebApiB.csproj
replicas: 1
bindings:
- port: 5264
# 启动项目
tye run
# 查看dapr中的服务注册情况
dapr list
# APP ID HTTP PORT GRPC PORT APP PORT COMMAND AGE CREATED DAPRD PID CLI PID APP PID RUN TEMPLATE PATH
# b-api 63754 63753 63748 1h 2023-08-12 12:00.58 24476 32412 0
# a-api 63751 63750 63746 1h 2023-08-12 12:00.58 32828 32396 0
注意:非k8s环境replicas不能大于1,否则抛错
Kubernetes Job
apiVersion: batch/v1
kind: Job
metadata:
name: job-with-shutdown
spec:
template:
metadata:
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "with-shutdown"
spec:
containers:
- name: job
image: alpine
command: ["/bin/sh", "-c", "apk --no-cache add curl && sleep 20 && curl -X POST localhost:3500/v1.0/shutdown"]
restartPolicy: Never
Kubernetes Job参考:https://zhuanlan.zhihu.com/p/382830573