Istio可观测性(链路)

可观测性的英文是 Observability,这是伴随着云原生技术发展产生的一个新兴词汇,在传统的 IT 中,并没有这种说法。简单来说,可观测性是通过系统输出信息到外部,以检测系统内部的运行状态Trace,通过内部打点的方式串联起微服务的各个组件。Metrics,通过输出服务的 Metrics 信息,达到外部监测的目的

链路追踪:通过在程序内打点记录日志的方式,记录每次请求的调用链路信息。特点是数据精准、细致,适合查看某一次请求的调用链路,一般用于查看某些响应较慢的接口瓶颈

监控指标:主要是用时序性数据库记录每个时间点的监控数据,一般通过主动拉取服务 Metrics 数据的方式记录,然后实时计算一段时间的数据,并通过图形界面的方式展现出来。它的特点是实时性强、可观测指标丰富,适合查看一段时间内的指标趋势

日志:日志是比较传统的可观测性组件了,无论是在单体服务时代还是微服务时代,我们都会用日志排查问题。日志的特点是数据比较离散,之间没有关联。当然,可以通过在日志中打印 TraceId 和链路追踪关联起来。一般日志要通过日志收集系统使用,比如常见的 ELK 日志系统。

在微服务架构中,随着服务和中间件数量变多,往往一个接口要请求几十次服务和上百次 DB 才能返回数据,链路过长,很难定位到底是哪个环节出了问题;又或者某个接口延时过高,也很难排查到底是链路中的哪个环节出了问题,这个时候就需要链路追踪系统帮忙了。

Trace 链路追踪原理

链路追踪系统基本源于 Google 的一篇 Dapper 论文,这篇论文详细解释了链路追踪的实现原理。

Dapper 通过一个全局唯一的 TraceId 表示请求调用链,并定义了 span,span 表示一次调用(可以是远程调用,也可以程序内的函数调用)。每个 span 包含了两个重要信息,一个是当前 SpanId,另外一个就是 ParentSpanId。

如何将 Trace 所需的信息传递给被调方服务呢?答案就是通过 HTTP 的 header 头传递下去,当然如果是其他协议,比如 Dubbo,就要想其他办法了。但 gRPC 和 HTTP 相对简单,只要通过 header 传递就可以了。

下面是这些 header 值的含义。

X-Request-ID:请求 ID,一般 Sidecar 会在入口层生成统一的请求 ID,用于一次请求在内部服务之间传递,方便通过请求 ID 查询一次请求的所有日志。

X-B3-TraceId:链路追踪的唯一标识,长度为  64 位。由网关层生成,一次外部请求使用唯一的 TraceId 。

X-B3-SpanId:SpanId 的长度是 64 位,表示当前操作在跟踪树中的位置。

X-B3-ParentSpanId:父 SpanId,如果该值不存在,表示是根节点。

X-B3-Sampled:采样率,当设置为 1 时,表示采样。

下面来看一个 Trace 的真实数据,方便更好的理解 Trace:

{"duration":2065,"operationName":"/ping","parentSpanID":"0","process.serviceName":"negri.sidecarserverlistener.myapp","process.tags.hostname":"MacBook-Pro-3.local","process.tags.ip":"192.168.1.88","spanID":"5f1db306ef459b2f","startTime":1609241265147010,"tags.http.method":"GET","tags.http.status_code":"200","tags.http.url":"/ping","tags.peer.address":"http://127.0.0.1:8888","tags.span.kind":"server","traceID":"5f1db306ef459b2f"}

通过上面的数据,可以了解这个接口的运行时间 duration,记录了服务名、TraceId、SpanId、ParentSpanId 等上面我们聊到的常用数据,另外还记录了所需要的一些自定义数据,放在了 Tags 字段中。

链路追踪系统,通过收集程序中的打点日志的方式,通常提供了以下功能。

排查根因:分析单次请求的调用链路,排查问题根因。

调用关系图:通过 Trace 中的服务信息,绘制服务调用关系图。

日志追踪:通过关联日志 RequestId,可以链接到日志系统,查看更详细的日志信息。

Jaeger

Jaeger 是 Uber 公司开源的、采用 Go 语言开发的分布式链路追踪系统,由以下几个模块组成。

jaeger-client:Jaeger 提供的符合 OpenTracing 标准的各种语言的 SDK,包括 Java、Go、Node.js 等。Client 负责收集 Trace 数据发送到 Agent。

jaeger-agent:jaeger-client 的代理程序,部署在所有宿主机上,这样的目的和 Sidecar 类似,屏蔽了一些路由和 Collector 节点发现的细节,让 Client 更加轻量化。Client 通过 UDP 协议和 Agent 通信,也避免了日志落盘再采集导致的一些性能问题。

jaeger-collector:负责收集 Agent 上报的链路追踪数据,并做一些数据验证工作,以及对数据做一些处理然后上报到存储系统。

jaeger-db:后端存储系统,支持 Cassandra 和 ElasticSearch。

jaeger-query:专门负责调用链查询的一个服务,提供一套独立的 UI 界面,用于绘制调用关系和展示服务链路。

spark-job:基于 Spark 的运算任务,可以计算服务的依赖关系、调用次数等。

Trace 链路追踪

先从代码层面看看 Istio 是如何接入 Trace 系统的。虽然网格代理 Envoy 能够自动识别 Trace 中的 header,在请求 upstream 的时候自动生成 span 并携带发送,但是如果要将整个链路追踪信息串在一起,还需要代码中额外携带一些链路追踪信息才能完成。

Istio 规定需要携带以下 header:

x-request-id
x-b3-traceid
x-b3-spanid
x-b3-parentspanid
x-b3-sampled
x-b3-flags
x-ot-span-context

首先看一下入口方法:

@app.route('/productpage')
@trace()
def front():
    product_id = 0  # TODO: replace default value
    headers = getForwardHeaders(request)
    user = session.get('user', '')
    product = getProduct(product_id)
    detailsStatus, details = getProductDetails(product_id, headers)

可以看到,通过 getForwardHeaders 方法,获取了在请求其他服务时需要传递的 header 参数,在getProductDetails 调用的时候,传递了通过 getForwardHeaders 方法获得的 header 参数。

下面看一下上述内容如何在代码中得以体现:

def getForwardHeaders(request):
    headers = {}
    # x-b3-*** 通过 opentracing 的库直接获取
    span = get_current_span() # 获取 downstream header 中传递的 span
    carrier = {}
    tracer.inject(
        span_context=span.context,
        format=Format.HTTP_HEADERS,
        carrier=carrier) # 将 trace header 注入 carrier
    headers.update(carrier) # 更新 headers    
    # 手动获取其他非 x-b3-*** 的 header
    if 'user' in session:
        headers['end-user'] = session['user']    
    # Keep this in sync with the headers in details and reviews.
    incoming_headers = [
    # All applications should propagate x-request-id. This header is included in access log statements and is used for consistent trace
    # sampling and log sampling decisions in Istio.
        'x-request-id',    
    # Lightstep tracing header. Propagate this if you use lightstep tracing
    # in Istio (see
    # https://istio.io/latest/docs/tasks/observability/distributed-tracing/lightstep/)
    # Note: this should probably be changed to use B3 or W3C TRACE_CONTEXT
    # Lightstep recommends using B3 or TRACE_CONTEXT and most application
    # libraries from lightstep do not support x-ot-span-context.
        'x-ot-span-context',
    # Datadog tracing header. Propagate these headers if you use Datadog
    # tracing.
        'x-datadog-trace-id',
        'x-datadog-parent-id',
        'x-datadog-sampling-priority',
        # W3C Trace Context. Compatible with OpenCensusAgent and Stackdriver Istio
        # configurations.
        'traceparent',
        'tracestate',
        # Cloud trace context. Compatible with OpenCensusAgent and Stackdriver Istio
        # configurations.
        'x-cloud-trace-context',
        # Grpc binary trace context. Compatible with OpenCensusAgent nad
        # Stackdriver Istio configurations.
        'grpc-trace-bin',
        # b3 trace headers. Compatible with Zipkin, OpenCensusAgent, and
        # Stackdriver Istio configurations. Commented out since they are
        # propagated by the OpenTracing tracer above.
    # 'x-b3-traceid',
        # 'x-b3-spanid',
        # 'x-b3-parentspanid',
        # 'x-b3-sampled',
        # 'x-b3-flags',
        # Application-specific headers to forward.
        'user-agent',
    ]
    # For Zipkin, always propagate b3 headers.
    # For Lightstep, always propagate the x-ot-span-context header.
    # For Datadog, propagate the corresponding datadog headers.
    # For OpenCensusAgent and Stackdriver configurations, you can choose any
    # set of compatible headers to propagate within your application. For
    # example, you can propagate b3 headers or W3C trace context headers with
    # the same result. This can also allow you to translate between context
    # propagation mechanisms between different applications.
    # 传递其他非 b3 header 的头信息
    for ihdr in incoming_headers:
        val = request.headers.get(ihdr)
        if val is not None:
            headers[ihdr] = val    
        return headers

可以看到,通过 Jaeger 的类库,自动将带有 b3 header 的数据存储到了 headers 中,其他的一些 Trace 规范,则需要通过 incoming_headers 自定义的方式自动传递。启动 Jaeger,通过 URL 或者 cURL 的方式多次访问 productpage:

选择 productpage 服务,可以看到如下页面:

点击一条具体的链路,进入详情页面,可以详细展示整个微服务调用的链路,包含每个阶段耗时的详细信息,方便排查具体哪个环节出现了问题:

 

点击 System Architecture 页面,可以看到整个微服务的调用关系展示:

 

 

posted @ 2023-01-19 10:48  muzinan110  阅读(300)  评论(0编辑  收藏  举报