当前位置:首页 > 公众号精选 > 架构师社区
[导读]题外话微服务架构作为云原生核心技术之一,提倡将单一应用程序划分成一组小的服务(微服务),服务之间互相协调、互相配合,为用户提供最终价值。但数量庞大的微服务实例治理起来给我们带来了很多问题,通常的做法都是引入相应组件完成,如API网关(apisix,kong,traefik)负责认...

题外话

微服务架构 作为云原生核心技术之一,提倡将单一应用程序划分成一组小的服务(微服务),服务之间互相协调、互相配合,为用户提供最终价值。

但数量庞大的微服务实例治理起来给我们带来了很多问题,通常的做法都是引入相应组件完成,如 API 网关 ( apisix, kong, traefik ) 负责认证鉴权、负载均衡、限流和静态响应处理;服务注册与发现中心 ( Consul, Etcd, ZooKeeper ) 负责管理维护微服务实例,记录服务实例元数据;可观察性方面包括 Metrics 监控 ( Prometheus ) 负责性能指标统计告警,Logging 日志 ( Loki, ELK ) 负责日志的收集查看,Tracing 链路追踪 ( OpenTracing, Jaeger ) 负责追踪具体的请求和绘制调用的拓扑关系。对于这种需要自行引入各种组件完成微服务治理的称为 侵入式架构 ,与之相对应的另外一种做法就是未来微服务架构 —— 服务网格 ( Service Mesh )

正文

本文主要介绍可观察性的链路追踪模块,我将按以下几个大纲逐步演进:

  • OpenTracing 介绍
  • Jaeger 介绍
  • Jaeger 部署
  • Jaeger 使用

OpenTracing 介绍

起源

实现分布式追踪的方式一般是在程序代码中进行埋点,采集调用的相关信息后发送到后端的一个追踪服务器进行分析处理。在这种实现方式中,应用代码需要依赖于追踪服务器的 API,导致业务逻辑和追踪的逻辑耦合。为了解决该问题,CNCF (云原生计算基金会)下的 OpenTracing 项目定义了一套分布式追踪的标准,以统一各种分布式追踪系统的实现。OpenTracing 中包含了一套分布式追踪的标准规范,各种语言的 API,以及实现了该标准的编程框架和函数库。参考[1]

OpenTracing 提供了平台无关、厂商无关的 API,因此开发者只需要对接 OpenTracing API,无需关心后端采用的到底是什么分布式追踪系统,Jager、Skywalking、LightStep 等都可以无缝切换。

数据模型


分布式链路追踪

OpenTracing 定义了以下数据模型:

  • Trace (调用链):一个 Trace 代表一个事务或者流程在(分布式)系统中的执行过程。例如来自客户端的一个请求从接收到处理完成的过程就是一个 Trace。
  • Span(跨度):Span 是分布式追踪的最小跟踪单位,一个 Trace 由多段 Span 组成。可以被理解为一次方法调用, 一个程序块的调用, 或者一次 RPC/数据库访问。只要是一个具有完整时间周期的程序访问,都可以被认为是一个 Span。
  • SpanContext(跨度上下文):分布式追踪的上下文信息,包括 Trace id,Span id 以及其它需要传递到下游服务的内容。一个 OpenTracing 的实现需要将 SpanContext 通过某种序列化协议 (Wire Protocol) 在进程边界上进行传递,以将不同进程中的 Span 关联到同一个 Trace 上。对于 HTTP 请求来说,SpanContext 一般是采用 HTTP header 进行传递的。
总结:多个 Span 共同组成一个有向无环图(DAG)形成了 Trace ,SpanContext 则用于将一个 Span 的上下文传递到其下游的 Span 中,以将这些 Span 关联起来。

例如:下面的示例 Trace 就是由 8 个 Span 组成的:参考[2]

以树的结构展示 Trace 调用链:

单个Trace中,span间的因果关系


        [Span A]  ←←←(the root span)
            |
      ------ ------
     |             |
 [Span B]      [Span C] ←←←(Span C 是 Span A 的孩子节点, ChildOf)
     |             |
 [Span D]       --- -------
               |           |
           [Span E]    [Span F] >>> [Span G] >>> [Span H]
                                       ↑
                                       ↑
                                       ↑
                         (Span G 在 Span F 后被调用, FollowsFrom)


基于时间轴的时序图展示 Trace 调用链:

单个Trace中,span间的时间关系


––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time

 [Span A···················································]
   [Span B··············································]
      [Span D··········································]
    [Span C········································]
         [Span E·······]        [Span F··] [Span G··] [Span H··]

OpenTracing API for Go

以官方博客例子为例[3]

安装

go get github.com/opentracing/opentracing-go
创建 main.go ,实现一个 Web 服务,并在请求流程中使用 OpenTracing API 进行埋点处理。

Show me the code !

package main

import (
 "fmt"
 "log"
 "math/rand"
 "net/http"
 "time"

 "github.com/opentracing/opentracing-go"
)

func main() {
 port := 8080
 addr := fmt.Sprintf(":%d", port)
 mux := http.NewServeMux()
 mux.HandleFunc("/", indexHandler)
 mux.HandleFunc("/home", homeHandler)
 mux.HandleFunc("/async", serviceHandler)
 mux.HandleFunc("/service", serviceHandler)
 mux.HandleFunc("/db", dbHandler)
 fmt.Printf("http://localhost:%d\n", port)
 log.Fatal(http.ListenAndServe(addr, mux))
}

// 主页 Html
func indexHandler(w http.ResponseWriter, r *http.Request) {
 w.Write([]byte(` 点击开始发起请求 `))
}

func homeHandler(w http.ResponseWriter, r *http.Request) {
 w.Write([]byte("开始请求...\n"))

 // 在入口处设置一个根节点 span
 span := opentracing.StartSpan("请求 /home")
 defer span.Finish()

 // 发起异步请求
 asyncReq, _ := http.NewRequest("GET""http://localhost:8080/async"nil)
 // 传递span的上下文信息
 // 将关于本地追踪调用的span context,设置到http header上,并传递出去
 err := span.Tracer().Inject(span.Context(),
  opentracing.TextMap,
  opentracing.HTTPHeadersCarrier(asyncReq.Header))
 if err != nil {
  log.Fatalf("[asyncReq]无法添加span context到http header: %v", err)
 }
 go func() {
  if _, err := http.DefaultClient.Do(asyncReq); err != nil {
   // 请求失败,为span设置tags和logs
   span.SetTag("error"true)
   span.LogKV(fmt.Sprintf("请求 /async error: %v", err))
  }
 }()

 time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)

 // 发起同步请求
 syncReq, _ := http.NewRequest("GET""http://localhost:8080/service"nil)
 err = span.Tracer().Inject(span.Context(),
  opentracing.TextMap,
  opentracing.HTTPHeadersCarrier(syncReq.Header))
 if err != nil {
  log.Fatalf("[syncReq]无法添加span context到http header: %v", err)
 }
 if _, err = http.DefaultClient.Do(syncReq); err != nil {
  span.SetTag("error"true)
  span.LogKV(fmt.Sprintf("请求 /service error: %v", err))
 }
 w.Write([]byte("请求结束!"))
}

// 模拟业务请求
func serviceHandler(w http.ResponseWriter, r *http.Request) {
 // 通过http header,提取span元数据信息
 var sp opentracing.Span
 opName := r.URL.Path
 wireContext, err := opentracing.GlobalTracer().Extract(
  opentracing.TextMap,
  opentracing.HTTPHeadersCarrier(r.Header))
 if err != nil {
  // 获取失败,则直接新建一个根节点 span
  sp = opentracing.StartSpan(opName)
 } else {
  sp = opentracing.StartSpan(opName, opentracing.ChildOf(wireContext))
 }
 defer sp.Finish()

 dbReq, _ := http.NewRequest("GET""http://localhost:8080/db"nil)
 err = sp.Tracer().Inject(sp.Context(),
  opentracing.TextMap,
  opentracing.HTTPHeadersCarrier(dbReq.Header))
 if err != nil {
  log.Fatalf("[dbReq]无法添加span context到http header: %v", err)
 }
 if _, err = http.DefaultClient.Do(dbReq); err != nil {
  sp.SetTag("error"true)
  sp.LogKV("请求 /db error", err)
 }

 time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
}

// 模拟DB调用
func dbHandler(w http.ResponseWriter, r *http.Request) {
 // 通过http header,提取span元数据信息
 var sp opentracing.Span
 opName := r.URL.Path
 wireContext, err := opentracing.GlobalTracer().Extract(
  opentracing.TextMap,
  opentracing.HTTPHeadersCarrier(r.Header))
 if err != nil {
  // 获取失败,则直接新建一个根节点 span
  sp = opentracing.StartSpan(opName)
 } else {
  sp = opentracing.StartSpan(opName, opentracing.ChildOf(wireContext))
 }
 defer sp.Finish()

 time.Sleep(time.Duration(rand.Intn(200)) * time.Millisecond)
}
最后,只需要在应用程序启动时连接到任意实现了 OpenTracing 标准的链路追踪系统即可。详见下文的 Jaeger 使用。

Jaeger 介绍

Jaeger 受 Dapper 和 OpenZipkin 的启发,是 Uber Technologies 开源的分布式跟踪系统,遵循 OpenTracing 标准,功能包括:

  • 分布式上下文传播
  • 监控分布式事务
  • 执行根原因分析
  • 服务依赖分析
  • 优化性能和延迟时间

架构

Jaeger 既可以部署为一体式二进制文件 (ALL IN ONE),其中所有 Jaeger 后端组件都运行在单个进程中,也可以部署为可扩展的分布式系统 (高可用架构)


分布式链路追踪

主要有以下几个组件:

  • Jaeger Client : OpenTracing API 的具体语言实现。它们可以用来为各种现有开源框架提供分布式追踪工具。
  • Jaeger Agent : Jaeger 代理是一个网络守护进程,它会监听通过 UDP 发送的 span,并发送到收集程序。这个代理应被放置在要管理的应用程序的同一主机上。这通常是通过如 Kubernetes 等容器环境中的 sidecar 来实现的。
  • Jaeger Collector : 与代理类似,该收集器可以接收 span,并将其放入内部队列以便进行处理。这允许收集器立即返回到客户端/代理,而不需要等待 span 进入存储。
  • Storage : 收集器需要一个持久的存储后端。Jaeger 带有一个可插入的机制用于 span 存储。
  • Query : Query 是一个从存储中检索 trace 的服务。
  • Ingester : 可选组件。Jaeger 可以使用 Apache Kafka 作为收集器和实际后备存储之间的缓冲。Ingester 是一个从 Kafka 读取数据并写入另一个存储后端的服务。
  • Jaeger Console : Jaeger 提供了一个用户界面,可让您可视觉地查看所分发的追踪数据。在搜索页面中,您可以查找 trace,并查看组成一个独立 trace 的 span 详情。

Jaeger 部署

Jaeger 部署方案主要围绕以下几个方面:

  • ALL IN ONE 还是分布式
  • 后端存储的选择(Elasticsearch、Cassandra 甚至 memory)
  • 是否引入 Kafka 作为中间缓冲器
  • Jaeger Agent 代理安装方式:sidecar 还是 DaemonSet
  • 安装工具的选择:Operator 还是 Helm chart
仁者见仁智者见智,结合自身业务场景选择适合自己的即可。

本文为了简化操作,就以 Operator Jaeger Agent sidecar memory ALL IN ONE 为例。

  1. 在 Kubernetes 上安装 Jaeger Operator
# 创建 observability 命名空间
kubectl create namespace observability
# 创建 crd 资源
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/crds/jaegertracing.io_jaegers_crd.yaml
# 声明用户权限
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/service_account.yaml
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role.yaml
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/role_binding.yaml
# 部署 Jaeger Operator
kubectl create -n observability -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/operator.yaml
  1. 获得集群范围的权限,可选
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role.yaml
kubectl create -f https://raw.githubusercontent.com/jaegertracing/jaeger-operator/master/deploy/cluster_role_binding.yaml
  1. 查看 Jaeger Operator 是否部署成功
$ kubectl get deployment jaeger-operator -n observability
NAME              READY   UP-TO-DATE   AVAILABLE   AGE
jaeger-operator   1/1     1            1           10s
  1. 使用 Jaeger Operator 部署 Jaeger ,创建 Jaeger 定制资源 参考[4]
apiVersion: jaegertracing.io/v1
kind: Jaeger
metadata:
  name: my-jaeger
spec:
  strategy: allInOne # 部署策略
  allInOne:
    image: jaegertracing/all-in-one:latest
    options:
      log-level: debug # 日志等级
  storage:
    type: memory # 可选 Cassandra、Elasticsearch
    options:
      memory:
        max-traces: 100000
  ingress:
    enabled: false
  agent:
    strategy: sidecar # 代理部署策略可选 DaemonSet
  query:
    serviceType: NodePort # 用户界面使用 NodePort
$ kubectl apply -f my-jaeger.yaml -n observability
jaeger.jaegertracing.io/my-jaeger created

$
 kubectl get jaeger -n observability
NAME        STATUS   VERSION   STRATEGY   STORAGE   AGE
my-jaeger                      allinone   memory    10s

$
 kubectl get svc -n observability
NAME                           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                  AGE
jaeger-operator-metrics        ClusterIP   10.103.46.73             8383/TCP,8686/TCP                        3m33s
my-jaeger-agent                ClusterIP   None                     5775/UDP,5778/TCP,6831/UDP,6832/UDP      15s
my-jaeger-collector            ClusterIP   10.111.136.244           9411/TCP,14250/TCP,14267/TCP,14268/TCP   15s
my-jaeger-collector-headless   ClusterIP   None                     9411/TCP,14250/TCP,14267/TCP,14268/TCP   15s
my-jaeger-query                NodePort    10.105.255.201           16686:32710/TCP,16685:32493/TCP          15s
访问 jaeger 用户界面 http://集群域名:32710

分布式链路追踪
恭喜成功看到土拨鼠。

Jaeger 使用

继续回到上文的 OpenTracing API for Go 示例,现在就可以将我们的应用程序连接到 Jaeger 了。

安装 Jaeger Client Go

go get -u github.com/uber/jaeger-client-go
main.go 添加 init 初始化函数

func init() {
 cfg := jaegercfg.Configuration{
  Sampler: 
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭