一、为什么要关注Jaeger的依赖分析
1.1 云原生时代的“迷雾”
现在我们做应用,很少是单服务了,都是拆成一堆小服务,比如用户服务、商品服务、订单服务、支付服务,用户点一下“下单”按钮,背后要调三四个服务,甚至更多。一旦下单慢了,你不知道是哪个环节拖了后腿——是用户服务返回慢,还是库存服务没响应,或者是支付服务卡了?就像在迷宫里找出口,全是岔路,太费劲了。 很多人用Jaeger是为了查单次请求的链路,比如看某一次下单请求的每个环节花了多久,但如果是系统级的性能问题,比如某个时段所有下单都变慢,单次查太耗时间,这时候Jaeger的依赖分析就派上用场了。
1.2 Jaeger依赖分析不是“附加功能”
很多人不知道,Jaeger不仅能查单链路,还能自动收集所有服务之间的调用关系,生成一张可视化的依赖图——哪个服务调用了哪个服务,调用了多少次,每次平均花多久,有没有错误。这张图就是解决“迷雾”的钥匙,不用你一个个去翻请求链路,就能看到整个系统的服务调用脉络。
二、Jaeger依赖分析怎么用(附完整示例)
2.1 先搭个能生成依赖的极简环境
我们用Go语言做两个简单的服务,模拟下单流程:服务A是“入口服务”,接收用户下单请求,调用服务B“库存校验服务”,再把结果返回给Jaeger。所有上报逻辑用Jaeger官方的Go SDK,确保依赖数据能被正确收集。 技术栈:Go 1.20 + Jaeger 1.45 + Jaeger Go SDK 首先是服务A的代码:
package main
import (
"io"
"log"
"net/http"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func initJaeger(serviceName string) (opentracing.Tracer, io.Closer) {
// Jaeger客户端基础配置,采样率设为1,确保所有请求都上报
cfg := config.Configuration{
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LocalAgentHostPort: "localhost:6831", // Jaeger Agent地址
},
}
tracer, closer, err := cfg.New(serviceName, config.Logger(jaeger.StdLogger))
if err != nil {
log.Fatalf("初始化Jaeger失败: %v", err)
}
opentracing.SetGlobalTracer(tracer)
return tracer, closer
}
func handler(w http.ResponseWriter, r *http.Request) {
// 开始一个新的Span,代表一次下单请求
span, ctx := opentracing.StartSpanFromContext(r.Context(), "serviceA.handle_order")
defer span.Finish()
// 调用服务B,校验库存
req, _ := http.NewRequest("GET", "http://localhost:8081/check_stock", nil)
// 把Span的上下文传到服务B,确保链路关联
opentracing.GlobalTracer().Inject(span.Context(), opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(req.Header))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
http.Error(w, "下单失败", http.StatusInternalServerError)
span.SetTag("error", true)
return
}
defer resp.Body.Close()
w.Write([]byte("下单成功"))
}
func main() {
_, closer := initJaeger("serviceA")
defer closer.Close()
http.HandleFunc("/order", handler)
log.Println("服务A启动,端口8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
然后是服务B的代码:
package main
import (
"io"
"log"
"net/http"
"time"
"github.com/opentracing/opentracing-go"
"github.com/uber/jaeger-client-go"
"github.com/uber/jaeger-client-go/config"
)
func initJaeger(serviceName string) (opentracing.Tracer, io.Closer) {
cfg := config.Configuration{
Sampler: &config.SamplerConfig{
Type: jaeger.SamplerTypeConst,
Param: 1,
},
Reporter: &config.ReporterConfig{
LocalAgentHostPort: "localhost:6831",
},
}
tracer, closer, err := cfg.New(serviceName, config.Logger(jaeger.StdLogger))
if err != nil {
log.Fatalf("初始化Jaeger失败: %v", err)
}
opentracing.SetGlobalTracer(tracer)
return tracer, closer
}
func checkStockHandler(w http.ResponseWriter, r *http.Request) {
// 从请求头中提取Span上下文,关联链路
spanCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header))
span := opentracing.StartSpan("serviceB.check_stock", opentracing.ChildOf(spanCtx))
defer span.Finish()
// 模拟库存校验的延迟,正常为50ms,调整值可模拟异常
time.Sleep(50 * time.Millisecond)
w.Write([]byte("库存正常"))
}
func main() {
_, closer := initJaeger("serviceB")
defer closer.Close()
http.HandleFunc("/check_stock", checkStockHandler)
log.Println("服务B启动,端口8081")
log.Fatal(http.ListenAndServe(":8081", nil))
}
把这两个服务跑起来,再启动Jaeger All-in-one容器(命令:docker run -d -p 6831:6831/udp -p 16686:16686 jaegertracing/all-in-one:latest),然后用curl访问服务A的/order接口,多次请求后,打开Jaeger UI的“依赖”页,就能看到服务A和服务B的调用关系了。
2.2 依赖分析的核心看点
在Jaeger的依赖页,你能看到三个关键信息:第一,服务之间的连接关系,比如箭头从服务A指向服务B,说明A调用B;第二,每条连接上的统计数据,比如调用次数、平均延迟、错误率;第三,有没有异常的连接,比如某条连接的延迟突然变大,或者错误数变多。 比如刚才的示例里,服务A和服务B的连接显示平均延迟是50ms,调用次数是10,这是正常情况。如果我们把服务B里的延迟改成500ms,再请求,就能看到这条连接的平均延迟变成了500ms,不用查单链路,一眼就能找到问题出在A到B的依赖上。
三、Jaeger依赖分析的典型应用场景
3.1 微服务集群的全局性能定位
当整个系统的吞吐量下降,或者某个功能响应变慢时,你不用在几十甚至上百个服务里挨个查,直接看Jaeger的依赖图,找那些延迟比平均值高很多的服务,或者调用次数激增的连接。比如某天突然有很多用户下单,依赖图里服务A到服务B的调用次数从平时的每分钟100次涨到了每分钟1000次,就能知道是流量突增,不是单个服务的问题;如果是某条连接的延迟突然翻倍,那就是这个依赖的服务出了问题。
3.2 架构重构前的依赖梳理
如果要把某个服务拆成多个小服务,或者合并几个服务,先看依赖分析图,知道这个服务调用了哪些其他服务,被哪些服务调用,这样才能预估重构的工作量,避免拆完后出现依赖断裂的问题。比如服务B调用了服务C和服务D,服务A和服务E都调用了服务B,那重构服务B的时候,要同时考虑上游和下游的调用情况。
3.3 故障根因快速排查
线上出了故障,比如支付服务报错,看依赖图,支付服务的上游是订单服务,下游是银行接口,只要看支付服务的连接,就能知道是订单服务传的参数有问题,还是银行接口超时,不用去翻所有的链路,节省排查时间。
四、Jaeger依赖分析的技术优缺点
4.1 优点
首先是可视化,把复杂的服务调用关系变成直观的图,不用手动整理;然后是和Jaeger的链路追踪无缝结合,从依赖图里点某个服务,就能直接跳转到该服务的所有链路,深入排查;还有是自动收集,只要埋点正确,不用额外写代码生成依赖数据,省了很多功夫;另外支持统计,能看到调用频率、延迟、错误率等指标,帮助做决策。
4.2 缺点
第一个是依赖埋点的完整性,如果某个服务没做Jaeger埋点,那这个服务的依赖就不会被收集,依赖图就不完整;第二个是对非HTTP的调用,比如RPC或者gRPC,需要额外的埋点配置,不然可能收集不到;第三个是数据量的问题,当服务很多、调用很频繁时,Jaeger存储的依赖数据会变多,需要定期清理,不然影响查询性能。
五、Jaeger依赖分析的注意事项
5.1 埋点要覆盖所有核心服务的入口和出口
要想依赖分析准确,每个服务的入口(接收请求的地方)和出口(调用其他服务的地方)都要埋Jaeger的Span,不然Jaeger不知道这个服务调用了谁,或者谁调用了它。比如刚才的示例里,服务A的handler里要埋调用服务B的Span,服务B的handler里要埋收到A的请求的Span,这样两个服务的依赖才会被记录。
5.2 区分正常流量和异常流量的波动
有时候依赖的延迟变高,可能是正常的流量突增,比如大促的时候,调用次数变多,延迟也会高,但这不是服务本身的问题,只是资源不够;如果是平时非高峰时段,延迟突然变高,那才是真正的性能问题,要重点排查。
5.3 定期校验依赖图的准确性
随着服务的迭代,新的服务上线,旧的服务下线,或者服务之间的调用关系变了,Jaeger的依赖图不会自动更新,所以要定期去核对,删掉已经不存在的服务,或者调整新的依赖关系,保证依赖图的准确性。
六、总结
Jaeger的依赖分析不是一个小众功能,而是微服务架构下性能瓶颈定位的利器。它把复杂的服务调用关系变成直观的可视化信息,帮我们快速找到性能问题的根源,不用在海量的请求链路里盲目查找。不管是日常的性能优化,还是故障排查,或者是架构重构,依赖分析都能帮我们节省大量的时间和精力,对于云原生应用来说,用好Jaeger的依赖分析,相当于给系统装了一个“全局体检仪”,能提前发现潜在的问题,也能快速定位已有的故障。
评论
围绕“Jaeger服务依赖分析功能深度拆解助力云原生应用架构性能瓶颈定位”参与讨论