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

Return to the regular view of this page.

服务调用

实现直接且安全的服务间方法调用

1 - 服务调用概述

服务调用API模块的概述

通过服务调用,您的应用程序可以使用标准的gRPCHTTP协议可靠且安全地与其他应用程序进行通信。

在许多基于微服务的应用程序中,多个服务需要能够相互通信。这种服务间通信要求应用程序开发人员处理以下问题:

  • 服务发现。 如何找到我的不同服务?
  • 标准化服务间的API调用。 如何在服务之间调用方法?
  • 安全的服务间通信。 如何通过加密安全地调用其他服务并对方法应用访问控制?
  • 缓解请求超时或失败。 如何处理重试和瞬态错误?
  • 实现可观测性和追踪。 如何使用追踪查看带有指标的调用图以诊断生产中的问题?

服务调用API

Dapr通过提供一个类似反向代理的服务调用API来解决这些挑战,该API内置了服务发现,并利用了分布式追踪、指标、错误处理、加密等功能。

Dapr采用sidecar架构。要使用Dapr调用应用程序:

  • 您在Dapr实例上使用invoke API。
  • 每个应用程序与其自己的Dapr实例通信。
  • Dapr实例相互发现并通信。

以下概述视频和演示展示了Dapr服务调用的工作原理。

下图概述了Dapr的服务调用在两个集成Dapr的应用程序之间的工作原理。

显示服务调用步骤的图示
  1. 服务A发起一个HTTP或gRPC调用,目标是服务B。调用发送到本地Dapr sidecar。
  2. Dapr使用正在运行的名称解析组件在给定的托管平台上发现服务B的位置。
  3. Dapr将消息转发到服务B的Dapr sidecar
    • 注意:所有Dapr sidecar之间的调用都通过gRPC进行以提高性能。只有服务与Dapr sidecar之间的调用可以是HTTP或gRPC。
  4. 服务B的Dapr sidecar将请求转发到服务B上的指定端点(或方法)。服务B然后运行其业务逻辑代码。
  5. 服务B向服务A发送响应。响应发送到服务B的sidecar。
  6. Dapr将响应转发到服务A的Dapr sidecar。
  7. 服务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协议提供服务调用请求的轮询负载均衡,例如在单台机器或多台联网的物理机器上。

下图显示了其工作原理的示例。如果您有一个应用程序实例,应用程序ID为FrontEnd,以及三个应用程序实例,应用程序ID为Cart,并且您从FrontEnd应用程序调用Cart应用程序,Dapr在三个实例之间进行轮询。这些实例可以在同一台机器上或不同的机器上。

显示服务调用步骤的图示

注意:应用程序ID在_应用程序_中是唯一的,而不是应用程序实例。无论该应用程序存在多少个实例(由于扩展),它们都将共享相同的应用程序ID。

可交换的服务发现

Dapr可以在多种托管平台上运行。为了启用可交换的服务发现,Dapr使用名称解析组件。例如,Kubernetes名称解析组件使用Kubernetes DNS服务来解析在集群中运行的其他应用程序的位置。

自托管机器可以使用mDNS名称解析组件。作为替代方案,您可以使用SQLite名称解析组件在单节点环境中运行Dapr,并用于本地开发场景。属于集群的Dapr sidecar将其信息存储在本地机器上的SQLite数据库中。

Consul名称解析组件特别适合多机部署,并且可以在任何托管环境中使用,包括Kubernetes、多台虚拟机或自托管。

HTTP服务调用的流式处理

您可以在HTTP服务调用中将数据作为流处理。这可以在使用Dapr通过HTTP调用另一个服务时提供性能和内存利用率的改进,尤其是在请求或响应体较大的情况下。

下图演示了数据流的六个步骤。

显示表中描述的服务调用步骤的图示
  1. 请求:“应用程序A"到"Dapr sidecar A”
  2. 请求:“Dapr sidecar A"到"Dapr sidecar B”
  3. 请求:“Dapr sidecar B"到"应用程序B”
  4. 响应:“应用程序B"到"Dapr sidecar B”
  5. 响应:“Dapr sidecar B"到"Dapr sidecar A”
  6. 响应:“Dapr sidecar A"到"应用程序A”

示例架构

按照上述调用顺序,假设您有如Hello World教程中描述的应用程序,其中一个Python应用程序调用一个Node.js应用程序。在这种情况下,Python应用程序将是"服务A",Node.js应用程序将是"服务B"。

下图再次显示了本地机器上的1-7序列,显示了API调用:

  1. Node.js应用程序的Dapr应用程序ID为nodeapp。Python应用程序通过POST http://localhost:3500/v1.0/invoke/nodeapp/method/neworder调用Node.js应用程序的neworder方法,该请求首先发送到Python应用程序的本地Dapr sidecar。
  2. Dapr使用名称解析组件(在这种情况下是自托管时的mDNS)发现Node.js应用程序的位置,该组件在您的本地机器上运行。
  3. Dapr使用刚刚接收到的位置将请求转发到Node.js应用程序的sidecar。
  4. Node.js应用程序的sidecar将请求转发到Node.js应用程序。Node.js应用程序执行其业务逻辑,记录传入消息,然后将订单ID持久化到Redis(图中未显示)。
  5. Node.js应用程序通过Node.js sidecar向Python应用程序发送响应。
  6. Dapr将响应转发到Python Dapr sidecar。
  7. Python应用程序接收响应。

试用服务调用

快速入门和教程

Dapr文档包含多个利用服务调用构建模块的快速入门,适用于不同的示例架构。为了直观地理解服务调用API及其功能,我们建议从我们的快速入门开始:

快速入门/教程描述
服务调用快速入门这个快速入门让您直接与服务调用构建模块进行交互。
Hello World教程本教程展示了如何在本地机器上运行服务调用和状态管理构建模块。
Hello World Kubernetes教程本教程演示了如何在Kubernetes中使用Dapr,并涵盖了服务调用和状态管理构建模块。

直接在您的应用程序中开始使用服务调用

想跳过快速入门?没问题。您可以直接在应用程序中试用服务调用构建模块,以安全地与其他服务通信。在Dapr安装完成后,您可以通过以下方式开始使用服务调用API。

使用以下方式调用服务:

  • HTTP和gRPC服务调用(推荐的设置方法)
  • 直接调用API - 除了代理,还有一个选项可以直接调用服务调用API以调用GET端点。只需将您的地址URL更新为localhost:<dapr-http-port>,您就可以直接调用API。您还可以在上面链接的HTTP代理文档中阅读更多关于此的信息。
  • SDKs - 如果您正在使用Dapr SDK,您可以直接通过SDK使用服务调用。选择您需要的SDK,并使用Dapr客户端调用服务。有关更多信息,请阅读Dapr SDKs

为了快速测试,尝试使用Dapr CLI进行服务调用:

  • Dapr CLI命令 - 一旦设置了Dapr CLI,使用dapr invoke --method <method-name>命令以及方法标志和感兴趣的方法。有关更多信息,请阅读Dapr CLI

下一步

2 - 操作指南:使用HTTP调用服务

通过服务调用实现服务之间的通信

本文演示了如何部署服务,每个服务都有一个唯一的应用程序ID,以便其他服务可以通过HTTP进行服务调用来发现并调用它们的端点。

示例服务的服务调用图示

为服务选择一个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。sidecar编程模型鼓励每个应用程序与其自己的Dapr实例交互。Dapr的sidecar会自动发现并相互通信。

以下是利用Dapr SDK进行服务调用的代码示例。

# 依赖
import random
from time import sleep
import logging
import requests

# 代码
logging.basicConfig(level = logging.INFO) 
while True:
    sleep(random.randrange(50, 5000) / 1000)
    orderId = random.randint(1, 1000)
    # 调用服务
    result = requests.post(
       url='%s/orders' % (base_url),
       data=json.dumps(order),
       headers=headers
    )    
    logging.info('Order requested: ' + str(orderId))
    logging.info('Result: ' + str(result))
// 依赖
import axios from "axios";

// 代码
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);
        });
    }
}

// 调用服务
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();
// 依赖
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;

// 代码
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);

               // 使用Dapr SDK调用方法
               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);
   	    }
       }
   }
}
// 依赖
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;

// 代码
@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;

            // 创建一个Map来表示请求体
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("orderId", orderId);
            // 根据需要向requestBody Map添加其他字段

            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)
		}

		// 将目标应用程序ID添加为头的一部分
		req.Header.Add("dapr-app-id", "order-processor")

		// 调用服务
		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:

  1. 将URL中的地址更改为localhost:<dapr-http-port>
  2. 添加一个dapr-app-id头来指定目标服务的ID,或者通过HTTP基本认证传递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

命名空间

支持命名空间的平台上运行时,您可以在应用程序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 sidecar。这为开发人员带来了以下 Dapr 服务调用 的优势:

  1. 双向认证
  2. 跟踪
  3. 指标
  4. 访问控制列表
  5. 网络级别的弹性
  6. 基于 API 令牌的认证

Dapr 支持代理所有类型的 gRPC 调用,包括一元和流式调用

第一步:运行 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,我们为应用分配了一个唯一的 ID,server,通过 --app-id 标志指定。

第二步:调用服务

以下示例展示了如何使用 Dapr 从 gRPC 客户端发现 Greeter 服务。 注意,客户端不是直接通过端口 50051 调用目标服务,而是通过端口 50007 调用其本地 Dapr sidecar,这样就提供了所有的服务调用功能,包括服务发现、跟踪、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 规范

第三步:查看跟踪和日志

上面的示例展示了如何直接调用本地或 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 时,由于其长时间存在的特性,弹性策略仅应用于“初始握手”。因此:

  • 如果流在初始握手后中断,Dapr 不会自动重新建立。您的应用将被通知流已结束,并需要重新创建它。
  • 重试策略仅影响初始连接“握手”。如果您的弹性策略包括重试,Dapr 将检测到建立与目标应用的初始连接失败,并将重试直到成功(或直到策略中定义的重试次数耗尽)。
  • 同样,弹性策略中定义的超时仅适用于初始“握手”。连接建立后,超时不再影响流。

相关链接

社区通话演示

观看此视频了解如何使用 Dapr 的 gRPC 代理功能:

4 - 如何:使用HTTP调用非Dapr端点

从Dapr应用程序中通过服务调用访问非Dapr端点

本文介绍如何通过Dapr使用HTTP调用非Dapr端点。

通过Dapr的服务调用API,您可以与使用或不使用Dapr的端点进行通信。使用Dapr调用非Dapr端点不仅提供了一致的API,还带来了以下Dapr服务调用的优势:

  • 应用弹性策略
  • 通过跟踪和指标实现调用的可观测性
  • 通过访问控制实现安全性
  • 利用中间件管道组件
  • 服务发现
  • 使用请求头进行身份验证

通过HTTP调用外部服务或非Dapr端点

有时您可能需要调用非Dapr的HTTP端点,例如:

  • 您可能只在应用程序的一部分中使用Dapr,尤其是在涉及旧系统时
  • 您可能无法访问代码以将现有应用程序迁移到Dapr
  • 您需要调用外部的HTTP服务

通过定义HTTPEndpoint资源,您可以声明性地配置与非Dapr端点的交互方式。然后,您可以使用服务调用URL来访问非Dapr端点。或者,您可以直接在服务调用URL中使用非Dapr的完全限定域名(FQDN)端点URL。

HttpEndpoint、FQDN URL和appId的优先级

在进行服务调用时,Dapr运行时遵循以下优先级顺序:

  1. 是否为命名的HTTPEndpoint资源?
  2. 是否为带有http://https://前缀的FQDN URL?
  3. 是否为appID

服务调用与非Dapr HTTP端点

下图概述了Dapr在调用非Dapr端点时的工作流程。

显示服务调用到非Dapr端点步骤的图示
  1. 服务A发起一个HTTP调用,目标是服务B(一个非Dapr端点)。调用被发送到本地的Dapr sidecar。
  2. Dapr通过HTTPEndpoint或FQDN URL定位服务B的位置,然后将消息转发给服务B。
  3. 服务B向服务A的Dapr sidecar发送响应。
  4. 服务A接收响应。

使用HTTPEndpoint资源或FQDN URL调用非Dapr端点

在与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
    

使用appId调用启用Dapr的应用程序

AppID用于通过appIDmy-method调用Dapr应用程序。阅读如何:使用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

相关链接

社区电话演示

观看此视频以了解如何使用服务调用来调用非Dapr端点。

5 - 指南:跨命名空间进行服务调用

在不同命名空间之间进行服务调用

在本文中,您将学习如何在不同命名空间之间进行服务调用。默认情况下,service-invocation支持通过简单引用应用程序ID(如nodeapp)来调用同一命名空间内的服务:

localhost:3500/v1.0/invoke/nodeapp/method/neworder

service-invocation也支持跨命名空间的调用。在所有支持的平台上,Dapr应用程序ID遵循包含目标命名空间的有效FQDN格式。您可以同时指定:

  • 应用程序ID(如nodeapp),以及
  • 应用程序所在的命名空间(如production)。

示例 1

调用位于production命名空间中nodeappneworder方法:

localhost:3500/v1.0/invoke/nodeapp.production/method/neworder

在使用service-invocation调用不同命名空间中的应用程序时,您需要使用命名空间来限定它。这在Kubernetes集群中的跨命名空间调用中非常有用。

示例 2

调用位于production命名空间中myappping方法:

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