Tracing 与 Metrics:可观测性最佳实践,让你的代码“开口说话”
各位程序猿/媛们,大家好!今天我们要聊聊一个高大上,但又无比接地气的话题:可观测性。 别听到“可观测性”这三个字就觉得头大,好像是运维大佬们才会关心的领域。 错!大错特错! 可观测性,其实就像给你的代码装上摄像头和麦克风,让它在你debug的时候,不再沉默是金,而是“开口说话”,告诉你它到底在干什么,遇到了什么问题。
想象一下,你的线上系统突然开始抽风,CPU 飙升,响应时间慢如蜗牛。 你焦头烂额,一遍遍地看日志,试图找出蛛丝马迹。 然而,日志就像大海捞针,信息碎片化,关联性差,你只能靠猜,靠蒙,靠玄学。
这时候,如果你拥有良好的可观测性,一切都会变得简单起来。 你可以清晰地看到每个请求的调用链,知道哪个服务耗时最长,哪个函数调用出现了错误,哪个SQL查询导致了数据库瓶颈。 你可以根据 metrics 实时监控系统的各项指标,及时发现异常,并在问题扩大之前进行干预。
所以,可观测性不是运维的专利,而是每一个程序员都应该掌握的技能。 它能让你写出更健壮、更易于维护的代码,也能让你在遇到问题时,不再束手无策,而是运筹帷幄,决胜千里。
今天,我们就来重点聊聊可观测性的两个核心支柱:Tracing (链路追踪) 和 Metrics (指标)。
一、 Tracing:追寻请求的足迹
想象一下,一个用户请求从你的网站/App 发起,经过 N 个服务,最终返回结果。 这个过程就像一趟旅行,Tracing 就是记录这趟旅行的轨迹,包括每个服务花费的时间,调用的其他服务,以及发生的任何错误。
1. Tracing 的核心概念:Span
Tracing 的基本单位是 Span。 一个 Span 代表一个独立的工作单元,例如一个函数调用,一个HTTP请求,或者一个数据库查询。
每个 Span 都包含以下信息:
- Operation Name (操作名称): 描述 Span 所代表的工作。 例如 "HTTP GET /users", "database.query"。
- Start Time (开始时间): Span 开始执行的时间。
- End Time (结束时间): Span 执行结束的时间。
- Duration (持续时间): Span 的执行时长,等于 End Time – Start Time。
- Tags (标签): 键值对形式的元数据,用于描述 Span 的更多信息。 例如 HTTP 状态码,数据库查询语句,用户ID等等。
- Logs (日志): Span 执行过程中产生的日志信息。
- Span Context (Span 上下文): 包含 Span ID, Trace ID, 以及其他用于关联 Span 的信息。
2. Trace ID 和 Span ID
- Trace ID: 一个 Trace 代表一个完整的请求链路。 属于同一个请求链路的所有 Span 共享同一个 Trace ID。
- Span ID: 一个 Span 的唯一标识符。
3. Parent Span ID
一个 Span 可以有父 Span (Parent Span),表示 Span 是由父 Span 触发的。 通过 Parent Span ID,可以将 Span 组织成树状结构,清晰地展示请求的调用链。
4. 示例代码(Python + Opentracing)
import opentracing
from opentracing.propagation import Format
import requests
import time
# 初始化 Tracer (这里使用 Jaeger 作为示例)
from jaeger_client import Config
def initialize_tracer(service_name):
config = Config(
config={
'sampler': {
'type': 'const',
'param': 1,
},
'logging': True,
},
service_name=service_name,
)
return config.initialize_tracer()
tracer = initialize_tracer('my-service')
def do_something():
with tracer.start_span('do_something') as span:
span.set_tag('level', 'DEBUG')
span.log_kv({'event': 'doing something important'})
time.sleep(0.1) # 模拟耗时操作
def call_other_service():
with tracer.start_span('call_other_service') as span:
# 注入 Span 上下文到 HTTP Header
headers = {}
tracer.inject(span_context=span.context, format=Format.HTTP_HEADERS, carrier=headers)
try:
response = requests.get('http://other-service:8080/api', headers=headers) # 假设 other-service 存在
span.set_tag('http.status_code', response.status_code)
span.log_kv({'event': 'response received', 'status_code': response.status_code})
response.raise_for_status() # 抛出异常,如果状态码不是 200
except requests.exceptions.RequestException as e:
span.set_tag('error', True)
span.log_kv({'event': 'request failed', 'error': str(e)})
def handle_request():
with tracer.start_span('handle_request') as span:
span.set_tag('user_id', '123')
do_something()
call_other_service()
if __name__ == '__main__':
handle_request()
tracer.close()
代码解释:
- 我们使用
opentracing
库来定义和管理 Span。 tracer.start_span()
创建一个新的 Span。span.set_tag()
添加标签。span.log_kv()
添加日志。tracer.inject()
将 Span 上下文注入到 HTTP Header 中,以便传递给下游服务。 下游服务需要从 HTTP Header 中提取 Span 上下文,并创建一个新的 Span,作为当前 Span 的子 Span。- 使用
with
语句可以确保 Span 在执行完毕后自动关闭。 tracer.close()
关闭 Tracer,释放资源。
5. Tracing 的好处:
- 性能分析: 快速定位性能瓶颈,找出耗时最长的服务或函数。
- 错误诊断: 清晰地看到错误发生的路径,快速定位问题根源。
- 依赖分析: 了解服务之间的依赖关系,为服务治理提供依据。
- 用户行为分析: 追踪用户请求的完整流程,了解用户行为模式。
6. 常用的 Tracing 工具:
- Jaeger: Uber 开源的分布式追踪系统,功能强大,易于部署。
- Zipkin: Twitter 开源的分布式追踪系统,历史悠久,社区活跃。
- SkyWalking: 国产开源的分布式追踪系统,支持多种协议和语言。
- Datadog APM: 商业化的 APM 工具,提供全面的监控和分析功能。
二、 Metrics:量化系统的健康状况
Metrics 是指可以量化的指标,用于衡量系统的性能和健康状况。 它们就像体检报告上的各项指标,可以让你随时了解系统的运行状态。
1. Metrics 的类型:
- Counter (计数器): 用于统计事件发生的次数。 例如,请求总数,错误总数,用户注册总数。 Counter 只能增加,不能减少。
- Gauge (仪表盘): 用于表示一个可以变化的数值。 例如,CPU 使用率,内存使用率,磁盘空间使用率。 Gauge 可以增加或减少。
- Histogram (直方图): 用于统计数据的分布情况。 例如,请求响应时间,数据包大小。 Histogram 可以让你了解数据的平均值,最小值,最大值,以及不同分位数的数值。
- Summary (摘要): 类似于 Histogram,但计算的是分位数,而不是统计数据的分布情况。 Summary 适合于对精度要求较高的场景。
2. Metrics 的命名规范:
- 使用有意义的名字,清晰地描述指标的含义。
- 使用统一的单位。
- 使用一致的标签。
- 避免使用过于宽泛的名字。
3. 示例代码(Python + Prometheus)
from prometheus_client import start_http_server, Summary, Counter, Gauge
import random
import time
# 创建 Metrics
REQUEST_TIME = Summary('request_processing_seconds', 'Time spent processing request')
REQUEST_COUNT = Counter('request_total', 'Total number of requests')
CPU_USAGE = Gauge('cpu_usage', 'CPU usage percentage')
# 模拟处理请求的函数
@REQUEST_TIME.time()
def process_request():
"""A dummy function that takes some time."""
REQUEST_COUNT.inc() # 增加请求计数器
time.sleep(random.random())
CPU_USAGE.set(random.randint(0,100)) # 设置 CPU 使用率
if __name__ == '__main__':
# 启动 Prometheus HTTP 服务,用于暴露 Metrics
start_http_server(8000)
print("Prometheus metrics server started on port 8000")
# 循环处理请求
while True:
process_request()
代码解释:
- 我们使用
prometheus_client
库来定义和暴露 Metrics。 Summary
,Counter
,Gauge
分别创建 Summary, Counter, Gauge 类型的 Metrics。@REQUEST_TIME.time()
是一个装饰器,用于自动统计函数的执行时间,并将其记录到 Summary 中。REQUEST_COUNT.inc()
增加请求计数器。CPU_USAGE.set()
设置 CPU 使用率。start_http_server()
启动一个 HTTP 服务,用于暴露 Metrics。 Prometheus 会定期从该 HTTP 服务抓取 Metrics 数据。
4. Metrics 的好处:
- 实时监控: 实时了解系统的运行状态,及时发现异常。
- 告警: 根据 Metrics 设置告警规则,当指标超过阈值时,自动发送告警通知。
- 容量规划: 根据 Metrics 数据进行容量规划,避免资源不足。
- 性能优化: 根据 Metrics 数据进行性能优化,提高系统效率。
5. 常用的 Metrics 工具:
- Prometheus: CNCF 毕业项目,功能强大,易于集成,是云原生时代最流行的监控系统。
- Grafana: 开源的数据可视化工具,可以与 Prometheus 等多种数据源集成,创建各种仪表盘。
- InfluxDB: 时序数据库,专门用于存储 Metrics 数据。
- Datadog: 商业化的监控工具,提供全面的监控和分析功能。
- New Relic: 商业化的 APM 工具,提供全面的监控和分析功能。
三、 Tracing 与 Metrics 的结合:打造强大的可观测性平台
Tracing 和 Metrics 各有所长,但如果将它们结合起来,就能发挥更大的威力。
1. 关联 Traces 和 Metrics
可以将 Trace ID 作为标签添加到 Metrics 中,这样就可以将 Traces 和 Metrics 关联起来。 例如,可以将请求的响应时间添加到 Histogram 中,并将 Trace ID 作为标签添加到 Histogram 中。 这样,就可以根据 Trace ID 查找请求的调用链,并查看该请求的响应时间分布情况。
2. 从 Traces 中提取 Metrics
可以从 Traces 中提取 Metrics 数据。 例如,可以从 Traces 中提取每个服务的请求数量,响应时间,错误率等指标。 这样,就可以根据 Traces 数据生成 Metrics,并进行分析。
3. 使用 Traces 来辅助 Metrics 的告警
当 Metrics 触发告警时,可以使用 Traces 来辅助诊断问题。 例如,当请求的错误率超过阈值时,可以使用 Traces 来查找导致错误的请求调用链,并定位问题根源。
4. 示例:使用 Prometheus + Jaeger 构建可观测性平台
- 使用 Prometheus 收集 Metrics 数据。
- 使用 Jaeger 收集 Traces 数据。
- 使用 Grafana 创建仪表盘,展示 Metrics 数据和 Traces 数据。
- 在 Grafana 中配置 Jaeger 数据源,以便从 Grafana 中跳转到 Jaeger 查看 Traces 数据。
- 配置 Prometheus 的告警规则,当 Metrics 超过阈值时,发送告警通知。
- 在告警通知中包含 Trace ID,以便快速定位问题根源。
四、 可观测性的最佳实践
- 尽早开始: 在项目初期就应该考虑可观测性,而不是等到上线后才开始亡羊补牢。
- 选择合适的工具: 根据项目的需求和预算,选择合适的 Tracing 和 Metrics 工具。
- 标准化: 制定统一的 Tracing 和 Metrics 规范,确保数据的一致性和可比性。
- 自动化: 尽可能自动化 Tracing 和 Metrics 的部署和配置。
- 持续改进: 不断优化 Tracing 和 Metrics 的配置,提高可观测性的质量。
- 关注用户体验: 可观测性的最终目标是提高用户体验,因此应该关注用户相关的指标,例如请求响应时间,错误率等。
- 代码埋点规范化: 统一埋点方式,避免过度埋点和遗漏埋点。 使用AOP等方式,减少代码侵入。
- 安全: 保护敏感数据,例如用户密码,信用卡信息等,避免在 Traces 和 Metrics 中泄露。
- 监控基础设施: 除了应用程序之外,还需要监控基础设施,例如服务器,数据库,网络等。
- 建立完善的告警体系: 根据不同的 Metrics 设置不同的告警级别,确保及时发现和处理问题。
- 可观测性文化: 在团队中推广可观测性文化,让每一个成员都意识到可观测性的重要性。
五、总结
可观测性是现代软件开发的重要组成部分。 通过 Tracing 和 Metrics,我们可以深入了解系统的运行状态,快速定位问题根源,并进行性能优化。 希望通过本文的介绍,能够帮助大家更好地理解和应用可观测性,让你的代码“开口说话”,让你的系统更加健壮和高效!
记住,可观测性不是一蹴而就的,而是一个持续改进的过程。 让我们一起努力,打造强大的可观测性平台,为用户提供更好的服务!
最后,祝大家编码愉快,bug 远离! 感谢阅读!