在微服务架构中,gRPC因为高性能、轻量的特性,常被用作服务间通信,尤其是流式调用的场景,比如IoT设备和后端的实时指令同步、电商订单的状态流推送等,这类流式调用的链路追踪一直是大家头疼的问题——普通的追踪工具往往只能抓到单条消息的痕迹,没法串联整个流的完整生命周期,今天我们就用APISIX作为网关层,打通SkyWalking和Zipkin,轻松实现gRPC流式调用的全链路追踪。
一、为什么要在网关层做gRPC流式调用的链路追踪?
1.1 微服务流式调用的追踪痛点
平时做接口调用,单条请求的追踪很简单,但是流式调用就不一样了:比如客户端给服务端发10条数据,每一条都属于同一个“流”,这时候你需要把所有10条消息的trace id设成同一个,才能看到整个流从进入网关到经过各个服务再到返回的完整过程。如果在每个服务里单独配置追踪,不仅要改每个服务的代码,还容易出现trace id传递出错的问题,而且不同语言的服务配置起来更麻烦。
1.2 APISIX做中间层的优势
APISIX是一个云原生的API网关,它的核心优势是所有功能都是插件化的,不需要修改业务服务代码。把APISIX放在入口,所有的请求(包括gRPC流式调用)都会经过它,这时候APISIX可以统一处理链路追踪的逻辑,同时打通SkyWalking和Zipkin两个主流的追踪系统,不管你用哪个工具排查问题,都能拿到完整的链路数据。
二、准备工作
2.1 环境依赖
我们需要部署四个核心组件:APISIX(网关)、SkyWalking(追踪分析)、Zipkin(追踪存储),还有我们自己写的gRPC服务和客户端。这里推荐用Docker快速部署,避免复杂的环境搭建。
2.2 核心组件的简单介绍
- APISIX:就像小区的大门,所有的外来请求都要经过它,它可以把请求转发到后端服务,还能插入各种功能插件(比如追踪插件)。
- SkyWalking:一个开源的APM工具,能帮你把各个服务的链路数据收集起来,生成可视化的链路图。
- Zipkin:另一个流行的开源追踪系统,和SkyWalking互补,适合快速查看单条链路的耗时。
三、具体实现步骤
这里我们用单一技术栈Go来写gRPC服务和客户端,确保示例统一,所有代码都用Markdown代码块包裹。 技术栈:Go 1.20 + APISIX 3.8 + SkyWalking 9.4 + Zipkin 2.23
3.1 部署组件(用Docker快速启动)
先启动SkyWalking、Zipkin和APISIX,执行以下命令:
# 启动SkyWalking OAP和UI
docker run -d --name skywalking -p 1234:1234 -p 11800:11800 apache/skywalking-oap-server:9.4.0
docker run -d --name skywalking-ui -p 8080:8080 --link skywalking:skywalking apache/skywalking-ui:9.4.0
# 启动Zipkin
docker run -d --name zipkin -p 9411:9411 openzipkin/zipkin:2.23.19
# 启动APISIX
docker run -d --name apisix -p 9080:9080 -p 9180:9180 apache/apisix:3.8.0-debian
3.2 配置APISIX打通两个追踪系统
我们需要给APISIX配置skywalking插件(同时对接SkyWalking和Zipkin),还有gRPC的路由规则。登录APISIX的管理端口,执行以下配置(用APISIX Admin API):
# 开启skywalking插件,配置SkyWalking和Zipkin的地址
curl http://127.0.0.1:9180/apisix/admin/plugin_configs/1 -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
"plugins": {
"skywalking": {
"service_name": "apisix-grpc-gateway",
"endpoint": "http://skywalking:12800",
"sample_ratio": 1.0,
"zipkin_endpoint": "http://zipkin:9411/api/v2/spans"
}
}
}'
# 配置gRPC路由,把请求转发到后端的gRPC服务(后面我们会写这个服务,地址是host.docker.internal:50051)
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
"uri": "/stream.*",
"plugins": {
"plugin_config_id": 1
},
"upstream": {
"type": "roundrobin",
"nodes": {
"host.docker.internal:50051": 1
}
}
}'
这里解释一下:skywalking插件的zipkin_endpoint参数,就是用来把链路数据同时发给Zipkin,这样两个系统都能看到数据。
3.3 编写gRPC流式服务和客户端
我们写一个简单的双向流式gRPC服务,用到Go的gRPC和SkyWalking的Go Agent,确保能把数据传给SkyWalking。
3.3.1 定义gRPC的proto文件
先写proto文件,定义流式服务:
syntax = "proto3";
package stream;
// 定义流式服务,双向流
service StreamService {
rpc SayHelloStream (stream HelloRequest) returns (stream HelloResponse);
}
// 客户端发送的请求消息
message HelloRequest {
string name = 1;
}
// 服务端返回的响应消息
message HelloResponse {
string message = 1;
}
把这个文件保存为stream.proto,然后用protoc生成Go代码:
protoc --go_out=. --go-grpc_out=. stream.proto
3.3.2 编写gRPC服务端代码
服务端需要集成SkyWalking的Go Agent,接收流式请求,处理每个消息:
package main
import (
"context"
"fmt"
"net"
"github.com/SkyAPM/go2sky"
grpc "github.com/SkyAPM/go2sky/plugins/grpc"
stream "your_project_path/stream" // 这里替换成你自己的proto生成的包路径
"google.golang.org/grpc"
)
func main() {
// 初始化SkyWalking Agent,上报数据到SkyWalking地址
tracer, err := go2sky.NewTracer("grpc-service", go2sky.WithServerAddr("skywalking:11800"))
if err != nil {
fmt.Printf("初始化SkyWalking失败: %v\n", err)
return
}
// 启动gRPC服务,注入SkyWalking拦截器
lis, err := net.Listen("tcp", ":50051")
if err != nil {
fmt.Printf("监听端口失败: %v\n", err)
return
}
s := grpc.NewServer(
grpc.UnaryInterceptor(grpc.UnaryServerInterceptor(tracer)),
grpc.StreamInterceptor(grpc.StreamServerInterceptor(tracer)),
)
// 注册流式服务
stream.RegisterStreamServiceServer(s, &server{})
fmt.Println("gRPC服务启动,端口50051")
if err := s.Serve(lis); err != nil {
fmt.Printf("启动服务失败: %v\n", err)
}
}
// 定义服务端结构体
type server struct {
stream.UnimplementedStreamServiceServer
}
// 实现双向流方法
func (s *server) SayHelloStream(stream stream.StreamService_SayHelloStreamServer) error {
// 循环接收客户端发来的消息,并发回响应
for {
req, err := stream.Recv()
if err != nil {
return err
}
// 收到消息,打印日志
fmt.Printf("收到客户端消息: %s\n", req.Name)
// 发送响应
err = stream.Send(&stream.HelloResponse{
Message: fmt.Sprintf("你好, %s!", req.Name),
})
if err != nil {
return err
}
}
}
这里的关键是加入了StreamInterceptor,让SkyWalking能捕获流式调用的span,这样整个流的追踪数据都会被上报。
3.3.3 编写gRPC客户端代码
客户端同样集成SkyWalking Agent,发送多条流式消息:
package main
import (
"context"
"fmt"
"github.com/SkyAPM/go2sky"
grpc "github.com/SkyAPM/go2sky/plugins/grpc"
stream "your_project_path/stream" // 替换成自己的路径
"google.golang.org/grpc"
)
func main() {
// 初始化SkyWalking Agent,和服务端保持一致
tracer, err := go2sky.NewTracer("grpc-client", go2sky.WithServerAddr("skywalking:11800"))
if err != nil {
fmt.Printf("初始化SkyWalking失败: %v\n", err)
return
}
// 连接gRPC服务,注入SkyWalking拦截器
conn, err := grpc.Dial("host.docker.internal:50051",
grpc.WithInsecure(),
grpc.WithUnaryInterceptor(grpc.UnaryClientInterceptor(tracer)),
grpc.WithStreamInterceptor(grpc.StreamClientInterceptor(tracer)),
)
if err != nil {
fmt.Printf("连接服务失败: %v\n", err)
return
}
defer conn.Close()
// 创建流式客户端
client := stream.NewStreamServiceClient(conn)
streamClient, err := client.SayHelloStream(context.Background())
if err != nil {
fmt.Printf("创建流式客户端失败: %v\n", err)
return
}
// 客户端发送5条消息,接收5条响应
for i := 1; i <= 5; i++ {
msg := &stream.HelloRequest{Name: fmt.Sprintf("用户%d", i)}
err = streamClient.Send(msg)
if err != nil {
fmt.Printf("发送消息失败: %v\n", err)
return
}
// 接收响应
resp, err := streamClient.Recv()
if err != nil {
fmt.Printf("接收响应失败: %v\n", err)
return
}
fmt.Printf("收到服务端响应: %s\n", resp.Message)
}
// 关闭流
streamClient.CloseSend()
}
四、场景与技术分析
4.1 适合的应用场景
这个方案最适合这几类场景:
- 微服务架构下的gRPC流式通信,比如实时日志同步、数据推送;
- 跨语言的服务调用,不管后端服务是Go、Java还是Python,只要开启SkyWalking的插件就能适配;
- 需要统一入口的链路追踪,用APISIX作为网关,所有流量的追踪数据都会被统一收集,不需要在每个服务里配置。
4.2 技术优缺点
优点
- 无侵入:所有的追踪逻辑都在APISIX网关层,不需要修改业务服务的代码,不管是新服务还是老服务都能快速接入;
- 兼容流式调用:APISIX的skywalking插件原生支持gRPC的client-stream、server-stream、bidirectional-stream三种类型,所有流的生命周期都能被追踪;
- 双系统兼容:同时对接SkyWalking和Zipkin,你可以根据自己的习惯选择哪个工具查看链路,SkyWalking适合分布式链路分析,Zipkin适合快速排查单链路耗时;
- 低维护:APISIX的插件配置简单,不需要在每个服务里安装追踪依赖,减少了运维成本。
缺点
- 网关额外开销:增加了一层网关,会有极少量的性能开销,对于性能要求极高的场景需要调整采样率或者优化APISIX配置;
- 组件依赖:需要部署APISIX、SkyWalking、Zipkin三个组件,对于小型项目来说可能有点复杂;
- 版本兼容性:APISIX、SkyWalking的插件版本需要匹配,比如APISIX 3.x对应SkyWalking 9.x,版本不匹配可能会导致追踪数据丢失。
4.3 注意事项
- 采样率设置:不要把sample_ratio设为100%,对于高并发的生产场景,设置10%或者更小的采样率,避免追踪数据太多影响性能;
- 版本匹配:一定要确认APISIX和SkyWalking的版本,比如APISIX 3.8需要搭配SkyWalking 9.0以上的版本,不然可能不支持流式追踪;
- 元数据传递:APISIX会自动传递trace id到下游服务,不需要额外配置,但是要确保后端服务也开启了SkyWalking的拦截器,不然追踪数据会断;
- 防火墙配置:如果容器部署,要确保APISIX能访问SkyWalking和Zipkin的地址,不然数据上报会失败。
五、效果验证与总结
5.1 查看追踪效果
启动gRPC服务和客户端后,你可以打开SkyWalking UI(地址是http://127.0.0.1:8080),搜索“apisix-grpc-gateway”或者“grpc-service”,就能看到gRPC流的完整链路,每个消息都会被串联成同一个trace id;打开Zipkin UI(地址是http://127.0.0.1:9411),输入对应的trace id,也能看到整个流的耗时和节点信息。
5.2 总结
这个方案把APISIX网关作为链路追踪的统一入口,打通了SkyWalking和Zipkin,完美解决了gRPC流式调用的追踪痛点,不需要修改业务代码,兼容性强,适合大多数微服务场景。不管是开发阶段排查问题,还是生产环境监控流量,这个方案都能帮你快速理清调用链路,提高排查效率。
评论
围绕“网关层面实现gRPC流式调用全链路追踪,APISIX打通SkyWalking与Zipkin”参与讨论