Uber jaeger--一个基于Go的分布式追踪系统

Jaeger-Uber开源的一个基于Go的分布式追踪系统

最近因工做须要在研究traing系统,最后选了jaeger,下面是一些总结,同时摘抄了网上的一些资料,并结合本身实践过程当中遇到的一些什么问题,欢迎指正,如你也在使用jaeger,或者想使用jaeger,途中遇到什么困难,可发邮件交流:honglouleiyan@163.comhtml

前言

​ 随着公司的发展,业务不断的增长,模块的不断拆分,系统间业务调用就变得越复杂,对定位线上故障带来很大困难。整个调用链不透明,犹如系统被蒙上一块黑纱,当线上遇到故障时,整个技术部就陷入痛苦的漩涡。这时候分布式追踪系统应运而生,如揭开了黑纱,让阳光照进黑暗。java

Opentracing相关文档

 

分布式追踪系统Jaeger

 

Jaeger是Uber开发的一套分布式追踪系统,已在Uber大规模使用。并在2017-9-13 加入CNCF 开源组织。使用Jaeger能够很是直观的展现整个分布式系统的调用链,由此能够很好发现和解决问题:github

 

 

jaeger架构

 

jaeger官网网址

https://www.jaegertracing.io/docs/web

jaeger github地址

https://github.com/jaegertracing/jaegerspring

jaeger 部署

使用docker部署官网资料和网站资料比较多,不过都是使用cassandra做为存储引擎,而且通常文章并无给出具体的建表语句,官网文档也没有找到建表语句!sql

若是你须要使用cassandra做为存储引擎的话,这篇文章仅供参考:https://blog.csdn.net/niyuelin1990/article/details/80225305

文章中有对应的导入jaeger表结构部门:

导入Jaeger表结构,这里不得不吐槽一下Jaeger!Jaeger二进制安装包里根本没有数据sql文件,并且没有任何文档告诉你SQL文件在哪里找,没安装文档!我表示是崩溃的,最终在源码目录的一个角落中找到SQL文件,路径展现一下:

$GOPATH/src/github.com/jaegertracing/jaeger/plugin/storage/cassandra/schema/v001.cql.tmpl
12
格式:
cqlsh -h HOST -p PORT -f fileName
cqlsh 10.100.7.46 -f $GOPATH/src/github.com/jaegertracing/jaeger/plugin/storage/cassandra/schema/v001.cql.tmpl 123

上面的命令是我搜索来的,由于在导入以前我已经手动一条一条加进去了(>﹏<)!若是很差用的话,读者能够直接cqlsh一条一条黏上去!!!!

 

下面本文将介绍如何使用elasticsearch存储引擎部署jaeger!

Jaeger组件

Agent

Agent是一个网络守护进程,监听经过UDP发送过来的Span,它会将其批量发送给collector。按照设计,Agent要被部署到全部主机上,做为基础设施。Agent将collector和客户端之间的路由与发现机制抽象了出来。

Collector

Collector从Jaeger Agent接收Trace,并经过一个处理管道对其进行处理。目前的管道会校验Trace、创建索引、执行转换并最终进行存储。存储是一个可插入的组件,如今支持Cassandra和elasticsearch。

Query

Query服务会从存储中检索Trace并经过UI界面进行展示,该UI界面经过React技术实现,其页面UI以下图所示,展示了一条Trace的详细信息。

存储

jaeger采集到的数据必须存储到某个存储引擎,目前支持Cassandra和elasticsearch

 

docker + elasticsearch安装

首先,你安装jaeger时,须要使用docker环境,

而后使用docker安装一个elasticsearch

docker run -d  --name elasticsearch --restart=always -p 9200:9200 -p 9300:9300 -e ES_JAVA_OPTS="-Xms512m -Xmx512m" elasticsearch:latest

注意:

一、此elasticsearch为单机版且数据存内存,若生产环境,请自行解决如何使用docker安装elasticsearch集群而且数据写入磁盘,elasticsearch版本请选择5.X,缘由以下(github issues:https://github.com/jaegertracing/jaeger/issues/665

jaeger elasticsearch版本请使用5.X的版本,官网上虽说明使用5.X和6.X都行,可是亲测使用6.2.4的es,会出现数据丢失:collector将数据写入elsasticsearch时会出现索引已存在的报错:

error:"trace_id":"89c90e9c1bc48622","span_id":"89c90e9c1bc48622","error":"elastic: Error 400 (Bad Request): index [jaeger-span-2018-05-27/5JHNIPoLRBe3c560r7FJlQ] already exists [type=resource_already_exists_exception]

 

 

​ 若你须要把elsticsearc 9200暴露到公网上,你注意Elasticsearch服务安全加固,可参考:https://www.sojson.com/blog/213.html

二、请使用docker安装elasticsearch,若未使用docker安装,下一步安装collector时会出现报错:

docker: Error response from daemon: could not get container for elasticsearch: No such container: elasticsearch.

容器中找不到对应的elasticsearch

 

docker + collector安装

若你安装的collector和elasticsearch是在同一台机器上,使用docker容易的--link命令就能够将collector和elasticsearch关联上,安装命令以下:

docker run -d --name jaeger-collector --restart=always --link elasticsearch:elasticsearch -e SPAN_STORAGE_TYPE=elasticsearch -e ES_SERVER_URLS=http://elasticsearch:9200 -e ES_USERNAME=elastic -p 14267:14267 -p 14268:14268 -p 9411:9411 jaegertracing/jaeger-collector

注意:

​ --link elasticsearch:elasticsearch,表明docker容易关联,该名字必须和你安装elasticsearch —name的名字相同

​ --SPAN_STORAGE_TYPE=elasticsearch 表明安装jaeger选择elasticsearch做为存储

-e ES_SERVER_URLS=http://elasticsearch:9200次条目表明你选择容器安装的elasticsearch的9200端口

-e ES_USERNAME elasticsearch的用户名:默认elastic,下同

-e ES_PASSWORD elasticsearch的密码

-e 其实就是表明的环境变量,其余变量你可使用如下语句查看:

docker run -e SPAN_STORAGE_TYPE=elasticsearch jaegertracing/jaeger-collector /go/bin/collector-linux --help

 

固然,通常生产环境你确定不会将collector和elasticsearch安装到同一台机器,至少你可能会安装多个collector,因此,如何跨机器的用collector链接此elasticsearch呢?

你能够用用如下命令:

docker run -d --name jaeger-collector  --restart=always -e SPAN_STORAGE_TYPE=elasticsearch -e ES_SERVER_URLS=http://你的es ip:9200 -e ES_USERNAME=elastic -p 14267:14267 -p 14268:14268 -p 9411:9411 jaegertracing/jaeger-collector

区别在于,你无需使用—link来进行容器互连,只需ES_SERVER_URLS填写对应的ip和port便可;

若是你想看启动是否成功,你可将命令中的“-d”改成“--rm”并删除“--restart=always” ,这样启动日志会即时打印到控制台

--rm命令选项,等价于在容器退出后,执行docker rm -v

--restart=always,一直执行,异常退出尝试重启

启动成功日志:

{"level":"info","ts":1527673610.349252,"caller":"healthcheck/handler.go:99","msg":"Health Check server started","http-port":14269,"status":"unavailable"}

{"level":"info","ts":1527673610.7525811,"caller":"static/strategy_store.go:76","msg":"No sampling strategies provided, using defaults"}

{"level":"info","ts":1527673610.752815,"caller":"collector/main.go:142","msg":"Registering metrics handler with HTTP server","route":"/metrics"}

{"level":"info","ts":1527673610.7528777,"caller":"collector/main.go:150","msg":"Starting Jaeger Collector HTTP server","http-port":14268}

{"level":"info","ts":1527673610.7529178,"caller":"healthcheck/handler.go:133","msg":"Health Check state change","status":"ready"}

 

如你出现如下错误:

"caller":"collector/main.go:102","msg":"Failed to init storage factory","error":"health check timeout: no Elasticsearch node available","errorVerbose":"no Elasticsearch node available

请检查你的elasticsearch地址,

 

 

docker + query安装

同collector同样,若你安装的collector和elasticsearch是在同一台机器上,使用docker容易的--link命令就能够将query和elasticsearch关联上,安装命令以下:

docker run -d --name jaeger-query --restart=always --link elasticsearch:elasticsearch -e SPAN_STORAGE_TYPE=elasticsearch -e ES_SERVER_URLS=http://elasticsearch:9200 -e ES_USERNAME=elastic -e ES_PASSWORD=你的密码 -p 16686:16686/tcp jaegertracing/jaeger-query

其余对应的操做,你参考collector便可,到了这一步,若是你能将collector部署好,那么部署query也是同样的;

注意,ES_USERNAME、ES_PASSWORD这两个环境变量,当你的elasticsearch未设置帐号密码时,你能够不填,也能够填上默认值,elasticsearch的默认ES_USERNAME=elastic,ES_PASSWORD=changeme

部署完成query以后,根据你暴露的端口号(-p 16686:16686/tcp),浏览器输入如下地址(将localhost换成你部署query的地址):

http://localhost:16686

你就会看到开篇的UI界面了,固然数据确定是空空如也。

 

 

docker + agent安装

根据uber jaeger官网的架构,agent通常是和jaeger-client部署在一块儿,agent做为一个基础架构,每一台应用(接入jaeger-client的应用)所在的机器都须要部署一个agent;

根据数据采集原理,jaeger-client采集到数据以后,是经过UDP端口发送到agent的,jaeger-client和agent部署在一块儿的好处是UDP传输数据都在应用所在的机器,可避免UDP的跨网络传输,多一层安全保障。

固然,架构多是多变的,你的agent可能不和jaeger-client所在的应用在一台机器,这个时候,jaeger-client就必须显示的指定其链接的agent的IP及port,具体作法后文jaeger-client对应模块会讲到。

前文提到,jaeger-client采集到数据以后,是经过UDP端口发送到agent的,agent接收到数据以后,使用Uber的Tchannel协议,将数据发送到collector,因此,agent是必须和collector相连的;

docker安装agent命令以下:

docker run   -d  --name jaeger-agent --restart=always -p 5775:5775/udp   -p 6831:6831/udp   -p 6832:6832/udp   -p 5778:5778/tcp   jaegertracing/jaeger-agent   /go/bin/agent-linux --collector.host-port=collector ip:14267

 

如前文所述,你可能不止一个collector,你可能须要这样:

docker run   -d  --name jaeger-agent --restart=always -p 5775:5775/udp   -p 6831:6831/udp   -p 6832:6832/udp   -p 5778:5778/tcp   jaegertracing/jaeger-agent   /go/bin/agent-linux --collector.host-port=collector ip1:14267,collector ip2:14267,collector ip3:14267

--collector.host-port=collector ip1:14267,collector ip2:14267,collector ip3:14267,用逗号分开,链接三个collector,这样的话,这三个collector只要一个存活,agent就能够吧数据传输完成,以免单点故障

 

二进制安装jaeger

以上,使用docker容器化的安装jaeger是很是方便的,而后加上Kubernetes,能够很好的作好监控管理;

具体使用Kubernetes安装jaeger,你可自行研究,官方github地址:https://github.com/jaegertracing/jaeger-kubernetes

固然你也能够不使用docker,linux安装jaeger网上资料不少,如:https://blog.csdn.net/niyuelin1990/article/details/80225305

二进制安装包地址:

https://github.com/jaegertracing/jaeger/releases

如安装agent,如咱们通常应用文件同样:

nohup ./jaeger-agent --collector.host-port=10.100.7.46:14267 1>1.log 2>2.log &

 

jaeger-client

目前jaeger官方支持如下客户端:

Language GitHub Repo
Go jaegertracing/jaeger-client-go
Java jaegertracing/jaeger-client-java
Node.js jaegertracing/jaeger-client-node
Python jaegertracing/jaeger-client-python
C++ jaegertracing/jaeger-client-cpp
C# jaegertracing/jaeger-client-csharp

请他语言也在开发中,具体请看: issue #366.

因为做者只会java开发,仅仅只能写点java client的东西;

Java-client

Jaeger tracing收集数据原理是第一个应用被调用的时候生成一个traceId,而后这个traceId会放到HTTP请求头里面将其传给下一个链路,而后每个链路里面登陆带有这个traceId,最后在elasticsearch/Cassandra里面讲采集到数据聚合成一个调用链路;

因此,jaeger应用场景为HTTP调用链相关的场景,对于dubbo这种RPC调用我的认为是不适用的。

以现有技术体系,目前成熟的框架有springmvc、springboot、springcloud,其中springboot、springcloud基本相同,本文只讲springmvc、springboot,由于两者有一些差异,须要特别处理;

 

springboot接入jaeger client

springboot 接入jaeger github地址以下:

http://planet.jboss.org/post/opentracing_spring_boot_instrumentation

一、在spring boot的项目pom.xml添加依赖

<dependency>

​ <groupId>io.opentracing.contrib</groupId>

​ <artifactId>opentracing-spring-web-autoconfigure</artifactId>

​ <version>0.3.0</version>

</dependency>

<!--添加jaeger-->

<dependency>

​ <groupId>com.uber.jaeger</groupId>

​ <artifactId>jaeger-core</artifactId>

​ <version>0.26.0</version>

</dependency>

二、注入jaeger bean

@Bean
    public Tracer jaegerTracer() {
        com.uber.jaeger.Configuration.SenderConfiguration senderConfiguration = new com.uber.jaeger.Configuration.SenderConfiguration();
        com.uber.jaeger.Configuration.ReporterConfiguration reporterConfiguration = new com.uber.jaeger.Configuration.ReporterConfiguration().withSender(senderConfiguration).withLogSpans(false).withMaxQueueSize(1000).withFlushInterval(100);
        com.uber.jaeger.Configuration.SamplerConfiguration samplerConfiguration = new com.uber.jaeger.Configuration.SamplerConfiguration().withType(ConstSampler.TYPE).withParam(1);
        com.uber.jaeger.Configuration configuration = new com.uber.jaeger.Configuration(traceAppName).withReporter(reporterConfiguration).withSampler(samplerConfiguration);
        return configuration.getTracer();
    }

请注意,此bean所属的类必须随着spring容器启动,已确保spring启动是此bean被注入:

即加上@Configuration 注解便可;

 

SenderConfiguration可供你选择数据上报方式,使用with*方法选择对应的参数:

senderConfiguration.withAgentHost(agent ip) —— 默认值为本机

senderConfiguration.withAgentPort(6831) —— 默认值6831

如上例:SenderConfiguration什么参数都没有,即默认选择本机agent,6831 UDP端口上报采集到的数据

 

HTTP直接上报

你也能够选择绕过agent,直接使用HTTP协议将数据上报给collector,这样,你上文中就能够没必要安装agent;

这是,你的SenderConfiguration设置如下参数:

senderConfiguration.withEndpoint("http://localhost:14268/api/traces");

localhost:14268 为你的collector的ip和端口号,这样你就能够把数据直接上报到collector

固然,你可能会有一些安全方面的考虑,你可使用下面的方式设置你的用户名和密码,或者你的token

senderConfiguration.withAuthPassword(password);

senderConfiguration.withAuthUsername(username);

senderConfiguration.withAuthToken(authToken);

 

ReporterConfiguration参数:

withSender -------选择发送方式

withLogSpans -------是否日志上报

withMaxQueueSize -------数据最大累计量

withFlushInterval -------报告间隔的刷新( ms )

你能够根据大家业务系统给的数据量选择合适的参数;

根据uber jaeger"不怜悯"数据原则,若你选择withMaxQueueSize为1000(条),withFlushInterval为1000(ms),即1000毫秒之内只会有1000条数据上报,其余数据会丢掉

 

SamplerConfiguration 参数:

SamplerConfiguration可设置你的采样策略:

withType 采样策略:

  1. ConstSampler,全量采集

  2. ProbabilisticSampler ,几率采集,默认万份之一

  3. RateLimitingSampler ,限速采集,每秒只能采集必定量的数据

  4. RemotelyControlledSampler ,一种动态采集策略,根据当前系统的访问量调节采集策略

withParam 采样率

withManagerHostPort 采样策略配置 默认为:localhost:5778

 

当使用uber jaeger时,若是你要在嵌入tracing的应用里面发送HTTP请求,你可能须要用到RestTemplate,不然你用的HTTP client会致使trace id丢失,从而致使调用链断裂;

因此你还须要注入RestTemplate bean,方式和jaeger bean同样

@Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder.build();
    }

说的再多,不过给你一个例子:

https://github.com/pavolloffay/opentracing-java-examples

 

Springmvc接入jaeger client

Springmvc 接入jaeger github地址以下:

https://github.com/opentracing-contrib/java-spring-web

一、在spring mvc的项目pom.xml添加依赖

<dependency>
            <groupId>io.opentracing.contrib</groupId>
            <artifactId>opentracing-spring-web</artifactId>
            <version>0.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.uber.jaeger</groupId>
            <artifactId>jaeger-core</artifactId>
            <version>0.26.0</version>
        </dependency>

注意和springboot的区别

 

想在spring mvc引入tracing功能,配置中是必须添加TracingFilter and TracingHandlerInterceptor,这两个类 是必须的,你能够经过手动注入或者CDI的方式注入

具体代码示例以下:

@EnableWebMvc
@Configuration
@Import({TracingBeansConfiguration.class})
public class SpringMVCConfiguration extends WebMvcConfigurerAdapter implements ServletContextListener {
​
    @Autowired
    private List<HandlerInterceptorSpanDecorator> spanDecorators;
​
    @Autowired
    private Tracer tracer;
    
​
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        GlobalTracer.register(tracer);
        registry.addInterceptor(new TracingHandlerInterceptor(tracer, spanDecorators));
    }
​
​
    @Bean
    public RestTemplate restTemplate(Tracer tracer) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.<ClientHttpRequestInterceptor>singletonList(
                new TracingRestTemplateInterceptor(tracer)));
        return restTemplate;
    }
​
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        sce.getServletContext().setAttribute(TracingFilter.SPAN_DECORATORS,
                Collections.singletonList(ServletFilterSpanDecorator.STANDARD_TAGS));
    }
​
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        
    }
}

这段代码:implements ServletContextListener

因此咱们须要在web.xml里面讲这个listener 配置进去

 <listener>
        <listener-class>com.xxx.ecm.platform.gw.server.trace.SpringMVCConfiguration</listener-class>
      </listener>

同事,咱们看到里面 引入了这个类:@Import({TracingBeansConfiguration.class})

TracingBeansConfiguration代码以下:

@org.springframework.context.annotation.Configuration
public class TracingBeansConfiguration {
​
    @Value("${trace.app.name}")
    private String traceAppName;
​
    @Bean
    public Tracer jaegerTracer() {
        com.uber.jaeger.Configuration.SenderConfiguration senderConfiguration = new com.uber.jaeger.Configuration.SenderConfiguration();
        com.uber.jaeger.Configuration.ReporterConfiguration reporterConfiguration = new com.uber.jaeger.Configuration.ReporterConfiguration().withSender(senderConfiguration).withLogSpans(false).withMaxQueueSize(1000).withFlushInterval(100);
        com.uber.jaeger.Configuration.SamplerConfiguration samplerConfiguration = new com.uber.jaeger.Configuration.SamplerConfiguration().withType(ConstSampler.TYPE).withParam(1);
        com.uber.jaeger.Configuration configuration = new com.uber.jaeger.Configuration(traceAppName).withReporter(reporterConfiguration).withSampler(samplerConfiguration);
        return configuration.getTracer();
    }
​
    @Bean
    public List<HandlerInterceptorSpanDecorator> spanDecorators() {
        return Arrays.asList(HandlerInterceptorSpanDecorator.STANDARD_LOGS,
                HandlerInterceptorSpanDecorator.HANDLER_METHOD_OPERATION_NAME);
    }

此class的做用就是初始化两个bean,Tracer bean和HandlerInterceptorSpanDecorator bean,以供SpringMVCConfiguration使用,

其中Tracer bean做用和配置和咱们使用spring boot相同,详细配置请参考前文。

另外还须要把tracing filter 配置到配置文件:

<!-- tracing filter -->
      <filter>
        <filter-name>tracingFilter</filter-name>
        <filter-class>io.opentracing.contrib.web.servlet.filter.TracingFilter</filter-class>
        <async-supported>true</async-supported>
      </filter>
      <filter-mapping>
        <filter-name>tracingFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>

 

OK!你已经在springmvc配置好你的系统了!(spring-web版本要4.3.8.RELEASE以上)

 

 

端口号说明

咱们从前文中能够看到,咱们安装jaeger各个组件的时候使用了不少端口号,具体这些端口号都是些什么做用呢?

下面将一一列举其做用:

elasticsearch暴露以下端口

端口号 协议 功能
9200 HTTP 经过http协议链接es使用的端口
9300 TCP 经过tcp协议链接es使用的端口

 

agent 暴露以下端口

端口号 协议 功能
5775 UDP 经过兼容性 thrift 协议,接收 zipkin thrift 类型的数据
6831 UDP 经过二进制 thrift 协议,接收 jaeger thrift 类型的数据
6832 UDP 经过二进制 thrift 协议,接收 jaeger thrift 类型的数据
5778 HTTP 可用于配置采样策略

collector 暴露以下端口

端口号 协议 功能
14267 TChannel 用于接收 jaeger-agent 发送来的 jaeger.thrift 格式的 span
14268 HTTP 能直接接收来自客户端的 jaeger.thrift 格式的 span
9411 HTTP 能经过 JSON 或 Thrift 接收 Zipkin spans,默认关闭

query 暴露以下端口

端口号 协议 功能
16686 HTTP 1. /api/* - API 端口路径 2. / - Jaeger UI 路径

 

 

jaeger dependencies

完成安装jaeger之后,你应该能够在jaeger ui上看到效果了,你能够采集到对应的数据,而且可以查询到调用链路。可是你会发现search按钮旁边,还有一个dependencies选项,你点开确什么也没有。

此时你还须要安装jaeger dependencies了,并且他须要定时执行,由于jaeger dependencies是在执行时去捞取对应的数据。

你能够定时执行如下代码:

STORAGE=elasticsearch ES_NODES=http://localhost:9200 java -jar jaeger-spark-dependencies.jar

ES_NODES为前面安装的es地址

jaeger-spark-dependencies.jar 怎么来的?

你能够搜索对应的资料下载,可是建议你下载官方源码,本身打包,github地址以下:

https://github.com/jaegertracing/spark-dependencies

下载源码执行mvn clean install -DskipTests打包,或许你能够crontab定时执行脚本,来跑天天的数据

 

另外,你也可使用docker执行:

 docker run  --rm  --name  spark-dependencies  --env STORAGE=elasticsearch --env ES_NODES=http://localhost:9200 jaegertracing/spark-dependencies

ES_NODES为前面安装的es地址

固然,至于docker怎么执行定时任务,或者Kubernetes怎么执行CronJob,你能够自行研究dokcer或Kubernetes相关的知识。固然,你能够crontab定时执行脚本。