- 作者:老汪软件技巧
- 发表时间: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。
查看追踪数据
处理请求时记录的所有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的一个绝佳选择