Go 链路追踪是一种用于收集和分析应用程序中的分布式调用信息的工具。它可以帮助开发人员快速定位和诊断分布式系统中出现的问题,并有效地改进应用性能。Go 链路追踪是一个开源项目,它使用 Go 语言来实现,并且可以在多个平台上运行。
Go 链路追踪的核心功能是通过采集和分析应用程序中的分布式请求信息来诊断问题。它使用一个名为“Span”的数据单元来表示一个特定的请求,并将其作为一个树形图显示出来。Span 具有多个属性,包括时间戳、请求 ID、服务名、方法名、耗时、错误信息等。通过对 Span 的分析,开发人员可以快速找出问题所在,并根据 Span 的耗时数据优化代码以减少性能问题。
// 创建 Span span := tracer.StartSpan("operation_name") defer span.Finish() // 设置 Span 属性 span.SetTag("key", "value") // 追踪子 Span childSpan := tracer.StartSpan("child_operation", opentracing.ChildOf(span.Context())) defer childSpan.Finish()
在本章节中,我们将之前介绍HTTP Client&Server
的示例修改为GRPC
微服务,并演示如何使用GoFrame
框架开发一个简单的GRPC
服务端和客户端,并且为GRPC
微服务增加链路跟踪特性。
本章节的示例代码位于:https://github.com/gogf/gf/tree/master/example/trace/grpc_with_db
syntax = "proto3";
package user;
option go_package = "protobuf/user";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
// User service for tracing demo.
service User {
rpc Insert(InsertReq) returns (InsertRes) {}
rpc Query(QueryReq) returns (QueryRes) {}
rpc Delete(DeleteReq) returns (DeleteRes) {}
}
message InsertReq {
string Name = 1 [(gogoproto.moretags) = "v:"required#Please input user name.""];
}
message InsertRes {
int32 Id = 1;
}
message QueryReq {
int32 Id = 1 [(gogoproto.moretags) = "v:"min:1#User id is required for querying.""];
}
message QueryRes {
int32 Id = 1;
string Name = 2;
}
message DeleteReq {
int32 Id = 1 [(gogoproto.moretags) = "v:"min:1#User id is required for deleting.""];
}
message DeleteRes {}
这里使用到了第三方的 github.com/gogo/protobuf 开源项目,用于注入自定义的Golang struct标签。这里不详细介绍,感兴趣的小伙伴可以自行了解。未来Katyusha
微服务框架的官网文档也会做对这块详细介绍,包括GRPC
工程目录、开发规范、开发工具、拦截器、注册发现、负载均衡等设计话题。
package main
import (
"context"
"fmt"
"time"
"github.com/gogf/gf/contrib/trace/jaeger/v2"
"github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gcache"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/katyusha/krpc"
)
type server struct{}
const (
ServiceName = "grpc-server-with-db"
JaegerUdpEndpoint = "localhost:6831"
)
func main() {
var ctx = gctx.New()
tp, err := jaeger.Init(ServiceName, JaegerUdpEndpoint)
if err != nil {
g.Log().Fatal(ctx, err)
}
defer tp.Shutdown(ctx)
// Set ORM cache adapter with redis.
g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
s := krpc.Server.NewGrpcServer()
user.RegisterUserServer(s.Server, &server{})
s.Run()
}
// Insert is a route handler for inserting user info into database.
func (s *server) Insert(ctx context.Context, req *user.InsertReq) (res *user.InsertRes, err error) {
result, err := g.Model("user").Ctx(ctx).Insert(g.Map{
"name": req.Name,
})
if err != nil {
return nil, err
}
id, _ := result.LastInsertId()
res = &user.InsertRes{
Id: int32(id),
}
return
}
// Query is a route handler for querying user info. It firstly retrieves the info from redis,
// if there"s nothing in the redis, it then does db select.
func (s *server) Query(ctx context.Context, req *user.QueryReq) (res *user.QueryRes, err error) {
err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: 5 * time.Second,
Name: s.userCacheKey(req.Id),
Force: false,
}).WherePri(req.Id).Scan(&res)
if err != nil {
return nil, err
}
return
}
// Delete is a route handler for deleting specified user info.
func (s *server) Delete(ctx context.Context, req *user.DeleteReq) (res *user.DeleteRes, err error) {
err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
Duration: -1,
Name: s.userCacheKey(req.Id),
Force: false,
}).WherePri(req.Id).Scan(&res)
return
}
func (s *server) userCacheKey(id int32) string {
return fmt.Sprintf(`userInfo:%d`, id)
}
服务端代码简要说明:
jaeger.Init
方法初始化Jaeger
。GRPC
协议。Redis
缓存:g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
Cache
方法启用ORM
的缓存特性,之前已经做过介绍,这里不再赘述。package main
import (
"github.com/gogf/gf/contrib/trace/jaeger/v2"
"github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/gtrace"
"github.com/gogf/gf/v2/os/gctx"
)
const (
ServiceName = "grpc-client-with-db"
JaegerUdpEndpoint = "localhost:6831"
)
func main() {
var ctx = gctx.New()
tp, err := jaeger.Init(ServiceName, JaegerUdpEndpoint)
if err != nil {
g.Log().Fatal(ctx, err)
}
defer tp.Shutdown(ctx)
StartRequests()
}
func StartRequests() {
ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests")
defer span.End()
var client, err = user.NewClient()
if err != nil {
g.Log().Fatalf(ctx, `%+v`, err)
}
// Baggage.
ctx = gtrace.SetBaggageValue(ctx, "uid", 100)
// Insert.
insertRes, err := client.User().Insert(ctx, &user.InsertReq{
Name: "john",
})
if err != nil {
g.Log().Fatalf(ctx, `%+v`, err)
}
g.Log().Info(ctx, "insert id:", insertRes.Id)
// Query.
queryRes, err := client.User().Query(ctx, &user.QueryReq{
Id: insertRes.Id,
})
if err != nil {
g.Log().Errorf(ctx, `%+v`, err)
return
}
g.Log().Info(ctx, "query result:", queryRes)
// Delete.
_, err = client.User().Delete(ctx, &user.DeleteReq{
Id: insertRes.Id,
})
if err != nil {
g.Log().Errorf(ctx, `%+v`, err)
return
}
g.Log().Info(ctx, "delete id:", insertRes.Id)
// Delete with error.
_, err = client.User().Delete(ctx, &user.DeleteReq{
Id: -1,
})
if err != nil {
g.Log().Errorf(ctx, `%+v`, err)
return
}
g.Log().Info(ctx, "delete id:", -1)
}
客户端代码简要说明:
jaeger.Init
方法初始化Jaeger
。Katyusha
框架封装好了,开发者只需要关心业务逻辑实现即可,启动服务端:
启动客户端:
这里客户端的执行最后报了一个错误,那是我们故意为之,目的是演示GRPC
报错时的链路信息展示。我们打开jaeger
查看一下链路跟踪信息:
可以看到本次请求涉及到两个服务:tracing-grpc-client
和tracing-grpc-server
,即客户端和服务端。整个请求链路涉及到17个span
,客户端5个span
,服务端12个span
,并且产生了2个错误。我们点击查看详情:
我们点击查看一下最后接口调用错误的span
情况:
看起来像个参数校验错误,点击查看Events/Logs
中的请求参数:
查看Process
中的Log
信息可以看到,是由于传递的参数为-1,不满足校验规则,因此在数据校验的时候报错返回了。
由于orm
、redis
、logging
组件在之前的章节中已经介绍过链路信息,因此我们这里主要介绍GRPC Client&Server
的链路信息。
Attribute/Tag
|
说明
|
---|---|
net.peer.ip
|
请求的目标IP。 |
net.peer.port
|
请求的目标端口。 |
rpc.grpc.status_code
|
GRPC 的内部状态码,0 表示成功,非0 表示失败。 |
rpc.service
|
RPC 的服务名称,注意这里是RPC 而不是GRPC ,因为这里是通用定义,客户端支持多种RPC 通信协议,GRPC 只是其中一种。 |
rpc.method
|
RPC 的方法名称。 |
rpc.system
|
RPC 协议类型,如:grpc , thrift 等。 |
Event/Log
|
说明
|
---|---|
grpc.metadata.outgoing
|
GRPC 客户端请求提交的Metadata 信息,可能会比较大。 |
grpc.request.baggage
|
GRPC 客户端请求提交的Baggage 信息,用于服务间链路信息传递。 |
grpc.request.message
|
|
grpc.response.message
|
GRPC 客户端请求接收返回的的Message 信息,可能会比较大。仅对Unary 请求类型有效。 |
GRPC Server
端的Attributes
含义同GRPC Client
,在同一请求中,打印的数据基本一致。
GRPC Server
端的Events
与GRPC Client
不同的是,在同一请求中,服务端接收到的metadata
为grpc.metadata.incoming
,其他同GRPC Client
。
ORM支持传递自定义的context上下文变量,用于异步IO控制、上下文信息传递(特别是链路跟踪信息的传递)、以及嵌套事...
资源管理设计的目标之一是在开发阶段不影响静态文件的开发管理,只有在发布的时候执行打包即可,打包完毕后清理临时文件,因此只...
基本介绍支持并发安全开关选项的map容器,最常用的数据结构。该模块包含多个数据结构的map容器:HashMap、TreeMap和List...
基本介绍动态大小的并发安全队列。同时,gqueue也支持固定队列大小,固定队列大小时队列效率和标准库的channel无异。使...
约瑟夫问题我们使用ring来模拟一下约瑟夫问题:著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特后,39个犹太...