GRPC: 如何实现分布式日志跟踪?
简介: 本文将介绍如何在 gRPC 分布式场景中,实现 API 的日志跟踪。
介绍
本文将介绍如何在 gRPC 分布式场景中,实现 API 的日志追踪。
什么是 API 日志追踪?
一个 API 请求会跨多个微服务,我们希望通过一个唯一的 ID 检索到整个链路的日志。
我们将会使用 rk-boot 来启动 gRPC 服务。
请访问如下地址获取完整教程:
安装
go get github.com/rookie-ninja/rk-boot
快速开始
rk-boot 默认集成了 grpc-gateway,并且会默认启动。
我们会创建 /api/v1/greeter API 进行验证,同时开启 logging, meta 和 tracing 拦截器以达到目的。
1. 创建 api/v1/greeter.proto
syntax = "proto3";
package api.v1;
option go_package = "api/v1/greeter";
service Greeter {
rpc Greeter (GreeterRequest) returns (GreeterResponse) {}
}
message GreeterRequest {
string name = 1;
}
message GreeterResponse {
string message = 1;
}

2. 创建 api/v1/gw_mapping.yaml
type: google.api.Service
config_version: 3
# Please refer google.api.Http in https://github.com/googleapis/googleapis/blob/master/google/api/http.proto file for details.
http:
rules:
- selector: api.v1.Greeter.Greeter
get: /api/v1/greeter

3. 创建 buf.yaml
version: v1beta1
name: github.com/rk-dev/rk-demo
build:
roots:
- api

4. 创建 buf.gen.yaml
version: v1beta1
plugins:
# protoc-gen-go needs to be installed, generate go files based on proto files
- name: go
out: api/gen
opt:
- paths=source_relative
# protoc-gen-go-grpc needs to be installed, generate grpc go files based on proto files
- name: go-grpc
out: api/gen
opt:
- paths=source_relative
- require_unimplemented_servers=false
# protoc-gen-grpc-gateway needs to be installed, generate grpc-gateway go files based on proto files
- name: grpc-gateway
out: api/gen
opt:
- paths=source_relative
- grpc_api_configuration=api/v1/gw_mapping.yaml
# protoc-gen-openapiv2 needs to be installed, generate swagger config files based on proto files
- name: openapiv2
out: api/gen
opt:
- grpc_api_configuration=api/v1/gw_mapping.yaml

5. 编译 proto file
$ buf generate

如下的文件会被创建。
$ tree api/gen
api/gen
└── v1
├── greeter.pb.go
├── greeter.pb.gw.go
├── greeter.swagger.json
└── greeter_grpc.pb.go
1 directory, 4 files

6. 创建 bootA.yaml & serverA.go
Server-A 监听 1949 端口,并且发送请求给 Server-B。
我们通过 rkgrpcctx.InjectSpanToNewContext() 方法把 Tracing 信息注入到 Context 中,发送给 Server-B。
---
grpc:
- name: greeter # Name of grpc entry
port: 1949 # Port of grpc entry
enabled: true # Enable grpc entry
interceptors:
loggingZap:
enabled: true
meta:
enabled: true
tracingTelemetry:
enabled: true

package main
import (
"context"
"demo/api/gen/v1"
"fmt"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-grpc/interceptor/context"
"google.golang.org/grpc"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot(rkboot.WithBootConfigPath("bootA.yaml"))
// Get grpc entry with name
grpcEntry := boot.GetGrpcEntry("greeter")
grpcEntry.AddRegFuncGrpc(registerGreeter)
grpcEntry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
func registerGreeter(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServer{})
}
type GreeterServer struct{}
func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
// Call serverB at 2008 with grpc client
opts := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithInsecure(),
}
conn, _ := grpc.Dial("localhost:2008", opts...)
defer conn.Close()
client := greeter.NewGreeterClient(conn)
// Inject current trace information into context
newCtx := rkgrpcctx.InjectSpanToNewContext(ctx)
client.Greeter(newCtx, &greeter.GreeterRequest{Name: "A"})
return &greeter.GreeterResponse{
Message: fmt.Sprintf("Hello %s!", request.Name),
}, nil
}

7. 创建 bootB.yaml & serverB.go
Server-B 监听 2008 端口。
---
grpc:
- name: greeter # Name of grpc entry
port: 2008 # Port of grpc entry
enabled: true # Enable grpc entry
interceptors:
loggingZap:
enabled: true
meta:
enabled: true
tracingTelemetry:
enabled: true

package main
import (
"context"
"demo/api/gen/v1"
"fmt"
"github.com/rookie-ninja/rk-boot"
"google.golang.org/grpc"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot(rkboot.WithBootConfigPath("bootB.yaml"))
// Get grpc entry with name
grpcEntry := boot.GetGrpcEntry("greeter")
grpcEntry.AddRegFuncGrpc(registerGreeterB)
grpcEntry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
func registerGreeterB(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServerB{})
}
type GreeterServerB struct{}
func (server *GreeterServerB) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
return &greeter.GreeterResponse{
Message: fmt.Sprintf("Hello %s!", request.Name),
}, nil
}

8. 文件夹结构
├── api
│ ├── gen
│ │ └── v1
│ │ ├── greeter.pb.go
│ │ ├── greeter.pb.gw.go
│ │ ├── greeter.swagger.json
│ │ └── greeter_grpc.pb.go
│ └── v1
│ ├── greeter.proto
│ └── gw_mapping.yaml
├── bootA.yaml
├── bootB.yaml
├── buf.gen.yaml
├── buf.yaml
├── go.mod
├── go.sum
├── serverA.go
└── serverB.go

9. 启动 ServerA & ServerB
$ go run serverA.go
$ go run serverB.go

10. 往 ServerA 发送请求
¥ curl "localhost:1949/api/v1/greeter?name=rk-dev"

11. 验证日志
两个服务的日志中,会有同样的 traceId,不同的 requestId。
我们可以通过 grep traceId 来追踪 RPC。
- ServerA
------------------------------------------------------------------------
endTime=2021-10-20T00:02:21.739688+08:00
...
ids={"eventId":"0d145356-998a-4999-ab62-6f1b805274a0","requestId":"0d145356-998a-4999-ab62-6f1b805274a0","traceId":"c36a45eb076066df39fa407174012369"}
...
operation=/api.v1.Greeter/Greeter
resCode=OK
eventStatus=Ended
EOE

- ServerB
------------------------------------------------------------------------
endTime=2021-10-20T00:02:21.739125+08:00
...
ids={"eventId":"8858a6eb-e953-42ad-bdc3-c466bbbd798e","requestId":"8858a6eb-e953-42ad-bdc3-c466bbbd798e","traceId":"c36a45eb076066df39fa407174012369"}
...
operation=/api.v1.Greeter/Greeter
resCode=OK
eventStatus=Ended
EOE

概念
当我们没有使用例如 jaeger 调用链服务的时候,我们希望通过日志来追踪分布式系统里的 RPC 请求。
rk-boot 的拦截器会通过 openTelemetry 库来向日志写入 traceId 来追踪 RPC。
当启动了日志拦截器,原数据拦截器,调用链拦截器的时候,拦截器会往日志里写入如下三种 ID。
EventId
当启动了日志拦截器,EventId 会自动生成。
---
grpc:
- name: greeter # Name of grpc entry
port: 1949 # Port of grpc entry
enabled: true # Enable grpc entry
interceptors:
loggingZap:
enabled: true

------------------------------------------------------------------------
...
ids={"eventId":"cd617f0c-2d93-45e1-bef0-95c89972530d"}
...

RequestId
当启动了日志拦截器和原数据拦截器,RequestId 和 EventId 会自动生成,并且这两个 ID 会一致。
---
grpc:
- name: greeter # Name of grpc entry
port: 1949 # Port of grpc entry
enabled: true # Enable grpc entry
interceptors:
loggingZap:
enabled: true
meta:
enabled: true

------------------------------------------------------------------------
...
ids={"eventId":"8226ba9b-424e-4e19-ba63-d37ca69028b3","requestId":"8226ba9b-424e-4e19-ba63-d37ca69028b3"}
...

即使用户覆盖了 RequestId,EventId 也会保持一致。
rkgrpcctx.AddHeaderToClient(ctx, rkgrpcctx.RequestIdKey, "overridden-request-id")

------------------------------------------------------------------------
...
ids={"eventId":"overridden-request-id","requestId":"overridden-request-id"}
...

TraceId
当启动了调用链拦截器,traceId 会自动生成。
---
grpc:
- name: greeter # Name of grpc entry
port: 1949 # Port of grpc entry
enabled: true # Enable grpc entry
interceptors:
loggingZap:
enabled: true
meta:
enabled: true
tracingTelemetry:
enabled: true

------------------------------------------------------------------------
...
ids={"eventId":"dd19cf9a-c7be-486c-b29d-7af777a78ebe","requestId":"dd19cf9a-c7be-486c-b29d-7af777a78ebe","traceId":"316a7b475ff500a76bfcd6147036951c"}
...

原文链接
本文为阿里云原创内容,未经允许不得转载。
GRPC: 如何实现分布式日志跟踪?的更多相关文章
- 【SpringCloud构建微服务系列】分布式链路跟踪Spring Cloud Sleuth
一.背景 随着业务的发展,系统规模越来越大,各微服务直接的调用关系也变得越来越复杂.通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用协同产生最后的请求结果,几乎每一个前端请求都会形成一 ...
- 循序渐进看Java web日志跟踪(1)-Tomcat 日志追踪与配置
日志,是软件运行过程中,对各类操作中重要信息的记录. 日志跟踪,不管对于怎么样的项目来说,都是非常重要的一部分,它关系到项目后期的维护和排错,起着举足轻重的作用.项目开发过程中,对日志的记录规则,也将 ...
- spring-cloud-sleuth 和 分布式链路跟踪系统
==================spring-cloud-sleuth==================spring-cloud-sleuth 可以用来增强 log 的跟踪识别能力, 经常在微服 ...
- springcloud 分布式服务跟踪sleuth+zipkin
原文:https://www.jianshu.com/p/6ef0b76b9c26 分布式服务跟踪需求 随着分布式服务越来越多,调用关系越来越复杂,组合接口越来越多,要进行分布式服务跟踪监控的需求也越 ...
- 第11章 分布式服务跟踪: Spring Cloud Sleuth
通常一个由客户端发起的请求在后端系统中会经过多个不同的微服务调用来协同产生最后的请求结果, 在复杂的微服务架构系统中, 几乎每一个前端请求都会形成一条复杂的分布式服务调用链路, 在每条链路中任何一个依 ...
- 跟我学SpringCloud | 第十一篇:使用Spring Cloud Sleuth和Zipkin进行分布式链路跟踪
SpringCloud系列教程 | 第十一篇:使用Spring Cloud Sleuth和Zipkin进行分布式链路跟踪 Springboot: 2.1.6.RELEASE SpringCloud: ...
- Spring Cloud第九篇 | 分布式服务跟踪Sleuth
本文是Spring Cloud专栏的第九篇文章,了解前八篇文章内容有助于更好的理解本文: Spring Cloud第一篇 | Spring Cloud前言及其常用组件介绍概览 Spring Cl ...
- Java生鲜电商平台-SpringCloud分布式请求跟踪系统设计与实践
Java生鲜电商平台-SpringCloud分布式请求跟踪系统设计与实践 Java生鲜电商平台微服务现状 某个服务挂了,导致上游大量报警,如何快速定位哪个服务出问题? 某个核心挂了,导致大量报错,如何 ...
- 【Spring Cloud】Spring Cloud之Spring Cloud Sleuth,分布式服务跟踪(1)
一.Spring Cloud Sleuth组件的作用 为微服务架构增加分布式服务跟踪的能力,对于每个请求,进行全链路调用的跟踪,可以帮助我们快速发现错误根源以及监控分析每条请求链路上的性能瓶颈等. 二 ...
- SpringCloud入门(十一):Sleuth 与 Zipkin分布式链路跟踪
现今业界分布式服务跟踪的理论基础主要来自于 Google 的一篇论文<Dapper, a Large-Scale Distributed Systems Tracing Infrastructu ...
随机推荐
- 【个人笔记】Nestjs使用TypeORM注意点
在Nestjs使用TypeORM还是有一些注意点. entities路径配置注意点 在nestjs中使用TypeORM,需要配置数据库连接(以MySQL为例).需要特别注意的是配置参数里面的entit ...
- 【专访蓝景科技】5G+实时云渲染赋能数字孪生,共建元宇宙
2021年伊始,元宇宙 概念不断扩展探讨与深入,国内外科技巨头扎堆布局元宇宙. 元宇宙第一股------ROBLOX 上市.字节跳动 90亿收购国内TOP1VR厂商PICO.FACEBOOK改名为 ...
- 你是怎么理解ES6中 Decorator 的?使用场景?
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 一.介绍 Decorator,即装饰器,从名字上很容易让我们联想到装饰者模式 简单来讲,装饰者模式就是一种在不改变原类和使用继承的情况下, ...
- 《.NET内存管理宝典 》(Pro .NET Memory Management) 阅读指南 - 第7章
本章勘误: 暂无,等待细心的你告诉我哦. 本章注解: 暂无 本章释疑: 暂无,等待你的提问 致谢: MVP 林德熙 MVP 吕毅 sPhinX 相关链接 试读记录
- KingbaseES V8R6备份恢复案例之---sys_restore实现schema转换
**案例说明:** sys_restore用于sys_dump备份的数据恢复,在实际的应用中有需求,将从sys_dump备份对象从原schema中转换到到另外的schema,sys_restore支持 ...
- UE4蓝图对Actor的引用
通过关卡蓝图调用 在关卡中放置一个Actor,在关卡蓝图中右键 create a reference to actor,即可 注意使用该方法创建时,需要现在关卡中选择上该类Actor 当Actor生成 ...
- archlinux xfce 出现[FAILED] Failed to mount /boot, systemctl status boot.mount出现 mount: /boot: unknown filesystem type 'vfat' 滚挂了
开机显示:[FAILED] Failed to mount /boot 使用命令systemctl status boot.mount后出现 mount: /boot: unknown filesys ...
- ET介绍——单线程异步
单线程异步 前面几个例子都是多线程实现的异步,但是异步显然不仅仅是多线程的.我们在之前的例子中使用了Sleep来实现时间的等待,每一个计时器都需要使用一个线程,会导致线程切换频繁,这个实现效率很低,平 ...
- 21 JSONP
JSONP 为了解决浏览器跨域问题. jquery提供了jsonp请求. 在网页端如果见到了服务器返回的数据是: xxxxxxxxxxdjsfkldasjfkldasjklfjadsklfjasd ...
- OpenHarmony社区运营报告(2023年4月)
本月快讯 • 2023年4月9日,OpenAtom OpenHarmony(以下简称"OpenHarmony")3.2 Release新版本发布.相比一年前的OpenHarmo ...