This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Dapr 快速入门

通过代码示例快速掌握 Dapr,帮助您轻松上手

通过我们的 Dapr 快速入门指南,结合代码示例,帮助您轻松掌握 Dapr。

开始之前

快速入门

快速入门描述
服务调用通过 HTTP 或 gRPC 实现两个服务之间的同步通信。
发布和订阅通过消息实现两个服务之间的异步通信。
工作流在长时间运行的应用中协调业务流程,确保容错和状态管理。
状态管理以键/值对形式存储服务数据,支持多种状态存储。
绑定使用输入绑定响应外部事件,使用输出绑定执行操作。
参与者运行微服务和简单客户端,展示 Dapr 参与者的状态化对象模式。
秘密管理安全获取和管理敏感信息。
配置获取配置项并监听配置更新。
弹性为 Dapr API 请求定义和应用容错策略。
加密使用 Dapr 的加密 API 进行数据加密和解密。
作业使用 Dapr 的作业 API 进行作业调度、检索和删除。

1 - 快速入门:服务调用

开始使用 Dapr 的服务调用模块

通过 Dapr 的服务调用模块,您的应用程序可以稳定且安全地与其他应用程序进行通信。

显示服务调用步骤的图示

Dapr 提供了多种服务调用的方法,您可以根据具体需求进行选择。在本教程中,您将启用结账服务,通过 HTTP 代理调用订单处理服务中的方法,具体步骤如下:

概述文章中了解更多关于 Dapr 服务调用方法的信息。

使用多应用程序运行

在继续本教程之前,请选择您偏好的编程语言。

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/python/http

order-processorcheckout 应用安装依赖项:

cd ./order-processor
pip3 install -r requirements.txt
cd ../checkout
pip3 install -r requirements.txt
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 Dapr 边车:

  • order-processor 服务
  • checkout 服务
dapr run -f .

注意:在 Windows 中,由于未定义 Python3.exe,您可能需要在运行 dapr run -f . 之前将 dapr.yaml 文件中的 python3 更改为 python

预期输出

== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully

发生了什么?

在本教程中运行 dapr run -f . 使用 dapr.yaml 多应用程序运行模板文件启动了 订阅者发布者 应用程序。

dapr.yaml 多应用程序运行模板文件

使用 dapr run -f . 运行 多应用程序运行模板文件 将启动项目中的所有应用程序。在本教程中,dapr.yaml 文件包含以下内容:

version: 1
apps:
  - appDirPath: ./order-processor/
    appID: order-processor
    appPort: 8001
    command: ["python3", "app.py"]
  - appID: checkout
    appDirPath: ./checkout/
    command: ["python3", "app.py"]
order-processor 服务

order-processor 服务接收来自 checkout 服务的调用:

@app.route('/orders', methods=['POST'])
def getOrder():
    data = request.json
    print('Order received : ' + json.dumps(data), flush=True)
    return json.dumps({'success': True}), 200, {
        'ContentType': 'application/json'}


app.run(port=8001)

checkout 服务

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

headers = {'dapr-app-id': 'order-processor'}

result = requests.post(
    url='%s/orders' % (base_url),
    data=json.dumps(order),
    headers=headers
)

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/javascript/http

order-processorcheckout 应用安装依赖项:

cd ./order-processor
npm install
cd ../checkout
npm install
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 Dapr 边车:

  • order-processor 服务
  • checkout 服务
dapr run -f .

预期输出

== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully

发生了什么?

在本教程中运行 dapr run -f . 使用 dapr.yaml 多应用程序运行模板文件启动了 订阅者发布者 应用程序。

dapr.yaml 多应用程序运行模板文件

使用 dapr run -f . 运行 多应用程序运行模板文件 将启动项目中的所有应用程序。在本教程中,dapr.yaml 文件包含以下内容:

version: 1
apps:
  - appDirPath: ./order-processor/
    appID: order-processor
    appPort: 5001
    command: ["npm", "start"]
  - appID: checkout
    appDirPath: ./checkout/
    command: ["npm", "start"]
order-processor 服务

order-processor 服务接收来自 checkout 服务的调用:

app.post('/orders', (req, res) => {
    console.log("Order received:", req.body);
    res.sendStatus(200);
});
checkout 服务

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

let axiosConfig = {
  headers: {
      "dapr-app-id": "order-processor"
  }
};
const res = await axios.post(`${DAPR_HOST}:${DAPR_HTTP_PORT}/orders`, order , axiosConfig);
console.log("Order passed: " + res.config.data);

步骤 1:准备工作

在此示例中,您需要:

注意: .NET 6 是此版本中 Dapr .NET SDK 包的最低支持版本。仅 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/csharp/http

order-processorcheckout 应用安装依赖项:

cd ./order-processor
dotnet restore
dotnet build
cd ../checkout
dotnet restore
dotnet build
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 Dapr 边车:

  • order-processor 服务
  • checkout 服务
dapr run -f .

预期输出

== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully

发生了什么?

在本教程中运行 dapr run -f . 使用 dapr.yaml 多应用程序运行模板文件启动了 订阅者发布者 应用程序。

dapr.yaml 多应用程序运行模板文件

使用 dapr run -f . 运行 多应用程序运行模板文件 将启动项目中的所有应用程序。在本教程中,dapr.yaml 文件包含以下内容:

version: 1
apps:
  - appDirPath: ./order-processor/
    appID: order-processor
    appPort: 7001
    command: ["dotnet", "run"]
  - appID: checkout
    appDirPath: ./checkout/
    command: ["dotnet", "run"]
order-processor 服务

order-processor 服务接收来自 checkout 服务的调用:

app.MapPost("/orders", (Order order) =>
{
    Console.WriteLine("Order received : " + order);
    return order.ToString();
});
checkout 服务

checkout 服务的 Program.cs 文件中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

client.DefaultRequestHeaders.Add("dapr-app-id", "order-processor");

var response = await client.PostAsync($"{baseURL}/orders", content);
    Console.WriteLine("Order passed: " + order);

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/java/http

order-processorcheckout 应用安装依赖项:

cd ./order-processor
mvn clean install
cd ../checkout
mvn clean install
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 Dapr 边车:

  • order-processor 服务
  • checkout 服务
dapr run -f .

预期输出

== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully

发生了什么?

在本教程中运行 dapr run -f . 使用 dapr.yaml 多应用程序运行模板文件启动了 订阅者发布者 应用程序。

dapr.yaml 多应用程序运行模板文件

使用 dapr run -f . 运行 多应用程序运行模板文件 将启动项目中的所有应用程序。在本教程中,dapr.yaml 文件包含以下内容:

version: 1
apps:
  - appDirPath: ./order-processor/
    appID: order-processor
    appPort: 9001
    command: ["java", "-jar", "target/OrderProcessingService-0.0.1-SNAPSHOT.jar"]
  - appID: checkout
    appDirPath: ./checkout/
    command: ["java", "-jar", "target/CheckoutService-0.0.1-SNAPSHOT.jar"]
order-processor 服务

order-processor 服务接收来自 checkout 服务的调用:

public String processOrders(@RequestBody Order body) {
        System.out.println("Order received: "+ body.getOrderId());
        return "CID" + body.getOrderId();
    }
checkout 服务

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

.header("Content-Type", "application/json")
.header("dapr-app-id", "order-processor")

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Order passed: "+ orderId)

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门克隆目录的根目录,导航到快速入门目录。

cd service_invocation/go/http

order-processorcheckout 应用安装依赖项:

cd ./order-processor
go build .
cd ../checkout
go build .
cd ..

步骤 3:运行 order-processorcheckout 服务

使用以下命令,同时运行以下服务及其各自的 Dapr 边车:

  • order-processor 服务
  • checkout 服务
dapr run -f .

预期输出

== APP - order-processor == Order received : Order { orderId = 1 }
== APP - checkout == Order passed: Order { OrderId = 1 }
== APP - order-processor == Order received : Order { orderId = 2 }
== APP - checkout == Order passed: Order { OrderId = 2 }
== APP - order-processor == Order received : Order { orderId = 3 }
== APP - checkout == Order passed: Order { OrderId = 3 }
== APP - order-processor == Order received : Order { orderId = 4 }
== APP - checkout == Order passed: Order { OrderId = 4 }
== APP - order-processor == Order received : Order { orderId = 5 }
== APP - checkout == Order passed: Order { OrderId = 5 }
== APP - order-processor == Order received : Order { orderId = 6 }
== APP - checkout == Order passed: Order { OrderId = 6 }
== APP - order-processor == Order received : Order { orderId = 7 }
== APP - checkout == Order passed: Order { OrderId = 7 }
== APP - order-processor == Order received : Order { orderId = 8 }
== APP - checkout == Order passed: Order { OrderId = 8 }
== APP - order-processor == Order received : Order { orderId = 9 }
== APP - checkout == Order passed: Order { OrderId = 9 }
== APP - order-processor == Order received : Order { orderId = 10 }
== APP - checkout == Order passed: Order { OrderId = 10 }
== APP - order-processor == Order received : Order { orderId = 11 }
== APP - checkout == Order passed: Order { OrderId = 11 }
== APP - order-processor == Order received : Order { orderId = 12 }
== APP - checkout == Order passed: Order { OrderId = 12 }
== APP - order-processor == Order received : Order { orderId = 13 }
== APP - checkout == Order passed: Order { OrderId = 13 }
== APP - order-processor == Order received : Order { orderId = 14 }
== APP - checkout == Order passed: Order { OrderId = 14 }
== APP - order-processor == Order received : Order { orderId = 15 }
== APP - checkout == Order passed: Order { OrderId = 15 }
== APP - order-processor == Order received : Order { orderId = 16 }
== APP - checkout == Order passed: Order { OrderId = 16 }
== APP - order-processor == Order received : Order { orderId = 17 }
== APP - checkout == Order passed: Order { OrderId = 17 }
== APP - order-processor == Order received : Order { orderId = 18 }
== APP - checkout == Order passed: Order { OrderId = 18 }
== APP - order-processor == Order received : Order { orderId = 19 }
== APP - checkout == Order passed: Order { OrderId = 19 }
== APP - order-processor == Order received : Order { orderId = 20 }
== APP - checkout == Order passed: Order { OrderId = 20 }
Exited App successfully

发生了什么?

在本教程中运行 dapr run -f . 使用 dapr.yaml 多应用程序运行模板文件启动了 订阅者发布者 应用程序。

dapr.yaml 多应用程序运行模板文件

使用 dapr run -f . 运行 多应用程序运行模板文件 将启动项目中的所有应用程序。在本教程中,dapr.yaml 文件包含以下内容:

version: 1
apps:
  - appDirPath: ./order-processor/
    appID: order-processor
    appPort: 6006
    command: ["go", "run", "."]
  - appID: checkout
    appDirPath: ./checkout/
    command: ["go", "run", "."]
order-processor 服务

order-processor 服务中,每个订单通过 HTTP POST 请求接收并由 getOrder 函数处理。

func getOrder(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Order received : %s", string(data))
}
checkout 服务

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

req.Header.Add("dapr-app-id", "order-processor")

response, err := client.Do(req)

一次运行一个应用程序

在继续本教程之前,请选择您偏好的编程语言。

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 order-processor 服务

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd service_invocation/python/http/order-processor

安装依赖项并构建应用程序:

pip3 install -r requirements.txt 

在 Dapr 边车旁边运行 order-processor 服务。

dapr run --app-port 8001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- python3 app.py

注意:在 Windows 中,由于未定义 Python3.exe,您可能需要使用 python app.py 而不是 python3 app.py

@app.route('/orders', methods=['POST'])
def getOrder():
    data = request.json
    print('Order received : ' + json.dumps(data), flush=True)
    return json.dumps({'success': True}), 200, {
        'ContentType': 'application/json'}


app.run(port=8001)

步骤 4:运行 checkout 服务

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd service_invocation/python/http/checkout

安装依赖项并构建应用程序:

pip3 install -r requirements.txt 

在 Dapr 边车旁边运行 checkout 服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- python3 app.py

注意:在 Windows 中,由于未定义 Python3.exe,您可能需要使用 python app.py 而不是 python3 app.py

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

headers = {'dapr-app-id': 'order-processor'}

result = requests.post(
    url='%s/orders' % (base_url),
    data=json.dumps(order),
    headers=headers
)

步骤 5:使用多应用程序运行

您可以使用 多应用程序运行模板 运行本教程中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,只需运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例发现并相互通信。

checkout 服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor 服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 order-processor 服务

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd service_invocation/javascript/http/order-processor

安装依赖项:

npm install

在 Dapr 边车旁边运行 order-processor 服务。

dapr run --app-port 5001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- npm start
app.post('/orders', (req, res) => {
    console.log("Order received:", req.body);
    res.sendStatus(200);
});

步骤 4:运行 checkout 服务

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd service_invocation/javascript/http/checkout

安装依赖项:

npm install

在 Dapr 边车旁边运行 checkout 服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- npm start

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

let axiosConfig = {
  headers: {
      "dapr-app-id": "order-processor"
  }
};
const res = await axios.post(`${DAPR_HOST}:${DAPR_HTTP_PORT}/orders`, order , axiosConfig);
console.log("Order passed: " + res.config.data);

步骤 5:使用多应用程序运行

您可以使用 多应用程序运行模板 运行本教程中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,只需运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例发现并相互通信。

checkout 服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}
== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor 服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}
== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 order-processor 服务

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd service_invocation/csharp/http/order-processor

安装依赖项:

dotnet restore
dotnet build

在 Dapr 边车旁边运行 order-processor 服务。

dapr run --app-port 7001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- dotnet run

以下是订单处理器 Program.cs 文件中的工作代码块。

app.MapPost("/orders", (Order order) =>
{
    Console.WriteLine("Order received : " + order);
    return order.ToString();
});

步骤 4:运行 checkout 服务

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd service_invocation/csharp/http/checkout

安装依赖项:

dotnet restore
dotnet build

在 Dapr 边车旁边运行 checkout 服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- dotnet run

checkout 服务的 Program.cs 文件中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

var client = new HttpClient();
client.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json"));

client.DefaultRequestHeaders.Add("dapr-app-id", "order-processor");

var response = await client.PostAsync($"{baseURL}/orders", content);
    Console.WriteLine("Order passed: " + order);

步骤 5:使用多应用程序运行

您可以使用 多应用程序运行模板 运行本教程中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,只需运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例发现并相互通信。

checkout 服务输出:

== APP == Order passed: Order { OrderId: 1 }
== APP == Order passed: Order { OrderId: 2 }
== APP == Order passed: Order { OrderId: 3 }
== APP == Order passed: Order { OrderId: 4 }
== APP == Order passed: Order { OrderId: 5 }
== APP == Order passed: Order { OrderId: 6 }
== APP == Order passed: Order { OrderId: 7 }
== APP == Order passed: Order { OrderId: 8 }
== APP == Order passed: Order { OrderId: 9 }
== APP == Order passed: Order { OrderId: 10 }

order-processor 服务输出:

== APP == Order received: Order { OrderId: 1 }
== APP == Order received: Order { OrderId: 2 }
== APP == Order received: Order { OrderId: 3 }
== APP == Order received: Order { OrderId: 4 }
== APP == Order received: Order { OrderId: 5 }
== APP == Order received: Order { OrderId: 6 }
== APP == Order received: Order { OrderId: 7 }
== APP == Order received: Order { OrderId: 8 }
== APP == Order received: Order { OrderId: 9 }
== APP == Order received: Order { OrderId: 10 }

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 order-processor 服务

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd service_invocation/java/http/order-processor

安装依赖项:

mvn clean install

在 Dapr 边车旁边运行 order-processor 服务。

dapr run --app-id order-processor --app-port 9001 --app-protocol http --dapr-http-port 3501 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
public String processOrders(@RequestBody Order body) {
        System.out.println("Order received: "+ body.getOrderId());
        return "CID" + body.getOrderId();
    }

步骤 4:运行 checkout 服务

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd service_invocation/java/http/checkout

安装依赖项:

mvn clean install

在 Dapr 边车旁边运行 checkout 服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

.header("Content-Type", "application/json")
.header("dapr-app-id", "order-processor")

HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Order passed: "+ orderId)

步骤 5:使用多应用程序运行

您可以使用 多应用程序运行模板 运行本教程中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,只需运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例发现并相互通信。

checkout 服务输出:

== APP == Order passed: 1
== APP == Order passed: 2
== APP == Order passed: 3
== APP == Order passed: 4
== APP == Order passed: 5
== APP == Order passed: 6
== APP == Order passed: 7
== APP == Order passed: 8
== APP == Order passed: 9
== APP == Order passed: 10

order-processor 服务输出:

== APP == Order received: 1
== APP == Order received: 2
== APP == Order received: 3
== APP == Order received: 4
== APP == Order received: 5
== APP == Order received: 6
== APP == Order received: 7
== APP == Order received: 8
== APP == Order received: 9
== APP == Order received: 10

步骤 1:准备工作

在此示例中,您需要:

步骤 2:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:运行 order-processor 服务

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd service_invocation/go/http/order-processor

安装依赖项:

go build .

在 Dapr 边车旁边运行 order-processor 服务。

dapr run --app-port 6006 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- go run .

每个订单通过 HTTP POST 请求接收并由 getOrder 函数处理。

func getOrder(w http.ResponseWriter, r *http.Request) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Fatal(err)
	}
	log.Printf("Order received : %s", string(data))
}

步骤 4:运行 checkout 服务

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd service_invocation/go/http/checkout

安装依赖项:

go build .

在 Dapr 边车旁边运行 checkout 服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- go run .

checkout 服务中,您会注意到无需重写应用程序代码即可使用 Dapr 的服务调用。您只需添加 dapr-app-id 头,该头指定目标服务的 ID,即可启用服务调用。

req.Header.Add("dapr-app-id", "order-processor")

response, err := client.Do(req)

步骤 5:使用多应用程序运行

您可以使用 多应用程序运行模板 运行本教程中的 Dapr 应用程序。无需为 order-processorcheckout 应用程序运行两个单独的 dapr run 命令,只需运行以下命令:

dapr run -f .

要停止所有应用程序,请运行:

dapr stop -f .

步骤 6:查看服务调用输出

Dapr 在任何 Dapr 实例上调用应用程序。在代码中,边车编程模型鼓励每个应用程序与其自己的 Dapr 实例通信。然后,Dapr 实例发现并相互通信。

checkout 服务输出:

== APP == Order passed:  {"orderId":1}
== APP == Order passed:  {"orderId":2}
== APP == Order passed:  {"orderId":3}
== APP == Order passed:  {"orderId":4}
== APP == Order passed:  {"orderId":5}
== APP == Order passed:  {"orderId":6}
== APP == Order passed:  {"orderId":7}
== APP == Order passed:  {"orderId":8}
== APP == Order passed:  {"orderId":9}
== APP == Order passed:  {"orderId":10}

order-processor 服务输出:

== APP == Order received :  {"orderId":1}
== APP == Order received :  {"orderId":2}
== APP == Order received :  {"orderId":3}
== APP == Order received :  {"orderId":4}
== APP == Order received :  {"orderId":5}
== APP == Order received :  {"orderId":6}
== APP == Order received :  {"orderId":7}
== APP == Order received :  {"orderId":8}
== APP == Order received :  {"orderId":9}
== APP == Order received :  {"orderId":10}

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的 discord 频道进行讨论。

下一步

探索 Dapr 教程 >>

2 - 快速入门:发布和订阅

开始使用 Dapr 的发布和订阅构建块

我们来了解一下 Dapr 的发布和订阅 (Pub/sub) 构建块。在这个快速入门中,您将运行发布者和订阅者微服务,以演示 Dapr 如何实现 Pub/sub 模式。

  1. 使用发布者服务,开发者可以不断地将消息发布到某个主题。
  2. Pub/sub 组件 会对这些消息进行排队或代理。我们下面的示例使用 Redis,您也可以使用 RabbitMQ、Kafka 等。
  3. 订阅该主题的订阅者会从队列中获取消息并进行处理。

您可以通过以下两种方式尝试此 Pub/sub 快速入门:

使用多应用运行

在继续快速入门之前,请选择您偏好的 Dapr SDK 语言。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录,导航到 pub/sub 目录:

cd pub_sub/python/sdk

order-processorcheckout 应用安装依赖项:

cd ./checkout
pip3 install -r requirements.txt
cd ..
cd ./order-processor
pip3 install -r requirements.txt
cd ..
cd ./order-processor-fastapi
pip3 install -r requirements.txt
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 Dapr sidecar:

  • order-processor 订阅者
  • checkout 发布者
dapr run -f .

注意:由于 Windows 中未定义 Python3.exe,您可能需要在运行 dapr run -f . 之前将 dapr.yaml 文件中的 python3 更改为 python

预期输出

== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully

发生了什么?

当您在 Dapr 安装期间运行 dapr init 时,以下 YAML 文件已在 .dapr/components 目录中生成:

在此快速入门中运行 dapr run -f . 启动了 订阅者发布者 应用程序。

dapr.yaml 多应用运行模板文件

使用 dapr run -f . 运行 多应用运行模板文件 启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../components/
apps:
  - appID: order-processor-sdk
    appDirPath: ./order-processor/
    appPort: 6001
    command: ["uvicorn", "app:app"]
  - appID: checkout-sdk
    appDirPath: ./checkout/
    command: ["python3", "app.py"]
pubsub.yaml 组件文件

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在组件 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。
order-processor 订阅者

order-processor 订阅者中,您订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

# 注册 Dapr pub/sub 订阅
@app.route('/dapr/subscribe', methods=['GET'])
def subscribe():
    subscriptions = [{
        'pubsubname': 'orderpubsub',
        'topic': 'orders',
        'route': 'orders'
    }]
    print('Dapr pub/sub 已订阅: ' + json.dumps(subscriptions))
    return jsonify(subscriptions)


# Dapr 订阅在 /dapr/subscribe 中设置此路由
@app.route('/orders', methods=['POST'])
def orders_subscriber():
    event = from_http(request.headers, request.get_data())
    print('订阅者收到: ' + event.data['orderid'], flush=True)
    return json.dumps({'success': True}), 200, {
        'ContentType': 'application/json'}


app.run(port=5001)
checkout 发布者

checkout 发布者中,您将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

with DaprClient() as client:
    # 使用 Dapr PubSub 发布事件/消息
    result = client.publish_event(
        pubsub_name='orderpubsub',
        topic_name='orders',
        data=json.dumps(order),
        data_content_type='application/json',
    )

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录,导航到 pub/sub 目录:

cd pub_sub/javascript/sdk

order-processorcheckout 应用安装依赖项:

cd ./order-processor
npm install
cd ..
cd ./checkout
npm install
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 Dapr sidecar:

  • order-processor 订阅者
  • checkout 发布者
dapr run -f .

预期输出

== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully

发生了什么?

当您在 Dapr 安装期间运行 dapr init 时,以下 YAML 文件已在 .dapr/components 目录中生成:

在此快速入门中运行 dapr run -f . 启动了 订阅者发布者 应用程序。

dapr.yaml 多应用运行模板文件

使用 dapr run -f . 运行 多应用运行模板文件 启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../components/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    appPort: 5002
    command: ["npm", "run", "start"]
  - appID: checkout-sdk
    appDirPath: ./checkout/
    command: ["npm", "run", "start"]
pubsub.yaml 组件文件

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在组件 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。
order-processor 订阅者

order-processor 订阅者中,您订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

server.pubsub.subscribe("orderpubsub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data)));
checkout 发布者

checkout 发布者服务中,您将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

const client = new DaprClient();

await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order);
console.log("Published data: " + JSON.stringify(order));

步骤 1:先决条件

对于此示例,您将需要:

注意: .NET 6 是此版本中 Dapr .NET SDK 包的最低支持版本。仅 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录,导航到 pub/sub 目录:

cd pub_sub/csharp/sdk

order-processorcheckout 应用安装依赖项:

cd ./order-processor
dotnet restore
dotnet build
cd ../checkout
dotnet restore
dotnet build
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 Dapr sidecar:

  • order-processor 订阅者
  • checkout 发布者
dapr run -f .

预期输出

== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully

发生了什么?

当您在 Dapr 安装期间运行 dapr init 时,以下 YAML 文件已在 .dapr/components 目录中生成:

在此快速入门中运行 dapr run -f . 启动了 订阅者发布者 应用程序。

dapr.yaml 多应用运行模板文件

使用 dapr run -f . 运行 多应用运行模板文件 启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../components/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    appPort: 7006
    command: ["dotnet", "run"]
  - appID: checkout-sdk
    appDirPath: ./checkout/
    command: ["dotnet", "run"]
pubsub.yaml 组件文件

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在组件 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。
order-processor 订阅者

order-processor 订阅者中,您订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

// Dapr 订阅在 [Topic] 中将 orders 主题路由到此路由
app.MapPost("/orders", [Topic("orderpubsub", "orders")] (Order order) => {
    Console.WriteLine("订阅者收到: " + order);
    return Results.Ok(order);
});

public record Order([property: JsonPropertyName("orderId")] int OrderId);
checkout 发布者

checkout 发布者中,您将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

using var client = new DaprClientBuilder().Build();
await client.PublishEventAsync("orderpubsub", "orders", order);
Console.WriteLine("Published data: " + order);

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录,导航到 pub/sub 目录:

cd pub_sub/java/sdk

order-processorcheckout 应用安装依赖项:

cd ./order-processor
mvn clean install
cd ..
cd ./checkout
mvn clean install
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 Dapr sidecar:

  • order-processor 订阅者
  • checkout 发布者
dapr run -f .

预期输出

== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully

发生了什么?

当您在 Dapr 安装期间运行 dapr init 时,以下 YAML 文件已在 .dapr/components 目录中生成:

在此快速入门中运行 dapr run -f . 启动了 订阅者发布者 应用程序。

dapr.yaml 多应用运行模板文件

使用 dapr run -f . 运行 多应用运行模板文件 启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../components/
apps:
  - appID: order-processor-sdk
    appDirPath: ./order-processor/target/
    appPort: 8080
    command: ["java", "-jar", "OrderProcessingService-0.0.1-SNAPSHOT.jar"]
  - appID: checkout-sdk
    appDirPath: ./checkout/target/
    command: ["java", "-jar", "CheckoutService-0.0.1-SNAPSHOT.jar"]
pubsub.yaml 组件文件

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在组件 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。
order-processor 订阅者

order-processor 订阅者中,您订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

@Topic(name = "orders", pubsubName = "orderpubsub")
@PostMapping(path = "/orders", consumes = MediaType.ALL_VALUE)
public Mono<ResponseEntity> getCheckout(@RequestBody(required = false) CloudEvent<Order> cloudEvent) {
    return Mono.fromSupplier(() -> {
        try {
            logger.info("订阅者收到: " + cloudEvent.getData().getOrderId());
            return ResponseEntity.ok("SUCCESS");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
}
checkout 发布者

checkout 发布者中,您将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

DaprClient client = new DaprClientBuilder().build();
client.publishEvent(
		PUBSUB_NAME,
		TOPIC_NAME,
		order).block();
logger.info("Published data: " + order.getOrderId());

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

从快速入门目录的根目录,导航到 pub/sub 目录:

cd pub_sub/go/sdk

order-processorcheckout 应用安装依赖项:

cd ./order-processor
go build .
cd ../checkout
go build .
cd ..

步骤 3:运行发布者和订阅者

使用以下命令,同时运行以下服务及其各自的 Dapr sidecar:

  • order-processor 订阅者
  • checkout 发布者
dapr run -f .

预期输出

== APP - checkout-sdk == Published data: Order { OrderId = 1 }
== APP - order-processor == Subscriber received : Order { OrderId = 1 }
== APP - checkout-sdk == Published data: Order { OrderId = 2 }
== APP - order-processor == Subscriber received : Order { OrderId = 2 }
== APP - checkout-sdk == Published data: Order { OrderId = 3 }
== APP - order-processor == Subscriber received : Order { OrderId = 3 }
== APP - checkout-sdk == Published data: Order { OrderId = 4 }
== APP - order-processor == Subscriber received : Order { OrderId = 4 }
== APP - checkout-sdk == Published data: Order { OrderId = 5 }
== APP - order-processor == Subscriber received : Order { OrderId = 5 }
== APP - checkout-sdk == Published data: Order { OrderId = 6 }
== APP - order-processor == Subscriber received : Order { OrderId = 6 }
== APP - checkout-sdk == Published data: Order { OrderId = 7 }
== APP - order-processor == Subscriber received : Order { OrderId = 7 }
== APP - checkout-sdk == Published data: Order { OrderId = 8 }
== APP - order-processor == Subscriber received : Order { OrderId = 8 }
== APP - checkout-sdk == Published data: Order { OrderId = 9 }
== APP - order-processor == Subscriber received : Order { OrderId = 9 }
== APP - checkout-sdk == Published data: Order { OrderId = 10 }
== APP - order-processor == Subscriber received : Order { OrderId = 10 }
Exited App successfully

发生了什么?

当您在 Dapr 安装期间运行 dapr init 时,以下 YAML 文件已在 .dapr/components 目录中生成:

在此快速入门中运行 dapr run -f . 启动了 订阅者发布者 应用程序。

dapr.yaml 多应用运行模板文件

使用 dapr run -f . 运行 多应用运行模板文件 启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../components/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    appPort: 6005
    command: ["go", "run", "."]
  - appID: checkout-sdk
    appDirPath: ./checkout/
    command: ["go", "run", "."]
pubsub.yaml 组件文件

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在组件 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。
order-processor 订阅者

order-processor 订阅者中,您订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
	fmt.Println("订阅者收到: ", e.Data)
	return false, nil
}
checkout 发布者

checkout 发布者中,您将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

client, err := dapr.NewClient()

if err := client.PublishEvent(ctx, PUBSUB_NAME, PUBSUB_TOPIC, []byte(order)); err != nil {
    panic(err)
}

fmt.Println("Published data: ", order)

一次运行一个应用程序

在继续快速入门之前,请选择您偏好的 Dapr SDK 语言。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd pub_sub/python/sdk/order-processor

安装依赖项:

pip3 install -r requirements.txt

在 Dapr sidecar 的旁边运行 order-processor 订阅者服务。

dapr run --app-id order-processor --resources-path ../../../components/ --app-port 6002 -- python3 app.py

注意:由于 Windows 中未定义 Python3.exe,您可能需要使用 python app.py 而不是 python3 app.py

order-processor 订阅者中,我们订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

# 注册 Dapr pub/sub 订阅
@app.route('/dapr/subscribe', methods=['GET'])
def subscribe():
    subscriptions = [{
        'pubsubname': 'orderpubsub',
        'topic': 'orders',
        'route': 'orders'
    }]
    print('Dapr pub/sub 已订阅: ' + json.dumps(subscriptions))
    return jsonify(subscriptions)


# Dapr 订阅在 /dapr/subscribe 中设置此路由
@app.route('/orders', methods=['POST'])
def orders_subscriber():
    event = from_http(request.headers, request.get_data())
    print('订阅者收到: ' + event.data['orderid'], flush=True)
    return json.dumps({'success': True}), 200, {
        'ContentType': 'application/json'}


app.run(port=5001)

步骤 4:发布主题

在新的终端窗口中,导航到 checkout 目录。

cd pub_sub/python/sdk/checkout

安装依赖项:

pip3 install -r requirements.txt

在 Dapr sidecar 的旁边运行 checkout 发布者服务。

dapr run --app-id checkout --resources-path ../../../components/ -- python3 app.py

注意:由于 Windows 中未定义 Python3.exe,您可能需要使用 python app.py 而不是 python3 app.py

checkout 发布者中,我们将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

with DaprClient() as client:
    # 使用 Dapr PubSub 发布事件/消息
    result = client.publish_event(
        pubsub_name='orderpubsub',
        topic_name='orders',
        data=json.dumps(order),
        data_content_type='application/json',
    )

步骤 5:查看 Pub/sub 输出

发布者将订单发送到 Dapr sidecar,而订阅者接收它们。

发布者输出:

== APP == INFO:root:Published data: {"orderId": 1}
== APP == INFO:root:Published data: {"orderId": 2}
== APP == INFO:root:Published data: {"orderId": 3}
== APP == INFO:root:Published data: {"orderId": 4}
== APP == INFO:root:Published data: {"orderId": 5}
== APP == INFO:root:Published data: {"orderId": 6}
== APP == INFO:root:Published data: {"orderId": 7}
== APP == INFO:root:Published data: {"orderId": 8}
== APP == INFO:root:Published data: {"orderId": 9}
== APP == INFO:root:Published data: {"orderId": 10}

订阅者输出:

== APP == INFO:root:Subscriber received: {"orderId": 1}
== APP == INFO:root:Subscriber received: {"orderId": 2}
== APP == INFO:root:Subscriber received: {"orderId": 3}
== APP == INFO:root:Subscriber received: {"orderId": 4}
== APP == INFO:root:Subscriber received: {"orderId": 5}
== APP == INFO:root:Subscriber received: {"orderId": 6}
== APP == INFO:root:Subscriber received: {"orderId": 7}
== APP == INFO:root:Subscriber received: {"orderId": 8}
== APP == INFO:root:Subscriber received: {"orderId": 9}
== APP == INFO:root:Subscriber received: {"orderId": 10}
pubsub.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地机器上运行一个 Redis 容器,位置:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/pubsub.yaml

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd pub_sub/javascript/sdk/order-processor

安装依赖项,其中将包括来自 JavaScript SDK 的 @dapr/dapr 包:

npm install

验证服务目录中包含以下文件:

  • package.json
  • package-lock.json

在 Dapr sidecar 的旁边运行 order-processor 订阅者服务。

dapr run --app-port 5002 --app-id order-processing --app-protocol http --dapr-http-port 3501 --resources-path ../../../components -- npm run start

order-processor 订阅者中,我们订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

server.pubsub.subscribe("orderpubsub", "orders", (data) => console.log("Subscriber received: " + JSON.stringify(data)));

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd pub_sub/javascript/sdk/checkout

安装依赖项,其中将包括来自 JavaScript SDK 的 @dapr/dapr 包:

npm install

验证服务目录中包含以下文件:

  • package.json
  • package-lock.json

在 Dapr sidecar 的旁边运行 checkout 发布者服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --resources-path ../../../components -- npm run start

checkout 发布者服务中,我们将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

const client = new DaprClient();

await client.pubsub.publish(PUBSUB_NAME, PUBSUB_TOPIC, order);
console.log("Published data: " + JSON.stringify(order));

步骤 5:查看 Pub/sub 输出

请注意,如上代码中所指定,发布者将一个随机数推送到 Dapr sidecar,而订阅者接收它。

发布者输出:

== APP == Published data: {"orderId":1}
== APP == Published data: {"orderId":2}
== APP == Published data: {"orderId":3}
== APP == Published data: {"orderId":4}
== APP == Published data: {"orderId":5}
== APP == Published data: {"orderId":6}
== APP == Published data: {"orderId":7}
== APP == Published data: {"orderId":8}
== APP == Published data: {"orderId":9}
== APP == Published data: {"orderId":10}

订阅者输出:

== APP == Subscriber received: {"orderId":1}
== APP == Subscriber received: {"orderId":2}
== APP == Subscriber received: {"orderId":3}
== APP == Subscriber received: {"orderId":4}
== APP == Subscriber received: {"orderId":5}
== APP == Subscriber received: {"orderId":6}
== APP == Subscriber received: {"orderId":7}
== APP == Subscriber received: {"orderId":8}
== APP == Subscriber received: {"orderId":9}
== APP == Subscriber received: {"orderId":10}
pubsub.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地机器上运行一个 Redis 容器,位置:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/pubsub.yaml

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd pub_sub/csharp/sdk/order-processor

恢复 NuGet 包:

dotnet restore
dotnet build

在 Dapr sidecar 的旁边运行 order-processor 订阅者服务。

dapr run --app-id order-processor --resources-path ../../../components --app-port 7006 -- dotnet run

order-processor 订阅者中,我们订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

// Dapr 订阅在 [Topic] 中将 orders 主题路由到此路由
app.MapPost("/orders", [Topic("orderpubsub", "orders")] (Order order) => {
    Console.WriteLine("订阅者收到: " + order);
    return Results.Ok(order);
});

public record Order([property: JsonPropertyName("orderId")] int OrderId);

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd pub_sub/csharp/sdk/checkout

恢复 NuGet 包:

dotnet restore
dotnet build

在 Dapr sidecar 的旁边运行 checkout 发布者服务。

dapr run --app-id checkout --resources-path ../../../components -- dotnet run

checkout 发布者中,我们将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

using var client = new DaprClientBuilder().Build();
await client.PublishEventAsync("orderpubsub", "orders", order);
Console.WriteLine("Published data: " + order);

步骤 5:查看 Pub/sub 输出

请注意,如上代码中所指定,发布者将一个随机数推送到 Dapr sidecar,而订阅者接收它。

发布者输出:

== APP == Published data: Order { OrderId = 1 }
== APP == Published data: Order { OrderId = 2 }
== APP == Published data: Order { OrderId = 3 }
== APP == Published data: Order { OrderId = 4 }
== APP == Published data: Order { OrderId = 5 }
== APP == Published data: Order { OrderId = 6 }
== APP == Published data: Order { OrderId = 7 }
== APP == Published data: Order { OrderId = 8 }
== APP == Published data: Order { OrderId = 9 }
== APP == Published data: Order { OrderId = 10 }

订阅者输出:

== APP == Subscriber received: Order { OrderId = 1 }
== APP == Subscriber received: Order { OrderId = 2 }
== APP == Subscriber received: Order { OrderId = 3 }
== APP == Subscriber received: Order { OrderId = 4 }
== APP == Subscriber received: Order { OrderId = 5 }
== APP == Subscriber received: Order { OrderId = 6 }
== APP == Subscriber received: Order { OrderId = 7 }
== APP == Subscriber received: Order { OrderId = 8 }
== APP == Subscriber received: Order { OrderId = 9 }
== APP == Subscriber received: Order { OrderId = 10 }
pubsub.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地机器上运行一个 Redis 容器,位置:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/pubsub.yaml

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd pub_sub/java/sdk/order-processor

安装依赖项:

mvn clean install

在 Dapr sidecar 的旁边运行 order-processor 订阅者服务。

dapr run --app-port 8080 --app-id order-processor --resources-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

order-processor 订阅者中,我们订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

@Topic(name = "orders", pubsubName = "orderpubsub")
@PostMapping(path = "/orders", consumes = MediaType.ALL_VALUE)
public Mono<ResponseEntity> getCheckout(@RequestBody(required = false) CloudEvent<Order> cloudEvent) {
    return Mono.fromSupplier(() -> {
        try {
            logger.info("订阅者收到: " + cloudEvent.getData().getOrderId());
            return ResponseEntity.ok("SUCCESS");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    });
}

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd pub_sub/java/sdk/checkout

安装依赖项:

mvn clean install

在 Dapr sidecar 的旁边运行 checkout 发布者服务。

dapr run --app-id checkout --resources-path ../../../components -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar

checkout 发布者中,我们将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

DaprClient client = new DaprClientBuilder().build();
client.publishEvent(
		PUBSUB_NAME,
		TOPIC_NAME,
		order).block();
logger.info("Published data: " + order.getOrderId());

步骤 5:查看 Pub/sub 输出

请注意,如上代码中所指定,发布者将一个随机数推送到 Dapr sidecar,而订阅者接收它。

发布者输出:

== APP == 7194 [main] INFO com.service.CheckoutServiceApplication - Published data: 1
== APP == 12213 [main] INFO com.service.CheckoutServiceApplication - Published data: 2
== APP == 17233 [main] INFO com.service.CheckoutServiceApplication - Published data: 3
== APP == 22252 [main] INFO com.service.CheckoutServiceApplication - Published data: 4
== APP == 27276 [main] INFO com.service.CheckoutServiceApplication - Published data: 5
== APP == 32320 [main] INFO com.service.CheckoutServiceApplication - Published data: 6
== APP == 37340 [main] INFO com.service.CheckoutServiceApplication - Published data: 7
== APP == 42356 [main] INFO com.service.CheckoutServiceApplication - Published data: 8
== APP == 47386 [main] INFO com.service.CheckoutServiceApplication - Published data: 9
== APP == 52410 [main] INFO com.service.CheckoutServiceApplication - Published data: 10

订阅者输出:

== APP == 2022-03-07 13:31:19.551  INFO 43512 --- [nio-8080-exec-5] c.s.c.OrderProcessingServiceController   : 订阅者收到: 1
== APP == 2022-03-07 13:31:19.552  INFO 43512 --- [nio-8080-exec-9] c.s.c.OrderProcessingServiceController   : 订阅者收到: 2
== APP == 2022-03-07 13:31:19.551  INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController   : 订阅者收到: 3
== APP == 2022-03-07 13:31:19.552  INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController   : 订阅者收到: 4
== APP == 2022-03-07 13:31:19.553  INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController   : 订阅者收到: 5
== APP == 2022-03-07 13:31:19.553  INFO 43512 --- [nio-8080-exec-9] c.s.c.OrderProcessingServiceController   : 订阅者收到: 6
== APP == 2022-03-07 13:31:22.849  INFO 43512 --- [nio-8080-exec-3] c.s.c.OrderProcessingServiceController   : 订阅者收到: 7
== APP == 2022-03-07 13:31:27.866  INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController   : 订阅者收到: 8
== APP == 2022-03-07 13:31:32.895  INFO 43512 --- [nio-8080-exec-6] c.s.c.OrderProcessingServiceController   : 订阅者收到: 9
== APP == 2022-03-07 13:31:37.919  INFO 43512 --- [nio-8080-exec-2] c.s.c.OrderProcessingServiceController   : 订阅者收到: 10
pubsub.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地机器上运行一个 Redis 容器,位置:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/pubsub.yaml

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
scopes:
  - orderprocessing
  - checkout

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 3:订阅主题

在终端窗口中,从快速入门克隆目录的根目录导航到 order-processor 目录。

cd pub_sub/go/sdk/order-processor

安装依赖项并构建应用程序:

go build .

在 Dapr sidecar 的旁边运行 order-processor 订阅者服务。

dapr run --app-port 6005 --app-id order-processor-sdk --app-protocol http --dapr-http-port 3501 --resources-path ../../../components -- go run .

order-processor 订阅者中,我们订阅了名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。这使您的应用程序代码能够通过 Dapr sidecar 与 Redis 组件实例通信。

func eventHandler(ctx context.Context, e *common.TopicEvent) (retry bool, err error) {
	fmt.Println("订阅者收到: ", e.Data)
	return false, nil
}

步骤 4:发布主题

在新的终端窗口中,从快速入门克隆目录的根目录导航到 checkout 目录。

cd pub_sub/go/sdk/checkout

安装依赖项并构建应用程序:

go build .

在 Dapr sidecar 的旁边运行 checkout 发布者服务。

dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 --resources-path ../../../components -- go run .

checkout 发布者中,我们将 orderId 消息发布到名为 orderpubsub 的 Redis 实例 (如 pubsub.yaml 组件中定义的) 和主题 orders。服务一启动,它就会在循环中发布:

client, err := dapr.NewClient()

if err := client.PublishEvent(ctx, PUBSUB_NAME, PUBSUB_TOPIC, []byte(order)); err != nil {
    panic(err)
}

fmt.Println("Published data: ", order)

步骤 5:查看 Pub/sub 输出

请注意,如上代码中所指定,发布者将一个编号消息推送到 Dapr sidecar,而订阅者接收它。

发布者输出:

== APP == dapr client initializing for: 127.0.0.1:63293
== APP == Published data:  {"orderId":1}
== APP == Published data:  {"orderId":2}
== APP == Published data:  {"orderId":3}
== APP == Published data:  {"orderId":4}
== APP == Published data:  {"orderId":5}
== APP == Published data:  {"orderId":6}
== APP == Published data:  {"orderId":7}
== APP == Published data:  {"orderId":8}
== APP == Published data:  {"orderId":9}
== APP == Published data:  {"orderId":10}

订阅者输出:

== APP == 订阅者收到:  {"orderId":1}
== APP == 订阅者收到:  {"orderId":2}
== APP == 订阅者收到:  {"orderId":3}
== APP == 订阅者收到:  {"orderId":4}
== APP == 订阅者收到:  {"orderId":5}
== APP == 订阅者收到:  {"orderId":6}
== APP == 订阅者收到:  {"orderId":7}
== APP == 订阅者收到:  {"orderId":8}
== APP == 订阅者收到:  {"orderId":9}
== APP == 订阅者收到:  {"orderId":10}

注意:接收的顺序可能会有所不同。

pubsub.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis pubsub.yaml 并在您的本地机器上运行一个 Redis 容器,位置:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\pubsub.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/pubsub.yaml

使用 pubsub.yaml 组件,您可以轻松地更换底层组件而无需更改应用程序代码。

此快速入门中包含的 Redis pubsub.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: orderpubsub
spec:
  type: pubsub.redis
  version: v1
  metadata:
  - name: redisHost
    value: localhost:6379
  - name: redisPassword
    value: ""
scopes:
  - orderprocessing
  - checkout

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式。
  • spec/metadata 定义了与组件实例的连接。
  • scopes 指定哪个应用程序可以使用该组件。

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进的建议吗?

加入我们的 discord 频道 讨论。

下一步

探索 Dapr 教程 >>

3 - 快速入门:工作流

开始使用 Dapr 工作流构建块

让我们来了解一下 Dapr 的工作流构建块。在这个快速入门中,您将创建一个简单的控制台应用程序,演示 Dapr 的工作流编程模型和管理 API。

在本指南中,您将:

  • 运行 order-processor 应用程序。
  • 启动工作流并观察工作流活动/任务的执行。
  • 查看工作流逻辑和工作流活动,以及它们在代码中的表示。

在继续快速入门之前,请选择您偏好的 Dapr SDK 语言版本。

order-processor 控制台应用程序启动并管理 order_processing_workflow,该工作流模拟从商店购买商品。工作流由五个独特的工作流活动或任务组成:

  • notify_activity:使用记录器在整个工作流过程中打印消息。这些消息通知您:
    • 您的库存不足
    • 您的付款无法处理,等等。
  • process_payment_activity:处理和授权付款。
  • verify_inventory_activity:检查状态存储以确保有足够的库存可供购买。
  • update_inventory_activity:从状态存储中移除请求的商品,并使用新的剩余库存值更新存储。
  • request_approval_activity:如果付款金额超过 50,000 美元,则寻求经理的批准。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 order-processor 目录:

cd workflows/python/sdk/order-processor

安装 Dapr Python SDK 包:

pip3 install -r requirements.txt

返回到 python/sdk 目录:

cd ..

步骤 3:运行订单处理器应用程序

在终端中,使用 Multi-App Run 启动订单处理器应用程序及其 Dapr 边车。从 python/sdk 目录运行以下命令:

dapr run -f .

这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。

预期输出:

== APP == Starting order workflow, purchasing 10 of cars
== APP == 2023-06-06 09:35:52.945 durabletask-worker INFO: Successfully connected to 127.0.0.1:65406. Waiting for work items...
== APP == INFO:NotifyActivity:Received order f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars at $150000 !
== APP == INFO:VerifyInventoryActivity:Verifying inventory for order f4e1926e-3721-478d-be8a-f5bebd1995da of 10 cars
== APP == INFO:VerifyInventoryActivity:There are 100 Cars available for purchase
== APP == INFO:RequestApprovalActivity:Requesting approval for payment of 165000 USD for 10 cars
== APP == 2023-06-06 09:36:05.969 durabletask-worker INFO: f4e1926e-3721-478d-be8a-f5bebd1995da Event raised: manager_approval
== APP == INFO:NotifyActivity:Payment for order f4e1926e-3721-478d-be8a-f5bebd1995da has been approved!
== APP == INFO:ProcessPaymentActivity:Processing payment: f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars at 150000 USD
== APP == INFO:ProcessPaymentActivity:Payment for request ID f4e1926e-3721-478d-be8a-f5bebd1995da processed successfully
== APP == INFO:UpdateInventoryActivity:Checking inventory for order f4e1926e-3721-478d-be8a-f5bebd1995da for 10 cars
== APP == INFO:UpdateInventoryActivity:There are now 90 cars left in stock
== APP == INFO:NotifyActivity:Order f4e1926e-3721-478d-be8a-f5bebd1995da has completed!
== APP == 2023-06-06 09:36:06.106 durabletask-worker INFO: f4e1926e-3721-478d-be8a-f5bebd1995da: Orchestration completed with status: COMPLETED
== APP == Workflow completed! Result: Completed
== APP == Purchase of item is  Completed

(可选)步骤 4:在 Zipkin 中查看

运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:

docker run -d -p 9411:9411 openzipkin/zipkin

在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

发生了什么?

当您运行 dapr run -f . 时:

  1. 为工作流生成了一个唯一的订单 ID(在上面的示例中为 f4e1926e-3721-478d-be8a-f5bebd1995da),并调度了工作流。
  2. NotifyActivity 工作流活动发送通知,表示已收到 10 辆车的订单。
  3. ReserveInventoryActivity 工作流活动检查库存数据,确定您是否可以供应订购的商品,并响应库存中的汽车数量。
  4. 您的工作流启动并通知您其状态。
  5. ProcessPaymentActivity 工作流活动开始处理订单 f4e1926e-3721-478d-be8a-f5bebd1995da 的付款,并确认是否成功。
  6. UpdateInventoryActivity 工作流活动在订单处理后更新库存中的当前可用汽车。
  7. NotifyActivity 工作流活动发送通知,表示订单 f4e1926e-3721-478d-be8a-f5bebd1995da 已完成。
  8. 工作流终止为已完成。

order-processor/app.py

在应用程序的程序文件中,您将会:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的工作流活动
class WorkflowConsoleApp:    
    def main(self):
        # Register workflow and activities
        workflowRuntime = WorkflowRuntime(settings.DAPR_RUNTIME_HOST, settings.DAPR_GRPC_PORT)
        workflowRuntime.register_workflow(order_processing_workflow)
        workflowRuntime.register_activity(notify_activity)
        workflowRuntime.register_activity(requst_approval_activity)
        workflowRuntime.register_activity(verify_inventory_activity)
        workflowRuntime.register_activity(process_payment_activity)
        workflowRuntime.register_activity(update_inventory_activity)
        workflowRuntime.start()

        print("==========Begin the purchase of item:==========", flush=True)
        item_name = default_item_name
        order_quantity = 10

        total_cost = int(order_quantity) * baseInventory[item_name].per_item_cost
        order = OrderPayload(item_name=item_name, quantity=int(order_quantity), total_cost=total_cost)

        # Start Workflow
        print(f'Starting order workflow, purchasing {order_quantity} of {item_name}', flush=True)
        start_resp = daprClient.start_workflow(workflow_component=workflow_component,
                                               workflow_name=workflow_name,
                                               input=order)
        _id = start_resp.instance_id

        def prompt_for_approval(daprClient: DaprClient):
            daprClient.raise_workflow_event(instance_id=_id, workflow_component=workflow_component, 
                                            event_name="manager_approval", event_data={'approval': True})

        approval_seeked = False
        start_time = datetime.now()
        while True:
            time_delta = datetime.now() - start_time
            state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component)
            if not state:
                print("Workflow not found!")  # not expected
            elif state.runtime_status == "Completed" or\
                    state.runtime_status == "Failed" or\
                    state.runtime_status == "Terminated":
                print(f'Workflow completed! Result: {state.runtime_status}', flush=True)
                break
            if time_delta.total_seconds() >= 10:
                state = daprClient.get_workflow(instance_id=_id, workflow_component=workflow_component)
                if total_cost > 50000 and (
                    state.runtime_status != "Completed" or 
                    state.runtime_status != "Failed" or
                    state.runtime_status != "Terminated"
                    ) and not approval_seeked:
                    approval_seeked = True
                    threading.Thread(target=prompt_for_approval(daprClient), daemon=True).start()
            
        print("Purchase of item is ", state.runtime_status, flush=True)

    def restock_inventory(self, daprClient: DaprClient, baseInventory):
        for key, item in baseInventory.items():
            print(f'item: {item}')
            item_str = f'{{"name": "{item.item_name}", "quantity": {item.quantity},\
                          "per_item_cost": {item.per_item_cost}}}'
            daprClient.save_state("statestore-actors", key, item_str)

if __name__ == '__main__':
    app = WorkflowConsoleApp()
    app.main()

order-processor/workflow.py

workflow.py 中,工作流被定义为一个类,包含其所有相关任务(由工作流活动确定)。

  def order_processing_workflow(ctx: DaprWorkflowContext, order_payload_str: OrderPayload):
    """Defines the order processing workflow.
    When the order is received, the inventory is checked to see if there is enough inventory to
    fulfill the order. If there is enough inventory, the payment is processed and the inventory is
    updated. If there is not enough inventory, the order is rejected.
    If the total order is greater than $50,000, the order is sent to a manager for approval.
    """
    order_id = ctx.instance_id
    order_payload=json.loads(order_payload_str)
    yield ctx.call_activity(notify_activity, 
                            input=Notification(message=('Received order ' +order_id+ ' for '
                                               +f'{order_payload["quantity"]}' +' ' +f'{order_payload["item_name"]}'
                                               +' at $'+f'{order_payload["total_cost"]}' +' !')))
    result = yield ctx.call_activity(verify_inventory_activity,
                                     input=InventoryRequest(request_id=order_id,
                                                            item_name=order_payload["item_name"],
                                                            quantity=order_payload["quantity"]))
    if not result.success:
        yield ctx.call_activity(notify_activity,
                                input=Notification(message='Insufficient inventory for '
                                                   +f'{order_payload["item_name"]}'+'!'))
        return OrderResult(processed=False)
    
    if order_payload["total_cost"] > 50000:
        yield ctx.call_activity(requst_approval_activity, input=order_payload)
        approval_task = ctx.wait_for_external_event("manager_approval")
        timeout_event = ctx.create_timer(timedelta(seconds=200))
        winner = yield when_any([approval_task, timeout_event])
        if winner == timeout_event:
            yield ctx.call_activity(notify_activity, 
                                    input=Notification(message='Payment for order '+order_id
                                                       +' has been cancelled due to timeout!'))
            return OrderResult(processed=False)
        approval_result = yield approval_task
        if approval_result["approval"]:
            yield ctx.call_activity(notify_activity, input=Notification(
                message=f'Payment for order {order_id} has been approved!'))
        else:
            yield ctx.call_activity(notify_activity, input=Notification(
                message=f'Payment for order {order_id} has been rejected!'))
            return OrderResult(processed=False)    
    
    yield ctx.call_activity(process_payment_activity, input=PaymentRequest(
        request_id=order_id, item_being_purchased=order_payload["item_name"],
        amount=order_payload["total_cost"], quantity=order_payload["quantity"]))

    try:
        yield ctx.call_activity(update_inventory_activity, 
                                input=PaymentRequest(request_id=order_id,
                                                     item_being_purchased=order_payload["item_name"],
                                                     amount=order_payload["total_cost"],
                                                     quantity=order_payload["quantity"]))
    except Exception:
        yield ctx.call_activity(notify_activity, 
                                input=Notification(message=f'Order {order_id} Failed!'))
        return OrderResult(processed=False)

    yield ctx.call_activity(notify_activity, input=Notification(
        message=f'Order {order_id} has completed!'))
    return OrderResult(processed=True) 

order-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。工作流由四个工作流活动或任务组成:

  • notifyActivity:使用记录器在整个工作流过程中打印消息。这些消息通知用户库存不足、付款无法处理等。
  • reserveInventoryActivity:检查状态存储以确保有足够的库存可供购买。
  • requestApprovalActivity:请求超过某个阈值的订单的批准
  • processPaymentActivity:处理和授权付款。
  • updateInventoryActivity:使用新的剩余库存值更新状态存储。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 order-processor 目录:

cd workflows/javascript/sdk/order-processor

安装依赖项:

cd ./javascript/sdk
npm install
npm run build

步骤 3:运行订单处理器应用程序

在终端中,使用 Multi-App Run 启动订单处理器应用程序及其 Dapr 边车。从 javascript/sdk 目录运行以下命令:

dapr run -f .

这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。

预期输出:

== APP - workflowApp == == APP == Orchestration scheduled with ID: 0c332155-1e02-453a-a333-28cfc7777642
== APP - workflowApp == == APP == Waiting 30 seconds for instance 0c332155-1e02-453a-a333-28cfc7777642 to complete...
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 0 history event...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, EXECUTIONSTARTED=1]
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Received order 0c332155-1e02-453a-a333-28cfc7777642 for 10 item1 at a total cost of 100
== APP - workflowApp == == APP == Activity notifyActivity completed with output undefined (0 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 3 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Reserving inventory for 0c332155-1e02-453a-a333-28cfc7777642 of 10 item1
== APP - workflowApp == == APP == 2024-02-16T03:15:59.498Z INFO [HTTPClient, HTTPClient] Sidecar Started
== APP - workflowApp == == APP == There are 100 item1 in stock
== APP - workflowApp == == APP == Activity reserveInventoryActivity completed with output {"success":true,"inventoryItem":{"perItemCost":100,"quantity":100,"itemName":"item1"}} (86 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 6 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Processing payment for order item1
== APP - workflowApp == == APP == Payment of 100 for 10 item1 processed successfully
== APP - workflowApp == == APP == Activity processPaymentActivity completed with output true (4 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 9 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == Updating inventory for 0c332155-1e02-453a-a333-28cfc7777642 of 10 item1
== APP - workflowApp == == APP == Inventory updated for 0c332155-1e02-453a-a333-28cfc7777642, there are now 90 item1 in stock
== APP - workflowApp == == APP == Activity updateInventoryActivity completed with output {"success":true,"inventoryItem":{"perItemCost":100,"quantity":90,"itemName":"item1"}} (85 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 12 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Waiting for 1 task(s) and 0 event(s) to complete...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Returning 1 action(s)
== APP - workflowApp == == APP == Received "Activity Request" work item
== APP - workflowApp == == APP == order 0c332155-1e02-453a-a333-28cfc7777642 processed successfully!
== APP - workflowApp == == APP == Activity notifyActivity completed with output undefined (0 chars)
== APP - workflowApp == == APP == Received "Orchestrator Request" work item with instance id '0c332155-1e02-453a-a333-28cfc7777642'
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Rebuilding local state with 15 history event...
== APP - workflowApp == == APP == Processing order 0c332155-1e02-453a-a333-28cfc7777642...
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Processing 2 new history event(s): [ORCHESTRATORSTARTED=1, TASKCOMPLETED=1]
== APP - workflowApp == == APP == Order 0c332155-1e02-453a-a333-28cfc7777642 processed successfully!
== APP - workflowApp == == APP == 0c332155-1e02-453a-a333-28cfc7777642: Orchestration completed with status COMPLETED
== APP - workflowApp == time="2024-02-15T21:15:59.5589687-06:00" level=info msg="0c332155-1e02-453a-a333-28cfc7777642: 'orderProcessingWorkflow' completed with a COMPLETED status." app_id=activity-sequence-workflow instance=kaibocai-devbox scope=wfengine.backend type=log ver=1.12.4
== APP - workflowApp == == APP == Instance 0c332155-1e02-453a-a333-28cfc7777642 completed

(可选)步骤 4:在 Zipkin 中查看

运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:

docker run -d -p 9411:9411 openzipkin/zipkin

在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

发生了什么?

当您运行 dapr run -f . 时:

  1. 为工作流生成了一个唯一的订单 ID(在上面的示例中为 0c332155-1e02-453a-a333-28cfc7777642),并调度了工作流。
  2. notifyActivity 工作流活动发送通知,表示已收到 10 辆车的订单。
  3. reserveInventoryActivity 工作流活动检查库存数据,确定您是否可以供应订购的商品,并响应库存中的汽车数量。
  4. 您的工作流启动并通知您其状态。
  5. processPaymentActivity 工作流活动开始处理订单 0c332155-1e02-453a-a333-28cfc7777642 的付款,并确认是否成功。
  6. updateInventoryActivity 工作流活动在订单处理后更新库存中的当前可用汽车。
  7. notifyActivity 工作流活动发送通知,表示订单 0c332155-1e02-453a-a333-28cfc7777642 已完成。
  8. 工作流终止为已完成。

order-processor/workflowApp.ts

在应用程序文件中,您将会:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的工作流活动
import { DaprWorkflowClient, WorkflowRuntime, DaprClient } from "@dapr/dapr-dev";
import { InventoryItem, OrderPayload } from "./model";
import { notifyActivity, orderProcessingWorkflow, processPaymentActivity, requestApprovalActivity, reserveInventoryActivity, updateInventoryActivity } from "./orderProcessingWorkflow";

async function start() {
  // Update the gRPC client and worker to use a local address and port
  const workflowClient = new DaprWorkflowClient();
  const workflowWorker = new WorkflowRuntime();

  const daprClient = new DaprClient();
  const storeName = "statestore";

  const inventory = new InventoryItem("item1", 100, 100);
  const key = inventory.itemName;

  await daprClient.state.save(storeName, [
    {
      key: key,
      value: inventory,
    }
  ]);

  const order = new OrderPayload("item1", 100, 10);

  workflowWorker
  .registerWorkflow(orderProcessingWorkflow)
  .registerActivity(notifyActivity)
  .registerActivity(reserveInventoryActivity)
  .registerActivity(requestApprovalActivity)
  .registerActivity(processPaymentActivity)
  .registerActivity(updateInventoryActivity);

  // Wrap the worker startup in a try-catch block to handle any errors during startup
  try {
    await workflowWorker.start();
    console.log("Workflow runtime started successfully");
  } catch (error) {
    console.error("Error starting workflow runtime:", error);
  }

  // Schedule a new orchestration
  try {
    const id = await workflowClient.scheduleNewWorkflow(orderProcessingWorkflow, order);
    console.log(`Orchestration scheduled with ID: ${id}`);

    // Wait for orchestration completion
    const state = await workflowClient.waitForWorkflowCompletion(id, undefined, 30);

    console.log(`Orchestration completed! Result: ${state?.serializedOutput}`);
  } catch (error) {
    console.error("Error scheduling or waiting for orchestration:", error);
    throw error;
  }

  await workflowWorker.stop();
  await workflowClient.stop();
}

start().catch((e) => {
  console.error(e);
  process.exit(1);
});

order-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。工作流由四个工作流活动或任务组成:

  • NotifyActivity:使用记录器在整个工作流过程中打印消息
  • ReserveInventoryActivity:检查状态存储以确保有足够的库存可供购买
  • ProcessPaymentActivity:处理和授权付款
  • UpdateInventoryActivity:从状态存储中移除请求的商品,并使用新的剩余库存值更新存储

步骤 1:先决条件

对于此示例,您将需要:

注意: .NET 7 是 Dapr v1.15 中 Dapr.Workflows 支持的最低版本。只有 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 order-processor 目录:

cd workflows/csharp/sdk/order-processor

安装依赖项:

dotnet restore
dotnet build

返回到 csharp/sdk 目录:

cd ..

步骤 3:运行订单处理器应用程序

在终端中,使用 Multi-App Run 启动订单处理器应用程序及其 Dapr 边车。从 csharp/sdk 目录运行以下命令:

dapr run -f .

这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。

预期输出:

== APP == Starting workflow 6d2abcc9 purchasing 10 Cars

== APP == info: Microsoft.DurableTask.Client.Grpc.GrpcDurableTaskClient[40]
== APP ==       Scheduling new OrderProcessingWorkflow orchestration with instance ID '6d2abcc9' and 47 bytes of input data.
== APP == info: WorkflowConsoleApp.Activities.NotifyActivity[0]
== APP ==       Received order 6d2abcc9 for 10 Cars at $15000
== APP == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
== APP ==       Reserving inventory for order 6d2abcc9 of 10 Cars
== APP == info: WorkflowConsoleApp.Activities.ReserveInventoryActivity[0]
== APP ==       There are: 100, Cars available for purchase

== APP == Your workflow has started. Here is the status of the workflow: Dapr.Workflow.WorkflowState

== APP == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
== APP ==       Processing payment: 6d2abcc9 for 10 Cars at $15000
== APP == info: WorkflowConsoleApp.Activities.ProcessPaymentActivity[0]
== APP ==       Payment for request ID '6d2abcc9' processed successfully
== APP == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[0]
== APP ==       Checking Inventory for: Order# 6d2abcc9 for 10 Cars
== APP == info: WorkflowConsoleApp.Activities.UpdateInventoryActivity[0]
== APP ==       There are now: 90 Cars left in stock
== APP == info: WorkflowConsoleApp.Activities.NotifyActivity[0]
== APP ==       Order 6d2abcc9 has completed!

== APP == Workflow Status: Completed

(可选)步骤 4:在 Zipkin 中查看

运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:

docker run -d -p 9411:9411 openzipkin/zipkin

在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

发生了什么?

当您运行 dapr run -f . 时:

  1. 为工作流生成了一个唯一的订单 ID(在上面的示例中为 6d2abcc9),并调度了工作流。
  2. NotifyActivity 工作流活动发送通知,表示已收到 10 辆车的订单。
  3. ReserveInventoryActivity 工作流活动检查库存数据,确定您是否可以供应订购的商品,并响应库存中的汽车数量。
  4. 您的工作流启动并通知您其状态。
  5. ProcessPaymentActivity 工作流活动开始处理订单 6d2abcc9 的付款,并确认是否成功。
  6. UpdateInventoryActivity 工作流活动在订单处理后更新库存中的当前可用汽车。
  7. NotifyActivity 工作流活动发送通知,表示订单 6d2abcc9 已完成。
  8. 工作流终止为已完成。

order-processor/Program.cs

在应用程序的程序文件中,您将会:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的工作流活动
using Dapr.Client;
using Dapr.Workflow;
//...

{
    services.AddDaprWorkflow(options =>
    {
        // Note that it's also possible to register a lambda function as the workflow
        // or activity implementation instead of a class.
        options.RegisterWorkflow<OrderProcessingWorkflow>();

        // These are the activities that get invoked by the workflow(s).
        options.RegisterActivity<NotifyActivity>();
        options.RegisterActivity<ReserveInventoryActivity>();
        options.RegisterActivity<ProcessPaymentActivity>();
        options.RegisterActivity<UpdateInventoryActivity>();
    });
};

//...

// Generate a unique ID for the workflow
string orderId = Guid.NewGuid().ToString()[..8];
string itemToPurchase = "Cars";
int ammountToPurchase = 10;

// Construct the order
OrderPayload orderInfo = new OrderPayload(itemToPurchase, 15000, ammountToPurchase);

// Start the workflow
Console.WriteLine("Starting workflow {0} purchasing {1} {2}", orderId, ammountToPurchase, itemToPurchase);

await daprWorkflowClient.ScheduleNewWorkflowAsync(
    name: nameof(OrderProcessingWorkflow),
    input: orderInfo,
    instanceId: orderId);

// Wait for the workflow to start and confirm the input
WorkflowState state = await daprWorkflowClient.WaitForWorkflowStartAsync(
    instanceId: orderId);

Console.WriteLine($"{nameof(OrderProcessingWorkflow)} (ID = {orderId}) started successfully with {state.ReadInputAs<OrderPayload>()}");

// Wait for the workflow to complete
using var ctx = new CancellationTokenSource(TimeSpan.FromSeconds(5));
state = await daprClient.WaitForWorkflowCompletionAsync(
    instanceId: orderId,
    cancellation: ctx.Token);

Console.WriteLine("Workflow Status: {0}", state.ReadCustomStatusAs<string>());

order-processor/Workflows/OrderProcessingWorkflow.cs

OrderProcessingWorkflow.cs 中,工作流被定义为一个类,包含其所有相关任务(由工作流活动确定)。

using Dapr.Workflow;
//...

class OrderProcessingWorkflow : Workflow<OrderPayload, OrderResult>
    {
        public override async Task<OrderResult> RunAsync(WorkflowContext context, OrderPayload order)
        {
            string orderId = context.InstanceId;

            // Notify the user that an order has come through
            await context.CallActivityAsync(
                nameof(NotifyActivity),
                new Notification($"Received order {orderId} for {order.Quantity} {order.Name} at ${order.TotalCost}"));

            string requestId = context.InstanceId;

            // Determine if there is enough of the item available for purchase by checking the inventory
            InventoryResult result = await context.CallActivityAsync<InventoryResult>(
                nameof(ReserveInventoryActivity),
                new InventoryRequest(RequestId: orderId, order.Name, order.Quantity));

            // If there is insufficient inventory, fail and let the user know 
            if (!result.Success)
            {
                // End the workflow here since we don't have sufficient inventory
                await context.CallActivityAsync(
                    nameof(NotifyActivity),
                    new Notification($"Insufficient inventory for {order.Name}"));
                return new OrderResult(Processed: false);
            }

            // There is enough inventory available so the user can purchase the item(s). Process their payment
            await context.CallActivityAsync(
                nameof(ProcessPaymentActivity),
                new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost));

            try
            {
                // There is enough inventory available so the user can purchase the item(s). Process their payment
                await context.CallActivityAsync(
                    nameof(UpdateInventoryActivity),
                    new PaymentRequest(RequestId: orderId, order.Name, order.Quantity, order.TotalCost));                
            }
            catch (WorkflowTaskFailedException)
            {
                // Let them know their payment was processed
                await context.CallActivityAsync(
                    nameof(NotifyActivity),
                    new Notification($"Order {orderId} Failed! You are now getting a refund"));
                return new OrderResult(Processed: false);
            }

            // Let them know their payment was processed
            await context.CallActivityAsync(
                nameof(NotifyActivity),
                new Notification($"Order {orderId} has completed!"));

            // End the workflow with a success result
            return new OrderResult(Processed: true);
        }
    }

order-processor/Activities 目录

Activities 目录包含工作流使用的四个工作流活动,定义在以下文件中:

  • NotifyActivity.cs
  • ReserveInventoryActivity.cs
  • ProcessPaymentActivity.cs
  • UpdateInventoryActivity.cs

观看演示

观看此视频以了解 Dapr Workflow .NET 演示

order-processor 控制台应用程序启动并管理订单处理工作流的生命周期,该工作流在状态存储中存储和检索数据。工作流由四个工作流活动或任务组成:

  • NotifyActivity:使用记录器在整个工作流过程中打印消息
  • RequestApprovalActivity:请求处理付款的批准
  • ReserveInventoryActivity:检查状态存储以确保有足够的库存可供购买
  • ProcessPaymentActivity:处理和授权付款
  • UpdateInventoryActivity:从状态存储中移除请求的商品,并使用新的剩余库存值更新存储

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

导航到 order-processor 目录:

cd workflows/java/sdk/order-processor

安装依赖项:

mvn clean install

返回到 java/sdk 目录:

cd ..

步骤 3:运行订单处理器应用程序

在终端中,使用 Multi-App Run 启动订单处理器应用程序及其 Dapr 边车。从 java/sdk 目录运行以下命令:

cd workflows/java/sdk
dapr run -f .

这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。

预期输出:

== APP == *** Welcome to the Dapr Workflow console app sample!
== APP == *** Using this app, you can place orders that start workflows.
== APP == Start workflow runtime
== APP == Sep 20, 2023 3:23:05 PM com.microsoft.durabletask.DurableTaskGrpcWorker startAndBlock
== APP == INFO: Durable Task worker is connecting to sidecar at 127.0.0.1:50001.

== APP == ==========Begin the purchase of item:==========
== APP == Starting order workflow, purchasing 10 of cars

== APP == scheduled new workflow instance of OrderProcessingWorkflow with instance ID: edceba90-9c45-4be8-ad40-60d16e060797
== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Starting Workflow: io.dapr.quickstarts.workflows.OrderProcessingWorkflow
== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Instance ID(order ID): edceba90-9c45-4be8-ad40-60d16e060797
== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Current Orchestration Time: 2023-09-20T19:23:09.755Z
== APP == [Thread-0] INFO io.dapr.workflows.WorkflowContext - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Received Order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP == workflow instance edceba90-9c45-4be8-ad40-60d16e060797 started
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserving inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - There are 100 cars available for purchase
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ReserveInventoryActivity - Reserved inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.RequestApprovalActivity - Approved requesting approval for order: OrderPayload [itemName=cars, totalCost=150000, quantity=10]
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Processing payment: edceba90-9c45-4be8-ad40-60d16e060797 for 10 cars at $150000
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.ProcessPaymentActivity - Payment for request ID 'edceba90-9c45-4be8-ad40-60d16e060797' processed successfully
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updating inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797' of 10 cars
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.UpdateInventoryActivity - Updated inventory for order 'edceba90-9c45-4be8-ad40-60d16e060797': there are now 90 cars left in stock
== APP == [Thread-0] INFO io.dapr.quickstarts.workflows.activities.NotifyActivity - Order completed! : edceba90-9c45-4be8-ad40-60d16e060797

== APP == workflow instance edceba90-9c45-4be8-ad40-60d16e060797 completed, out is: {"processed":true}

(可选)步骤 4:在 Zipkin 中查看

运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:

docker run -d -p 9411:9411 openzipkin/zipkin

在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

发生了什么?

当您运行 dapr run -f . 时:

  1. 为工作流生成了一个唯一的订单 ID(在上面的示例中为 edceba90-9c45-4be8-ad40-60d16e060797),并调度了工作流。
  2. NotifyActivity 工作流活动发送通知,表示已收到 10 辆车的订单。
  3. ReserveInventoryActivity 工作流活动检查库存数据,确定您是否可以供应订购的商品,并响应库存中的汽车数量。
  4. 一旦获得批准,您的工作流启动并通知您其状态。
  5. ProcessPaymentActivity 工作流活动开始处理订单 edceba90-9c45-4be8-ad40-60d16e060797 的付款,并确认是否成功。
  6. UpdateInventoryActivity 工作流活动在订单处理后更新库存中的当前可用汽车。
  7. NotifyActivity 工作流活动发送通知,表示订单 edceba90-9c45-4be8-ad40-60d16e060797 已完成。
  8. 工作流终止为已完成。

order-processor/WorkflowConsoleApp.java

在应用程序的程序文件中,您将会:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的工作流活动
package io.dapr.quickstarts.workflows;
import io.dapr.client.DaprClient;
import io.dapr.client.DaprClientBuilder;
import io.dapr.workflows.client.DaprWorkflowClient;

public class WorkflowConsoleApp {

  private static final String STATE_STORE_NAME = "statestore-actors";

  // ...
  public static void main(String[] args) throws Exception {
    System.out.println("*** Welcome to the Dapr Workflow console app sample!");
    System.out.println("*** Using this app, you can place orders that start workflows.");
    // Wait for the sidecar to become available
    Thread.sleep(5 * 1000);

    // Register the OrderProcessingWorkflow and its activities with the builder.
    WorkflowRuntimeBuilder builder = new WorkflowRuntimeBuilder().registerWorkflow(OrderProcessingWorkflow.class);
    builder.registerActivity(NotifyActivity.class);
    builder.registerActivity(ProcessPaymentActivity.class);
    builder.registerActivity(RequestApprovalActivity.class);
    builder.registerActivity(ReserveInventoryActivity.class);
    builder.registerActivity(UpdateInventoryActivity.class);

    // Build the workflow runtime
    try (WorkflowRuntime runtime = builder.build()) {
      System.out.println("Start workflow runtime");
      runtime.start(false);
    }

    InventoryItem inventory = prepareInventoryAndOrder();

    DaprWorkflowClient workflowClient = new DaprWorkflowClient();
    try (workflowClient) {
      executeWorkflow(workflowClient, inventory);
    }

  }

  // Start the workflow runtime, pulling and executing tasks
  private static void executeWorkflow(DaprWorkflowClient workflowClient, InventoryItem inventory) {
    System.out.println("==========Begin the purchase of item:==========");
    String itemName = inventory.getName();
    int orderQuantity = inventory.getQuantity();
    int totalcost = orderQuantity * inventory.getPerItemCost();
    OrderPayload order = new OrderPayload();
    order.setItemName(itemName);
    order.setQuantity(orderQuantity);
    order.setTotalCost(totalcost);
    System.out.println("Starting order workflow, purchasing " + orderQuantity + " of " + itemName);

    String instanceId = workflowClient.scheduleNewWorkflow(OrderProcessingWorkflow.class, order);
    System.out.printf("scheduled new workflow instance of OrderProcessingWorkflow with instance ID: %s%n",
        instanceId);

    // Check workflow instance start status
    try {
      workflowClient.waitForInstanceStart(instanceId, Duration.ofSeconds(10), false);
      System.out.printf("workflow instance %s started%n", instanceId);
    } catch (TimeoutException e) {
      System.out.printf("workflow instance %s did not start within 10 seconds%n", instanceId);
      return;
    }

    // Check workflow instance complete status
    try {
      WorkflowInstanceStatus workflowStatus = workflowClient.waitForInstanceCompletion(instanceId,
          Duration.ofSeconds(30),
          true);
      if (workflowStatus != null) {
        System.out.printf("workflow instance %s completed, out is: %s %n", instanceId,
            workflowStatus.getSerializedOutput());
      } else {
        System.out.printf("workflow instance %s not found%n", instanceId);
      }
    } catch (TimeoutException e) {
      System.out.printf("workflow instance %s did not complete within 30 seconds%n", instanceId);
    }

  }

  private static InventoryItem prepareInventoryAndOrder() {
    // prepare 100 cars in inventory
    InventoryItem inventory = new InventoryItem();
    inventory.setName("cars");
    inventory.setPerItemCost(15000);
    inventory.setQuantity(100);
    DaprClient daprClient = new DaprClientBuilder().build();
    restockInventory(daprClient, inventory);

    // prepare order for 10 cars
    InventoryItem order = new InventoryItem();
    order.setName("cars");
    order.setPerItemCost(15000);
    order.setQuantity(10);
    return order;
  }

  private static void restockInventory(DaprClient daprClient, InventoryItem inventory) {
    String key = inventory.getName();
    daprClient.saveState(STATE_STORE_NAME, key, inventory).block();
  }
}

OrderProcessingWorkflow.java

OrderProcessingWorkflow.java 中,工作流被定义为一个类,包含其所有相关任务(由工作流活动确定)。

package io.dapr.quickstarts.workflows;
import io.dapr.workflows.Workflow;

public class OrderProcessingWorkflow extends Workflow {

  @Override
  public WorkflowStub create() {
    return ctx -> {
      Logger logger = ctx.getLogger();
      String orderId = ctx.getInstanceId();
      logger.info("Starting Workflow: " + ctx.getName());
      logger.info("Instance ID(order ID): " + orderId);
      logger.info("Current Orchestration Time: " + ctx.getCurrentInstant());

      OrderPayload order = ctx.getInput(OrderPayload.class);
      logger.info("Received Order: " + order.toString());
      OrderResult orderResult = new OrderResult();
      orderResult.setProcessed(false);

      // Notify the user that an order has come through
      Notification notification = new Notification();
      notification.setMessage("Received Order: " + order.toString());
      ctx.callActivity(NotifyActivity.class.getName(), notification).await();

      // Determine if there is enough of the item available for purchase by checking
      // the inventory
      InventoryRequest inventoryRequest = new InventoryRequest();
      inventoryRequest.setRequestId(orderId);
      inventoryRequest.setItemName(order.getItemName());
      inventoryRequest.setQuantity(order.getQuantity());
      InventoryResult inventoryResult = ctx.callActivity(ReserveInventoryActivity.class.getName(),
          inventoryRequest, InventoryResult.class).await();

      // If there is insufficient inventory, fail and let the user know
      if (!inventoryResult.isSuccess()) {
        notification.setMessage("Insufficient inventory for order : " + order.getItemName());
        ctx.callActivity(NotifyActivity.class.getName(), notification).await();
        ctx.complete(orderResult);
        return;
      }

      // Require orders over a certain threshold to be approved
      if (order.getTotalCost() > 5000) {
        ApprovalResult approvalResult = ctx.callActivity(RequestApprovalActivity.class.getName(),
            order, ApprovalResult.class).await();
        if (approvalResult != ApprovalResult.Approved) {
          notification.setMessage("Order " + order.getItemName() + " was not approved.");
          ctx.callActivity(NotifyActivity.class.getName(), notification).await();
          ctx.complete(orderResult);
          return;
        }
      }

      // There is enough inventory available so the user can purchase the item(s).
      // Process their payment
      PaymentRequest paymentRequest = new PaymentRequest();
      paymentRequest.setRequestId(orderId);
      paymentRequest.setItemBeingPurchased(order.getItemName());
      paymentRequest.setQuantity(order.getQuantity());
      paymentRequest.setAmount(order.getTotalCost());
      boolean isOK = ctx.callActivity(ProcessPaymentActivity.class.getName(),
          paymentRequest, boolean.class).await();
      if (!isOK) {
        notification.setMessage("Payment failed for order : " + orderId);
        ctx.callActivity(NotifyActivity.class.getName(), notification).await();
        ctx.complete(orderResult);
        return;
      }

      inventoryResult = ctx.callActivity(UpdateInventoryActivity.class.getName(),
          inventoryRequest, InventoryResult.class).await();
      if (!inventoryResult.isSuccess()) {
        // If there is an error updating the inventory, refund the user
        // paymentRequest.setAmount(-1 * paymentRequest.getAmount());
        // ctx.callActivity(ProcessPaymentActivity.class.getName(),
        // paymentRequest).await();

        // Let users know their payment processing failed
        notification.setMessage("Order failed to update inventory! : " + orderId);
        ctx.callActivity(NotifyActivity.class.getName(), notification).await();
        ctx.complete(orderResult);
        return;
      }

      // Let user know their order was processed
      notification.setMessage("Order completed! : " + orderId);
      ctx.callActivity(NotifyActivity.class.getName(), notification).await();

      // Complete the workflow with order result is processed
      orderResult.setProcessed(true);
      ctx.complete(orderResult);
    };
  }

}

activities 目录

Activities 目录包含工作流使用的四个工作流活动,定义在以下文件中:

order-processor 控制台应用程序启动并管理 OrderProcessingWorkflow 工作流,该工作流模拟从商店购买商品。工作流由五个独特的工作流活动或任务组成:

  • NotifyActivity:使用记录器在整个工作流过程中打印消息。这些消息通知您:
    • 您的库存不足
    • 您的付款无法处理,等等。
  • ProcessPaymentActivity:处理和授权付款。
  • VerifyInventoryActivity:检查状态存储以确保有足够的库存可供购买。
  • UpdateInventoryActivity:从状态存储中移除请求的商品,并使用新的剩余库存值更新存储。
  • RequestApprovalActivity:如果付款金额超过 50,000 美元,则寻求经理的批准。

步骤 1:先决条件

对于此示例,您将需要:

步骤 2:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在新的终端窗口中,导航到 sdk 目录:

cd workflows/go/sdk

步骤 3:运行订单处理器应用程序

在终端中,使用 Multi-App Run 启动订单处理器应用程序及其 Dapr 边车。从 go/sdk 目录运行以下命令:

dapr run -f .

这将启动具有唯一工作流 ID 的 order-processor 应用程序并运行工作流活动。

预期输出:

== APP - order-processor == *** Welcome to the Dapr Workflow console app sample!
== APP - order-processor == *** Using this app, you can place orders that start workflows.
== APP - order-processor == dapr client initializing for: 127.0.0.1:50056
== APP - order-processor == adding base stock item: paperclip
== APP - order-processor == 2024/02/01 12:59:52 work item listener started
== APP - order-processor == INFO: 2024/02/01 12:59:52 starting background processor
== APP - order-processor == adding base stock item: cars
== APP - order-processor == adding base stock item: computers
== APP - order-processor == ==========Begin the purchase of item:==========
== APP - order-processor == NotifyActivity: Received order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 for 10 cars - $150000
== APP - order-processor == VerifyInventoryActivity: Verifying inventory for order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 of 10 cars
== APP - order-processor == VerifyInventoryActivity: There are 100 cars available for purchase
== APP - order-processor == RequestApprovalActivity: Requesting approval for payment of 150000USD for 10 cars
== APP - order-processor == NotifyActivity: Payment for order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 has been approved!
== APP - order-processor == ProcessPaymentActivity: 48ee83b7-5d80-48d5-97f9-6b372f5480a5 for 10 - cars (150000USD)
== APP - order-processor == UpdateInventoryActivity: Checking Inventory for order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 for 10 * cars
== APP - order-processor == UpdateInventoryActivity: There are now 90 cars left in stock
== APP - order-processor == NotifyActivity: Order 48ee83b7-5d80-48d5-97f9-6b372f5480a5 has completed!
== APP - order-processor == Workflow completed - result: COMPLETED
== APP - order-processor == Purchase of item is complete

使用 CTRL+C 或以下命令停止 Dapr 工作流:

dapr stop -f .

(可选)步骤 4:在 Zipkin 中查看

运行 dapr init 会启动 openzipkin/zipkin Docker 容器。如果容器已停止运行,请使用以下命令启动 Zipkin Docker 容器:

docker run -d -p 9411:9411 openzipkin/zipkin

在 Zipkin Web UI 中查看工作流跟踪跨度(通常位于 http://localhost:9411/zipkin/)。

发生了什么?

当您运行 dapr run 时:

  1. 为工作流生成了一个唯一的订单 ID(在上面的示例中为 48ee83b7-5d80-48d5-97f9-6b372f5480a5),并调度了工作流。
  2. NotifyActivity 工作流活动发送通知,表示已收到 10 辆车的订单。
  3. ReserveInventoryActivity 工作流活动检查库存数据,确定您是否可以供应订购的商品,并响应库存中的汽车数量。
  4. 您的工作流启动并通知您其状态。
  5. ProcessPaymentActivity 工作流活动开始处理订单 48ee83b7-5d80-48d5-97f9-6b372f5480a5 的付款,并确认是否成功。
  6. UpdateInventoryActivity 工作流活动在订单处理后更新库存中的当前可用汽车。
  7. NotifyActivity 工作流活动发送通知,表示订单 48ee83b7-5d80-48d5-97f9-6b372f5480a5 已完成。
  8. 工作流终止为已完成。

order-processor/main.go

在应用程序的程序文件中,您将会:

  • 生成唯一的工作流订单 ID
  • 调度工作流
  • 检索工作流状态
  • 注册工作流及其调用的工作流活动
func main() {
	fmt.Println("*** Welcome to the Dapr Workflow console app sample!")
	fmt.Println("*** Using this app, you can place orders that start workflows.")

  // ...

  // Register workflow and activities
	if err := w.RegisterWorkflow(OrderProcessingWorkflow); err != nil {
		log.Fatal(err)
	}
	if err := w.RegisterActivity(NotifyActivity); err != nil {
		log.Fatal(err)
	}
	if err := w.RegisterActivity(RequestApprovalActivity); err != nil {
		log.Fatal(err)
	}
	if err := w.RegisterActivity(VerifyInventoryActivity); err != nil {
		log.Fatal(err)
	}
	if err := w.RegisterActivity(ProcessPaymentActivity); err != nil {
		log.Fatal(err)
	}
	if err := w.RegisterActivity(UpdateInventoryActivity); err != nil {
		log.Fatal(err)
	}

  // Build and start workflow runtime, pulling and executing tasks
	if err := w.Start(); err != nil {
		log.Fatal(err)
	}

	daprClient, err := client.NewClient()
	if err != nil {
		log.Fatalf("failed to initialise dapr client: %v", err)
	}
	wfClient, err := workflow.NewClient(workflow.WithDaprClient(daprClient))
	if err != nil {
		log.Fatalf("failed to initialise workflow client: %v", err)
	}

  // Check inventory
	inventory := []InventoryItem{
		{ItemName: "paperclip", PerItemCost: 5, Quantity: 100},
		{ItemName: "cars", PerItemCost: 15000, Quantity: 100},
		{ItemName: "computers", PerItemCost: 500, Quantity: 100},
	}
	if err := restockInventory(daprClient, inventory); err != nil {
		log.Fatalf("failed to restock: %v", err)
	}

	fmt.Println("==========Begin the purchase of item:==========")

	itemName := defaultItemName
	orderQuantity := 10

	totalCost := inventory[1].PerItemCost * orderQuantity

	orderPayload := OrderPayload{
		ItemName:  itemName,
		Quantity:  orderQuantity,
		TotalCost: totalCost,
	}

  // Start workflow events, like receiving order, verifying inventory, and processing payment 
	id, err := wfClient.ScheduleNewWorkflow(context.Background(), workflowName, workflow.WithInput(orderPayload))
	if err != nil {
		log.Fatalf("failed to start workflow: %v", err)
	}

	// ...

  // Notification that workflow has completed or failed
	for {
		timeDelta := time.Since(startTime)
		metadata, err := wfClient.FetchWorkflowMetadata(context.Background(), id)
		if err != nil {
			log.Fatalf("failed to fetch workflow: %v", err)
		}
		if (metadata.RuntimeStatus == workflow.StatusCompleted) || (metadata.RuntimeStatus == workflow.StatusFailed) || (metadata.RuntimeStatus == workflow.StatusTerminated) {
			fmt.Printf("Workflow completed - result: %v\n", metadata.RuntimeStatus.String())
			break
		}
		if timeDelta.Seconds() >= 10 {
			metadata, err := wfClient.FetchWorkflowMetadata(context.Background(), id)
			if err != nil {
				log.Fatalf("failed to fetch workflow: %v", err)
			}
			if totalCost > 50000 && !approvalSought && ((metadata.RuntimeStatus != workflow.StatusCompleted) || (metadata.RuntimeStatus != workflow.StatusFailed) || (metadata.RuntimeStatus != workflow.StatusTerminated)) {
				approvalSought = true
				promptForApproval(id)
			}
		}
		// Sleep to not DoS the dapr dev instance
		time.Sleep(time.Second)
	}

	fmt.Println("Purchase of item is complete")
}

// Request approval (RequestApprovalActivity)
func promptForApproval(id string) {
	wfClient, err := workflow.NewClient()
	if err != nil {
		log.Fatalf("failed to initialise wfClient: %v", err)
	}
	if err := wfClient.RaiseEvent(context.Background(), id, "manager_approval"); err != nil {
		log.Fatal(err)
	}
}

// Update inventory for remaining stock (UpdateInventoryActivity)
func restockInventory(daprClient client.Client, inventory []InventoryItem) error {
	for _, item := range inventory {
		itemSerialized, err := json.Marshal(item)
		if err != nil {
			return err
		}
		fmt.Printf("adding base stock item: %s\n", item.ItemName)
		if err := daprClient.SaveState(context.Background(), stateStoreName, item.ItemName, itemSerialized, nil); err != nil {
			return err
		}
	}
	return nil
}

同时,OrderProcessingWorkflow 及其活动在 workflow.go 中定义为方法

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的Discord 频道进行讨论。

下一步

探索 Dapr 教程 >>

4 - 快速入门:状态管理

开始使用 Dapr 的状态管理模块

本文将介绍 Dapr 的状态管理模块。在本快速入门指南中,您将学习如何使用 Redis 状态存储来保存、获取和删除状态。您可以选择以下两种方式之一:

虽然本示例使用了 Redis,您也可以替换为其他支持的状态存储

使用多应用运行

在开始之前,请选择您偏好的编程语言对应的 Dapr SDK。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/python/sdk/order-processor

安装依赖项:

pip3 install -r requirements.txt 

使用 多应用运行在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run -f .

注意:在 Windows 系统中,由于未定义 Python3.exe,您可能需要在运行 dapr run -f . 之前将 dapr.yaml 文件中的 python3 修改为 python

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

with DaprClient() as client:

    # 将状态保存到状态存储中
    client.save_state(DAPR_STORE_NAME, orderId, str(order))
    logging.info('Saving Order: %s', order)

    # 从状态存储中获取状态
    result = client.get_state(DAPR_STORE_NAME, orderId)
    logging.info('Result after get: ' + str(result.data))

    # 从状态存储中删除状态
    client.delete_state(store_name=DAPR_STORE_NAME, key=orderId)
    logging.info('Deleting Order: %s', order)

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
dapr.yaml 多应用运行模板文件

当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../resources/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    command: ["python3" , "app.py"]
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 还会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/javascript/sdk/order-processor

安装依赖项:

npm install

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run -f .

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

const client = new DaprClient()

// 将状态保存到状态存储中
await client.state.save(DAPR_STATE_STORE_NAME, order)
console.log("Saving Order: ", order)

// 从状态存储中获取状态
const savedOrder = await client.state.get(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Getting Order: ", savedOrder)

// 从状态存储中删除状态
await client.state.delete(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Deleting Order: ", order)

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == > order-processor@1.0.0 start
== APP == > node index.js
== APP == Saving Order:  { orderId: 1 }
== APP == Saving Order:  { orderId: 2 }
== APP == Saving Order:  { orderId: 3 }
== APP == Saving Order:  { orderId: 4 }
== APP == Saving Order:  { orderId: 5 }
== APP == Getting Order:  { orderId: 1 }
== APP == Deleting Order:  { orderId: 1 }
== APP == Getting Order:  { orderId: 2 }
== APP == Deleting Order:  { orderId: 2 }
== APP == Getting Order:  { orderId: 3 }
== APP == Deleting Order:  { orderId: 3 }
== APP == Getting Order:  { orderId: 4 }
== APP == Deleting Order:  { orderId: 4 }
== APP == Getting Order:  { orderId: 5 }
== APP == Deleting Order:  { orderId: 5 }
dapr.yaml 多应用运行模板文件

当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../resources/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    command: ["npm", "run", "start"]
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

注意: .NET 6 是此版本中 Dapr .NET SDK 包的最低支持版本。只有 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/csharp/sdk/order-processor

安装依赖项:

dotnet restore
dotnet build

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run -f .

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

var client = new DaprClientBuilder().Build();

// 将状态保存到状态存储中
await client.SaveStateAsync(DAPR_STORE_NAME, orderId.ToString(), order.ToString());
Console.WriteLine("Saving Order: " + order);

// 从状态存储中获取状态
var result = await client.GetStateAsync<string>(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Getting Order: " + result);

// 从状态存储中删除状态
await client.DeleteStateAsync(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Deleting Order: " + order);

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == Saving Order: Order { orderId = 1 }
== APP == Getting Order: Order { orderId = 1 }
== APP == Deleting Order: Order { orderId = 1 }
== APP == Saving Order: Order { orderId = 2 }
== APP == Getting Order: Order { orderId = 2 }
== APP == Deleting Order: Order { orderId = 2 }
== APP == Saving Order: Order { orderId = 3 }
== APP == Getting Order: Order { orderId = 3 }
== APP == Deleting Order: Order { orderId = 3 }
== APP == Saving Order: Order { orderId = 4 }
== APP == Getting Order: Order { orderId = 4 }
== APP == Deleting Order: Order { orderId = 4 }
== APP == Saving Order: Order { orderId = 5 }
== APP == Getting Order: Order { orderId = 5 }
== APP == Deleting Order: Order { orderId = 5 }
dapr.yaml 多应用运行模板文件

当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../../resources/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    command: ["dotnet", "run"]
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/java/sdk/order-processor

安装依赖项:

mvn clean install

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run -f .

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

try (DaprClient client = new DaprClientBuilder().build()) {
  for (int i = 1; i <= 10; i++) {
    int orderId = i;
    Order order = new Order();
    order.setOrderId(orderId);

    // 将状态保存到状态存储中
    client.saveState(DAPR_STATE_STORE, String.valueOf(orderId), order).block();
    LOGGER.info("Saving Order: " + order.getOrderId());

    // 从状态存储中获取状态
    State<Order> response = client.getState(DAPR_STATE_STORE, String.valueOf(orderId), Order.class).block();
    LOGGER.info("Getting Order: " + response.getValue().getOrderId());

    // 从状态存储中删除状态
    client.deleteState(DAPR_STATE_STORE, String.valueOf(orderId)).block();
    LOGGER.info("Deleting Order: " + orderId);
    TimeUnit.MILLISECONDS.sleep(1000);
  }

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
dapr.yaml 多应用运行模板文件

当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../resources/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    command: ["java", "-jar", "target/OrderProcessingService-0.0.1-SNAPSHOT.jar"]
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/go/sdk/order-processor

安装依赖项:

go build .

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run -f .

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

  client, err := dapr.NewClient()

  // 将状态保存到状态存储中
  _ = client.SaveState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId), []byte(order))
  log.Print("Saving Order: " + string(order))

  // 从状态存储中获取状态
  result, _ := client.GetState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
  fmt.Println("Getting Order: " + string(result.Value))

  // 从状态存储中删除状态
  _ = client.DeleteState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
  log.Print("Deleting Order: " + string(order))

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == dapr client initializing for: 127.0.0.1:53689
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":1}
== APP == Getting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":2}
== APP == Getting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":3}
== APP == Getting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":4}
== APP == Getting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":5}
== APP == Getting Order: {"orderId":5}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5}
dapr.yaml 多应用运行模板文件

当您运行 dapr init 时,Dapr 会创建一个名为 dapr.yaml 的默认多应用运行模板文件。运行 dapr run -f 会启动项目中的所有应用程序。在此示例中,dapr.yaml 文件包含以下内容:

version: 1
common:
  resourcesPath: ../../resources/
apps:
  - appID: order-processor
    appDirPath: ./order-processor/
    command: ["go", "run", "."]
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

一次运行一个应用程序

在开始之前,请选择您偏好的编程语言对应的 Dapr SDK。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/python/sdk/order-processor

安装依赖项:

pip3 install -r requirements.txt

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../resources/ -- python3 app.py

注意:在 Windows 系统中,由于未定义 Python3.exe,您可能需要使用 python app.py 而不是 python3 app.py

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

with DaprClient() as client:

    # 将状态保存到状态存储中
    client.save_state(DAPR_STORE_NAME, orderId, str(order))
    logging.info('Saving Order: %s', order)

    # 从状态存储中获取状态
    result = client.get_state(DAPR_STORE_NAME, orderId)
    logging.info('Result after get: ' + str(result.data))

    # 从状态存储中删除状态
    client.delete_state(store_name=DAPR_STORE_NAME, key=orderId)
    logging.info('Deleting Order: %s', order)

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/javascript/sdk/order-processor

安装依赖项,其中将包括 JavaScript SDK 的 @dapr/dapr 包:

npm install

验证服务目录中包含以下文件:

  • package.json
  • package-lock.json

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../resources/ -- npm run start

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

const client = new DaprClient()

// 将状态保存到状态存储中
await client.state.save(DAPR_STATE_STORE_NAME, order)
console.log("Saving Order: ", order)

// 从状态存储中获取状态
const savedOrder = await client.state.get(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Getting Order: ", savedOrder)

// 从状态存储中删除状态
await client.state.delete(DAPR_STATE_STORE_NAME, order.orderId)
console.log("Deleting Order: ", order)

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == > order-processor@1.0.0 start
== APP == > node index.js
== APP == Saving Order:  { orderId: 1 }
== APP == Saving Order:  { orderId: 2 }
== APP == Saving Order:  { orderId: 3 }
== APP == Saving Order:  { orderId: 4 }
== APP == Saving Order:  { orderId: 5 }
== APP == Getting Order:  { orderId: 1 }
== APP == Deleting Order:  { orderId: 1 }
== APP == Getting Order:  { orderId: 2 }
== APP == Deleting Order:  { orderId: 2 }
== APP == Getting Order:  { orderId: 3 }
== APP == Deleting Order:  { orderId: 3 }
== APP == Getting Order:  { orderId: 4 }
== APP == Deleting Order:  { orderId: 4 }
== APP == Getting Order:  { orderId: 5 }
== APP == Deleting Order:  { orderId: 5 }
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/csharp/sdk/order-processor

回忆 NuGet 包:

dotnet restore
dotnet build

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

var client = new DaprClientBuilder().Build();

// 将状态保存到状态存储中
await client.SaveStateAsync(DAPR_STORE_NAME, orderId.ToString(), order.ToString());
Console.WriteLine("Saving Order: " + order);

// 从状态存储中获取状态
var result = await client.GetStateAsync<string>(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Getting Order: " + result);

// 从状态存储中删除状态
await client.DeleteStateAsync(DAPR_STORE_NAME, orderId.ToString());
Console.WriteLine("Deleting Order: " + order);

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == Saving Order: Order { orderId = 1 }
== APP == Getting Order: Order { orderId = 1 }
== APP == Deleting Order: Order { orderId = 1 }
== APP == Saving Order: Order { orderId = 2 }
== APP == Getting Order: Order { orderId = 2 }
== APP == Deleting Order: Order { orderId = 2 }
== APP == Saving Order: Order { orderId = 3 }
== APP == Getting Order: Order { orderId = 3 }
== APP == Deleting Order: Order { orderId = 3 }
== APP == Saving Order: Order { orderId = 4 }
== APP == Getting Order: Order { orderId = 4 }
== APP == Deleting Order: Order { orderId = 4 }
== APP == Saving Order: Order { orderId = 5 }
== APP == Getting Order: Order { orderId = 5 }
== APP == Deleting Order: Order { orderId = 5 }
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/java/sdk/order-processor

安装依赖项:

mvn clean install

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../resources -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

try (DaprClient client = new DaprClientBuilder().build()) {
  for (int i = 1; i <= 10; i++) {
    int orderId = i;
    Order order = new Order();
    order.setOrderId(orderId);

    // 将状态保存到状态存储中
    client.saveState(DAPR_STATE_STORE, String.valueOf(orderId), order).block();
    LOGGER.info("Saving Order: " + order.getOrderId());

    // 从状态存储中获取状态
    State<Order> response = client.getState(DAPR_STATE_STORE, String.valueOf(orderId), Order.class).block();
    LOGGER.info("Getting Order: " + response.getValue().getOrderId());

    // 从状态存储中删除状态
    client.deleteState(DAPR_STATE_STORE, String.valueOf(orderId)).block();
    LOGGER.info("Deleting Order: " + orderId);
    TimeUnit.MILLISECONDS.sleep(1000);
  }

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == INFO:root:Saving Order: {'orderId': '1'}
== APP == INFO:root:Result after get: b"{'orderId': '1'}"
== APP == INFO:root:Deleting Order: {'orderId': '1'}
== APP == INFO:root:Saving Order: {'orderId': '2'}
== APP == INFO:root:Result after get: b"{'orderId': '2'}"
== APP == INFO:root:Deleting Order: {'orderId': '2'}
== APP == INFO:root:Saving Order: {'orderId': '3'}
== APP == INFO:root:Result after get: b"{'orderId': '3'}"
== APP == INFO:root:Deleting Order: {'orderId': '3'}
== APP == INFO:root:Saving Order: {'orderId': '4'}
== APP == INFO:root:Result after get: b"{'orderId': '4'}"
== APP == INFO:root:Deleting Order: {'orderId': '4'}
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:操作服务状态

在终端中,进入 order-processor 目录。

cd state_management/go/sdk/order-processor

安装依赖项并构建应用程序:

go build .

在 Dapr sidecar 旁边启动 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../resources -- go run .

order-processor 服务会将 orderId 键/值对写入、读取并删除到statestore.yaml 组件中定义的 statestore 实例中。服务启动后,会自动执行一个循环。

  client, err := dapr.NewClient()

  // 将状态保存到状态存储中
  _ = client.SaveState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId), []byte(order))
  log.Print("Saving Order: " + string(order))

  // 从状态存储中获取状态
  result, _ := client.GetState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
  fmt.Println("Getting Order: " + string(result.Value))

  // 从状态存储中删除状态
  _ = client.DeleteState(ctx, STATE_STORE_NAME, strconv.Itoa(orderId))
  log.Print("Deleting Order: " + string(order))

步骤 3:查看 order-processor 输出

如上代码所示,应用程序会将状态保存在 Dapr 状态存储中,读取后再删除。

Order-processor 输出:

== APP == dapr client initializing for: 127.0.0.1:53689
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":1}
== APP == Getting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":1}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":2}
== APP == Getting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":2}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":3}
== APP == Getting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":3}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":4}
== APP == Getting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":4}
== APP == 2022/04/01 09:16:03 Saving Order: {"orderId":5}
== APP == Getting Order: {"orderId":5}
== APP == 2022/04/01 09:16:03 Deleting Order: {"orderId":5}
statestore.yaml 组件文件

当您运行 dapr init 时,Dapr 会创建一个默认的 Redis statestore.yaml 并在本地机器上运行一个 Redis 容器,位置如下:

  • 在 Windows 上,位于 %UserProfile%\.dapr\components\statestore.yaml
  • 在 Linux/MacOS 上,位于 ~/.dapr/components/statestore.yaml

使用 statestore.yaml 组件,您可以轻松地替换状态存储而无需进行代码更改。

此快速入门中包含的 Redis statestore.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"

在 YAML 文件中:

  • metadata/name 是您的应用程序与组件通信的方式(在代码示例中称为 DAPR_STORE_NAME)。
  • spec/metadata 定义了组件使用的 Redis 实例的连接。

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的discord 频道进行讨论。

下一步

探索 Dapr 教程 >>

5 - 快速入门:输入和输出绑定

开始使用 Dapr 的绑定构建块

我们来了解一下 Dapr 的绑定构建块。通过使用绑定,您可以:

  • 让您的应用程序响应来自外部系统的事件。
  • 与外部系统进行交互。

在本快速入门中,您将使用输入 Cron 绑定每 10 秒调度一次批处理脚本。该脚本处理一个 JSON 文件,并使用 PostgreSQL Dapr 绑定将数据输出到 SQL 数据库。

在继续快速入门之前,请选择您偏好的 Dapr SDK 语言版本。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:本地运行 PostgreSQL Docker 容器

在您的机器上通过 Docker 容器本地运行 PostgreSQL 实例。快速入门示例中包含一个 Docker Compose 文件,用于本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。

在终端窗口中,从快速入门克隆目录的根目录导航到 bindings/db 目录。

cd bindings/db

运行以下命令以设置容器:

docker compose up

验证容器是否在本地运行。

docker ps

输出应包括:

CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS                    NAMES
55305d1d378b   postgres   "docker-entrypoint.s…"   3 seconds ago   Up 2 seconds   0.0.0.0:5432->5432/tcp   sql_db

步骤 3:调度 Cron 作业并写入数据库

在新的终端窗口中,导航到 SDK 目录。

cd bindings/python/sdk/batch

安装依赖项:

pip3 install -r requirements.txt

与 Dapr 边车一起运行 batch-sdk 服务。

dapr run --app-id batch-sdk --app-port 50051 --resources-path ../../../components -- python3 app.py

注意:在 Windows 中,由于未定义 Python3.exe,您可能需要使用 python app.py 而不是 python3 app.py

process_batch 函数中的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器通过 Dapr 边车在您的应用程序中查找通过 HTTP POST 调用的路由。

# 由 Dapr 输入绑定触发
@app.route('/' + cron_binding_name, methods=['POST'])
def process_batch():

batch-sdk 服务使用在 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定将 OrderIdCustomerPrice 记录插入到 orders 表中。

with DaprClient() as d:
    sqlCmd = ('insert into orders (orderid, customer, price) values ' +
              '(%s, \'%s\', %s)' % (order_line['orderid'],
                                    order_line['customer'],
                                    order_line['price']))
    payload = {'sql': sqlCmd}

    print(sqlCmd, flush=True)

    try:
        # 使用 Dapr 输出绑定通过 HTTP Post 插入订单
        resp = d.invoke_binding(binding_name=sql_binding, operation='exec',
                                binding_metadata=payload, data='')
        return resp
    except Exception as e:
        print(e, flush=True)
        raise SystemExit(e)

步骤 4:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 print 语句输出:

== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32)
== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4)
== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56)
== APP == Finished processing batch

在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。

cd bindings/db

运行以下命令以启动交互式 psql CLI:

docker exec -i -t postgres psql --username postgres  -p 5432 -h localhost --no-password

admin=# 提示符下,切换到 orders 表:

\c orders;

orders=# 提示符下,选择所有行:

select * from orders;

输出应如下所示:

 orderid |  customer  | price
---------+------------+--------
       1 | John Smith | 100.32
       2 | Jane Bond  |   15.4
       3 | Tony James |  35.56

components\binding-cron.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: quickstarts
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s" # 有效的 cron 调度
  - name: direction
    value: "input" # cron 绑定的方向

注意: binding-cron.yamlmetadata 部分包含一个 Cron 表达式,指定绑定被调用的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL

使用 binding-postgresql.yaml 组件,您可以轻松地更换后端数据库绑定,而无需进行代码更改。

为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: sqldb
  namespace: quickstarts
spec:
  type: bindings.postgresql
  version: v1
  metadata:
  - name: url # 必需
    value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
  - name: direction
    value: "output" # postgresql 绑定的方向

在 YAML 文件中:

  • spec/type 指定 PostgreSQL 用于此绑定。
  • spec/metadata 定义组件使用的 PostgreSQL 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:本地运行 PostgreSQL Docker 容器

在您的机器上通过 Docker 容器本地运行 PostgreSQL 实例。快速入门示例中包含一个 Docker Compose 文件,用于本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。

在终端窗口中,从快速入门克隆目录的根目录导航到 bindings/db 目录。

cd bindings/db

运行以下命令以设置容器:

docker compose up

验证容器是否在本地运行。

docker ps

输出应包括:

CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS                    NAMES
55305d1d378b   postgres   "docker-entrypoint.s…"   3 seconds ago   Up 2 seconds   0.0.0.0:5432->5432/tcp   sql_db

步骤 3:调度 Cron 作业并写入数据库

在新的终端窗口中,导航到 SDK 目录。

cd bindings/javascript/sdk/batch

安装依赖项:

npm install

与 Dapr 边车一起运行 batch-sdk 服务。

dapr run --app-id batch-sdk --app-port 5002 --dapr-http-port 3500 --resources-path ../../../components -- node index.js 

process_batch 函数中的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器通过 Dapr 边车在您的应用程序中查找通过 HTTP POST 调用的路由。

async function start() {
    await server.binding.receive(cronBindingName,processBatch);
    await server.start();
}

batch-sdk 服务使用在 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定将 OrderIdCustomerPrice 记录插入到 orders 表中。

async function processBatch(){
    const loc = '../../orders.json';
    fs.readFile(loc, 'utf8', (err, data) => {
        const orders = JSON.parse(data).orders;
        orders.forEach(order => {
            let sqlCmd = `insert into orders (orderid, customer, price) values (${order.orderid}, '${order.customer}', ${order.price});`;
            let payload = `{  "sql": "${sqlCmd}" } `;
            console.log(payload);
            client.binding.send(postgresBindingName, "exec", "", JSON.parse(payload));
        });
        console.log('Finished processing batch');
      });
    return 0;
}

步骤 4:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 print 语句输出:

== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32)
== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4)
== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56)

在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。

cd bindings/db

运行以下命令以启动交互式 Postgres CLI:

docker exec -i -t postgres psql --username postgres  -p 5432 -h localhost --no-password

admin=# 提示符下,切换到 orders 表:

\c orders;

orders=# 提示符下,选择所有行:

select * from orders;

输出应如下所示:

 orderid |  customer  | price
---------+------------+--------
       1 | John Smith | 100.32
       2 | Jane Bond  |   15.4
       3 | Tony James |  35.56

components\binding-cron.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: quickstarts
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s" # 有效的 cron 调度
  - name: direction
    value: "input" # cron 绑定的方向

注意: binding-cron.yamlmetadata 部分包含一个 Cron 表达式,指定绑定被调用的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL

使用 binding-postgresql.yaml 组件,您可以轻松地更换后端数据库绑定,而无需进行代码更改。

为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: sqldb
  namespace: quickstarts
spec:
  type: bindings.postgresql
  version: v1
  metadata:
  - name: url # 必需
    value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
  - name: direction
    value: "output" # postgresql 绑定的方向

在 YAML 文件中:

  • spec/type 指定 PostgreSQL 用于此绑定。
  • spec/metadata 定义组件使用的 PostgreSQL 实例的连接。

先决条件

您需要准备以下环境:

注意: .NET 6 是此版本中 Dapr .NET SDK 包的最低支持版本。仅 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:本地运行 PostgreSQL Docker 容器

在您的机器上通过 Docker 容器本地运行 PostgreSQL 实例。快速入门示例中包含一个 Docker Compose 文件,用于本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。

在终端窗口中,从快速入门克隆目录的根目录导航到 bindings/db 目录。

cd bindings/db

运行以下命令以设置容器:

docker compose up

验证容器是否在本地运行。

docker ps

输出应包括:

CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS                    NAMES
55305d1d378b   postgres   "docker-entrypoint.s…"   3 seconds ago   Up 2 seconds   0.0.0.0:5432->5432/tcp   sql_db

步骤 3:调度 Cron 作业并写入数据库

在新的终端窗口中,导航到 SDK 目录。

cd bindings/csharp/sdk/batch

安装依赖项:

dotnet restore
dotnet build batch.csproj

与 Dapr 边车一起运行 batch-sdk 服务。

dapr run --app-id batch-sdk --app-port 7002 --resources-path ../../../components -- dotnet run

process_batch 函数中的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器通过 Dapr 边车在您的应用程序中查找通过 HTTP POST 调用的路由。

app.MapPost("/" + cronBindingName, async () => {
// ...
});

batch-sdk 服务使用在 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定将 OrderIdCustomerPrice 记录插入到 orders 表中。

// ...
string jsonFile = File.ReadAllText("../../../orders.json");
var ordersArray = JsonSerializer.Deserialize<Orders>(jsonFile);
using var client = new DaprClientBuilder().Build();
foreach(Order ord in ordersArray?.orders ?? new Order[] {}){
    var sqlText = $"insert into orders (orderid, customer, price) values ({ord.OrderId}, '{ord.Customer}', {ord.Price});";
    var command = new Dictionary<string,string>(){
        {"sql",
        sqlText}
    };
// ...
}

// 使用 Dapr 输出绑定通过 Dapr Client SDK 插入订单
await client.InvokeBindingAsync(bindingName: sqlBindingName, operation: "exec", data: "", metadata: command);

步骤 4:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 print 语句输出:

== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32);
== APP == insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4);
== APP == insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56);
== APP == Finished processing batch

在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。

cd bindings/db

运行以下命令以启动交互式 Postgres CLI:

docker exec -i -t postgres psql --username postgres  -p 5432 -h localhost --no-password

admin=# 提示符下,切换到 orders 表:

\c orders;

orders=# 提示符下,选择所有行:

select * from orders;

输出应如下所示:

 orderid |  customer  | price
---------+------------+--------
       1 | John Smith | 100.32
       2 | Jane Bond  |   15.4
       3 | Tony James |  35.56

components\binding-cron.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: quickstarts
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s" # 有效的 cron 调度
  - name: direction
    value: "input" # cron 绑定的方向

注意: binding-cron.yamlmetadata 部分包含一个 Cron 表达式,指定绑定被调用的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL

使用 binding-postgresql.yaml 组件,您可以轻松地更换后端数据库绑定,而无需进行代码更改。

为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: sqldb
  namespace: quickstarts
spec:
  type: bindings.postgresql
  version: v1
  metadata:
  - name: url # 必需
    value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
  - name: direction
    value: "output" # postgresql 绑定的方向

在 YAML 文件中:

  • spec/type 指定 PostgreSQL 用于此绑定。
  • spec/metadata 定义组件使用的 PostgreSQL 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:本地运行 PostgreSQL Docker 容器

在您的机器上通过 Docker 容器本地运行 PostgreSQL 实例。快速入门示例中包含一个 Docker Compose 文件,用于本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。

在终端窗口中,从快速入门克隆目录的根目录导航到 bindings/db 目录。

cd bindings/db

运行以下命令以设置容器:

docker compose up

验证容器是否在本地运行。

docker ps

输出应包括:

CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS                    NAMES
55305d1d378b   postgres   "docker-entrypoint.s…"   3 seconds ago   Up 2 seconds   0.0.0.0:5432->5432/tcp   sql_db

步骤 3:调度 Cron 作业并写入数据库

在新的终端窗口中,导航到 SDK 目录。

cd bindings/java/sdk/batch

安装依赖项:

mvn clean install

与 Dapr 边车一起运行 batch-sdk 服务。

dapr run --app-id batch-sdk --app-port 8080 --resources-path ../../../components -- java -jar target/BatchProcessingService-0.0.1-SNAPSHOT.jar

process_batch 函数中的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器通过 Dapr 边车在您的应用程序中查找通过 HTTP POST 调用的路由。

@PostMapping(path = cronBindingPath, consumes = MediaType.ALL_VALUE)
public ResponseEntity<String> processBatch() throws IOException, Exception

batch-sdk 服务使用在 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定将 OrderIdCustomerPrice 记录插入到 orders 表中。

try (DaprClient client = new DaprClientBuilder().build()) {

    for (Order order : ordList.orders) {
        String sqlText = String.format(
            "insert into orders (orderid, customer, price) " +
            "values (%s, '%s', %s);", 
            order.orderid, order.customer, order.price);
        logger.info(sqlText);
    
        Map<String, String> metadata = new HashMap<String, String>();
        metadata.put("sql", sqlText);
 
        // 使用 Dapr SDK 调用 sql 输出绑定
        client.invokeBinding(sqlBindingName, "exec", null, metadata).block();
    } 

    logger.info("Finished processing batch");

    return ResponseEntity.ok("Finished processing batch");
}

步骤 4:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 print 语句输出:

== APP == 2022-06-22 16:39:17.012  INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController   : Processing batch..
== APP == 2022-06-22 16:39:17.268  INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController   : insert into orders (orderid, customer, price) values (1, 'John Smith', 100.32);
== APP == 2022-06-22 16:39:17.838  INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController   : insert into orders (orderid, customer, price) values (2, 'Jane Bond', 15.4);
== APP == 2022-06-22 16:39:17.844  INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController   : insert into orders (orderid, customer, price) values (3, 'Tony James', 35.56);
== APP == 2022-06-22 16:39:17.848  INFO 35772 --- [nio-8080-exec-4] c.s.c.BatchProcessingServiceController   : Finished processing batch

在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。

cd bindings/db

运行以下命令以启动交互式 Postgres CLI:

docker exec -i -t postgres psql --username postgres  -p 5432 -h localhost --no-password

admin=# 提示符下,切换到 orders 表:

\c orders;

orders=# 提示符下,选择所有行:

select * from orders;

输出应如下所示:

 orderid |  customer  | price
---------+------------+--------
       1 | John Smith | 100.32
       2 | Jane Bond  |   15.4
       3 | Tony James |  35.56

components\binding-cron.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: quickstarts
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s" # 有效的 cron 调度
  - name: direction
    value: "input" # cron 绑定的方向

注意: binding-cron.yamlmetadata 部分包含一个 Cron 表达式,指定绑定被调用的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL

使用 binding-postgresql.yaml 组件,您可以轻松地更换后端数据库绑定,而无需进行代码更改。

为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: sqldb
  namespace: quickstarts
spec:
  type: bindings.postgresql
  version: v1
  metadata:
  - name: url # 必需
    value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
  - name: direction
    value: "output" # postgresql 绑定的方向

在 YAML 文件中:

  • spec/type 指定 PostgreSQL 用于此绑定。
  • spec/metadata 定义组件使用的 PostgreSQL 实例的连接。

先决条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:本地运行 PostgreSQL Docker 容器

在您的机器上通过 Docker 容器本地运行 PostgreSQL 实例。快速入门示例中包含一个 Docker Compose 文件,用于本地自定义、构建、运行和初始化带有默认 orders 表的 postgres 容器。

在终端窗口中,从快速入门克隆目录的根目录导航到 bindings/db 目录。

cd bindings/db

运行以下命令以设置容器:

docker compose up

验证容器是否在本地运行。

docker ps

输出应包括:

CONTAINER ID   IMAGE      COMMAND                  CREATED         STATUS         PORTS                    NAMES
55305d1d378b   postgres   "docker-entrypoint.s…"   3 seconds ago   Up 2 seconds   0.0.0.0:5432->5432/tcp   sql_db

步骤 3:调度 Cron 作业并写入数据库

在新的终端窗口中,导航到 SDK 目录。

cd bindings/go/sdk/batch

安装依赖项:

go build .

与 Dapr 边车一起运行 batch-sdk 服务。

dapr run --app-id batch-sdk --app-port 6002 --dapr-http-port 3502 --dapr-grpc-port 60002 --resources-path ../../../components -- go run .

process_batch 函数中的代码每 10 秒执行一次(在 components 目录中的 binding-cron.yaml 中定义)。绑定触发器通过 Dapr 边车在您的应用程序中查找通过 HTTP POST 调用的路由。

// 由 Dapr 输入绑定触发
r.HandleFunc("/"+cronBindingName, processBatch).Methods("POST")

batch-sdk 服务使用在 binding-postgresql.yaml 组件中定义的 PostgreSQL 输出绑定将 OrderIdCustomerPrice 记录插入到 orders 表中。

func sqlOutput(order Order) (err error) {

	client, err := dapr.NewClient()
	if err != nil {
		return err
	}

	ctx := context.Background()

	sqlCmd := fmt.Sprintf("insert into orders (orderid, customer, price) values (%d, '%s', %s);", order.OrderId, order.Customer, strconv.FormatFloat(order.Price, 'f', 2, 64))
	fmt.Println(sqlCmd)

	// 使用 Dapr 输出绑定通过 Dapr SDK 插入订单
	in := &dapr.InvokeBindingRequest{
		Name:      sqlBindingName,
		Operation: "exec",
		Data:      []byte(""),
		Metadata:  map[string]string{"sql": sqlCmd},
	}
	err = client.InvokeOutputBinding(ctx, in)
	if err != nil {
		return err
	}

	return nil
}

步骤 4:查看作业的输出

请注意,如上所述,代码使用 OrderIdCustomerPrice 作为负载调用输出绑定。

您的输出绑定的 print 语句输出:

== APP == Processing batch..
== APP == insert into orders (orderid, customer, price) values(1, 'John Smith', 100.32)
== APP == insert into orders (orderid, customer, price) values(2, 'Jane Bond', 15.4)
== APP == insert into orders (orderid, customer, price) values(3, 'Tony James', 35.56)

在新的终端中,验证相同的数据已插入到数据库中。导航到 bindings/db 目录。

cd bindings/db

运行以下命令以启动交互式 Postgres CLI:

docker exec -i -t postgres psql --username postgres  -p 5432 -h localhost --no-password

admin=# 提示符下,切换到 orders 表:

\c orders;

orders=# 提示符下,选择所有行:

select * from orders;

输出应如下所示:

 orderid |  customer  | price
---------+------------+--------
       1 | John Smith | 100.32
       2 | Jane Bond  |   15.4
       3 | Tony James |  35.56

components\binding-cron.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

为此快速入门包含的 Cron binding-cron.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: cron
  namespace: quickstarts
spec:
  type: bindings.cron
  version: v1
  metadata:
  - name: schedule
    value: "@every 10s" # 有效的 cron 调度
  - name: direction
    value: "input" # cron 绑定的方向

注意: binding-cron.yamlmetadata 部分包含一个 Cron 表达式,指定绑定被调用的频率。

component\binding-postgresql.yaml 组件文件

当您执行 dapr run 命令并指定组件路径时,Dapr 边车:

  • 启动 PostgreSQL 绑定构建块
  • 使用 binding-postgresql.yaml 文件中指定的设置连接到 PostgreSQL

使用 binding-postgresql.yaml 组件,您可以轻松地更换后端数据库绑定,而无需进行代码更改。

为此快速入门包含的 PostgreSQL binding-postgresql.yaml 文件包含以下内容:

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: sqldb
  namespace: quickstarts
spec:
  type: bindings.postgresql
  version: v1
  metadata:
  - name: url # 必需
    value: "user=postgres password=docker host=localhost port=5432 dbname=orders pool_min_conns=1 pool_max_conns=10"
  - name: direction
    value: "output" # postgresql 绑定的方向

在 YAML 文件中:

  • spec/type 指定 PostgreSQL 用于此绑定。
  • spec/metadata 定义组件使用的 PostgreSQL 实例的连接。

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的 discord 频道进行讨论。

下一步

探索 Dapr 教程 >>

6 - 快速入门:Actors

开始使用 Dapr 的 Actors 构建模块

我们来了解一下 Dapr 的 Actors 构建模块。在这个快速入门中,您将运行一个智能设备微服务和一个简单的控制台客户端,以演示 Dapr Actors 中的有状态对象模式。

目前,您可以通过 .NET SDK 体验这个 actors 快速入门。

以下是 .NET actors 快速入门的简要概述:

  1. 使用 SmartDevice.Service 微服务,您将托管:
    • 两个 SmokeDetectorActor 烟雾报警对象
    • 一个 ControllerActor 对象,用于指挥和控制智能设备
  2. 使用 SmartDevice.Client 控制台应用程序,客户端应用程序与每个 actor 或控制器交互,以执行聚合操作。
  3. SmartDevice.Interfaces 包含服务和客户端应用程序使用的共享接口和数据类型。

先决条件

对于这个示例,您将需要:

注意: .NET 6 是此版本中 Dapr .NET SDK 包的最低支持版本。只有 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 1:设置环境

克隆 Quickstarts 仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤 2:运行服务应用程序

在一个新的终端窗口中,导航到 actors/csharp/sdk/service 目录并恢复依赖项:

cd actors/csharp/sdk/service
dotnet build

运行 SmartDevice.Service,这将启动服务本身和 Dapr 边车:

dapr run --app-id actorservice --app-port 5001 --dapr-http-port 3500 --resources-path ../../../resources -- dotnet run --urls=http://localhost:5001/

预期输出:

== APP == info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
== APP ==       Request starting HTTP/1.1 GET http://127.0.0.1:5001/healthz - -
== APP == info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
== APP ==       Executing endpoint 'Dapr Actors Health Check'
== APP == info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
== APP ==       Executed endpoint 'Dapr Actors Health Check'
== APP == info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
== APP ==       Request finished HTTP/1.1 GET http://127.0.0.1:5001/healthz - - - 200 - text/plain 5.2599ms

步骤 3:运行客户端应用程序

在一个新的终端实例中,导航到 actors/csharp/sdk/client 目录并安装依赖项:

cd ./actors/csharp/sdk/client
dotnet build

运行 SmartDevice.Client 应用程序:

dapr run --app-id actorclient -- dotnet run

预期输出:

== APP == Startup up...
== APP == Calling SetDataAsync on SmokeDetectorActor:1...
== APP == Got response: Success
== APP == Calling GetDataAsync on SmokeDetectorActor:1...
== APP == Device 1 state: Location: First Floor, Status: Ready
== APP == Calling SetDataAsync on SmokeDetectorActor:2...
== APP == Got response: Success
== APP == Calling GetDataAsync on SmokeDetectorActor:2...
== APP == Device 2 state: Location: Second Floor, Status: Ready
== APP == Registering the IDs of both Devices...
== APP == Registered devices: 1, 2
== APP == Detecting smoke on Device 1...
== APP == Device 1 state: Location: First Floor, Status: Alarm
== APP == Device 2 state: Location: Second Floor, Status: Alarm
== APP == Sleeping for 16 seconds before checking status again to see reminders fire and clear alarms
== APP == Device 1 state: Location: First Floor, Status: Ready
== APP == Device 2 state: Location: Second Floor, Status: Ready

(可选)步骤 4:在 Zipkin 中查看

如果您在本地机器上为 Dapr 配置了 Zipkin,您可以在 Zipkin Web UI 中查看 actor 与客户端的交互(通常在 http://localhost:9411/zipkin/)。

发生了什么?

当您运行客户端应用程序时,发生了一些事情:

  1. 两个 SmokeDetectorActor actors 在 客户端应用程序中创建 并使用对象状态初始化:

    • ActorProxy.Create<ISmartDevice>(actorId, actorType)
    • proxySmartDevice.SetDataAsync(data)

    这些对象是可重入的并持有状态,如 proxySmartDevice.GetDataAsync() 所示。

    // Actor Ids 和类型
    var deviceId1 = "1";
    var deviceId2 = "2";
    var smokeDetectorActorType = "SmokeDetectorActor";
    var controllerActorType = "ControllerActor";
    
    Console.WriteLine("Startup up...");
    
    // ActorId 唯一标识第一个设备的第一个 actor 实例
    var deviceActorId1 = new ActorId(deviceId1);
    
    // 创建将存储在第一个 actor 中的数据类的新实例
    var deviceData1 = new SmartDeviceData(){
        Location = "First Floor",
        Status = "Ready",
    };
    
    // 使用服务实现的相同接口创建本地代理。
    var proxySmartDevice1 = ActorProxy.Create<ISmartDevice>(deviceActorId1, smokeDetectorActorType);
    
    // 现在您可以使用 actor 接口调用 actor 的方法。
    Console.WriteLine($"Calling SetDataAsync on {smokeDetectorActorType}:{deviceActorId1}...");
    var setDataResponse1 = await proxySmartDevice1.SetDataAsync(deviceData1);
    Console.WriteLine($"Got response: {setDataResponse1}");
    
    Console.WriteLine($"Calling GetDataAsync on {smokeDetectorActorType}:{deviceActorId1}...");
    var storedDeviceData1 = await proxySmartDevice1.GetDataAsync();
    Console.WriteLine($"Device 1 state: {storedDeviceData1}");
    
    // 为第二个设备创建第二个 actor
    var deviceActorId2 = new ActorId(deviceId2);
    
    // 创建将存储在第一个 actor 中的数据类的新实例
    var deviceData2 = new SmartDeviceData(){
        Location = "Second Floor",
        Status = "Ready",
    };
    
    // 使用服务实现的相同接口创建本地代理。
    var proxySmartDevice2 = ActorProxy.Create<ISmartDevice>(deviceActorId2, smokeDetectorActorType);
    
    // 现在您可以使用 actor 接口调用第二个 actor 的方法。
    Console.WriteLine($"Calling SetDataAsync on {smokeDetectorActorType}:{deviceActorId2}...");
    var setDataResponse2 = await proxySmartDevice2.SetDataAsync(deviceData2);
    Console.WriteLine($"Got response: {setDataResponse2}");
    
    Console.WriteLine($"Calling GetDataAsync on {smokeDetectorActorType}:{deviceActorId2}...");
    var storedDeviceData2 = await proxySmartDevice2.GetDataAsync();
    Console.WriteLine($"Device 2 state: {storedDeviceData2}");
    
  2. SmokeDetectorActor 1DetectSmokeAsync 方法被调用

     public async Task DetectSmokeAsync()
     {
         var controllerActorId = new ActorId("controller");
         var controllerActorType = "ControllerActor";
         var controllerProxy = ProxyFactory.CreateActorProxy<IController>(controllerActorId, controllerActorType);
         await controllerProxy.TriggerAlarmForAllDetectors();
     }
    
  3. ControllerActorTriggerAlarmForAllDetectors 方法被调用。当检测到烟雾时,ControllerActor 内部触发所有警报

    public async Task TriggerAlarmForAllDetectors()
    {
        var deviceIds =  await ListRegisteredDeviceIdsAsync();
        foreach (var deviceId in deviceIds)
        {
            var actorId = new ActorId(deviceId);
            var proxySmartDevice = ProxyFactory.CreateActorProxy<ISmartDevice>(actorId, "SmokeDetectorActor");
            await proxySmartDevice.SoundAlarm();
        }
    
        // 注册一个提醒,每 15 秒刷新并清除警报状态
        await this.RegisterReminderAsync("AlarmRefreshReminder", null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
    }
    

    控制台 打印一条消息,指示检测到烟雾

    // 在设备 1 上检测到烟雾,触发所有设备的警报。
    Console.WriteLine($"Detecting smoke on Device 1...");
    proxySmartDevice1 = ActorProxy.Create<ISmartDevice>(deviceActorId1, smokeDetectorActorType);
    await proxySmartDevice1.DetectSmokeAsync();   
    
  4. SmokeDetectorActor 12SoundAlarm 方法 被调用。

    storedDeviceData1 = await proxySmartDevice1.GetDataAsync();
    Console.WriteLine($"Device 1 state: {storedDeviceData1}");
    storedDeviceData2 = await proxySmartDevice2.GetDataAsync();
    Console.WriteLine($"Device 2 state: {storedDeviceData2}");
    
  5. ControllerActor 还使用 RegisterReminderAsync 创建一个持久提醒,在 15 秒后调用 ClearAlarm

    // 注册一个提醒,每 15 秒刷新并清除警报状态
    await this.RegisterReminderAsync("AlarmRefreshReminder", null, TimeSpan.FromSeconds(15), TimeSpan.FromSeconds(15));
    

要了解示例的完整上下文,请查看以下代码:

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进的建议吗?

加入我们的 discord 频道 讨论。

下一步

了解更多关于 Actor 构建模块 的信息

探索 Dapr 教程 >>

7 - 快速入门:机密管理

开始使用Dapr的机密管理构建块

Dapr提供了一个专用的机密API,允许开发者从机密存储中检索机密。在本快速入门中,您将:

  1. 运行一个带有机密存储组件的微服务。
  2. 在应用程序代码中使用Dapr机密API检索机密。
示例服务的机密管理图示。

在继续快速入门之前,请选择您偏好的编程语言对应的Dapr SDK。

前置条件

您需要准备以下环境:

步骤1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:检索机密

在终端窗口中,导航到order-processor目录。

cd secrets_management/python/sdk/order-processor

安装依赖项:

pip3 install -r requirements.txt

运行order-processor服务及其Dapr sidecar。

dapr run --app-id order-processor --resources-path ../../../components/ -- python3 app.py

注意:在Windows中,可能需要使用python app.py而不是python3 app.py

背后的原理

order-processor服务

请注意,order-processor服务配置如下:

  • 使用在local-secret-store.yaml组件中定义的DAPR_SECRET_STORE
  • 使用在secrets.json中定义的机密。
# app.py
DAPR_SECRET_STORE = 'localsecretstore'
SECRET_NAME = 'secret'
with DaprClient() as client:
    secret = client.get_secret(store_name=DAPR_SECRET_STORE, key=SECRET_NAME)
    logging.info('Fetched Secret: %s', secret.secret)

local-secret-store.yaml组件

DAPR_SECRET_STORElocal-secret-store.yaml组件文件中定义,位于secrets_management/components

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: secrets.json
  - name: nestedSeparator
    value: ":"

在YAML文件中:

  • metadata/name是应用程序引用组件的名称(在代码示例中称为DAPR_SECRET_STORE)。
  • spec/metadata定义了组件使用的机密的连接信息。

secrets.json文件

SECRET_NAMEsecrets.json文件中定义,位于secrets_management/python/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤3:查看order-processor输出

如上面的应用程序代码中所示,order-processor服务通过Dapr机密存储检索机密并在控制台中显示。

Order-processor输出:

== APP == INFO:root:Fetched Secret: {'secret': 'YourPasskeyHere'}

前置条件

您需要准备以下环境:

步骤1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:检索机密

在终端窗口中,导航到order-processor目录。

cd secrets_management/javascript/sdk/order-processor

安装依赖项:

npm install

运行order-processor服务及其Dapr sidecar。

dapr run --app-id order-processor --resources-path ../../../components/ -- npm start

背后的原理

order-processor服务

请注意,order-processor服务配置如下:

  • 使用在local-secret-store.yaml组件中定义的DAPR_SECRET_STORE
  • 使用在secrets.json中定义的机密。
// index.js
const DAPR_SECRET_STORE = "localsecretstore";
const SECRET_NAME = "secret";

async function main() {
    // ...
    const secret = await client.secret.get(DAPR_SECRET_STORE, SECRET_NAME);
    console.log("Fetched Secret: " + JSON.stringify(secret));
}

local-secret-store.yaml组件

DAPR_SECRET_STORElocal-secret-store.yaml组件文件中定义,位于secrets_management/components

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: secrets.json
  - name: nestedSeparator
    value: ":"

在YAML文件中:

  • metadata/name是应用程序引用组件的名称(在代码示例中称为DAPR_SECRET_STORE)。
  • spec/metadata定义了组件使用的机密的连接信息。

secrets.json文件

SECRET_NAMEsecrets.json文件中定义,位于secrets_management/javascript/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤3:查看order-processor输出

如上面的应用程序代码中所示,order-processor服务通过Dapr机密存储检索机密并在控制台中显示。

Order-processor输出:

== APP ==
== APP == > order-processor@1.0.0 start
== APP == > node index.js
== APP ==
== APP == Fetched Secret: {"secret":"YourPasskeyHere"}

前置条件

您需要准备以下环境:

注意: .NET 6是此版本中Dapr .NET SDK包的最低支持版本。只有.NET 8和.NET 9将在Dapr v1.16及以后版本中得到支持。

步骤1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:检索机密

在终端窗口中,导航到order-processor目录。

cd secrets_management/csharp/sdk/order-processor

安装依赖项:

dotnet restore
dotnet build

运行order-processor服务及其Dapr sidecar。

dapr run --app-id order-processor --resources-path ../../../components/ -- dotnet run

背后的原理

order-processor服务

请注意,order-processor服务配置如下:

  • 使用在local-secret-store.yaml组件中定义的DAPR_SECRET_STORE
  • 使用在secrets.json中定义的机密。
// Program.cs
const string DAPR_SECRET_STORE = "localsecretstore";
const string SECRET_NAME = "secret";
var client = new DaprClientBuilder().Build();

var secret = await client.GetSecretAsync(DAPR_SECRET_STORE, SECRET_NAME);
var secretValue = string.Join(", ", secret);
Console.WriteLine($"Fetched Secret: {secretValue}");

local-secret-store.yaml组件

DAPR_SECRET_STORElocal-secret-store.yaml组件文件中定义,位于secrets_management/components

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: secrets.json
  - name: nestedSeparator
    value: ":"

在YAML文件中:

  • metadata/name是应用程序引用组件的名称(在代码示例中称为DAPR_SECRET_STORE)。
  • spec/metadata定义了组件使用的机密的连接信息。

secrets.json文件

SECRET_NAMEsecrets.json文件中定义,位于secrets_management/csharp/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤3:查看order-processor输出

如上面的应用程序代码中所示,order-processor服务通过Dapr机密存储检索机密并在控制台中显示。

Order-processor输出:

== APP == Fetched Secret: [secret, YourPasskeyHere]

前置条件

您需要准备以下环境:

步骤1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:检索机密

在终端窗口中,导航到order-processor目录。

cd secrets_management/java/sdk/order-processor

安装依赖项:

mvn clean install

运行order-processor服务及其Dapr sidecar。

dapr run --app-id order-processor --resources-path ../../../components/ -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

背后的原理

order-processor服务

请注意,order-processor服务配置如下:

  • 使用在local-secret-store.yaml组件中定义的DAPR_SECRET_STORE
  • 使用在secrets.json中定义的机密。
// OrderProcessingServiceApplication.java
private static final String SECRET_STORE_NAME = "localsecretstore";
// ...
    Map<String, String> secret = client.getSecret(SECRET_STORE_NAME, "secret").block();
    System.out.println("Fetched Secret: " + secret);

local-secret-store.yaml组件

DAPR_SECRET_STORElocal-secret-store.yaml组件文件中定义,位于secrets_management/components

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: secrets.json
  - name: nestedSeparator
    value: ":"

在YAML文件中:

  • metadata/name是应用程序引用组件的名称(在代码示例中称为DAPR_SECRET_STORE)。
  • spec/metadata定义了组件使用的机密的连接信息。

secrets.json文件

SECRET_NAMEsecrets.json文件中定义,位于secrets_management/java/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤3:查看order-processor输出

如上面的应用程序代码中所示,order-processor服务通过Dapr机密存储检索机密并在控制台中显示。

Order-processor输出:

== APP == Fetched Secret: {secret=YourPasskeyHere}

前置条件

您需要准备以下环境:

步骤1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:检索机密

在终端窗口中,导航到order-processor目录。

cd secrets_management/go/sdk/order-processor

安装依赖项:

go build .

运行order-processor服务及其Dapr sidecar。

dapr run --app-id order-processor --resources-path ../../../components/ -- go run .

背后的原理

order-processor服务

请注意,order-processor服务配置如下:

  • 使用在local-secret-store.yaml组件中定义的DAPR_SECRET_STORE
  • 使用在secrets.json中定义的机密。
const DAPR_SECRET_STORE = "localsecretstore"
const SECRET_NAME = "secret"
// ...
secret, err := client.GetSecret(ctx, DAPR_SECRET_STORE, SECRET_NAME, nil)
if secret != nil {
    fmt.Println("Fetched Secret: ", secret[SECRET_NAME])
}

local-secret-store.yaml组件

DAPR_SECRET_STORElocal-secret-store.yaml组件文件中定义,位于secrets_management/components

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localsecretstore
  namespace: default
spec:
  type: secretstores.local.file
  version: v1
  metadata:
  - name: secretsFile
    value: secrets.json
  - name: nestedSeparator
    value: ":"

在YAML文件中:

  • metadata/name是应用程序引用组件的名称(在代码示例中称为DAPR_SECRET_STORE)。
  • spec/metadata定义了组件使用的机密的连接信息。

secrets.json文件

SECRET_NAMEsecrets.json文件中定义,位于secrets_management/go/sdk/order-processor

{
    "secret": "YourPasskeyHere"
}

步骤3:查看order-processor输出

如上面的应用程序代码中所示,order-processor服务通过Dapr机密存储检索机密并在控制台中显示。

Order-processor输出:

== APP == Fetched Secret:  YourPasskeyHere

告诉我们您的想法!

我们正在不断改进我们的快速入门示例,重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的discord频道讨论。

下一步

探索Dapr教程 >>

8 - 快速入门:配置

开始使用 Dapr 的配置模块

接下来,我们将介绍 Dapr 的配置模块。配置项通常具有动态特性,并且与应用程序的需求紧密相关。配置项是包含配置信息的键/值对,例如:

  • 应用程序 ID
  • 分区键
  • 数据库名称等

在本快速入门中,您将运行一个使用配置 API 的 order-processor 微服务。该服务将:

  1. 从配置存储中获取配置项。
  2. 订阅配置更新。
展示配置 API 快速入门流程的图示,使用了键/值对。

在继续快速入门之前,请选择您偏好的 Dapr SDK 语言版本。

前提条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

克隆后,打开一个新终端并运行以下命令,为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 order-processor 服务

从快速入门克隆目录的根目录,导航到 order-processor 目录。

cd configuration/python/sdk/order-processor

安装依赖项:

pip3 install -r requirements.txt

在 Dapr 边车环境中运行 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../components/ --app-port 6001 -- python3 app.py

注意:在 Windows 中,您可能需要使用 python app.py 而不是 python3 app.py

预期输出:

== APP == Configuration for orderId1 : value: "101"
== APP ==
== APP == Configuration for orderId2 : value: "102"
== APP ==
== APP == App unsubscribed from config changes

(可选)步骤 3:更新配置项值

应用程序取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"

再次运行 order-processor 服务:

dapr run --app-id order-processor --resources-path ../../../components/ --app-port 6001 -- python3 app.py

注意:在 Windows 中,您可能需要使用 python app.py 而不是 python3 app.py

应用程序将显示更新后的配置值:

== APP == Configuration for orderId1 : value: "103"
== APP ==
== APP == Configuration for orderId2 : value: "104"
== APP ==

order-processor 服务

order-processor 服务包括以下代码:

  • 从配置存储中获取配置项
  • 订阅配置更新(您之前在 CLI 中进行的操作)
  • 取消订阅配置更新,并在 20 秒不活动后退出应用程序。

获取配置项:

# 从配置存储中获取配置项
for config_item in CONFIGURATION_ITEMS:
    config = client.get_configuration(store_name=DAPR_CONFIGURATION_STORE, keys=[config_item], config_metadata={})
    print(f"Configuration for {config_item} : {config.items[config_item]}", flush=True)

订阅配置更新:

# 订阅配置更改
configuration = await client.subscribe_configuration(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS)

取消订阅配置更新并退出应用程序:

# 取消订阅配置更新
unsubscribed = True
for config_item in CONFIGURATION_ITEMS:
    unsub_item = client.unsubscribe_configuration(DAPR_CONFIGURATION_STORE, config_item)
    #...
if unsubscribed == True:
    print("App unsubscribed from config changes", flush=True)

前提条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

克隆后,打开一个新终端并运行以下命令,为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 order-processor 服务

从快速入门克隆目录的根目录,导航到 order-processor 目录。

cd configuration/javascript/sdk/order-processor

安装依赖项:

npm install

在 Dapr 边车环境中运行 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../components/ --app-protocol grpc --dapr-grpc-port 3500 -- node index.js

预期输出:

== APP == Configuration for orderId1: {"key":"orderId1","value":"101","version":"","metadata":{}}
== APP == Configuration for orderId2: {"key":"orderId2","value":"102","version":"","metadata":{}}
== APP == App unsubscribed to config changes

(可选)步骤 3:更新配置项值

应用程序取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"

再次运行 order-processor 服务:

dapr run --app-id order-processor --resources-path ../../../components/ --app-protocol grpc --dapr-grpc-port 3500 -- node index.js

应用程序将显示更新后的配置值:

== APP == Configuration for orderId1: {"key":"orderId1","value":"103","version":"","metadata":{}}
== APP == Configuration for orderId2: {"key":"orderId2","value":"104","version":"","metadata":{}}

order-processor 服务

order-processor 服务包括以下代码:

  • 从配置存储中获取配置项
  • 订阅配置更新(您之前在 CLI 中进行的操作)
  • 取消订阅配置更新,并在 20 秒不活动后退出应用程序。

获取配置项:

// 从配置存储中获取配置项
//...
  const config = await client.configuration.get(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS);
  Object.keys(config.items).forEach((key) => {
    console.log("Configuration for " + key + ":", JSON.stringify(config.items[key]));
  });

订阅配置更新:

// 订阅配置更新
try {
  const stream = await client.configuration.subscribeWithKeys(
    DAPR_CONFIGURATION_STORE,
    CONFIGURATION_ITEMS,
    (config) => {
      console.log("Configuration update", JSON.stringify(config.items));
    }
  );

取消订阅配置更新并退出应用程序:

// 取消订阅配置更新并在 20 秒后退出应用程序
setTimeout(() => {
  stream.stop();
  console.log("App unsubscribed to config changes");
  process.exit(0);
},

前提条件

您需要准备以下环境:

注意: .NET 6 是此版本中 Dapr .NET SDK 包的最低支持版本。仅 .NET 8 和 .NET 9 将在 Dapr v1.16 及更高版本中得到支持。

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

克隆后,打开一个新终端并运行以下命令,为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 order-processor 服务

从快速入门克隆目录的根目录,导航到 order-processor 目录。

cd configuration/csharp/sdk/order-processor

恢复 NuGet 包:

dotnet restore
dotnet build

在 Dapr 边车环境中运行 order-processor 服务。

dapr run --app-id order-processor-http --resources-path ../../../components/ --app-port 7001 -- dotnet run --project .

预期输出:

== APP == Configuration for orderId1: {"Value":"101","Version":"","Metadata":{}}
== APP == Configuration for orderId2: {"Value":"102","Version":"","Metadata":{}}
== APP == App unsubscribed from config changes

(可选)步骤 3:更新配置项值

应用程序取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"

再次运行 order-processor 服务:

dapr run --app-id order-processor-http --resources-path ../../../components/ --app-port 7001 -- dotnet run --project .

应用程序将显示更新后的配置值:

== APP == Configuration for orderId1: {"Value":"103","Version":"","Metadata":{}}
== APP == Configuration for orderId2: {"Value":"104","Version":"","Metadata":{}}

order-processor 服务

order-processor 服务包括以下代码:

  • 从配置存储中获取配置项
  • 订阅配置更新(您之前在 CLI 中进行的操作)
  • 取消订阅配置更新,并在 20 秒不活动后退出应用程序。

获取配置项:

// 从配置存储中获取配置
GetConfigurationResponse config = await client.GetConfiguration(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS);
foreach (var item in config.Items)
{
  var cfg = System.Text.Json.JsonSerializer.Serialize(item.Value);
  Console.WriteLine("Configuration for " + item.Key + ": " + cfg);
}

订阅配置更新:

// 订阅配置更新
SubscribeConfigurationResponse subscribe = await client.SubscribeConfiguration(DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS);

取消订阅配置更新并退出应用程序:

// 取消订阅配置更新并退出应用程序
try
{
  client.UnsubscribeConfiguration(DAPR_CONFIGURATION_STORE, subscriptionId);
  Console.WriteLine("App unsubscribed from config changes");
  Environment.Exit(0);
}

前提条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

克隆后,打开一个新终端并运行以下命令,为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 order-processor 服务

从快速入门克隆目录的根目录,导航到 order-processor 目录。

cd configuration/java/sdk/order-processor

安装依赖项:

mvn clean install

在 Dapr 边车环境中运行 order-processor 服务。

dapr run --app-id order-processor --resources-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

预期输出:

== APP == Configuration for orderId1: {'value':'101'}
== APP == Configuration for orderId2: {'value':'102'}
== APP == App unsubscribed to config changes

(可选)步骤 3:更新配置项值

应用程序取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"

再次运行 order-processor 服务:

dapr run --app-id order-processor --resources-path ../../../components -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

应用程序将显示更新后的配置值:

== APP == Configuration for orderId1: {'value':'103'}
== APP == Configuration for orderId2: {'value':'104'}

order-processor 服务

order-processor 服务包括以下代码:

  • 从配置存储中获取配置项
  • 订阅配置更新(您之前在 CLI 中进行的操作)
  • 取消订阅配置更新,并在 20 秒不活动后退出应用程序。

获取配置项:

// 从配置存储中获取配置项
try (DaprPreviewClient client = (new DaprClientBuilder()).buildPreviewClient()) {
    for (String configurationItem : CONFIGURATION_ITEMS) {
        ConfigurationItem item = client.getConfiguration(DAPR_CONFIGURATON_STORE, configurationItem).block();
        System.out.println("Configuration for " + configurationItem + ": {'value':'" + item.getValue() + "'}");
    }

订阅配置更新:

// 订阅配置更改
Flux<SubscribeConfigurationResponse> subscription = client.subscribeConfiguration(DAPR_CONFIGURATON_STORE,
        CONFIGURATION_ITEMS.toArray(String[]::new));

取消订阅配置更新并退出应用程序:

// 取消订阅配置更改
UnsubscribeConfigurationResponse unsubscribe = client
        .unsubscribeConfiguration(subscriptionId, DAPR_CONFIGURATON_STORE).block();
if (unsubscribe.getIsUnsubscribed()) {
    System.out.println("App unsubscribed to config changes");
}

前提条件

您需要准备以下环境:

步骤 1:设置环境

克隆快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

克隆后,打开一个新终端并运行以下命令,为配置项 orderId1orderId2 设置值。

docker exec dapr_redis redis-cli MSET orderId1 "101" orderId2 "102"

步骤 2:运行 order-processor 服务

从快速入门克隆目录的根目录,导航到 order-processor 目录。

cd configuration/go/sdk/order-processor

在 Dapr 边车环境中运行 order-processor 服务。

dapr run --app-id order-processor --app-port 6001 --resources-path ../../../components -- go run .

预期输出:

== APP == Configuration for orderId1: {"Value":"101","Version":"","Metadata":null}
== APP == Configuration for orderId2: {"Value":"102","Version":"","Metadata":null}
== APP == dapr configuration subscribe finished.
== APP == App unsubscribed to config changes

(可选)步骤 3:更新配置项值

应用程序取消订阅后,尝试更新配置项值。使用以下命令更改 orderId1orderId2 的值:

docker exec dapr_redis redis-cli MSET orderId1 "103" orderId2 "104"

再次运行 order-processor 服务:

dapr run --app-id order-processor --app-port 6001 --resources-path ../../../components -- go run .

应用程序将显示更新后的配置值:

== APP == Configuration for orderId1: {"Value":"103","Version":"","Metadata":null}
== APP == Configuration for orderId2: {"Value":"104","Version":"","Metadata":null}

order-processor 服务

order-processor 服务包括以下代码:

  • 从配置存储中获取配置项
  • 订阅配置更新(您之前在 CLI 中进行的操作)
  • 取消订阅配置更新,并在 20 秒不活动后退出应用程序。

获取配置项:

// 从配置存储中获取配置项
for _, item := range CONFIGURATION_ITEMS {
	config, err := client.GetConfigurationItem(ctx, DAPR_CONFIGURATION_STORE, item)
	//...
	c, _ := json.Marshal(config)
	fmt.Println("Configuration for " + item + ": " + string(c))
}

订阅配置更新:

// 订阅配置更改
err = client.SubscribeConfigurationItems(ctx, DAPR_CONFIGURATION_STORE, CONFIGURATION_ITEMS, func(id string, config map[string]*dapr.ConfigurationItem) {
	// 应用程序首次订阅配置更改时仅返回订阅 ID
	if len(config) == 0 {
		fmt.Println("App subscribed to config changes with subscription id: " + id)
		subscriptionId = id
		return
	}
})

取消订阅配置更新并退出应用程序:

// 取消订阅配置更新并在 20 秒后退出应用程序
select {
case <-ctx.Done():
	err = client.UnsubscribeConfigurationItems(context.Background(), DAPR_CONFIGURATION_STORE, subscriptionId)
    //...
	{
		fmt.Println("App unsubscribed to config changes")
	}

演示

观看此视频演示配置 API 快速入门

告诉我们您的想法!

我们正在不断改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的Discord 频道讨论。

下一步

探索 Dapr 教程 >>

9 - 快速入门:加密技术

开始使用 Dapr 加密构建块

我们来了解一下 Dapr 的加密构建块。在这个快速入门中,您将创建一个应用程序,使用 Dapr 加密 API 来加密和解密数据。您将:

  • 加密并解密一个短字符串(使用 RSA 密钥),在内存中读取结果,使用 Go 的字节切片格式。
  • 加密并解密一个大文件(使用 AES 密钥),通过流的方式将加密和解密的数据存储到文件中。

目前,您可以使用 Go SDK 体验加密 API。

此快速入门包括一个名为 crypto-quickstart 的 JavaScript 应用程序。

前置条件

对于此示例,您将需要:

步骤 1:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端中,从根目录导航到加密示例。

cd cryptography/javascript/sdk

导航到包含源代码的文件夹:

cd ./crypto-quickstart

安装依赖项:

npm install

步骤 2:使用 Dapr 运行应用程序

应用程序代码定义了两个必需的密钥:

  • 私有 RSA 密钥
  • 一个 256 位对称(AES)密钥

使用 OpenSSL 生成两个密钥:一个 RSA 密钥和一个 AES 密钥,并将它们写入两个文件:

mkdir -p keys
# 生成一个私有 RSA 密钥,4096 位密钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
# 为 AES 生成一个 256 位密钥
openssl rand -out keys/symmetric-key-256 32

使用 Dapr 运行 Go 服务应用程序:

dapr run --app-id crypto-quickstart --resources-path ../../../components/ -- npm start

预期输出

== APP == 2023-10-25T14:30:50.435Z INFO [GRPCClient, GRPCClient] Opening connection to 127.0.0.1:58173
== APP == == Encrypting message using buffers
== APP == Encrypted the message, got 856 bytes
== APP == == Decrypting message using buffers
== APP == Decrypted the message, got 24 bytes
== APP == The secret is "passw0rd"
== APP == == Encrypting message using streams
== APP == Encrypting federico-di-dio-photography-Q4g0Q-eVVEg-unsplash.jpg to encrypted.out
== APP == Encrypted the message to encrypted.out
== APP == == Decrypting message using streams
== APP == Decrypting encrypted.out to decrypted.out.jpg
== APP == Decrypted the message to decrypted.out.jpg

发生了什么?

local-storage.yaml

之前,您在 crypto-quickstarts 中创建了一个名为 keys 的目录。在 local-storage 组件 YAML 中,path 元数据映射到新创建的 keys 目录。

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localstorage
spec:
  type: crypto.dapr.localstorage
  version: v1
  metadata:
    - name: path
      # 路径相对于示例所在的文件夹
      value: ./keys

index.mjs

应用程序文件 使用您生成的 RSA 和 AES 密钥加密和解密消息和文件。应用程序创建了一个新的 Dapr SDK 客户端:

async function start() {
  const client = new DaprClient({
    daprHost,
    daprPort,
    communicationProtocol: CommunicationProtocolEnum.GRPC,
  });

  // 从缓冲区加密和解密消息
  await encryptDecryptBuffer(client);

  // 使用流加密和解密消息
  await encryptDecryptStream(client);
}
使用 RSA 密钥加密和解密字符串

一旦创建了客户端,应用程序就会加密一条消息:

async function encryptDecryptBuffer(client) {
  // 要加密的消息
  const plaintext = `The secret is "passw0rd"`

  // 首先,加密消息
  console.log("== Encrypting message using buffers");

  const encrypted = await client.crypto.encrypt(plaintext, {
    componentName: "localstorage",
    keyName: "rsa-private-key.pem",
    keyWrapAlgorithm: "RSA",
  });

  console.log("Encrypted the message, got", encrypted.length, "bytes");

然后应用程序解密消息:

  // 解密消息
  console.log("== Decrypting message using buffers");
  const decrypted = await client.crypto.decrypt(encrypted, {
    componentName: "localstorage",
  });

  console.log("Decrypted the message, got", decrypted.length, "bytes");
  console.log(decrypted.toString("utf8"));

  // ...
}
使用 AES 密钥加密和解密大文件

接下来,应用程序加密一个大图像文件:

async function encryptDecryptStream(client) {
  // 首先,加密消息
  console.log("== Encrypting message using streams");
  console.log("Encrypting", testFileName, "to encrypted.out");

  await pipeline(
    createReadStream(testFileName),
    await client.crypto.encrypt({
      componentName: "localstorage",
      keyName: "symmetric-key-256",
      keyWrapAlgorithm: "A256KW",
    }),
    createWriteStream("encrypted.out"),
  );

  console.log("Encrypted the message to encrypted.out");

然后应用程序解密大图像文件:

  // 解密消息
  console.log("== Decrypting message using streams");
  console.log("Decrypting encrypted.out to decrypted.out.jpg");
  await pipeline(
    createReadStream("encrypted.out"),
    await client.crypto.decrypt({
      componentName: "localstorage",
    }),
    createWriteStream("decrypted.out.jpg"),
  );

  console.log("Decrypted the message to decrypted.out.jpg");
}

此快速入门包括一个名为 crypto-quickstart 的 Go 应用程序。

前置条件

对于此示例,您将需要:

步骤 1:设置环境

克隆 快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端中,从根目录导航到加密示例。

cd cryptography/go/sdk

步骤 2:使用 Dapr 运行应用程序

导航到包含源代码的文件夹:

cd ./crypto-quickstart

应用程序代码定义了两个必需的密钥:

  • 私有 RSA 密钥
  • 一个 256 位对称(AES)密钥

使用 OpenSSL 生成两个密钥:一个 RSA 密钥和一个 AES 密钥,并将它们写入两个文件:

mkdir -p keys
# 生成一个私有 RSA 密钥,4096 位密钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out keys/rsa-private-key.pem
# 为 AES 生成一个 256 位密钥
openssl rand -out keys/symmetric-key-256 32

使用 Dapr 运行 Go 服务应用程序:

dapr run --app-id crypto-quickstart --resources-path ../../../components/ -- go run .

预期输出

== APP == dapr client initializing for: 127.0.0.1:52407
== APP == Encrypted the message, got 856 bytes
== APP == Decrypted the message, got 24 bytes
== APP == The secret is "passw0rd"
== APP == Wrote decrypted data to encrypted.out
== APP == Wrote decrypted data to decrypted.out.jpg

发生了什么?

local-storage.yaml

之前,您在 crypto-quickstarts 中创建了一个名为 keys 的目录。在 local-storage 组件 YAML 中,path 元数据映射到新创建的 keys 目录。

apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
  name: localstorage
spec:
  type: crypto.dapr.localstorage
  version: v1
  metadata:
    - name: path
      # 路径相对于示例所在的文件夹
      value: ./keys

app.go

应用程序文件 使用您生成的 RSA 和 AES 密钥加密和解密消息和文件。应用程序创建了一个新的 Dapr SDK 客户端:

func main() {
	// 创建一个新的 Dapr SDK 客户端
	client, err := dapr.NewClient()
    
    //...

	// 步骤 1:使用 RSA 密钥加密字符串,然后解密并在终端中显示输出
	encryptDecryptString(client)

	// 步骤 2:加密大文件,然后使用 AES 密钥解密
	encryptDecryptFile(client)
}
使用 RSA 密钥加密和解密字符串

一旦创建了客户端,应用程序就会加密一条消息:

func encryptDecryptString(client dapr.Client) {
    // ...

	// 加密消息
	encStream, err := client.Encrypt(context.Background(),
		strings.NewReader(message),
		dapr.EncryptOptions{
			ComponentName: CryptoComponentName,
			// 要使用的密钥名称
			// 由于这是一个 RSA 密钥,我们将其指定为密钥包装算法
			KeyName:          RSAKeyName,
			KeyWrapAlgorithm: "RSA",
		},
	)

    // ...

	// 该方法返回一个可读流,我们在内存中完整读取
	encBytes, err := io.ReadAll(encStream)
    // ...

	fmt.Printf("Encrypted the message, got %d bytes\n", len(encBytes))

然后应用程序解密消息:

	// 现在,解密加密的数据
	decStream, err := client.Decrypt(context.Background(),
		bytes.NewReader(encBytes),
		dapr.DecryptOptions{
			// 我们只需要传递组件的名称
			ComponentName: CryptoComponentName,
			// 传递密钥名称是可选的
			KeyName: RSAKeyName,
		},
	)

    // ...

	// 该方法返回一个可读流,我们在内存中完整读取
	decBytes, err := io.ReadAll(decStream)

    // ...

	// 在控制台上打印消息
	fmt.Printf("Decrypted the message, got %d bytes\n", len(decBytes))
	fmt.Println(string(decBytes))
}
使用 AES 密钥加密和解密大文件

接下来,应用程序加密一个大图像文件:

func encryptDecryptFile(client dapr.Client) {
	const fileName = "liuguangxi-66ouBTTs_x0-unsplash.jpg"

	// 获取输入文件的可读流
	plaintextF, err := os.Open(fileName)

    // ...

	defer plaintextF.Close()

	// 加密文件
	encStream, err := client.Encrypt(context.Background(),
		plaintextF,
		dapr.EncryptOptions{
			ComponentName: CryptoComponentName,
			// 要使用的密钥名称
			// 由于这是一个对称密钥,我们将 AES 指定为密钥包装算法
			KeyName:          SymmetricKeyName,
			KeyWrapAlgorithm: "AES",
		},
	)

    // ...

	// 将加密数据写入文件 "encrypted.out"
	encryptedF, err := os.Create("encrypted.out")

    // ...

	encryptedF.Close()

	fmt.Println("Wrote decrypted data to encrypted.out")

然后应用程序解密大图像文件:

	// 现在,解密加密的数据
	// 首先,再次打开文件 "encrypted.out",这次用于读取
	encryptedF, err = os.Open("encrypted.out")

    // ...

	defer encryptedF.Close()

	// 现在,解密加密的数据
	decStream, err := client.Decrypt(context.Background(),
		encryptedF,
		dapr.DecryptOptions{
			// 我们只需要传递组件的名称
			ComponentName: CryptoComponentName,
			// 传递密钥名称是可选的
			KeyName: SymmetricKeyName,
		},
	)

    // ...

	// 将解密数据写入文件 "decrypted.out.jpg"
	decryptedF, err := os.Create("decrypted.out.jpg")

    // ...

	decryptedF.Close()

	fmt.Println("Wrote decrypted data to decrypted.out.jpg")
}

观看演示

观看此 Dapr 社区电话 #83 中的加密 API 演示视频

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的 discord 频道进行讨论。

下一步

探索 Dapr 教程 >>

10 - 快速入门:作业

开始使用 Dapr 作业构建块

Dapr 作业构建块 允许您在特定时间或间隔调度和运行作业。在本快速入门中,您将学习如何使用 Dapr 的作业 API 来调度、获取和删除作业。

您可以通过以下两种方式来体验此作业快速入门:

同时运行多个应用

在开始之前,请选择您偏好的 Dapr SDK 语言。目前,您可以使用 Go SDK 来试验作业 API。

本快速入门包含两个应用程序:

  • job-scheduler.go 负责调度、检索和删除作业。
  • job-service.go 负责处理已调度的作业。

步骤 1:准备工作

您需要以下工具:

步骤 2:设置环境

克隆 快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

从仓库根目录导航到作业目录:

cd jobs/go/sdk

步骤 3:调度作业

运行应用程序并调度作业:

dapr run -f .

预期输出

== APP - job-service == dapr 客户端初始化中:127.0.0.1:6281
== APP - job-service == 已注册作业处理程序:R2-D2
== APP - job-service == 已注册作业处理程序:C-3PO
== APP - job-service == 已注册作业处理程序:BB-8
== APP - job-service == 在端口启动服务器:6200
== APP - job-service == 作业已调度:R2-D2
== APP - job-service == 作业已调度:C-3PO
== APP - job-service == 2024/07/17 18:09:59 作业:{name:"C-3PO"  due_time:"10s"  data:{value:"{\"droid\":\"C-3PO\",\"Task\":\"Memory Wipe\"}"}}
== APP - job-scheduler == 获取作业响应:{"droid":"C-3PO","Task":"Memory Wipe"}
== APP - job-service == 作业已调度:BB-8
== APP - job-service == 2024/07/17 18:09:59 作业:{name:"BB-8"  due_time:"15s"  data:{value:"{\"droid\":\"BB-8\",\"Task\":\"Internal Gyroscope Check\"}"}}
== APP - job-scheduler == 获取作业响应:{"droid":"BB-8","Task":"Internal Gyroscope Check"}
== APP - job-scheduler == 已删除作业:BB-8

5 秒后,终端输出应显示 R2-D2 作业正在处理:

== APP - job-service == 启动机器人:R2-D2
== APP - job-service == 执行维护作业:Oil Change

10 秒后,终端输出应显示 C3-PO 作业正在处理:

== APP - job-service == 启动机器人:C-3PO
== APP - job-service == 执行维护作业:Memory Wipe

完成后,您可以使用以下命令停止并清理应用程序进程。

dapr stop -f .

发生了什么?

在 Dapr 安装期间运行 dapr init 时:

在此快速入门中运行 dapr run -f . 启动了 job-schedulerjob-service。在终端输出中,您可以看到以下作业正在调度、检索和删除。

  • R2-D2 作业正在调度。
  • C-3PO 作业正在调度。
  • C-3PO 作业正在检索。
  • BB-8 作业正在调度。
  • BB-8 作业正在检索。
  • BB-8 作业正在删除。
  • R2-D2 作业在 5 秒后执行。
  • R2-D2 作业在 10 秒后执行。

dapr.yaml 多应用运行模板文件

使用 dapr run -f . 运行 多应用运行模板文件 启动项目中的所有应用程序。在此快速入门中,dapr.yaml 文件包含以下内容:

version: 1
apps:
  - appDirPath: ./job-service/
    appID: job-service
    appPort: 6200
    daprGRPCPort: 6281
    appProtocol: grpc
    command: ["go", "run", "."]
  - appDirPath: ./job-scheduler/
    appID: job-scheduler
    appPort: 6300
    command: ["go", "run", "."]

job-service 应用

job-service 应用程序创建服务调用处理程序以管理作业的生命周期(scheduleJobgetJobdeleteJob)。

if err := server.AddServiceInvocationHandler("scheduleJob", scheduleJob); err != nil {
	log.Fatalf("error adding invocation handler: %v", err)
}

if err := server.AddServiceInvocationHandler("getJob", getJob); err != nil {
	log.Fatalf("error adding invocation handler: %v", err)
}

if err := server.AddServiceInvocationHandler("deleteJob", deleteJob); err != nil {
	log.Fatalf("error adding invocation handler: %v", err)
}

接下来,为所有机器人注册作业事件处理程序:

for _, jobName := range jobNames {
	if err := server.AddJobEventHandler(jobName, handleJob); err != nil {
		log.Fatalf("failed to register job event handler: %v", err)
	}
	fmt.Println("Registered job handler for: ", jobName)
}

fmt.Println("Starting server on port: " + appPort)
if err = server.Start(); err != nil {
	log.Fatalf("failed to start server: %v", err)
}

然后,job-service 调用处理调度、获取、删除和处理作业事件的函数。

// 处理调度 DroidJob 的处理程序
func scheduleJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {

	if in == nil {
		err = errors.New("no invocation parameter")
		return
	}

	droidJob := DroidJob{}
	err = json.Unmarshal(in.Data, &droidJob)
	if err != nil {
		fmt.Println("failed to unmarshal job: ", err)
		return nil, err
	}

	jobData := JobData{
		Droid: droidJob.Name,
		Task:  droidJob.Job,
	}

	content, err := json.Marshal(jobData)
	if err != nil {
		fmt.Printf("Error marshalling job content")
		return nil, err
	}

	// 调度作业
	job := daprc.Job{
		Name:    droidJob.Name,
		DueTime: droidJob.DueTime,
		Data: &anypb.Any{
			Value: content,
		},
	}

	err = app.daprClient.ScheduleJobAlpha1(ctx, &job)
	if err != nil {
		fmt.Println("failed to schedule job. err: ", err)
		return nil, err
	}

	fmt.Println("Job scheduled: ", droidJob.Name)

	out = &common.Content{
		Data:        in.Data,
		ContentType: in.ContentType,
		DataTypeURL: in.DataTypeURL,
	}

	return out, err

}

// 处理按名称获取作业的处理程序
func getJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {

	if in == nil {
		err = errors.New("no invocation parameter")
		return nil, err
	}

	job, err := app.daprClient.GetJobAlpha1(ctx, string(in.Data))
	if err != nil {
		fmt.Println("failed to get job. err: ", err)
	}

	out = &common.Content{
		Data:        job.Data.Value,
		ContentType: in.ContentType,
		DataTypeURL: in.DataTypeURL,
	}

	return out, err
}

// 处理按名称删除作业的处理程序
func deleteJob(ctx context.Context, in *common.InvocationEvent) (out *common.Content, err error) {
	if in == nil {
		err = errors.New("no invocation parameter")
		return nil, err
	}

	err = app.daprClient.DeleteJobAlpha1(ctx, string(in.Data))
	if err != nil {
		fmt.Println("failed to delete job. err: ", err)
	}

	out = &common.Content{
		Data:        in.Data,
		ContentType: in.ContentType,
		DataTypeURL: in.DataTypeURL,
	}

	return out, err
}

// 处理作业事件的处理程序
func handleJob(ctx context.Context, job *common.JobEvent) error {
    var jobData common.Job
    if err := json.Unmarshal(job.Data, &jobData); err != nil {
        return fmt.Errorf("failed to unmarshal job: %v", err)
    }

    var jobPayload JobData
    if err := json.Unmarshal(job.Data, &jobPayload); err != nil {
        return fmt.Errorf("failed to unmarshal payload: %v", err)
    }

    fmt.Println("Starting droid:", jobPayload.Droid)
    fmt.Println("Executing maintenance job:", jobPayload.Task)

    return nil
}

job-scheduler 应用

job-scheduler 应用程序中,首先将 R2D2、C3PO 和 BB8 作业定义为 []DroidJob

droidJobs := []DroidJob{
	{Name: "R2-D2", Job: "Oil Change", DueTime: "5s"},
	{Name: "C-3PO", Job: "Memory Wipe", DueTime: "15s"},
	{Name: "BB-8", Job: "Internal Gyroscope Check", DueTime: "30s"},
}

然后使用作业 API 调度、检索和删除作业。正如您从终端输出中看到的,首先调度 R2D2 作业:

// 调度 R2D2 作业
err = schedule(droidJobs[0])
if err != nil {
	log.Fatalln("Error scheduling job: ", err)
}

然后调度 C3PO 作业,并返回作业数据:

// 调度 C-3PO 作业
err = schedule(droidJobs[1])
if err != nil {
	log.Fatalln("Error scheduling job: ", err)
}

// 获取 C-3PO 作业
resp, err := get(droidJobs[1])
if err != nil {
	log.Fatalln("Error retrieving job: ", err)
}
fmt.Println("Get job response: ", resp)

然后调度、检索和删除 BB8 作业:

// 调度 BB-8 作业
err = schedule(droidJobs[2])
if err != nil {
	log.Fatalln("Error scheduling job: ", err)
}

// 获取 BB-8 作业
resp, err = get(droidJobs[2])
if err != nil {
	log.Fatalln("Error retrieving job: ", err)
}
fmt.Println("Get job response: ", resp)

// 删除 BB-8 作业
err = delete(droidJobs[2])
if err != nil {
	log.Fatalln("Error deleting job: ", err)
}
fmt.Println("Job deleted: ", droidJobs[2].Name)

job-scheduler.go 还定义了 schedulegetdelete 函数,从 job-service.go 调用。

// 通过从 job-service 调用 grpc 服务并传递 DroidJob 作为参数来调度作业
func schedule(droidJob DroidJob) error {
	jobData, err := json.Marshal(droidJob)
	if err != nil {
		fmt.Println("Error marshalling job content")
		return err
	}

	content := &daprc.DataContent{
		ContentType: "application/json",
		Data:        []byte(jobData),
	}

	// 调度作业
	_, err = app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "scheduleJob", "POST", content)
	if err != nil {
		fmt.Println("Error invoking method: ", err)
		return err
	}

	return nil
}

// 通过从 job-service 调用 grpc 服务并传递作业名称作为参数来获取作业
func get(droidJob DroidJob) (string, error) {
	content := &daprc.DataContent{
		ContentType: "text/plain",
		Data:        []byte(droidJob.Name),
	}

	// 获取作业
	resp, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "getJob", "GET", content)
	if err != nil {
		fmt.Println("Error invoking method: ", err)
		return "", err
	}

	return string(resp), nil
}

// 通过从 job-service 调用 grpc 服务并传递作业名称作为参数来删除作业
func delete(droidJob DroidJob) error {
	content := &daprc.DataContent{
		ContentType: "text/plain",
		Data:        []byte(droidJob.Name),
	}

	_, err := app.daprClient.InvokeMethodWithContent(context.Background(), "job-service", "deleteJob", "DELETE", content)
	if err != nil {
		fmt.Println("Error invoking method: ", err)
		return err
	}

	return nil
}

逐个运行作业应用程序

本快速入门包含两个应用程序:

  • job-scheduler.go 负责调度、检索和删除作业。
  • job-service.go 负责处理已调度的作业。

步骤 1:准备工作

您需要以下工具:

步骤 2:设置环境

克隆 快速入门仓库中的示例

git clone https://github.com/dapr/quickstarts.git

从仓库根目录导航到作业目录:

cd jobs/go/sdk

步骤 3:调度作业

在终端中运行 job-service 应用:

dapr run --app-id job-service --app-port 6200 --dapr-grpc-port 6281 --app-protocol grpc -- go run .

预期输出

== APP == dapr 客户端初始化中:127.0.0.1:6281
== APP == 已注册作业处理程序:R2-D2
== APP == 已注册作业处理程序:C-3PO
== APP == 已注册作业处理程序:BB-8
== APP == 在端口启动服务器:6200

在新终端窗口中运行 job-scheduler 应用:

dapr run --app-id job-scheduler --app-port 6300 -- go run .

预期输出

== APP == dapr 客户端初始化中:
== APP == 获取作业响应:{"droid":"C-3PO","Task":"Memory Wipe"}
== APP == 获取作业响应:{"droid":"BB-8","Task":"Internal Gyroscope Check"}
== APP == 作业已删除:BB-8

返回到 job-service 应用的终端窗口。输出应为:

== APP == 作业已调度:R2-D2
== APP == 作业已调度:C-3PO
== APP == 2024/07/17 18:25:36 作业:{name:"C-3PO"  due_time:"10s"  data:{value:"{\"droid\":\"C-3PO\",\"Task\":\"Memory Wipe\"}"}}
== APP == 作业已调度:BB-8
== APP == 2024/07/17 18:25:36 作业:{name:"BB-8"  due_time:"15s"  data:{value:"{\"droid\":\"BB-8\",\"Task\":\"Internal Gyroscope Check\"}"}}
== APP == 启动机器人:R2-D2
== APP == 执行维护作业:Oil Change
== APP == 启动机器人:C-3PO
== APP == 执行维护作业:Memory Wipe

解读当您运行 dapr runjob-servicejob-scheduler 应用程序中发生的事情。

观看演示

观看使用 Go HTTP 示例的作业 API 演示,录制于 Dapr 社区电话 #107

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的 discord 频道 参与讨论。

下一步

探索 Dapr 教程 >>

11 - 可靠性快速入门

开始探索 Dapr 的可靠性组件

11.1 - 快速入门:从服务到组件的弹性

通过Dapr的状态管理API来了解其弹性功能

通过模拟系统故障来了解Dapr的弹性功能。在本快速入门中,您将:

  • 运行一个微服务应用程序,该应用程序通过Dapr的状态管理API持续保存和检索状态。
  • 通过模拟系统故障来触发弹性策略。
  • 解决故障后,微服务应用程序将恢复。
显示应用于Dapr API的弹性示意图

在继续快速入门之前,请选择您偏好的Dapr SDK语言。

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到order-processor目录。

cd ../state_management/python/sdk/order-processor

安装依赖项

pip3 install -r requirements.txt 

步骤2:运行应用程序

在Dapr边车的支持下运行order-processor服务。然后,Dapr边车会加载位于资源目录中的弹性规范:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - order-processor

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        duration: 5s
        maxRetries: -1

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s
        trip: consecutiveFailures >= 5

  targets:
    components:
      statestore:
        outbound:
          retry: retryForever
          circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- python3

应用程序启动后,order-processor服务会将orderId键值对写入和读取到statestore的Redis实例中statestore.yaml组件中定义

== APP == Saving Order:  { orderId: '1' }
== APP == Getting Order:  { orderId: '1' }
== APP == Saving Order:  { orderId: '2' }
== APP == Getting Order:  { orderId: '2' }
== APP == Saving Order:  { orderId: '3' }
== APP == Getting Order:  { orderId: '3' }
== APP == Saving Order:  { orderId: '4' }
== APP == Getting Order:  { orderId: '4' }

步骤3:引入故障

通过停止在开发机器上执行dapr init时初始化的Redis容器实例来模拟故障。一旦实例停止,来自order-processor服务的写入和读取操作将开始失败。

由于resiliency.yaml规范将statestore定义为组件目标,所有失败的请求将自动应用重试和断路器策略:

  targets:
    components:
      statestore:
        outbound:
          retry: retryForever
          circuitBreaker: simpleCB

在新的终端窗口中,运行以下命令以停止Redis:

docker stop dapr_redis

一旦Redis停止,请求开始失败,并应用名为retryForever的重试策略。以下输出显示了来自order-processor服务的日志:

INFO[0006] Error processing operation component[statestore] output. Retrying...

根据retryForever策略,重试将以5秒间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦5次连续重试失败,断路器策略simpleCB被触发,断路器打开,停止所有请求:

INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open 
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed  

只要Redis容器停止,这种半开/打开行为将继续。

步骤3:移除故障

当您在机器上重新启动Redis容器后,应用程序将无缝恢复并继续之前的操作。

docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.  
== APP == Saving Order:  { orderId: '5' }
== APP == Getting Order:  { orderId: '5' }
== APP == Saving Order:  { orderId: '6' }
== APP == Getting Order:  { orderId: '6' }
== APP == Saving Order:  { orderId: '7' }
== APP == Getting Order:  { orderId: '7' }
== APP == Saving Order:  { orderId: '8' }
== APP == Getting Order:  { orderId: '8' }
== APP == Saving Order:  { orderId: '9' }
== APP == Getting Order:  { orderId: '9' }

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到order-processor目录。

cd ../state_management/javascript/sdk/order-processor

安装依赖项

npm install

步骤2:运行应用程序

在Dapr边车的支持下运行order-processor服务。然后,Dapr边车会加载位于资源目录中的弹性规范:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- npm start

应用程序启动后,order-processor服务会将orderId键值对写入和读取到statestore的Redis实例中statestore.yaml组件中定义

== APP == Saving Order:  { orderId: '1' }
== APP == Getting Order:  { orderId: '1' }
== APP == Saving Order:  { orderId: '2' }
== APP == Getting Order:  { orderId: '2' }
== APP == Saving Order:  { orderId: '3' }
== APP == Getting Order:  { orderId: '3' }
== APP == Saving Order:  { orderId: '4' }
== APP == Getting Order:  { orderId: '4' }

步骤3:引入故障

通过停止在开发机器上执行dapr init时初始化的Redis容器实例来模拟故障。一旦实例停止,来自order-processor服务的写入和读取操作将开始失败。

由于resiliency.yaml规范将statestore定义为组件目标,所有失败的请求将自动应用重试和断路器策略:

  targets:
    components:
      statestore:
        outbound:
          retry: retryForever
          circuitBreaker: simpleCB

在新的终端窗口中,运行以下命令以停止Redis:

docker stop dapr_redis

一旦Redis停止,请求开始失败,并应用名为retryForever的重试策略。以下输出显示了来自order-processor服务的日志:

INFO[0006] Error processing operation component[statestore] output. Retrying...

根据retryForever策略,重试将以5秒间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦5次连续重试失败,断路器策略simpleCB被触发,断路器打开,停止所有请求:

INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open 
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed  

只要Redis容器停止,这种半开/打开行为将继续。

步骤3:移除故障

当您在机器上重新启动Redis容器后,应用程序将无缝恢复并继续之前的操作。

docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.  
== APP == Saving Order:  { orderId: '5' }
== APP == Getting Order:  { orderId: '5' }
== APP == Saving Order:  { orderId: '6' }
== APP == Getting Order:  { orderId: '6' }
== APP == Saving Order:  { orderId: '7' }
== APP == Getting Order:  { orderId: '7' }
== APP == Saving Order:  { orderId: '8' }
== APP == Getting Order:  { orderId: '8' }
== APP == Saving Order:  { orderId: '9' }
== APP == Getting Order:  { orderId: '9' }

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到order-processor目录。

cd ../state_management/csharp/sdk/order-processor

安装依赖项

dotnet restore
dotnet build

步骤2:运行应用程序

在Dapr边车的支持下运行order-processor服务。然后,Dapr边车会加载位于资源目录中的弹性规范:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- dotnet run

应用程序启动后,order-processor服务会将orderId键值对写入和读取到statestore的Redis实例中statestore.yaml组件中定义

== APP == Saving Order:  { orderId: '1' }
== APP == Getting Order:  { orderId: '1' }
== APP == Saving Order:  { orderId: '2' }
== APP == Getting Order:  { orderId: '2' }
== APP == Saving Order:  { orderId: '3' }
== APP == Getting Order:  { orderId: '3' }
== APP == Saving Order:  { orderId: '4' }
== APP == Getting Order:  { orderId: '4' }

步骤3:引入故障

通过停止在开发机器上执行dapr init时初始化的Redis容器实例来模拟故障。一旦实例停止,来自order-processor服务的写入和读取操作将开始失败。

由于resiliency.yaml规范将statestore定义为组件目标,所有失败的请求将自动应用重试和断路器策略:

  targets:
    components:
      statestore:
        outbound:
          retry: retryForever
          circuitBreaker: simpleCB

在新的终端窗口中,运行以下命令以停止Redis:

docker stop dapr_redis

一旦Redis停止,请求开始失败,并应用名为retryForever的重试策略。以下输出显示了来自order-processor服务的日志:

INFO[0006] Error processing operation component[statestore] output. Retrying...

根据retryForever策略,重试将以5秒间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦5次连续重试失败,断路器策略simpleCB被触发,断路器打开,停止所有请求:

INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open 
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed  

只要Redis容器停止,这种半开/打开行为将继续。

步骤3:移除故障

当您在机器上重新启动Redis容器后,应用程序将无缝恢复并继续之前的操作。

docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.  
== APP == Saving Order:  { orderId: '5' }
== APP == Getting Order:  { orderId: '5' }
== APP == Saving Order:  { orderId: '6' }
== APP == Getting Order:  { orderId: '6' }
== APP == Saving Order:  { orderId: '7' }
== APP == Getting Order:  { orderId: '7' }
== APP == Saving Order:  { orderId: '8' }
== APP == Getting Order:  { orderId: '8' }
== APP == Saving Order:  { orderId: '9' }
== APP == Getting Order:  { orderId: '9' }

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到order-processor目录。

cd ../state_management/java/sdk/order-processor

安装依赖项

mvn clean install

步骤2:运行应用程序

在Dapr边车的支持下运行order-processor服务。然后,Dapr边车会加载位于资源目录中的弹性规范:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources/ -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

应用程序启动后,order-processor服务会将orderId键值对写入和读取到statestore的Redis实例中statestore.yaml组件中定义

== APP == Saving Order:  { orderId: '1' }
== APP == Getting Order:  { orderId: '1' }
== APP == Saving Order:  { orderId: '2' }
== APP == Getting Order:  { orderId: '2' }
== APP == Saving Order:  { orderId: '3' }
== APP == Getting Order:  { orderId: '3' }
== APP == Saving Order:  { orderId: '4' }
== APP == Getting Order:  { orderId: '4' }

步骤3:引入故障

通过停止在开发机器上执行dapr init时初始化的Redis容器实例来模拟故障。一旦实例停止,来自order-processor服务的写入和读取操作将开始失败。

由于resiliency.yaml规范将statestore定义为组件目标,所有失败的请求将自动应用重试和断路器策略:

  targets:
    components:
      statestore:
        outbound:
          retry: retryForever
          circuitBreaker: simpleCB

在新的终端窗口中,运行以下命令以停止Redis:

docker stop dapr_redis

一旦Redis停止,请求开始失败,并应用名为retryForever的重试策略。以下输出显示了来自order-processor服务的日志:

INFO[0006] Error processing operation component[statestore] output. Retrying...

根据retryForever策略,重试将以5秒间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦5次连续重试失败,断路器策略simpleCB被触发,断路器打开,停止所有请求:

INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open 
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed  

只要Redis容器停止,这种半开/打开行为将继续。

步骤3:移除故障

当您在机器上重新启动Redis容器后,应用程序将无缝恢复并继续之前的操作。

docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.  
== APP == Saving Order:  { orderId: '5' }
== APP == Getting Order:  { orderId: '5' }
== APP == Saving Order:  { orderId: '6' }
== APP == Getting Order:  { orderId: '6' }
== APP == Saving Order:  { orderId: '7' }
== APP == Getting Order:  { orderId: '7' }
== APP == Saving Order:  { orderId: '8' }
== APP == Getting Order:  { orderId: '8' }
== APP == Saving Order:  { orderId: '9' }
== APP == Getting Order:  { orderId: '9' }

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆快速入门仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

在终端窗口中,导航到order-processor目录。

cd ../state_management/go/sdk/order-processor

安装依赖项

go build .

步骤2:运行应用程序

在Dapr边车的支持下运行order-processor服务。然后,Dapr边车会加载位于资源目录中的弹性规范:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB
dapr run --app-id order-processor --resources-path ../../../resources -- go run .

应用程序启动后,order-processor服务会将orderId键值对写入和读取到statestore的Redis实例中statestore.yaml组件中定义

== APP == Saving Order:  { orderId: '1' }
== APP == Getting Order:  { orderId: '1' }
== APP == Saving Order:  { orderId: '2' }
== APP == Getting Order:  { orderId: '2' }
== APP == Saving Order:  { orderId: '3' }
== APP == Getting Order:  { orderId: '3' }
== APP == Saving Order:  { orderId: '4' }
== APP == Getting Order:  { orderId: '4' }

步骤3:引入故障

通过停止在开发机器上执行dapr init时初始化的Redis容器实例来模拟故障。一旦实例停止,来自order-processor服务的写入和读取操作将开始失败。

由于resiliency.yaml规范将statestore定义为组件目标,所有失败的请求将自动应用重试和断路器策略:

  targets:
    components:
      statestore:
        outbound:
          retry: retryForever
          circuitBreaker: simpleCB

在新的终端窗口中,运行以下命令以停止Redis:

docker stop dapr_redis

一旦Redis停止,请求开始失败,并应用名为retryForever的重试策略。以下输出显示了来自order-processor服务的日志:

INFO[0006] Error processing operation component[statestore] output. Retrying...

根据retryForever策略,重试将以5秒间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦5次连续重试失败,断路器策略simpleCB被触发,断路器打开,停止所有请求:

INFO[0026] Circuit breaker "simpleCB-statestore" changed state from closed to open
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0031] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0031] Circuit breaker "simpleCB-statestore" changed state from half-open to open 
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from open to half-open  
INFO[0036] Circuit breaker "simpleCB-statestore" changed state from half-open to closed  

只要Redis容器停止,这种半开/打开行为将继续。

步骤3:移除故障

当您在机器上重新启动Redis容器后,应用程序将无缝恢复并继续之前的操作。

docker start dapr_redis
INFO[0036] Recovered processing operation component[statestore] output.  
== APP == Saving Order:  { orderId: '5' }
== APP == Getting Order:  { orderId: '5' }
== APP == Saving Order:  { orderId: '6' }
== APP == Getting Order:  { orderId: '6' }
== APP == Saving Order:  { orderId: '7' }
== APP == Getting Order:  { orderId: '7' }
== APP == Saving Order:  { orderId: '8' }
== APP == Getting Order:  { orderId: '8' }
== APP == Saving Order:  { orderId: '9' }
== APP == Getting Order:  { orderId: '9' }

告诉我们您的想法!

我们正在不断努力改进我们的快速入门示例,并重视您的反馈。您觉得这个快速入门有帮助吗?您有改进建议吗?

加入我们的discord频道讨论。

下一步

了解更多关于弹性功能及其如何与Dapr的构建块API协作。

探索Dapr教程 >>

11.2 - 入门指南:服务间的弹性

通过服务调用API开始使用Dapr的弹性功能

通过模拟系统故障来测试Dapr的弹性功能。在本入门指南中,您将会:

  • 运行两个微服务应用程序:checkoutorder-processorcheckout将持续向order-processor发起Dapr服务调用请求。
  • 通过模拟系统故障来触发弹性机制。
  • 移除故障以让微服务应用程序恢复。
显示应用于Dapr API的弹性图示

在继续入门指南之前,选择您偏好的Dapr SDK语言版本。

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆入门指南仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:运行order-processor服务

在终端窗口中,从入门指南目录的根目录导航到order-processor目录。

cd service_invocation/python/http/order-processor

安装依赖项:

pip3 install -r requirements.txt

运行order-processor服务及其Dapr sidecar。

dapr run --app-port 8001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- python3 app.py

步骤3:运行checkout服务应用程序

在新的终端窗口中,从入门指南目录的根目录导航到checkout目录。

cd service_invocation/python/http/checkout

安装依赖项:

pip3 install -r requirements.txt

运行checkout服务及其Dapr sidecar。

dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- python3 app.py

Dapr sidecar随后加载位于资源目录中的弹性配置:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

步骤4:查看服务调用输出

当两个服务和sidecar都在运行时,注意订单如何通过Dapr服务调用从checkout服务传递到order-processor服务。

checkout服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}

order-processor服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}

步骤5:引入故障

通过停止order-processor服务来模拟故障。一旦实例停止,来自checkout服务的服务调用操作将开始失败。

由于resiliency.yaml配置将order-processor服务定义为弹性目标,所有失败的请求将应用重试和断路器策略:

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

order-processor窗口中,停止服务:

CTRL + C

一旦第一个请求失败,名为retryForever的重试策略将被应用:

INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...  

重试将以5秒的间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦连续5次重试失败,断路器策略simpleCB将被触发,断路器打开,停止所有请求:

INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open  
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open   
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open     

这种半开/打开行为将持续到order-processor服务停止为止。

步骤6:移除故障

一旦您重新启动order-processor服务,应用程序将无缝恢复,继续接受订单请求。

order-processor服务终端中,重新启动应用程序:

dapr run --app-port 8001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- python3 app.py

checkout服务输出:

== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor服务输出:

== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆入门指南仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:运行order-processor服务

在终端窗口中,从入门指南目录的根目录导航到order-processor目录。

cd service_invocation/javascript/http/order-processor

安装依赖项:

npm install

运行order-processor服务及其Dapr sidecar。

dapr run --app-port 5001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- npm start

步骤3:运行checkout服务应用程序

在新的终端窗口中,从入门指南目录的根目录导航到checkout目录。

cd service_invocation/javascript/http/checkout

安装依赖项:

npm install

运行checkout服务及其Dapr sidecar。

dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- npm start

Dapr sidecar随后加载位于资源目录中的弹性配置:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

步骤4:查看服务调用输出

当两个服务和sidecar都在运行时,注意订单如何通过Dapr服务调用从checkout服务传递到order-processor服务。

checkout服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}

order-processor服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}

步骤5:引入故障

通过停止order-processor服务来模拟故障。一旦实例停止,来自checkout服务的服务调用操作将开始失败。

由于resiliency.yaml配置将order-processor服务定义为弹性目标,所有失败的请求将应用重试和断路器策略:

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

order-processor窗口中,停止服务:

CMD + C
CTRL + C

一旦第一个请求失败,名为retryForever的重试策略将被应用:

INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...  

重试将以5秒的间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦连续5次重试失败,断路器策略simpleCB将被触发,断路器打开,停止所有请求:

INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open  
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open   
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open     

这种半开/打开行为将持续到order-processor服务停止为止。

步骤6:移除故障

一旦您重新启动order-processor服务,应用程序将无缝恢复,继续接受订单请求。

order-processor服务终端中,重新启动应用程序:

dapr run --app-port 5001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- npm start

checkout服务输出:

== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor服务输出:

== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆入门指南仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:运行order-processor服务

在终端窗口中,从入门指南目录的根目录导航到order-processor目录。

cd service_invocation/csharp/http/order-processor

安装依赖项:

dotnet restore
dotnet build

运行order-processor服务及其Dapr sidecar。

dapr run --app-port 7001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- dotnet run

步骤3:运行checkout服务应用程序

在新的终端窗口中,从入门指南目录的根目录导航到checkout目录。

cd service_invocation/csharp/http/checkout

安装依赖项:

dotnet restore
dotnet build

运行checkout服务及其Dapr sidecar。

dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- dotnet run

Dapr sidecar随后加载位于资源目录中的弹性配置:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

步骤4:查看服务调用输出

当两个服务和sidecar都在运行时,注意订单如何通过Dapr服务调用从checkout服务传递到order-processor服务。

checkout服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}

order-processor服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}

步骤5:引入故障

通过停止order-processor服务来模拟故障。一旦实例停止,来自checkout服务的服务调用操作将开始失败。

由于resiliency.yaml配置将order-processor服务定义为弹性目标,所有失败的请求将应用重试和断路器策略:

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

order-processor窗口中,停止服务:

CMD + C
CTRL + C

一旦第一个请求失败,名为retryForever的重试策略将被应用:

INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...  

重试将以5秒的间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦连续5次重试失败,断路器策略simpleCB将被触发,断路器打开,停止所有请求:

INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open  
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open   
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open     

这种半开/打开行为将持续到order-processor服务停止为止。

步骤6:移除故障

一旦您重新启动order-processor服务,应用程序将无缝恢复,继续接受订单请求。

order-processor服务终端中,重新启动应用程序:

dapr run --app-port 7001 --app-id order-processor --app-protocol http --dapr-http-port 3501 -- dotnet run

checkout服务输出:

== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor服务输出:

== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆入门指南仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:运行order-processor服务

在终端窗口中,从入门指南目录的根目录导航到order-processor目录。

cd service_invocation/java/http/order-processor

安装依赖项:

mvn clean install

运行order-processor服务及其Dapr sidecar。

dapr run --app-id order-processor --resources-path ../../../resources/ --app-port 9001 --app-protocol http --dapr-http-port 3501 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

步骤3:运行checkout服务应用程序

在新的终端窗口中,从入门指南目录的根目录导航到checkout目录。

cd service_invocation/java/http/checkout

安装依赖项:

mvn clean install

运行checkout服务及其Dapr sidecar。

dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar

Dapr sidecar随后加载位于资源目录中的弹性配置:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

步骤4:查看服务调用输出

当两个服务和sidecar都在运行时,注意订单如何通过Dapr服务调用从checkout服务传递到order-processor服务。

checkout服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}

order-processor服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}

步骤5:引入故障

通过停止order-processor服务来模拟故障。一旦实例停止,来自checkout服务的服务调用操作将开始失败。

由于resiliency.yaml配置将order-processor服务定义为弹性目标,所有失败的请求将应用重试和断路器策略:

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

order-processor窗口中,停止服务:

CMD + C
CTRL + C

一旦第一个请求失败,名为retryForever的重试策略将被应用:

INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...  

重试将以5秒的间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦连续5次重试失败,断路器策略simpleCB将被触发,断路器打开,停止所有请求:

INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open  
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open   
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open     

这种半开/打开行为将持续到order-processor服务停止为止。

步骤6:移除故障

一旦您重新启动order-processor服务,应用程序将无缝恢复,继续接受订单请求。

order-processor服务终端中,重新启动应用程序:

dapr run --app-id order-processor --resources-path ../../../resources/ --app-port 9001 --app-protocol http --dapr-http-port 3501 -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar

checkout服务输出:

== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor服务输出:

== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

前提条件

对于此示例,您将需要:

步骤1:设置环境

克隆入门指南仓库中提供的示例

git clone https://github.com/dapr/quickstarts.git

步骤2:运行order-processor服务

在终端窗口中,从入门指南目录的根目录导航到order-processor目录。

cd service_invocation/go/http/order-processor

安装依赖项:

go build .

运行order-processor服务及其Dapr sidecar。

dapr run --app-port 6001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- go run .

步骤3:运行checkout服务应用程序

在新的终端窗口中,从入门指南目录的根目录导航到checkout目录。

cd service_invocation/go/http/checkout

安装依赖项:

go build .

运行checkout服务及其Dapr sidecar。

dapr run --app-id checkout --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3500 -- go run .

Dapr sidecar随后加载位于资源目录中的弹性配置:

apiVersion: dapr.io/v1alpha1
kind: Resiliency
metadata:
  name: myresiliency
scopes:
  - checkout

spec:
  policies:
    retries:
      retryForever:
        policy: constant
        maxInterval: 5s
        maxRetries: -1 

    circuitBreakers:
      simpleCB:
        maxRequests: 1
        timeout: 5s 
        trip: consecutiveFailures >= 5

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

步骤4:查看服务调用输出

当两个服务和sidecar都在运行时,注意订单如何通过Dapr服务调用从checkout服务传递到order-processor服务。

checkout服务输出:

== APP == Order passed: {"orderId": 1}
== APP == Order passed: {"orderId": 2}
== APP == Order passed: {"orderId": 3}
== APP == Order passed: {"orderId": 4}

order-processor服务输出:

== APP == Order received: {"orderId": 1}
== APP == Order received: {"orderId": 2}
== APP == Order received: {"orderId": 3}
== APP == Order received: {"orderId": 4}

步骤5:引入故障

通过停止order-processor服务来模拟故障。一旦实例停止,来自checkout服务的服务调用操作将开始失败。

由于resiliency.yaml配置将order-processor服务定义为弹性目标,所有失败的请求将应用重试和断路器策略:

  targets:
    apps:
      order-processor:
        retry: retryForever
        circuitBreaker: simpleCB

order-processor窗口中,停止服务:

CMD + C
CTRL + C

一旦第一个请求失败,名为retryForever的重试策略将被应用:

INFO[0005] Error processing operation endpoint[order-processor, order-processor:orders]. Retrying...  

重试将以5秒的间隔无限期地继续每个失败的请求。

retryForever:
  policy: constant
  maxInterval: 5s
  maxRetries: -1 

一旦连续5次重试失败,断路器策略simpleCB将被触发,断路器打开,停止所有请求:

INFO[0025] Circuit breaker "order-processor:orders" changed state from closed to open  
circuitBreakers:
  simpleCB:
  maxRequests: 1
  timeout: 5s 
  trip: consecutiveFailures >= 5

经过5秒后,断路器将切换到半开状态,允许一个请求通过以验证故障是否已解决。如果请求继续失败,断路器将再次触发回到打开状态。

INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open   
INFO[0030] Circuit breaker "order-processor:orders" changed state from open to half-open  
INFO[0030] Circuit breaker "order-processor:orders" changed state from half-open to open     

这种半开/打开行为将持续到order-processor服务停止为止。

步骤6:移除故障

一旦您重新启动order-processor服务,应用程序将无缝恢复,继续接受订单请求。

order-processor服务终端中,重新启动应用程序:

dapr run --app-port 6001 --app-id order-processor --resources-path ../../../resources/ --app-protocol http --dapr-http-port 3501 -- go run .

checkout服务输出:

== APP == Order passed: {"orderId": 5}
== APP == Order passed: {"orderId": 6}
== APP == Order passed: {"orderId": 7}
== APP == Order passed: {"orderId": 8}
== APP == Order passed: {"orderId": 9}
== APP == Order passed: {"orderId": 10}

order-processor服务输出:

== APP == Order received: {"orderId": 5}
== APP == Order received: {"orderId": 6}
== APP == Order received: {"orderId": 7}
== APP == Order received: {"orderId": 8}
== APP == Order received: {"orderId": 9}
== APP == Order received: {"orderId": 10}

告诉我们您的想法!

我们正在不断努力改进我们的入门指南示例,重视您的反馈。您觉得这个入门指南有帮助吗?您有改进建议吗?

加入我们的discord频道讨论。

下一步

访问此链接以获取有关Dapr弹性的更多信息。

探索Dapr教程 >>