1 - 服务调用概述
通过服务调用,您的应用程序可以使用标准的 gRPC 或 HTTP 协议可靠且安全地与其他应用程序通信。
在许多基于微服务的应用程序中,多个服务需要相互通信的能力。这种服务间通信要求应用程序开发者处理以下问题:
- 服务发现。 如何发现不同的服务?
- 标准化服务间 API 调用。 如何在不同服务之间调用方法?
- 安全的服务间通信。 如何通过加密安全地调用其他服务,并对方法实施访问控制?
- 缓解请求超时或故障。 如何处理重试和瞬态错误?
- 实现可观测性和追踪。 如何使用追踪来查看带有指标的调用图,以诊断生产环境中的问题?
服务调用 API
Dapr 通过提供服务调用 API 来应对这些挑战,该 API 的行为类似于具有内置服务发现功能的反向代理,同时利用了内置的分布式追踪、指标、错误处理、加密等功能。
Dapr 使用边车架构。要使用 Dapr 调用应用程序:
- 您可以在 Dapr 实例上使用
invokeAPI。 - 每个应用程序与其自己的 Dapr 实例通信。
- Dapr 实例之间相互发现和通信。
以下概述视频和演示 演示了 Dapr 服务调用的工作原理。
下图是 Dapr 服务调用在两个 Dapr 化应用程序之间工作方式的概述。

- 服务 A 发送 HTTP 或 gRPC 调用到服务 B。调用首先到达本地 Dapr 边车。
- Dapr 使用名称解析组件发现服务 B 的位置,该组件运行在给定的托管平台上。
- Dapr 将消息转发到服务 B 的 Dapr 边车
- 注意:Dapr 边车之间的所有调用都通过 gRPC 进行以提高性能。只有服务与 Dapr 边车之间的调用可以是 HTTP 或 gRPC。
- 服务 B 的 Dapr 边车将请求转发到服务 B 上的指定端点(或方法)。然后服务 B 执行其业务逻辑代码。
- 服务 B 向服务 A 发送响应。响应首先到达服务 B 的边车。
- Dapr 将响应转发到服务 A 的 Dapr 边车。
- 服务 A 接收响应。
您还可以使用服务调用 API 调用非 Dapr HTTP 端点。例如,您可能只在部分整体应用程序中使用 Dapr,可能无法访问用于将现有应用程序迁移到使用 Dapr 的代码,或者只是需要调用外部 HTTP 服务。阅读“如何:使用 HTTP 调用非 Dapr 端点” 获取更多信息。
功能特性
服务调用提供了多项功能,使您可以轻松地在应用程序之间调用方法或调用外部 HTTP 端点。
HTTP 和 gRPC 服务调用
- HTTP:如果您已经在应用程序中使用 HTTP 协议,使用 Dapr HTTP 头可能是最简单的入门方式。您无需更改现有的端点 URL;只需添加
dapr-app-id头即可。更多信息请参阅使用 HTTP 调用服务。 - gRPC:Dapr 允许用户保留自己的 proto 服务并原生使用 gRPC。这意味着您可以使用服务调用来调用现有的 gRPC 应用程序,而无需包含任何 Dapr SDK 或自定义 gRPC 服务。更多信息请参阅 Dapr 和 gRPC 操作指南。
服务到服务安全
通过 Dapr Sentry 服务,Dapr 应用程序之间的所有调用都可以使用托管平台上的相互(mTLS)身份验证来保证安全,包括自动证书轮换。
更多信息请阅读服务到服务安全文章。
包括重试在内的弹性
在发生调用失败和瞬态错误的情况下,服务调用提供了一种弹性功能,可以执行带退避时间的自动重试。了解更多,请参阅弹性文章。
可观测性追踪和指标
默认情况下,应用程序之间的所有调用都会被追踪,并收集指标以提供洞察和诊断。这在生产场景中尤为重要,提供服务间调用的调用图和指标。更多信息请阅读可观测性。
访问控制
通过访问策略,应用程序可以控制:
- 允许哪些应用程序调用它们。
- 哪些应用程序被授权执行操作。
例如,您可以限制包含人员信息的敏感应用程序被未经授权的应用程序访问。结合服务到服务安全通信,您可以提供软多租户部署。
更多信息请阅读服务调用的访问控制白名单文章。
命名空间作用域
您可以将应用程序作用域限定到命名空间,用于部署和安全目的,并可以调用部署在不同命名空间中的服务。更多信息请阅读跨命名空间的服务调用文章。
使用 mDNS 的轮询负载均衡
Dapr 使用 mDNS 协议提供服务调用的轮询负载均衡,例如在单机上或多个联网的物理机器上。
下图展示了这如何工作的示例。如果您有 1 个应用程序 ID 为 FrontEnd 的应用程序实例和 3 个应用程序 ID 为 Cart 的应用程序实例,当您从 FrontEnd 应用调用 Cart 应用时,Dapr 会在这 3 个实例之间轮询。这些实例可以在同一台机器上,也可以在不同的机器上。

注意:应用程序 ID 在每个_应用程序_中是唯一的,而不是应用程序实例。无论该应用程序存在多少个实例(由于扩展),它们都将共享相同的应用程序 ID。
可交换的服务发现
Dapr 可以在各种托管平台上运行。为了使服务调用支持可交换的服务发现,Dapr 使用名称解析组件。例如,Kubernetes 名称解析组件使用 Kubernetes DNS 服务来解析集群中运行的其他应用程序的位置。
自托管机器可以使用 mDNS 名称解析组件。作为替代方案,您可以使用 SQLite 名称解析组件在单节点环境和本地开发场景中运行 Dapr。作为集群一部分的 Dapr 边车将其信息存储在本地机器上的 SQLite 数据库中。
Consul 名称解析组件特别适用于多机器部署,可用于任何托管环境,包括 Kubernetes、多个虚拟机或自托管。
HTTP 服务调用的流式处理
您可以在 HTTP 服务调用中将数据作为流处理。当使用 Dapr 通过 HTTP 调用另一个服务并处理大型请求或响应体时,这可以提供性能和内存利用率方面的改进。
下图演示了数据流的六个步骤。

- 请求:“App A” 到 “Dapr sidecar A”
- 请求:“Dapr sidecar A” 到 “Dapr sidecar B”
- 请求:“Dapr sidecar B” 到 “App B”
- 响应:“App B” 到 “Dapr sidecar B”
- 响应:“Dapr sidecar B” 到 “Dapr sidecar A”
- 响应:“Dapr sidecar A” 到 “App A”
示例架构
按照上述调用顺序,假设您有 Hello World 教程 中描述的应用程序,其中一个 Python 应用程序调用一个 Node.js 应用程序。在这种情况下,Python 应用程序是"服务 A",Node.js 应用程序是"服务 B"。
下图再次展示了在本地机器上显示 API 调用的序列 1-7:

- Node.js 应用程序的 Dapr 应用程序 ID 为
nodeapp。Python 应用程序通过 POSThttp://localhost:3500/v1.0/invoke/nodeapp/method/neworder来调用 Node.js 应用程序的neworder方法,该请求首先到达 Python 应用程序的本地 Dapr 边车。 - Dapr 使用名称解析组件(在本例中为自托管时的 mDNS)发现 Node.js 应用程序的位置,该组件在您的本地机器上运行。
- Dapr 使用刚刚获得的位置将请求转发到 Node.js 应用程序的边车。
- Node.js 应用程序的边车将请求转发到 Node.js 应用程序。Node.js 应用程序执行业务逻辑,记录传入消息,然后将订单 ID 持久化到 Redis(图中未显示)。
- Node.js 应用程序通过 Node.js 边车向 Python 应用程序发送响应。
- Dapr 将响应转发到 Python Dapr 边车。
- Python 应用程序接收响应。
试用服务调用
快速入门和教程
Dapr 文档包含多个快速入门,展示了在不同示例架构中利用服务调用构建块的方式。要直接了解服务调用 API 及其功能,我们建议从以下快速入门开始:
| 快速入门/教程 | 描述 |
|---|---|
| 服务调用快速入门 | 此快速入门让您直接与服务调用构建块交互。 |
| Hello world 教程 | 本教程展示如何同时使用服务调用和状态管理构建块,全部在本地机器上运行。 |
| Hello world Kubernetes 教程 | 本教程介绍如何在 Kubernetes 中使用 Dapr,涵盖服务调用和状态管理构建块。 |
直接在您的应用程序中开始使用服务调用
想跳过快速入门吗?没问题。您可以直接在应用程序中试用服务调用构建块,与其他服务安全通信。安装 Dapr 后,您可以通过以下方式开始使用服务调用 API。
使用以下方式调用服务:
- HTTP 和 gRPC 服务调用(推荐的设置方法)
- HTTP - 只需添加
dapr-app-id头即可开始使用。点击此处了解更多:使用 HTTP 调用服务。 - gRPC - 对于基于 gRPC 的应用程序,服务调用 API 也可用。运行 gRPC 服务器,然后使用 Dapr CLI 调用服务。在配置 Dapr 使用 gRPC和使用 gRPC 调用服务中了解更多。
- HTTP - 只需添加
- 直接调用 API - 除了代理之外,还有一个选项是直接调用服务调用 API 来调用 GET 端点。只需将您的地址 URL 更新为
localhost:<dapr-http-port>,您就可以直接调用 API。您还可以在上面 HTTP 代理部分链接的"使用 HTTP 调用服务"文档中了解更多。 - SDK - 如果您使用的是 Dapr SDK,您可以通过 SDK 直接使用服务调用。选择您需要的 SDK 并使用 Dapr 客户端来调用服务。在 Dapr SDK中了解更多。
要进行快速测试,请尝试使用 Dapr CLI 进行服务调用:
- Dapr CLI 命令 - 设置 Dapr CLI 后,使用
dapr invoke --method <method-name>命令以及方法标志和感兴趣的方法。在 Dapr CLI中了解更多。
后续步骤
- 阅读服务调用 API 规范。此服务调用参考指南描述了如何调用其他服务上的方法。
- 了解服务调用性能数据。
- 查看可观测性。在这里您可以深入了解 Dapr 的监控工具,如追踪、指标和日志。
- 阅读我们的mTLS 加密、令牌身份验证和端点授权安全实践。
2 - 如何:使用 HTTP 调用服务
本文演示了如何部署服务,每个服务都有一个唯一的 application ID,其他服务可以使用 HTTP 上的服务调用来发现它们并调用其端点。

注意
如果你还没有尝试过,请先体验服务调用快速入门来快速了解如何使用服务调用 API。为你的服务选择一个 ID
Dapr 允许你为你的应用分配一个全局唯一的 ID。这个 ID 封装了你的应用的状态,无论它可能有多少个实例。
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- python3 checkout/app.py
dapr run --app-id order-processor --app-port 8001 --app-protocol http --dapr-http-port 3501 -- python3 order-processor/app.py
如果你的应用使用 TLS,你可以通过设置 --app-protocol https 来告诉 Dapr 通过 TLS 连接调用你的应用:
dapr run --app-id checkout --app-protocol https --dapr-http-port 3500 -- python3 checkout/app.py
dapr run --app-id order-processor --app-port 8001 --app-protocol https --dapr-http-port 3501 -- python3 order-processor/app.py
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- npm start
dapr run --app-id order-processor --app-port 5001 --app-protocol http --dapr-http-port 3501 -- npm start
如果你的应用使用 TLS,你可以通过设置 --app-protocol https 来告诉 Dapr 通过 TLS 连接调用你的应用:
dapr run --app-id checkout --dapr-http-port 3500 --app-protocol https -- npm start
dapr run --app-id order-processor --app-port 5001 --dapr-http-port 3501 --app-protocol https -- npm start
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- dotnet run
dapr run --app-id order-processor --app-port 7001 --app-protocol http --dapr-http-port 3501 -- dotnet run
如果你的应用使用 TLS,你可以通过设置 --app-protocol https 来告诉 Dapr 通过 TLS 连接调用你的应用:
dapr run --app-id checkout --dapr-http-port 3500 --app-protocol https -- dotnet run
dapr run --app-id order-processor --app-port 7001 --dapr-http-port 3501 --app-protocol https -- dotnet run
dapr run --app-id checkout --app-protocol http --dapr-http-port 3500 -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar
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
如果你的应用使用 TLS,你可以通过设置 --app-protocol https 来告诉 Dapr 通过 TLS 连接调用你的应用:
dapr run --app-id checkout --dapr-http-port 3500 --app-protocol https -- java -jar target/CheckoutService-0.0.1-SNAPSHOT.jar
dapr run --app-id order-processor --app-port 9001 --dapr-http-port 3501 --app-protocol https -- java -jar target/OrderProcessingService-0.0.1-SNAPSHOT.jar
dapr run --app-id checkout --dapr-http-port 3500 -- go run .
dapr run --app-id order-processor --app-port 6006 --app-protocol http --dapr-http-port 3501 -- go run .
如果你的应用使用 TLS,你可以通过设置 --app-protocol https 来告诉 Dapr 通过 TLS 连接调用你的应用:
dapr run --app-id checkout --dapr-http-port 3500 --app-protocol https -- go run .
dapr run --app-id order-processor --app-port 6006 --dapr-http-port 3501 --app-protocol https -- go run .
部署到 Kubernetes 时设置 app-id
在 Kubernetes 中,在你的 pod 上设置 dapr.io/app-id 注解:
apiVersion: apps/v1
kind: Deployment
metadata:
name: <language>-app
namespace: default
labels:
app: <language>-app
spec:
replicas: 1
selector:
matchLabels:
app: <language>-app
template:
metadata:
labels:
app: <language>-app
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "order-processor"
dapr.io/app-port: "6001"
...
如果你的应用使用 TLS 连接,你可以使用 app-protocol: "https" 注解(完整列表在这里)来告诉 Dapr 通过 TLS 调用你的应用。请注意,Dapr 不会验证应用提供的 TLS 证书。
调用服务
要使用 Dapr 调用应用程序,你可以在任何 Dapr 实例上使用 invoke API。边车编程模型鼓励每个应用程序与自己的 Dapr 实例交互。Dapr 边车之间相互发现和通信。
以下是利用 Dapr SDK 进行服务调用的代码示例。
#dependencies
import random
from time import sleep
import logging
import requests
#code
logging.basicConfig(level = logging.INFO)
while True:
sleep(random.randrange(50, 5000) / 1000)
orderId = random.randint(1, 1000)
#Invoke a service
result = requests.post(
url='%s/orders' % (base_url),
data=json.dumps(order),
headers=headers
)
logging.basicConfig(level = logging.INFO)
logging.info('Order requested: ' + str(orderId))
logging.info('Result: ' + str(result))
//dependencies
import axios from "axios";
//code
const daprHost = "127.0.0.1";
var main = function() {
for(var i=0;i<10;i++) {
sleep(5000);
var orderId = Math.floor(Math.random() * (1000 - 1) + 1);
start(orderId).catch((e) => {
console.error(e);
process.exit(1);
});
}
}
//Invoke a service
const result = await axios.post('order-processor' , "orders/" + orderId , axiosConfig);
console.log("Order requested: " + orderId);
console.log("Result: " + result.config.data);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
main();
//dependencies
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using System.Threading;
//code
namespace EventService
{
class Program
{
static async Task Main(string[] args)
{
while(true) {
await Task.Delay(5000)
var random = new Random();
var orderId = random.Next(1,1000);
//Using Dapr SDK to invoke a method
var order = new Order(orderId.ToString());
var httpClient = DaprClient.CreateInvokeHttpClient();
var response = await httpClient.PostAsJsonAsync("http://order-processor/orders", order);
var result = await response.Content.ReadAsStringAsync();
Console.WriteLine("Order requested: " + orderId);
Console.WriteLine("Result: " + result);
}
}
}
}
//dependencies
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;
//code
@SpringBootApplication
public class CheckoutServiceApplication {
private static final HttpClient httpClient = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();
public static void main(String[] args) throws InterruptedException, IOException {
while (true) {
TimeUnit.MILLISECONDS.sleep(5000);
Random random = new Random();
int orderId = random.nextInt(1000 - 1) + 1;
// Create a Map to represent the request body
Map<String, Object> requestBody = new HashMap<>();
requestBody.put("orderId", orderId);
// Add other fields to the requestBody Map as needed
HttpRequest request = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(new JSONObject(requestBody).toString()))
.uri(URI.create(dapr_url))
.header("Content-Type", "application/json")
.header("dapr-app-id", "order-processor")
.build();
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("Order passed: " + orderId);
TimeUnit.MILLISECONDS.sleep(1000);
log.info("Order requested: " + orderId);
log.info("Result: " + response.body());
}
}
}
package main
import (
"fmt"
"io"
"log"
"math/rand"
"net/http"
"os"
"time"
)
func main() {
daprHttpPort := os.Getenv("DAPR_HTTP_PORT")
if daprHttpPort == "" {
daprHttpPort = "3500"
}
client := &http.Client{
Timeout: 15 * time.Second,
}
for i := 0; i < 10; i++ {
time.Sleep(5000)
orderId := rand.Intn(1000-1) + 1
url := fmt.Sprintf("http://localhost:%s/checkout/%v", daprHttpPort, orderId)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
panic(err)
}
// Adding target app id as part of the header
req.Header.Add("dapr-app-id", "order-processor")
// Invoking a service
resp, err := client.Do(req)
if err != nil {
log.Fatal(err.Error())
}
b, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}
}
其他 URL 格式
要调用 ‘GET’ 端点:
curl http://localhost:3602/v1.0/invoke/checkout/method/checkout/100
为了尽可能避免更改 URL 路径,Dapr 提供了以下方式调用服务调用 API:
- 将 URL 中的地址改为
localhost:<dapr-http-port>。 - 添加一个
dapr-app-id头来指定目标服务的 ID,或者通过 HTTP Basic Auth 传递 ID:http://dapr-app-id:<service-id>@localhost:3602/path。
例如,以下命令:
curl http://localhost:3602/v1.0/invoke/checkout/method/checkout/100
等价于:
curl -H 'dapr-app-id: checkout' 'http://localhost:3602/checkout/100' -X POST
或者:
curl 'http://dapr-app-id:checkout@localhost:3602/checkout/100' -X POST
使用 CLI:
dapr invoke --app-id checkout --method checkout/100
在 URL 中包含查询字符串
你也可以在 URL 末尾附加查询字符串或片段,Dapr 会原样传递。这意味着如果你需要在服务调用中传递一些不属于负载或路径的额外参数,可以通过在 URL 末尾附加 ? 来实现,后跟用 = 分隔的键值对,用 & 分隔。例如:
curl 'http://dapr-app-id:checkout@localhost:3602/checkout/100?basket=1234&key=abc' -X POST
命名空间
在支持命名空间的平台上运行时,你需要在 app ID 中包含目标应用的命名空间。例如,遵循 <app>.<namespace> 格式,使用 checkout.production。
使用此示例,带命名空间调用服务如下:
curl http://localhost:3602/v1.0/invoke/checkout.production/method/checkout/100 -X POST
有关命名空间的更多信息,请参见跨命名空间 API 规范。
查看追踪和日志
上面的示例向你展示了如何直接调用在本地或 Kubernetes 中运行的不同服务。Dapr:
- 输出指标、追踪和日志信息,
- 允许你可视化服务之间的调用图并记录错误,以及
- 可选地,记录负载正文。
有关追踪和日志的更多信息,请参见可观测性文章。
相关链接
3 - 如何:使用 gRPC 调用服务
本文介绍如何使用 Dapr 通过 gRPC 连接服务。
通过使用 Dapr 的 gRPC 代理能力,你可以使用现有的基于 proto 的 gRPC 服务,并让流量通过 Dapr 边车。这样做可以为开发者带来以下 Dapr 服务调用 优势:
- 双向认证
- 追踪
- 指标
- 访问列表
- 网络级别的弹性
- 基于 API token 的认证
Dapr 允许代理各种类型的 gRPC 调用,包括一元调用和基于流的调用。
步骤 1:运行 gRPC 服务器
以下示例取自 “hello world” grpc-go 示例。虽然此示例使用 Go,但相同的概念适用于 gRPC 支持的所有编程语言。
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
port = ":50051"
)
// server 用于实现 helloworld.GreeterServer。
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello 实现 helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Printf("server listening at %v", lis.Addr())
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
此 Go 应用实现了 Greeter proto 服务,并公开了一个 SayHello 方法。
使用 Dapr CLI 运行 gRPC 服务器
dapr run --app-id server --app-port 50051 -- go run main.go
使用 Dapr CLI,我们通过 --app-id 标志为应用分配一个唯一 ID server。
步骤 2:调用服务
以下示例展示如何从 gRPC 客户端使用 Dapr 发现 Greeter 服务。
请注意,客户端不是直接在端口 50051 上调用目标服务,而是通过端口 50007 调用其本地 Dapr 边车,从而获得服务调用的所有能力,包括服务发现、追踪、mTLS 和重试。
package main
import (
"context"
"log"
"time"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
"google.golang.org/grpc/metadata"
)
const (
address = "localhost:50007"
)
func main() {
// 建立与服务器的连接。
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
defer cancel()
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: "Darth Tyrannus"})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
以下代码行告诉 Dapr 发现并调用名为 server 的应用:
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
所有支持 gRPC 的语言都允许添加元数据。以下是几个示例:
Metadata headers = new Metadata();
Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-app-id", "server");
GreeterService.ServiceBlockingStub stub = GreeterService.newBlockingStub(channel);
stub = MetadataUtils.attachHeaders(stub, header);
stub.SayHello(new HelloRequest() { Name = "Darth Malak" });
var metadata = new Metadata
{
{ "dapr-app-id", "server" }
};
var call = client.SayHello(new HelloRequest { Name = "Darth Nihilus" }, metadata);
metadata = (('dapr-app-id', 'server'),)
response = stub.SayHello(request={ name: 'Darth Revan' }, metadata=metadata)
const metadata = new grpc.Metadata();
metadata.add('dapr-app-id', 'server');
client.sayHello({ name: "Darth Malgus" }, metadata)
metadata = { 'dapr-app-id' : 'server' }
response = service.sayHello({ 'name': 'Darth Bane' }, metadata)
grpc::ClientContext context;
context.AddMetadata("dapr-app-id", "server");
使用 Dapr CLI 运行客户端
dapr run --app-id client --dapr-grpc-port 50007 -- go run main.go
查看遥测数据
如果你在本地运行 Dapr 并安装了 Zipkin,请在浏览器中打开 http://localhost:9411 查看客户端和服务器之间的追踪信息。
部署到 Kubernetes
在你的部署上设置以下 Dapr 注解:
apiVersion: apps/v1
kind: Deployment
metadata:
name: grpc-app
namespace: default
labels:
app: grpc-app
spec:
replicas: 1
selector:
matchLabels:
app: grpc-app
template:
metadata:
labels:
app: grpc-app
annotations:
dapr.io/enabled: "true"
dapr.io/app-id: "server"
dapr.io/app-protocol: "grpc"
dapr.io/app-port: "50051"
...
dapr.io/app-protocol: "grpc" 注解告诉 Dapr 使用 gRPC 调用应用。
如果你的应用使用 TLS 连接,你可以通过 app-protocol: "grpcs" 注解告诉 Dapr 通过 TLS 调用你的应用(完整列表见这里)。请注意,Dapr 不验证应用提供的 TLS 证书。
命名空间
在支持命名空间的平台上运行时,你需要在应用 ID 中包含目标应用的命名空间:myApp.production
例如,调用不同命名空间上的 gRPC 服务器:
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server.production")
有关命名空间的更多信息,请参阅跨命名空间 API 规范。
步骤 3:查看追踪和日志
上面的示例向你展示如何直接调用本地或 Kubernetes 上运行的不同服务。Dapr 输出指标、追踪和日志信息,使你能够可视化服务之间的调用图、记录错误,并可选择记录有效负载正文。
有关追踪和日志的更多信息,请参阅可观测性文章。
代理流式 RPC
使用 Dapr 通过 gRPC 代理流式 RPC 调用时,必须设置一个额外的元数据选项 dapr-stream,其值为 true。
例如:
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "server")
ctx = metadata.AppendToOutgoingContext(ctx, "dapr-stream", "true")
Metadata headers = new Metadata();
Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-app-id", "server");
Metadata.Key<String> jwtKey = Metadata.Key.of("dapr-stream", "true");
var metadata = new Metadata
{
{ "dapr-app-id", "server" },
{ "dapr-stream", "true" }
};
metadata = (('dapr-app-id', 'server'), ('dapr-stream', 'true'),)
const metadata = new grpc.Metadata();
metadata.add('dapr-app-id', 'server');
metadata.add('dapr-stream', 'true');
metadata = { 'dapr-app-id' : 'server' }
metadata = { 'dapr-stream' : 'true' }
grpc::ClientContext context;
context.AddMetadata("dapr-app-id", "server");
context.AddMetadata("dapr-stream", "true");
流式 gRPC 与弹性
目前,通过 gRPC 进行服务调用时不支持弹性策略。
代理流式 gRPC 时,由于其长期存在的特性,弹性策略仅应用于"初始握手"。因此:
- 如果流在初始握手后被中断,Dapr 不会自动重新建立它。你的应用将收到流已结束的通知,并需要重新创建它。
- 重试策略仅影响初始连接"握手"。如果你的弹性策略包含重试,Dapr 将检测建立与目标应用的初始连接时的失败,并将重试直到成功(或直到策略中定义的重试次数用尽)。
- 同样,弹性策略中定义的超时仅适用于初始"握手"。连接建立后,超时不再影响流。
相关链接
社区呼叫演示
观看此视频,了解如何使用 Dapr 的 gRPC 代理能力:
4 - 操作指南:使用 HTTP 调用非 Dapr 端点
本文演示如何使用 HTTP 通过 Dapr 调用非 Dapr 端点。
使用 Dapr 的服务调用 API,您可以与使用或不使用 Dapr 的端点进行通信。使用 Dapr 调用不使用 Dapr 的端点不仅能提供一致的 API,还能带来以下 Dapr 服务调用 优势:
- 能够应用弹性策略
- 通过追踪与指标实现可观测性调用
- 通过作用域实现安全访问控制
- 能够利用中间件管道组件
- 服务发现
- 通过使用标头进行身份验证
调用外部服务或非 Dapr 端点的 HTTP 服务调用
有时您需要调用非 Dapr HTTP 端点。例如:
- 您可能选择仅在整个应用程序的部分使用 Dapr,包括遗留开发
- 您可能无法访问代码来将现有应用程序迁移到使用 Dapr
- 您需要调用外部 HTTP 服务。
通过定义 HTTPEndpoint 资源,您可以声明式地定义与非 Dapr 端点交互的方式。然后使用服务调用 URL 来调用非 Dapr 端点。或者,您也可以直接将非 Dapr 的完全限定域名(FQDN)端点 URL 放入服务调用 URL 中。
HTTPEndpoint、FQDN URL 与 appId 之间的优先级顺序
使用服务调用时,Dapr 运行时遵循以下优先级顺序:
- 这是命名的
HTTPEndpoint资源吗? - 这是带有
http://或https://前缀的 FQDN URL 吗? - 这是
appID吗?
服务调用与非 Dapr HTTP 端点
下图概述了 Dapr 的服务调用在调用非 Dapr 端点时的工作原理。

- 服务 A 向服务 B(一个非 Dapr 端点)发起 HTTP 调用。该调用发送到本地 Dapr 边车。
- Dapr 使用
HTTPEndpoint或 FQDN URL 发现服务 B 的位置,然后将消息转发给服务 B。 - 服务 B 向服务 A 的 Dapr 边车发送响应。
- 服务 A 接收响应。
为非 Dapr 端点使用 HTTPEndpoint 资源或 FQDN URL
在与 Dapr 应用程序或非 Dapr 应用程序通信时,有两种方法可以调用非 Dapr 端点。Dapr 应用程序可以通过提供以下内容之一来调用非 Dapr 端点:
命名的
HTTPEndpoint资源,包括定义HTTPEndpoint资源类型。有关示例,请参阅 HTTPEndpoint 参考 指南。localhost:3500/v1.0/invoke/<HTTPEndpoint-name>/method/<my-method>例如,对于名为 “palpatine” 的
HTTPEndpoint资源和名为 “Order66” 的方法,调用方式如下:curl http://localhost:3500/v1.0/invoke/palpatine/method/order66指向非 Dapr 端点的 FQDN URL。
localhost:3500/v1.0/invoke/<URL>/method/<my-method>例如,对于名为
https://darthsidious.starwars的 FQDN 资源,调用方式如下:curl http://localhost:3500/v1.0/invoke/https://darthsidious.starwars/method/order66
调用启用 Dapr 的应用程序时使用 appId
调用使用 appID 的 Dapr 应用程序时,始终使用 AppID。有关更多信息,请阅读 操作指南:使用 HTTP 调用服务 指南。例如:
localhost:3500/v1.0/invoke/<appID>/method/<my-method>
curl http://localhost:3602/v1.0/invoke/orderprocessor/method/checkout
TLS 身份验证
使用 HTTPEndpoint 资源 允许您根据远程端点的身份验证要求,使用根证书、客户端证书和私钥的任意组合。
使用根证书的示例
apiVersion: dapr.io/v1alpha1
kind: HTTPEndpoint
metadata:
name: "external-http-endpoint-tls"
spec:
baseUrl: https://service-invocation-external:443
headers:
- name: "Accept-Language"
value: "en-US"
clientTLS:
rootCA:
secretKeyRef:
name: dapr-tls-client
key: ca.crt
使用客户端证书和私钥的示例
apiVersion: dapr.io/v1alpha1
kind: HTTPEndpoint
metadata:
name: "external-http-endpoint-tls"
spec:
baseUrl: https://service-invocation-external:443
headers:
- name: "Accept-Language"
value: "en-US"
clientTLS:
certificate:
secretKeyRef:
name: dapr-tls-client
key: tls.crt
privateKey:
secretKeyRef:
name: dapr-tls-key
key: tls.key
服务器发送事件
SSE 支持与流服务器和 MCP 服务器进行实时通信。
HTTP 端点支持 服务器发送事件(SSE)。
要使用 SSE,请在 HTTPEndpoint 资源或服务调用请求中将 Accept 标头设置为 text/event-stream。
apiVersion: dapr.io/v1alpha1
kind: HTTPEndpoint
metadata:
name: "mcp-server"
spec:
baseUrl: https://my-mcp-server:443
headers:
- name: "Accept"
value: "test/event-stream"
相关链接
社区电话演示
观看此 视频,了解如何使用服务调用调用非 Dapr 端点。
5 - 操作指南:跨命名空间的服务调用
在本文中,你将学习如何调用部署在不同命名空间中的服务。默认情况下,服务调用支持通过简单引用应用 ID(nodeapp)来调用同一命名空间内的服务:
localhost:3500/v1.0/invoke/nodeapp/method/neworder
服务调用也支持跨命名空间调用。在所有支持的托管平台上,Dapr 应用 ID 符合包含目标命名空间的有效 FQDN 格式。你可以同时指定:
- 应用 ID(
nodeapp),以及 - 应用运行的命名空间(
production)。
示例 1
调用 production 命名空间中 nodeapp 上的 neworder 方法:
localhost:3500/v1.0/invoke/nodeapp.production/method/neworder
使用服务调用调用命名空间中的应用程序时,需要用命名空间进行限定。这在 Kubernetes 集群中的跨命名空间调用中非常有用。
示例 2
调用 production 命名空间中 myapp 上的 ping 方法:
https://localhost:3500/v1.0/invoke/myapp.production/method/ping
示例 3
使用 curl 命令从外部 DNS 地址(此处为 api.demo.dapr.team)调用与示例 2 相同的 ping 方法,并提供 Dapr API 令牌进行身份验证:
MacOS/Linux:
curl -i -d '{ "message": "hello" }' \
-H "Content-type: application/json" \
-H "dapr-api-token: ${API_TOKEN}" \
https://api.demo.dapr.team/v1.0/invoke/myapp.production/method/ping