目录
- 一、链路追踪
- 1 - 链路追踪简介
- 2 - 链路追踪技术选型
- 3 - jaeger安装
- 4 - jaeger组成
- 5 - opentracing解析
- 二、go使用jaeger
- 1 - 发送单span
- 2 - 发送嵌套span
- 3 - grpc发送span
- 三、goods_web集成jaeger
- 1 - 添加nacos配置
- 2 - 添加中间件
- 3 - 初始化连接注入Tracer
- 4 - api中调用grpc带上ctx
- 5 - 优化otgrpc的源码
- 四、order_web集成jaeger
- 1 - 复制tracing和otgrpc到order_web中
- 2 - router修改
- 3 - grpc调用接口修改
- 4 - 初始化连接注入Tracer
- 5 - 添加nacos配置
- 五、同步srv层和web层的jaeger
- 1 - otgrpc原理分析
- 2 - 初始化jaeger
- 3 - 增加追踪的span
- 4 - 过滤掉健康检查的jaeger
- 六、完整源码
一、链路追踪
1 - 链路追踪简介
- 什么是链路追踪:分布式链路追踪就是将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示,比如各个服务节点上的耗时、请求具体到达哪台机器上、每个服务节点的请求状态等等
- 链路追踪主要功能:
- 故障快速定位:可以通过调用链结合业务日志快速定位错误信息
- 链路性能可视化:各个阶段链路耗时、服务依赖关系可以通过可视化界面展现出来
- 链路分析:通过分析链路耗时、服务依赖关系可以得到用户的行为路径,汇总分析应用在很多业务场景
2 - 链路追踪技术选型
3 - jaeger安装
4 - jaeger组成
分布式追踪系统发展很快,种类繁多,但核心步骤一般有三个:代码埋点(我们来实现)、数据存储、查询展示
- Jaeger组成
- Jaeger Client:为不同语言实现了符合OpenTracing标准的SDK.应用程序通过API写入数据,client library把trace信息按照应用程序指定的采样策略传递给jaeger-agent
- Agent:它是一个监听再UDP端口上接收span数据的网络守护进程,它会将数据批量发送给collector.它被设计成一个基础组件,部署到所有的宿主机上.Agent将client library和collector解耦,为client library屏蔽了路由和发现collector的细节
- collector:接收jaeger-agent发送来的数据.然后将数据写入到后端存储.collector被设计成无状态的组件,因此可以同事运行任意数量的jaeger-collector
- Data Store:后端存储被设计成一个可插拔的组件,支持将数据写入cassandra,elasticsearch
- Query:接收查询请求,然后从后端存储系统中检索trace并通过UI进行展示.Query是无状态的,可以启动多个实例,把他们部署再nginx这样的负载均衡器后面
5 - opentracing解析
- 以下内容摘自:https://cloud.tencent.com/developer/article/1846107
- 综述:这是正式的OpenTracing语义标准。OpenTracing是一个跨编程语言的标准,此文档会避免具有语言特性的概念。比如,我们在文档中使用"interface",因为所有的语言都包含"interface"这种概念
- 版本命名策略:OpenTracing标准使用Major.Minor版本命名策略(即:大版本.小版本),但不包含.Patch版本(即:补丁版本)。如果标准做出不向前兼容的改变,则使用“主版本”号提升。如果是向前兼容的改进,则进行小版本号提升,例如加入新的标准tag, log和SpanContext引用类型
- OpenTracing数据模型:
- OpenTracing中的Trace(调用链)通过归属于此调用链的Span来隐性的定义。 特别说明,一条Trace(调用链)可以被认为是一个由多个Span组成的有向无环图(DAG图), Span与Span的关系被命名为References
- 译者注: Span,可以被翻译为跨度,可以被理解为一次方法调用, 一个程序块的调用, 或者一次RPC/数据库访问.只要是一个具有完整时间周期的程序访问,都可以被认为是一个span
- OpenTracing API:OpenTracing标准中有三个重要的相互关联的类型,分别是Tracer, Span 和 SpanContext。下面,我们分别描述每种类型的行为,一般来说,每个行为都会在各语言实现层面上,会演变成一个方法,而实际上由于方法重载,很可能演变成一系列相似的方法
- Tracer:Tracer接口用来创建Span,以及处理如何处理Inject(serialize) 和 Extract (deserialize),用于跨进程边界传递
- Span:每个Span包含以下的状态
- An operation name,操作名称
- A start timestamp,起始时间
- A finish timestamp,结束时间
- Span Tag,一组键值对构成的Span标签集合。键值对中,键必须为string,值可以是字符串,布尔,或者数字类型
- Span Log,一组span的日志集合。 每次log操作包含一个键值对,以及一个时间戳。 键值对中,键必须为string,值可以是任意类型。 但是需要注意,不是所有的支持OpenTracing的Tracer,都需要支持所有的值类型
- SpanContext,Span上下文对象
- References(Span间关系),相关的零个或者多个Span(Span间通过SpanContext建立这种关系)
- SpanContext:每一个SpanContext包含以下状态
- 任何一个OpenTracing的实现,都需要将当前调用链的状态(例如:trace和span的id),依赖一个独特的Span去跨进程边界传输
- Baggage Items,Trace的随行数据,是一个键值对集合,它存在于trace中,也需要跨进程边界传输
- Span间关系:一个Span可以与一个或者多个SpanContexts存在因果关系。OpenTracing目前定义了两种关系:ChildOf(父子) 和 FollowsFrom(跟随)
二、go使用jaeger
1 - 发送单span
package main
import (
"time"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
)
func main() {
cfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.78.131:6831",
},
ServiceName: "mxshop",
}
tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
if err != nil {
panic(err)
}
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
span := opentracing.StartSpan("go-grpc-web")
time.Sleep(time.Second)
defer span.Finish()
}
- 执行完成在jaeger查看
2 - 发送嵌套span
- 直接新建2个span
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
spanA := opentracing.StartSpan("funcA")
time.Sleep(time.Millisecond * 500)
defer spanA.Finish()
spanB := opentracing.StartSpan("funcB")
time.Sleep(time.Millisecond * 1000)
defer spanB.Finish()
- 删除defer
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
spanA := opentracing.StartSpan("funcA")
time.Sleep(time.Millisecond * 500)
spanA.Finish()
spanB := opentracing.StartSpan("funcB")
time.Sleep(time.Millisecond * 1000)
spanB.Finish()
- 同tracer的span
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
parentSpan := tracer.StartSpan("main")
spanA := opentracing.StartSpan("funcAAA", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(time.Millisecond * 500)
spanA.Finish()
spanB := opentracing.StartSpan("funcBBB", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(time.Millisecond * 1000)
spanB.Finish()
parentSpan.Finish()
- 在两个span中间添加业务逻辑
parentSpan := tracer.StartSpan("main")
spanA := opentracing.StartSpan("funcAAA", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(time.Millisecond * 500)
spanA.Finish()
time.Sleep(time.Millisecond * 1000)
spanB := opentracing.StartSpan("funcBBB", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(time.Millisecond * 1000)
spanB.Finish()
parentSpan.Finish()
- 多级嵌套:main -> spanA -> spanB
parentSpan := tracer.StartSpan("main")
spanA := opentracing.StartSpan("funcAAA", opentracing.ChildOf(parentSpan.Context()))
time.Sleep(time.Millisecond * 500)
spanA.Finish()
time.Sleep(time.Millisecond * 1000)
spanB := opentracing.StartSpan("funcBBB", opentracing.ChildOf(spanA.Context()))
time.Sleep(time.Millisecond * 1000)
spanB.Finish()
parentSpan.Finish()
3 - grpc发送span
- 第三方grpc-opentracing:https://github.com/grpc-ecosystem/grpc-opentracing;直接clone下来使用
- server/server.go
package main
import (
"context"
"net"
"google.golang.org/grpc"
"my_test/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply,
error) {
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
g := grpc.NewServer()
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
- client/client.go
package main
import (
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"my_test/otgrpc"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
"google.golang.org/grpc"
"my_test/proto"
)
func main() {
cfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.78.131:6831",
},
ServiceName: "mxshop",
}
tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
if err != nil {
panic(err)
}
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
conn, err := grpc.Dial("127.0.0.1:50051",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())))
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
三、goods_web集成jaeger
1 - 添加nacos配置
- goods_web/config/config.go
type JaegerConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
Name string `mapstructure:"name" json:"name"`
}
type ServerConfig struct {
Name string `mapstructure:"name" json:"name"`
Host string `mapstructure:"host" json:"host"`
Tags []string `mapstructure:"tags" json:"tags"`
Port int `mapstructure:"port" json:"port"`
GoodsSrvInfo GoodsSrvConfig `mapstructure:"goods_srv" json:"goods_srv"`
JWTInfo JWTConfig `mapstructure:"jwt" json:"jwt"`
ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
JaegerInfo JaegerConfig `mapstructure:"jaeger" json:"jaeger"`
}
- nacos配置
{
"host": "192.168.124.9",
"name": "goods_web",
"port": 8082,
"tags": ["mxshop","imooc","bobby","goods","web"],
"goods_srv": {
"name": "goods_srv"
},
"jwt": {
"key": "VYLDYq3&hGWjWqF$K1ih"
},
"consul": {
"host": "192.168.124.51",
"port": 8500
},
"jaeger": {
"host": "192.168.124.51",
"port": 6381,
"name": "ja_web_api"
}
}
2 - 添加中间件
- goods_web/middlewares/tracing.go
package middlewares
import (
"fmt"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"web_api/goods_web/global"
"github.com/gin-gonic/gin"
jaegercfg "github.com/uber/jaeger-client-go/config"
)
func Trace() gin.HandlerFunc {
return func(ctx *gin.Context) {
cfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: fmt.Sprintf("%s:%d", global.ServerConfig.JaegerInfo.Host, global.ServerConfig.JaegerInfo.Port),
},
ServiceName: global.ServerConfig.JaegerInfo.Name,
}
tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
if err != nil {
panic(err)
}
opentracing.SetGlobalTracer(tracer)
defer closer.Close()
startSpan := tracer.StartSpan(ctx.Request.URL.Path)
defer startSpan.Finish()
ctx.Set("tracer", tracer)
ctx.Set("parentSpan", startSpan)
ctx.Next()
}
}
- goods_web/router/router_goods.go:router调用中间件
package router
import (
"github.com/gin-gonic/gin"
"web_api/goods_web/api/goods"
"web_api/goods_web/middlewares"
)
func InitGoodsRouter(Router *gin.RouterGroup) {
GoodsRouter := Router.Group("goods").Use(middlewares.Trace())
{
GoodsRouter.GET("", goods.List) //商品列表
//GoodsRouter.POST("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.New) //该接口需要管理员权限
GoodsRouter.POST("", goods.New) // 我们测试先将这2个接口权限关闭掉
GoodsRouter.GET("/:id", goods.Detail) //获取商品的详情
//GoodsRouter.DELETE("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Delete) //删除商品
GoodsRouter.DELETE("/:id", goods.Delete) // 我们测试先将这2个接口权限关闭掉
GoodsRouter.GET("/:id/stocks", goods.Stocks) //获取商品的库存
//GoodsRouter.PUT("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.Update)
GoodsRouter.PUT("/:id", goods.Update)
//GoodsRouter.PATCH("/:id",middlewares.JWTAuth(), middlewares.IsAdminAuth(), goods.UpdateStatus)
GoodsRouter.PATCH("/:id", goods.UpdateStatus)
}
}
3 - 初始化连接注入Tracer
- goods_web/initialize/init_srv_conn.go
package initialize
import (
"fmt"
_ "github.com/mbobakov/grpc-consul-resolver" // It's important
"github.com/opentracing/opentracing-go"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"web_api/goods_web/global"
"web_api/goods_web/proto"
"web_api/goods_web/utils/otgrpc"
)
func InitSrvConn() {
consulInfo := global.ServerConfig.ConsulInfo
userConn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
)
if err != nil {
zap.S().Fatal("[InitSrvConn] 连接 【商品服务失败】")
}
global.GoodsSrvClient = proto.NewGoodsClient(userConn)
}
4 - api中调用grpc带上ctx
- goods_web/api/goods/api_goods.go
func List(ctx *gin.Context) {
//省略。。。
request.Brand = int32(brandIdInt)
请求商品的service服务
r, err := global.GoodsSrvClient.GoodsList(context.WithValue(context.Background(), "ginContext", ctx), request)
if err != nil {
zap.S().Errorw("[List] 查询 【商品列表】失败")
api.HandleGrpcErrorToHttp(err, ctx)
return
}
//省略。。。
}
5 - 优化otgrpc的源码
- goods_web/utils/otgrpc/client.go:源码优化 -> 从ginContext中拿到tracer和parentSpan
func OpenTracingClientInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.UnaryClientInterceptor {
otgrpcOpts := newOptions()
otgrpcOpts.apply(optFuncs...)
return func(
ctx context.Context,
method string,
req, resp interface{},
cc *grpc.ClientConn,
invoker grpc.UnaryInvoker,
opts ...grpc.CallOption,
) error {
var err error
var parentCtx opentracing.SpanContext
if parent := opentracing.SpanFromContext(ctx); parent != nil {
parentCtx = parent.Context()
}
ginContext := ctx.Value("ginContext")
switch ginContext.(type) {
case *gin.Context:
if itracer, ok := ginContext.(*gin.Context).Get("tracer"); ok {
tracer = itracer.(opentracing.Tracer)
}
if parentSpan, ok := ginContext.(*gin.Context).Get("parentSpan"); ok {
parentCtx = parentSpan.(*jaegerClient.Span).Context()
}
}
//省略。。。
四、order_web集成jaeger
1 - 复制tracing和otgrpc到order_web中
2 - router修改
- order_web/router/router_order.go
package router
import (
"github.com/gin-gonic/gin"
"web_api/order_web/api/order"
"web_api/order_web/api/pay"
"web_api/order_web/middlewares"
)
func InitOrderRouter(Router *gin.RouterGroup) {
OrderRouter := Router.Group("orders").Use(middlewares.SetUserId()).Use(middlewares.Trace())
{
OrderRouter.GET("", order.List) // 订单列表
//BannerRouter.GET("", middlewares.JWTAuth(), middlewares.IsAdminAuth(), order.List) // 订单列表
OrderRouter.POST("", order.New) // 新建订单
OrderRouter.GET("/:id/", order.Detail) // 订单详情
}
PayRouter := Router.Group("pay")
{
PayRouter.POST("alipay/notify", pay.Notify)
}
}
3 - grpc调用接口修改
- order_web/api/order/api_order.go
func New(ctx *gin.Context) {
orderForm := forms.CreateOrderForm{}
if err := ctx.ShouldBindJSON(&orderForm); err != nil {
api.HandleValidatorError(ctx, err)
}
userId, _ := ctx.Get("userId")
rsp, err := global.OrderSrvClient.CreateOrder(context.WithValue(context.Background(), "ginContext", ctx), &proto.OrderRequest{
UserId: int32(userId.(uint)),
Name: orderForm.Name,
Mobile: orderForm.Mobile,
Address: orderForm.Address,
Post: orderForm.Post,
})
//省略
4 - 初始化连接注入Tracer
- order_web/initialize/init_srv_conn.go
package initialize
import (
"fmt"
_ "github.com/mbobakov/grpc-consul-resolver" // It's important
"github.com/opentracing/opentracing-go"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"web_api/order_web/global"
"web_api/order_web/proto"
"web_api/order_web/utils/otgrpc"
)
func InitSrvConn() {
consulInfo := global.ServerConfig.ConsulInfo
goodsConn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.GoodsSrvInfo.Name),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
)
if err != nil {
zap.S().Fatal("[InitSrvConn] 连接 【商品服务失败】")
}
global.GoodsSrvClient = proto.NewGoodsClient(goodsConn)
orderConn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.OrderSrvInfo.Name),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
)
if err != nil {
zap.S().Fatal("[InitSrvConn] 连接 【订单服务失败】")
}
global.OrderSrvClient = proto.NewOrderClient(orderConn)
inventoryConn, err := grpc.Dial(
fmt.Sprintf("consul://%s:%d/%s?wait=14s", consulInfo.Host, consulInfo.Port, global.ServerConfig.InventorySrvInfo.Name),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(opentracing.GlobalTracer())),
)
if err != nil {
zap.S().Fatal("[InitSrvConn] 连接 【库存服务失败】")
}
global.InventorySrvClient = proto.NewInventoryClient(inventoryConn)
}
5 - 添加nacos配置
- order_web/config/config.go
type JaegerConfig struct {
Host string `mapstructure:"host" json:"host"`
Port int `mapstructure:"port" json:"port"`
Name string `mapstructure:"name" json:"name"`
}
type ServerConfig struct {
Name string `mapstructure:"name" json:"name"`
Host string `mapstructure:"host" json:"host"`
Tags []string `mapstructure:"tags" json:"tags"`
Port int `mapstructure:"port" json:"port"`
GoodsSrvInfo SrvConfig `mapstructure:"goods_srv" json:"goods_srv"`
OrderSrvInfo SrvConfig `mapstructure:"order_srv" json:"order_srv"`
InventorySrvInfo SrvConfig `mapstructure:"inventory_srv" json:"inventory_srv"`
JWTInfo JWTConfig `mapstructure:"jwt" json:"jwt"`
ConsulInfo ConsulConfig `mapstructure:"consul" json:"consul"`
AliPayInfo AlipayConfig `mapstructure:"alipay" json:"alipay"`
JaegerInfo JaegerConfig `mapstructure:"consul" json:"jaeger"`
}
- nacos配置
{
"host": "192.168.124.9",
"name": "order_web",
"port": 8083,
"tags": ["mxshop","imooc","bobby","order","web"],
"goods_srv": {
"name": "goods_srv"
},
"order_srv": {
"name": "order_srv"
},
"inventory_srv": {
"name": "inventory_srv"
},
"jwt": {
"key": "VYLDYq3&hGWjWqF$K1ih"
},
"consul": {
"host": "192.168.124.51",
"port": 8500
},
"jaeger": {
"host": "192.168.124.51",
"port": 6381,
"name": "ja_order_api"
},
"alipay":{
"app_id":"2021000121645456",
"private_key":"MIIEowIBAAKCA",
"ali_public_key":"MIIBIjANBgkq",
"notify_url":"http://xxx",
"return_url":"http://127.0.0.1:8089"
}
}
五、同步srv层和web层的jaeger
1 - otgrpc原理分析
- order_web/utils/otgrpc/client.go
2 - 初始化jaeger
- order_srv/main.go:主要修改grpc.NewServer
package main
import (
"flag"
"fmt"
"github.com/apache/rocketmq-client-go/v2"
"github.com/apache/rocketmq-client-go/v2/consumer"
"github.com/opentracing/opentracing-go"
"nd/order_srv/handler"
"nd/order_srv/utils/otgrpc"
"net"
"os"
"os/signal"
"syscall"
"github.com/satori/go.uuid"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"github.com/uber/jaeger-client-go"
jaegercfg "github.com/uber/jaeger-client-go/config"
"nd/order_srv/global"
"nd/order_srv/initialize"
"nd/order_srv/proto"
"nd/order_srv/utils"
"nd/order_srv/utils/register/consul"
)
func main() {
IP := flag.String("ip", "0.0.0.0", "ip地址")
Port := flag.Int("port", 50060, "端口号") // 这个修改为0,如果我们从命令行带参数启动的话就不会为0
//初始化
initialize.InitLogger()
initialize.InitConfig()
initialize.InitDB()
initialize.InitSrvConn()
zap.S().Info(global.ServerConfig)
flag.Parse()
zap.S().Info("ip: ", *IP)
if *Port == 0 {
*Port, _ = utils.GetFreePort()
}
zap.S().Info("port: ", *Port)
//初始化jaeger
cfg := jaegercfg.Configuration{
Sampler: &jaegercfg.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &jaegercfg.ReporterConfig{
LogSpans: true,
LocalAgentHostPort: "192.168.124.51:6831",
},
ServiceName: "mxshop",
}
tracer, closer, err := cfg.NewTracer(jaegercfg.Logger(jaeger.StdLogger))
if err != nil {
panic(err)
}
opentracing.SetGlobalTracer(tracer)
server := grpc.NewServer(grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(tracer)))
proto.RegisterOrderServer(server, &handler.OrderServer{})
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", *IP, *Port))
if err != nil {
panic("failed to listen:" + err.Error())
}
//注册服务健康检查
grpc_health_v1.RegisterHealthServer(server, health.NewServer())
//服务注册
register_client := consul.NewRegistryClient(global.ServerConfig.ConsulInfo.Host, global.ServerConfig.ConsulInfo.Port)
serviceId := fmt.Sprintf("%s", uuid.NewV4())
err = register_client.Register(global.ServerConfig.Host, *Port, global.ServerConfig.Name, global.ServerConfig.Tags, serviceId)
if err != nil {
zap.S().Panic("服务注册失败:", err.Error())
}
zap.S().Debugf("启动服务器, 端口: %d", *Port)
go func() {
err = server.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}()
//监听订单超时topic
c, _ := rocketmq.NewPushConsumer(
consumer.WithNameServer([]string{"192.168.124.51:9876"}),
consumer.WithGroupName("mxshop-order"),
)
if err := c.Subscribe("order_timeout", consumer.MessageSelector{}, handler.OrderTimeout); err != nil {
fmt.Println("读取消息失败")
}
_ = c.Start()
//不能让主goroutine退出
//接收终止信号
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
_ = c.Shutdown()
_ = closer.Close()
if err = register_client.DeRegister(serviceId); err != nil {
zap.S().Info("注销失败:", err.Error())
} else {
zap.S().Info("注销成功:")
}
}
3 - 增加追踪的span
- order_srv/handler/handle_order.go:增加追踪的span
- CreateOrder中初始化context
func (*OrderServer) CreateOrder(ctx context.Context, req *proto.OrderRequest) (*proto.OrderInfoResponse, error) {
/*
新建订单
1. 从购物车中获取到选中的商品
2. 商品的价格自己查询 - 访问商品服务 (跨微服务)
3. 库存的扣减 - 访问库存服务 (跨微服务)
4. 订单的基本信息表 - 订单的商品信息表
5. 从购物车中删除已购买的记录
*/
orderListener := OrderListener{Ctx: ctx}
p, err := rocketmq.NewTransactionProducer(
&orderListener,
producer.WithNameServer([]string{"192.168.124.51:9876"}),
)
//省略。。。
- 在需要追踪的业务地方增加span
- 进入后从context先获取父span:
parentSpan := opentracing.SpanFromContext(o.Ctx)
- 追踪开始:
shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcart", opentracing.ChildOf(parentSpan.Context()))
- 追踪结束:
shopCartSpan.Finish()
func (o *OrderListener) ExecuteLocalTransaction(msg *primitive.Message) primitive.LocalTransactionState {
parentSpan := opentracing.SpanFromContext(o.Ctx)
var orderInfo model.OrderInfo
_ = json.Unmarshal(msg.Body, &orderInfo)
var goodsIds []int32
var shopCarts []model.ShoppingCart
goodsNumsMap := make(map[int32]int32)
shopCartSpan := opentracing.GlobalTracer().StartSpan("select_shopcart", opentracing.ChildOf(parentSpan.Context()))
if result := global.DB.Where(&model.ShoppingCart{User: orderInfo.User, Checked: true}).Find(&shopCarts); result.RowsAffected == 0 {
o.Code = codes.InvalidArgument
o.Detail = "没有选中结算的商品"
return primitive.RollbackMessageState
}
shopCartSpan.Finish()
4 - 过滤掉健康检查的jaeger
- order_srv/utils/otgrpc/server.go
func OpenTracingServerInterceptor(tracer opentracing.Tracer, optFuncs ...Option) grpc.UnaryServerInterceptor {
otgrpcOpts := newOptions()
otgrpcOpts.apply(optFuncs...)
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
if info.FullMethod != "/grpc.health.v1.Health/Check" {
spanContext, err := extractSpanContext(ctx, tracer)
//省略