在 分布式微服务架构中, 一个请求从用户发起到接收,后端服务可能要经过多个微服务间调用,及多个功能组件(mysql,redis...)和网络请求(http,udp),在这个过程中,任何地方都可能出现错误,导致此次请求失败; 这时就需要 能够掌握请求中每个阶段节点所做的事情,保证出现问题时,能准确迅速定位; 这种记录 请求每个关键节点信息并能以调用链路顺序的方式展现出来 就是链路追踪;
Instrumentation: 英文文档中 此单词含义是 "仪器仪表";在计算机领域中 为 "能够监测和度量某个系统的性能,分析系统错误,并能将追踪的数据进行记录"; 其实就是 检测数据或埋点数据; 理解此单词含义才能更加明白的阅读相关文档;
最开始时,各家公司厂商有自己的一套链路追踪的数据结构及展示形式,缺点在于 开发者如果想更换链路追踪系统,则代码中链路追踪部分也要修改,造成迁移难度巨大;
后来意识到这个问题,CNCF(Cloud Native Computing Foundation:云原生计算基金会) 出了 一个OpenTracing(基于API的标准,所有遥测或埋点数据必须通过此API标准发送数据到观测后端服务);
google出了一个OpenCensus(提供了一些语言的库,开发者可以使用这些库将数据发送到OpenCensus支持的观测后端服务,如jaeger,zipkin等等);
2019年时, google的OpenCensus和CNCF的OpenTracing 合并为 Opentelemetry(简称 OTel),归为CNCF孵化维护;
jaeger:uber公司开源的,用go语言实现的一个存储观测数据及提供web端查询的分布式系统; 此工具只支持trace数据格式;
实现步骤:
- docker run -d --name jaeger \
- -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
- -e COLLECTOR_OTLP_ENABLED=true \
- -p 6831:6831/udp \
- -p 6832:6832/udp \
- -p 5778:5778 \
- -p 16686:16686 \
- -p 4317:4317 \
- -p 4318:4318 \
- -p 14250:14250 \
- -p 14268:14268 \
- -p 14269:14269 \
- -p 9411:9411 \
- jaegertracing/all-in-one:1.37
- pip install opentelemetry-distro
- pip install opentelemetry-propagator-jaeger
- pip install opentelemetry-exporter-jaeger
opentelemetry-bootstrap -a install
- import json
- import os
- from random import randint
- import traceback
- import click
- from flask import request
- from opentelemetry.instrumentation.flask import FlaskInstrumentor
- from opentelemetry.instrumentation.grpc import GrpcInstrumentorClient
- from opentelemetry.instrumentation.pymongo import PymongoInstrumentor
- from opentelemetry.instrumentation.redis import RedisInstrumentor
- from opentelemetry.instrumentation.requests import RequestsInstrumentor
- from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
- from opentelemetry.propagators.jaeger import JaegerPropagator
- from opentelemetry.trace import Span
-
- from app import create_app
- from app.utils import json_success
-
- os.environ["LC_ALL"] = "en_US.utf-8"
- os.environ["LANG"] = "en_US.utf-8"
-
- from opentelemetry import trace
- from opentelemetry.exporter.jaeger.thrift import JaegerExporter
- from opentelemetry.sdk.resources import SERVICE_NAME, Resource, SERVICE_NAMESPACE
- from opentelemetry.sdk.trace import TracerProvider
- from opentelemetry.sdk.trace.export import SimpleSpanProcessor
-
- from opentelemetry.propagate import set_global_textmap
-
- # 设置 全局的从请求头中获取trace信息的方式为 Jaeger的uber-trace-id;其他的方式如 b3 等等;
- # 设置此选项,使微服务间能够根据 http请求头的uber-trace-id 将 跨多个微服务的请求归为同一个trace中,方便查看
- set_global_textmap(JaegerPropagator())
- # 设置trace的服务名及命令空间
- resource = Resource(attributes={
- SERVICE_NAME: "project_name",
- SERVICE_NAMESPACE: "test_env",
- })
- # 设置trace数据导出服务 为jaeger; 通过jaeger服务的6831(UDP协议)端口上传;
- jaeger_exporter = JaegerExporter(
- agent_host_name="localhost",
- agent_port=6831,
- )
- # 设置trace
- provider = TracerProvider(resource=resource)
- # 将trace中每个span数据发送给jaeger的方式,SimpleSpanProcessor(直接传输),其他如:BatchSpanProcessor(批量传输),ConcurrentMultiSpanProcessor(多线程并发传输)...
- processor = SimpleSpanProcessor(jaeger_exporter)
- provider.add_span_processor(processor)
- trace.set_tracer_provider(provider)
-
- # 自动生成span数据的各种涉及到IO的插件部分; 如 requests,grpc,mongo,redis,sqlalchemy等等;
- # 所有python可用自动埋点的插件在 https://github.com/Rgcsh/opentelemetry-python-contrib/tree/main/instrumentation 目录下
- # 注意 多数插件必须在 flask app生成之前定义;否则无法自动生成span(埋点失败)
- RequestsInstrumentor().instrument()
- GrpcInstrumentorClient().instrument()
- SQLAlchemyInstrumentor().instrument(enable_commenter=True, commenter_options={})
- PymongoInstrumentor().instrument()
- RedisInstrumentor().instrument()
-
- app = create_app()
-
-
- def request_hook(span: Span, _):
- """
- flask请求进入时 在before_request中的hook函数;可以将一些请求数据(如请求参数等等) 放到span中
- :param span:
- :param _:
- :return:
- """
-
- def request_params():
- """ 获取当前请求的参数
- """
- request_params = dict()
-
- # 获取请求参数
- request_params.update(request.args.to_dict())
-
- # 获取form表单中的参数
- request_params.update(request.form.to_dict())
-
- # 获取json参数
- json_params = None
- try:
- json_params = getattr(request, "json", None)
- except Exception:
- pass
-
- if isinstance(json_params, dict):
- request_params.update(json_params)
- if len(str(request_params)) > 1000:
- return "部分截取入参:" + str(request_params)[-1000:]
- return request_params
-
- # 在span中添加一些额外的数据,值不能 为dict类型(否则无法显示在jaeger UI上);
- span.set_attributes(
- {"request.params": json.dumps(request_params()) or '',
- "request.uid": request.headers.get("Access-User") or ''})
-
-
- # 对flask框架进行埋点,基本就是 生成一个请求相关的span数据
- # 此插件必须在flask app生成之后调用
- FlaskInstrumentor().instrument_app(app, request_hook=request_hook)
-
- # 获取一个全局的trace,在手动埋点时使用
- tracer = trace.get_tracer(__name__)
-
-
- @app.route("/roll", methods=["POST"])
- def roll_dice():
- """
- 一个测试路由
- :return:
- """
- # 手动设置span数据
- with tracer.start_as_current_span("do_roll") as rollspan:
- res = randint(1, 6)
-
- rollspan.set_attribute("roll.value", res)
- # 遇到错误时,可以通过此方式上传 错误栈span;
- rollspan.set_attributes({"error": "true", "stacktrace": traceback.format_exc()})
- return json_success()
-
-
- @click.command()
- @click.option("--port", "-p", help="Server run port", default=8080)
- @click.option("--host", "-h", help="Server run host", default="0.0.0.0")
- def start(host, port):
- """ 开启实例
- :param host: 监听的主机
- :param port: 监听的端口
- """
- app.run(host=host, port=int(port))
-
-
- if __name__ == "__main__":
- start()
监测效果:


代码示例说明:
微服务间通过请求头中的uber-trace-id的值作为trace_id的值来关联,保证他们属于同一个trace;
每个自动化生成检测数据 插件有其自身的 参数可以定义,实现额外的自定义功能;

总结:
opentelemetry(OTel)提供了常用语言(java,go,python等等)的库供开发者使用,用于自动或手动 生成检测数据并传输到众多后端检测服务中 的功能;
OTel python sdk推荐使用>=python3.7版本,但是在python3.6中也能使用,只是时间精度只能达到毫秒级别,无法达到纳秒级别;
作为开发者需要做的就是使用OTel及出现问题时,会使用web ui工具排查问题即可;
相关链接:
Getting Started — Jaeger documentation
GitHub - open-telemetry/opentelemetry-python: OpenTelemetry Python API and SDK
Getting Started | OpenTelemetry
OpenTelemetry-Python API Reference — OpenTelemetry Python documentation