【架构实战】可观测性体系:从监控到全链路追踪
【架构实战】可观测性体系从监控到全链路追踪字数统计约3700字一、真实故事引入从系统正常到用户投诉的谜题2024年夏天我们的电商系统上线了下单自动发券的新功能上线当天就有用户反馈“下单要转10多秒才能成功有时候还失败。” 我们第一时间查看监控大盘订单服务的CPU使用率不到30%内存充足QPS只有200多完全在正常范围内错误率也是0。所有监控指标都显示系统正常但用户就是觉得慢。那时候我们的可观测性体系只有监控指标没有全链路追踪就像医生只给病人量体温体温正常就认为没病却不知道病人其实胃疼。后来我们紧急引入了OpenTelemetryJaeger的全链路追踪体系终于找到了问题根源用户下单时订单服务会调用优惠券服务查询可用券优惠券服务又要查询数据库而数据库的商品ID字段没有加索引每次查询要2秒多一个下单流程要调用3次优惠券接口光这部分就花了6秒加上其他链路总耗时超过10秒。给数据库加上索引后单次查询降到50毫秒以内下单总耗时降到了1.5秒以内用户投诉立刻消失了。这次经历让我深刻意识到监控只是可观测性的冰山一角只有指标、日志、追踪三者结合才能真正理解系统的运行状态。二、概念原理可观测性到底是什么2.1 可观测性的定义可观测性Observability是指通过观察系统的外部输出 telemetry 数据来理解系统内部状态的能力。和监控不同监控关注系统是不是正常是被动的预先定义好要监控的指标超标就告警可观测性关注系统为什么不正常是主动的可以通过任意维度的数据查询定位问题的根因2.2 可观测性三大支柱指标Metrics时序数值数据比如CPU使用率、QPS、接口耗时、错误率等适合监控系统的整体状态和趋势日志Logs结构化的文本记录比如请求日志、错误堆栈、业务日志等适合排查具体问题追踪Traces记录一个请求从入口到出口的完整调用链路包括调用的所有服务、数据库、缓存、消息队列等适合定位分布式系统中的性能瓶颈三者是互补关系不能互相替代指标告诉你有问题日志告诉你哪里报错了追踪告诉你请求经过了哪些环节哪里慢了。2.3 全链路追踪核心原理全链路追踪的核心是Trace追踪和Span跨度Trace一个完整的请求链路用一个全局唯一的traceId标识Span链路中的一个操作单元比如一次服务调用、一次数据库查询、一次缓存读取每个Span有唯一的spanId并且有父Span的parentSpanId形成调用树上下文传播每次服务调用时把traceId和spanId传递到下游服务保证整个链路可以串联起来主流的全链路追踪标准有OpenTracing和OpenTelemetry现在OpenTelemetry已经成为CNCF的孵化项目是未来的主流标准。三、配置代码从零搭建可观测性体系我们的技术栈是Spring Boot Spring Cloud微服务K8s部署可观测性体系组件选型指标Micrometer Prometheus Grafana日志Logback Filebeat Elasticsearch Kibana追踪OpenTelemetry Jaeger3.1 指标采集配置Spring Boot Micrometer Prometheus1. 添加依赖pom.xmldependencies!-- Micrometer核心 --dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-core/artifactId/dependency!-- Prometheus exporter --dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-registry-prometheus/artifactId/dependency/dependencies2. 配置暴露Prometheus端点application.ymlmanagement:endpoints:web:exposure:include:prometheus,health,info,metricsendpoint:prometheus:enabled:truemetrics:export:prometheus:enabled:truetags:application:${spring.application.name}# 给所有指标加应用名标签3. Prometheus配置prometheus.ymlglobal:scrape_interval:15sscrape_configs:-job_name:spring-boot-appsmetrics_path:/actuator/prometheusstatic_configs:-targets:[coupon-service:8080,order-service:8080]# 微服务地址relabel_configs:-source_labels:[__address__]target_label:instance3.2 日志配置Logback traceId注入1. 添加依赖pom.xmldependencygroupIdnet.logstash.logback/groupIdartifactIdlogstash-logback-encoder/artifactIdversion7.4/version/dependency2. Logback配置logback-spring.xml输出JSON格式日志包含traceId和spanIdconfigurationappendernamejsonclassch.qos.logback.core.ConsoleAppenderencoderclassnet.logstash.logback.encoder.LogstashEncoder!-- 注入OpenTelemetry的traceId和spanId到日志 --includeMdcKeyNametraceId/includeMdcKeyNameincludeMdcKeyNamespanId/includeMdcKeyNameincludeMdcKeyNamespanId/includeMdcKeyNamecustomFields{application:coupon-service,env:prod}/customFields/encoder/appenderrootlevelinfoappender-refrefjson//root/configuration3.3 全链路追踪配置OpenTelemetry Jaeger1. 添加OpenTelemetry依赖pom.xmldependencies!-- OpenTelemetry Spring Boot Starter --dependencygroupIdio.opentelemetry.instrumentation/groupIdartifactIdopentelemetry-spring-boot-starter/artifactIdversion1.28.0/version/dependency!-- Jaeger Exporter --dependencygroupIdio.opentelemetry/groupIdartifactIdopentelemetry-exporter-jaeger/artifactIdversion1.28.0/version/dependency/dependencies2. OpenTelemetry配置application.ymlotel:traces:exporter:jaegerexporter:jaeger:endpoint:http://jaeger-collector:14250# Jaeger Collector地址resource:attributes:service.name:coupon-servicedeployment.environment:prodjavaagent:enabled:true# 启用javaagent自动埋点3. 部署JaegerDocker方式dockerrun-d--namejaeger\-eCOLLECTOR_ZIPKIN_HOST_PORT:9411\-eCOLLECTOR_OTLP_ENABLEDtrue\-p16686:16686\# UI端口-p4317:4317\# OTLP gRPC端口-p4318:4318\# OTLP HTTP端口-p9411:9411\# Zipkin端口jaegertracing/all-in-one:1.473.4 Filebeat配置日志采集到Elasticsearchfilebeat.inputs:-type:containerpaths:-/var/log/containers/*.logprocessors:-add_kubernetes_metadata:host:${NODE_NAME}matchers:-logs_path:logs_path:/var/log/containers/output.elasticsearch:hosts:[elasticsearch:9200]username:elasticpassword:changeme四、实战案例定位下单慢的完整过程当用户反馈下单慢时我们的排查流程如下4.1 第一步看指标大盘Grafana打开订单服务的Grafana仪表盘发现QPS200正常平均响应时间8秒远高于正常的500毫秒错误率0%没有报错指标只能告诉我们订单服务响应慢但不知道慢在哪里。4.2 第二步查全链路追踪Jaeger在Jaeger UI中搜索serviceorder-service找到耗时最长的trace发现订单服务的/api/order/create接口总耗时8.2秒其中调用coupon-service的/api/coupon/query接口花了6秒占总耗时的73%优惠券服务的这个接口又调用了数据库的select * from coupons where product_id?单次查询花了2秒4.3 第三步查日志Kibana在Kibana中搜索这个trace的traceId找到优惠券服务的日志发现查询语句确实是select * from coupons where product_id?且product_id字段没有索引。4.4 第四步解决问题给优惠券表的product_id字段加索引ALTERTABLEcouponsADDINDEXidx_product_id(product_id);重新部署后数据库查询降到50毫秒下单总耗时降到1.2秒问题解决。整个排查过程从原来的平均2小时缩短到15分钟这就是可观测性体系的威力。五、踩坑实录可观测性落地过程中的那些坑5.1 追踪数据量太大存储成本爆炸现象上线全链路追踪一周后Jaeger的存储用的是Elasticsearch占了500GB成本太高原因默认是100%采样所有的请求都生成trace数据量太大解决调整采样率只采样10%的普通请求100%采样错误请求和高延迟请求1秒otel:traces:sampler:parentbased_traceidratiosampler.arg:0.1# 10%采样同时配置Jaeger的过期策略只保留7天的追踪数据。5.2 跨线程调用丢失traceId现象用Async异步执行的逻辑在日志和追踪中找不到traceId原因Spring的异步线程没有传递MDC上下文OpenTelemetry的上下文也丢失了解决配置TaskDecorator传递上下文ConfigurationpublicclassAsyncConfig{BeanpublicTaskExecutortaskExecutor(){ThreadPoolTaskExecutorexecutornewThreadPoolTaskExecutor();executor.setTaskDecorator(newContextCopyingTaskDecorator());executor.initialize();returnexecutor;}}或者用OpenTelemetry的Context.current()手动传递上下文。5.3 日志和追踪上下文不关联现象日志中有traceId但Jaeger中搜不到对应的trace原因日志中的traceId是十六进制而Jaeger中的traceId是十进制格式不一致解决统一traceId的格式配置OpenTelemetry输出十六进制的traceId日志中也用同样的格式或者在日志中同时输出十进制和十六进制的traceId。5.4 指标维度太多Prometheus查询慢现象Prometheus的查询经常超时比如查http_server_requests_seconds_count要10多秒原因指标中加入了太多高基数标签比如user_id、order_id导致时间序列爆炸解决清理不必要的标签只保留低基数的标签如service、method、status_code高基数的标签放到日志中。5.5 OpenTelemetry和Jaeger版本不兼容现象追踪数据上报时报Unsupported version错误原因OpenTelemetry 1.28.0用的是Jaeger的Thrift协议而Jaeger 1.35.0默认用的是gRPC协议解决升级Jaeger到1.47.0或者配置OpenTelemetry用Jaeger的gRPC exporterotel:exporter:jaeger:endpoint:http://jaeger-collector:4317# gRPC端口protocol:grpc六、总结与思考6.1 核心总结可观测性体系的价值不是锦上添花而是分布式系统的眼睛指标告诉我们系统有没有病日志告诉我们系统哪里不舒服追踪告诉我们系统病因在哪里从监控到可观测性是从被动告警到主动诊断的升级对于微服务架构来说是必备能力。6.2 思考题如何平衡可观测性的成本和收益比如小团队要不要上全链路追踪如果系统中的请求量非常大比如10万QPS如何优化可观测性体系的性能可观测性数据和业务数据如何结合比如通过traceId关联订单号和用户ID快速定位某个用户的问题。6.3 个人观点很多团队觉得可观测性体系太复杂“成本太高”但我想说的是没有可观测性的微服务就像在黑盒子里跑出了问题只能靠猜。落地可观测性不需要一步到位可以分阶段第一阶段先上监控指标保证知道系统挂没挂第二阶段上结构化日志保证能查到错误堆栈第三阶段上全链路追踪保证能定位性能瓶颈另外不要盲目追求全量采集根据实际业务需求调整采样率避免不必要的成本浪费。对于核心业务比如支付、下单可以用全采样对于非核心业务比如推荐、评论可以用低采样。最后可观测性不是某个人的工作而是整个团队的工作开发人员要打日志、传上下文运维人员要维护可观测性组件测试人员要验证可观测性是否生效。只有全员参与才能构建真正有效的可观测性体系。