gogoclient java_链路跟踪-GRPC请求 - GoFrame官网 - 类似PHP-Laravel, Java-SpringBoot的Go企业级开发框架...

本文档介绍了如何使用GoFrame框架构建一个包含链路跟踪特性的GRPC服务端和客户端。通过GRPC协议实现微服务,结合Jaeger进行链路追踪,演示了服务端和客户端的拦截器使用,以及数据验证和错误处理。同时,展示了如何在Jaeger中查看和分析链路跟踪信息。
摘要由CSDN通过智能技术生成

在本章节中,我们将之前介绍HTTP Client&Server的示例修改为GRPC微服务,并演示如何使用GoFrame框架开发一个简单的GRPC服务端和客户端,并且为GRPC微服务增加链路跟踪特性。OK, Let's 撸!

目录结构

efae23bbca804dbac46fa3a3781f6684.png

Protocol

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工程目录、开发规范、开发工具、拦截器、注册发现、负载均衡等设计话题。

GRPC Server

package main

import (

"context"

"fmt"

"gftracing/examples/grpc+db+redis+log/protobuf/user"

"gftracing/tracing"

"github.com/gogf/gcache-adapter/adapter"

"github.com/gogf/gf/frame/g"

"github.com/gogf/katyusha/krpc"

"google.golang.org/grpc"

"net"

"time"

)

type server struct{}

const (

ServiceName = "tracing-grpc-server"

JaegerUdpEndpoint = "localhost:6831"

)

func main() {

flush, err := tracing.InitJaeger(ServiceName, JaegerUdpEndpoint)

if err != nil {

g.Log().Fatal(err)

}

defer flush()

g.DB().GetCache().SetAdapter(adapter.NewRedis(g.Redis()))

address := ":8000"

listen, err := net.Listen("tcp", address)

if err != nil {

g.Log().Fatalf("failed to listen: %v", err)

}

s := grpc.NewServer(

grpc.ChainUnaryInterceptor(

krpc.Server.UnaryError,

krpc.Server.UnaryRecover,

krpc.Server.UnaryTracing,

krpc.Server.UnaryValidate,

),

)

user.RegisterUserServer(s, &server{})

g.Log().Printf("grpc server starts listening on %s", address)

if err := s.Serve(listen); err != nil {

g.Log().Fatalf("failed to serve: %v", err)

}

}

// Insert is a route handler for inserting user info into dtabase.

func (s *server) Insert(ctx context.Context, req *user.InsertReq) (*user.InsertRes, error) {

res := user.InsertRes{}

result, err := g.Table("user").Ctx(ctx).Insert(g.Map{

"name": req.Name,

})

if err != nil {

return nil, err

}

id, _ := result.LastInsertId()

res.Id = int32(id)

return &res, nil

}

// 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) (*user.QueryRes, error) {

res := user.QueryRes{}

err := g.Table("user").

Ctx(ctx).

Cache(5*time.Second, s.userCacheKey(req.Id)).

WherePri(req.Id).

Scan(&res)

if err != nil {

return nil, err

}

return &res, nil

}

// Delete is a route handler for deleting specified user info.

func (s *server) Delete(ctx context.Context, req *user.DeleteReq) (*user.DeleteRes, error) {

res := user.DeleteRes{}

_, err := g.Table("user").

Ctx(ctx).

Cache(-1, s.userCacheKey(req.Id)).

WherePri(req.Id).

Delete()

if err != nil {

return nil, err

}

return &res, nil

}

func (s *server) userCacheKey(id int32) string {

return fmt.Sprintf(`userInfo:%d`, id)

}

服务端代码简要说明:

1、首先,客户端也是需要通过initTracer方法初始化Jaeger。

2、可以看到,业务逻辑和之前HTTP示例项目完全一致,只是接入层修改为了GRPC协议。

3、我们仍然通过缓存适配器的方式注入Redis缓存:

g.DB().GetCache().SetAdapter(adapter.NewRedis(g.Redis()))

4、关键技术点来了,拦截器。这里我们添加了若干个拦截器,并且都由Katyusha微服务框架提供,由于这里使用的GRPC原生的服务端创建方式,因此只是复用了Katyusha的一些拦截器,所有拦截器便需要显式设置:

Unary Interceptor说明

krpc.Server.UnaryError错误处理,增加了对gerror组件的支持,支持业务方便地定义错误码。

krpc.Server.UnaryRecover异常捕获,防止业务处理逻辑抛一个panic整个进程就崩溃。

krpc.Server.UnaryTracing链路跟踪,通过该拦截器启用服务端的链路跟踪特性。

krpc.Server.UnaryValidate数据校验,通过拦截器的形式自动对输入对象调用gvalid组件执行数据校验,校验失败直接返回。如果对象struct定义中不包含校验标签时,该拦截器什么都不会做,快速返回,不影响性能。

5、这里也是通过Cache方法启用ORM的缓存特性,之前已经做过介绍,这里不再赘述。

GRPC Client

package main

import (

"context"

"gftracing/examples/grpc+db+redis+log/protobuf/user"

"gftracing/tracing"

"github.com/gogf/gf/frame/g"

"github.com/gogf/gf/net/gtrace"

"github.com/gogf/katyusha/krpc"

"google.golang.org/grpc"

)

const (

ServiceName = "tracing-grpc-client"

JaegerUdpEndpoint = "localhost:6831"

)

func main() {

flush, err := tracing.InitJaeger(ServiceName, JaegerUdpEndpoint)

if err != nil {

g.Log().Fatal(err)

}

defer flush()

StartRequests()

}

func StartRequests() {

ctx, span := gtrace.NewSpan(context.Background(), "StartRequests")

defer span.End()

grpcClientOptions := make([]grpc.DialOption, 0)

grpcClientOptions = append(

grpcClientOptions,

grpc.WithInsecure(),

grpc.WithBlock(),

grpc.WithChainUnaryInterceptor(

krpc.Client.UnaryError,

krpc.Client.UnaryTracing,

),

)

conn, err := grpc.Dial(":8000", grpcClientOptions...)

if err != nil {

g.Log().Fatalf("did not connect: %v", err)

}

defer conn.Close()

client := user.NewUserClient(conn)

// Baggage.

ctx = gtrace.SetBaggageValue(ctx, "uid", 100)

// Insert.

insertRes, err := client.Insert(ctx, &user.InsertReq{

Name: "john",

})

if err != nil {

g.Log().Ctx(ctx).Fatalf(`%+v`, err)

}

g.Log().Ctx(ctx).Println("insert:", insertRes.Id)

// Query.

queryRes, err := client.Query(ctx, &user.QueryReq{

Id: insertRes.Id,

})

if err != nil {

g.Log().Ctx(ctx).Printf(`%+v`, err)

return

}

g.Log().Ctx(ctx).Println("query:", queryRes)

// Delete.

_, err = client.Delete(ctx, &user.DeleteReq{

Id: insertRes.Id,

})

if err != nil {

g.Log().Ctx(ctx).Printf(`%+v`, err)

return

}

g.Log().Ctx(ctx).Println("delete:", insertRes.Id)

// Delete with error.

_, err = client.Delete(ctx, &user.DeleteReq{

Id: -1,

})

if err != nil {

g.Log().Ctx(ctx).Printf(`%+v`, err)

return

}

g.Log().Ctx(ctx).Println("delete:", -1)

}

客户端代码简要说明:

1、首先,客户端也是需要通过initTracer方法初始化Jaeger。

2、主要的也是UnaryError和UnaryTracing两个拦截器的使用,作用同上服务端介绍。

效果查看

启动服务端:

2c15ad7bc3d9eba127da5c830555e301.png

启动客户端:

69ad3066ce2577a5d1f598c433175c1e.png

这里客户端的执行最后报了一个错误,那是我们故意为之,目的是演示GRPC报错时的链路信息展示。我们打开jaeger查看一下链路跟踪信息:

ce883dd15a55394104394c32e9e84947.png

可以看到本次请求涉及到两个服务:tracing-grpc-client和tracing-grpc-server,即客户端和服务端。整个请求链路涉及到17个span,客户端5个span,服务端12个span,并且产生了2个错误。我们点击查看详情:

29402c308e0640600be8907ff2849dcb.png

我们点击查看一下最后接口调用错误的span情况:

3ec804a9e10c373163a24b711174f1bd.png

看起来像个参数校验错误,点击查看Events/Logs中的请求参数:

6f5779c6242390ad7651399aa9d2d6e2.png

查看Process中的Log信息可以看到,是由于传递的参数为-1,不满足校验规则,因此在数据校验的时候报错返回了。

GRPC Client

由于orm、redis、logging组件在之前的章节中已经介绍过链路信息,因此我们这里主要介绍GRPC Client&Server的链路信息。

Attributes

d1f4cf66298eedb380cf809376453c09.png

Attribute/Tag说明

net.peer.ip请求的目标IP。

net.peer.port请求的目标端口。

rpc.grpc.status_codeGRPC的内部状态码,0表示成功,非0表示失败。

rpc.serviceRPC的服务名称,注意这里是RPC而不是GRPC,因为这里是通用定义,客户端支持多种RPC通信协议,GRPC只是其中一种。

rpc.methodRPC的方法名称。

rpc.systemRPC协议类型,如:grpc, thrift等。

Events/Logs

816f23e2d64d3f9fcd707a8f2de82feb.png

Event/Log说明

grpc.metadata.outgoingGRPC客户端请求提交的Metadata信息,可能会比较大。

grpc.request.baggageGRPC客户端请求提交的Baggage信息,用于服务间链路信息传递。

grpc.request.messageGRPC客户端请求提交的Message数据,可能会比较大,最大只记录512KB,如果超过该大小则忽略。仅对Unary请求类型有效。

grpc.response.messageGRPC客户端请求接收返回的的Message信息,可能会比较大。仅对Unary请求类型有效。

GRPC Server

Attributes

7cf00e800b7412798bf29fff456f0c57.png

GRPC Server端的Attributes含义同GRPC Client,在同一请求中,打印的数据基本一致。

Events

5c5acfac4469001f96d59e6e055f3629.png

GRPC Server端的Events与GRPC Client不同的是,在同一请求中,服务端接收到的metadata为grpc.metadata.incoming,其他同GRPC Client。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值