• 作者:老汪软件技巧
  • 发表时间:2024-09-18 10:03
  • 浏览量:

在微服务架构中,一个请求往往会涉及多个服务,服务调用链路错综复杂。为了排查和定位问题,我们需要一种有效的链路追踪方案。Jaeger是Uber开源的一款功能强大的分布式追踪系统,与GoFrame无缝集成,可以帮助我们实现端到端的请求链路追踪和性能分析。

安装Jaeger

首先,需要安装Jaeger的所有组件,包括jaeger-client、jaeger-agent、jaeger-collector和jaeger-query。可以使用Docker快速部署Jaeger:

docker run -d --name jaeger \
  -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 14250:14250 \
  -p 9411:9411 \
  jaegertracing/all-in-one:1.21

这会启动Jaeger的所有组件,并暴露必要的端口。

配置并初始化Jaeger Tracer

在GoFrame项目中引入Jaeger的Go客户端库:

go get github.com/uber/jaeger-client-go

然后在项目启动时配置并初始化Jaeger Tracer:

package main
import (
    "github.com/opentracing/opentracing-go"
    "github.com/uber/jaeger-client-go"
    "github.com/uber/jaeger-client-go/config"
)
func main() {
    // 从环境变量中解析Jaeger配置
    cfg, _ := config.FromEnv()
    // 初始化Tracer
    tracer, closer, _ := cfg.NewTracer(config.Logger(jaeger.StdLogger))
    defer closer.Close()
    // 将Tracer设置为全局
    opentracing.SetGlobalTracer(tracer)
}

这里从环境变量加载Jaeger配置,创建了Tracer实例,并将其设为全局Tracer。

记录Span

现在Tracer已经配置完毕,我们可以在请求处理逻辑中添加Span来记录关键的链路节点。

例如,在中间件中:

// 定义一个链路追踪的中间件
func TracingMiddleware(r *ghttp.Request) {
    // 从请求中提取父Span的上下文
    spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Request.Header))
    // 开始一个新的Span
    span := opentracing.GlobalTracer().StartSpan(r.URL.Path, opentracing.ChildOf(spanCtx))
    defer span.Finish()
    // 将新的Span上下文注入请求头传递
    opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Request.Header))
    // 将Span绑定到请求上下文
    r.SetCtx(opentracing.ContextWithSpan(r.Context(), span))
    // 继续执行下一步请求处理逻辑
    r.Middleware.Next()
}

这个中间件从请求中提取父Span的上下文,创建一个新的子Span,并将其注入请求头,然后绑定到请求的上下文。这样就在请求处理流程中建立了一个完整的Span层次和上下文传递。

我们还可以在具体的业务逻辑中记录Span:

// 一个简单的业务处理函数
func bizHandler(r *ghttp.Request) {
    // 从请求上下文获取Span
    span := opentracing.SpanFromContext(r.Context())
    // 一些处理逻辑...
    // 记录事件
    span.LogKV("event", "some event")
    // 记录标签
    span.SetTag("key", "value")
}

这里从请求上下文提取出Span,然后就可以用它来记录事件和标签,这些数据会附加在Span上报给Jaeger。

链路追踪sleuth__分布式链路追踪工具

查看追踪数据

处理请求时记录的所有Span数据都会发送给Jaeger收集器,我们可以在Jaeger UI方便地查询和展示这些链路信息。访问:16686就可以打开Jaeger UI![Jaeger UI][]

在搜索页面选择对应的服务和时间范围,就可以看到这段时间内所有的Trace。点进一个Trace,可以看到它的完整链路拓扑、每个Span的耗时和详细信息。

通过分析这些详尽的链路信息,我们就可以考察服务性能,发现瓶颈和异常,实现更好的监控和问题定位,保障服务质量。

处理错误

首先,我们可以定义一些自定义的错误类型,用来表示不同的错误情况。例如:

// 定义一个通用的错误类型
type MyError struct {
    Code    int
    Message string
}
func (e *MyError) Error() string {
    return fmt.Sprintf("error code: %d, message: %s", e.Code, e.Message)
}
// 定义一些具体的错误类型
var (
    ErrInvalidParam = &MyError{Code: 400, Message: "invalid parameter"}
    ErrInternal     = &MyError{Code: 500, Message: "internal error"}
    // ...
)

这里定义了一个通用的MyError类型,包含错误码和错误信息,同时实现了error接口。然后定义了一些具体的错误变量,表示不同的错误情况。

在处理请求的过程中,当出现错误时,我们可以将错误记录到对应的Span中。Jaeger提供了span.SetTag()方法来记录标签,我们可以用它来记录错误信息:

func bizHandler(r *ghttp.Request) {
    span := opentracing.SpanFromContext(r.Context())
    
    // 一些处理逻辑...
    
    // 模拟一个错误
    err := someFunc()
    if err != nil {
        // 记录错误标签
        span.SetTag("error", true)
        span.SetTag("error.code", err.(*MyError).Code)
        span.SetTag("error.message", err.(*MyError).Message)
        
        // 记录异常事件
        span.LogKV("event", "error", "error.kind", reflect.TypeOf(err).String(), "message", err.Error(), "stack", string(debug.Stack()))
    }
}

当发生错误时,我们用span.SetTag()记录布尔类型的error标签,表示有错误发生,同时记录error.code和error.message标签,传递错误的具体信息。

此外,我们还可以用span.LogKV()记录一个更详细的异常事件,包括错误类型、错误信息、调用堆栈等,方便事后排查问题。注意这里使用了debug.Stack()获取当前调用堆栈。

记录错误后,我们还需要返回适当的错误响应。可以利用GoFrame的错误处理中间件来统一处理错误:

func ErrorMiddleware(r *ghttp.Request) {
    r.Middleware.Next()
    
    // 判断是否有错误
    if err := r.GetError(); err != nil {
        // 提取错误信息
        code := gerror.Code(err).Code()
        message := err.Error()
        
        // 设置错误响应
        r.Response.WriteStatus(code)
        r.Response.WriteJson(g.Map{
            "code":    code,
            "message": message,
        })
    }
}

这个错误处理中间件在请求处理完毕后,判断请求上下文中是否存在错误,如果有错误就提取错误码和错误信息,返回对应的JSON错误响应。

这里利用了GoFrame的gerror包来提取错误码。如果err是我们定义的MyError类型,gerror.Code()就能识别出其中的Code字段。如果是其他类型的错误,则会返回默认的500错误码。

最后,我们将Tracing中间件和Error中间件组合起来,实现完整的请求处理链路。这样在请求处理过程中,会先经过Tracing中间件,创建Span并记录链路信息。当请求处理完成后,再经过Error中间件,处理可能发生的错误并返回响应。

现在,当请求处理中发生错误时,错误信息会被记录到Span中,并通过Jaeger上报展示。我们可以在Jaeger UI方便地查看和搜索这些错误。

总结

以上就是在GoFrame项目中集成使用Jaeger链路追踪的主要步骤:首先部署Jaeger、然后配置Tracer、通过Middleware或在业务中记录Span,最后在Jaeger UI分析追踪数据。Jaeger提供了开箱即用的分布式追踪能力,GoFrame又使集成和使用变得非常简单,二者结合可以让我们以很低的成本实现强大的微服务链路跟踪。在微服务开发和治理中,链路跟踪已经不可或缺,Jaeger + GoFrame无疑是Gopher的一个绝佳选择


上一条查看详情 +外部安全
下一条 查看详情 +没有了