Jaeger-Uber开源的一个基于Go的分布式追踪系统
最近因工作需要在研究traing系统,最后选了jaeger,下面是一些总结,同时摘抄了网上的一些资料,并结合自己实践过程中遇到的一些什么问题,欢迎指正,如你也在使用jaeger,或者想使用jaeger,途中遇到什么困难,可发邮件交流:honglouleiyan@163.com
前言
随着公司的发展,业务不断的增加,模块的不断拆分,系统间业务调用就变得越复杂,对定位线上故障带来很大困难。整个调用链不透明,犹如系统被蒙上一块黑纱,当线上遇到故障时,整个技术部就陷入痛苦的漩涡。这时候分布式追踪系统应运而生,如揭开了黑纱,让阳光照进黑暗。
Opentracing相关文档
分布式追踪系统Jaeger
Jaeger是Uber开发的一套分布式追踪系统,已在Uber大规模使用。并在2017-9-13 加入CNCF 开源组织。使用Jaeger可以非常直观的展示整个分布式系统的调用链,由此可以很好发现和解决问题:
jaeger架构
jaeger官网网址
https://www.jaegertracing.io/docs/
jaeger github地址
https://github.com/jaegertracing/jaeger
jaeger 部署
使用docker部署官网资料和网站资料比较多,不过都是使用cassandra作为存储引擎,并且一般文章并没有给出具体的建表语句,官网文档也没有找到建表语句!
如果你需要使用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
注意:
1、此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
2、请使用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的地址):
你就会看到开篇的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
1、在spring boot的项目pom.xml添加依赖
io.opentracing.contrib
opentracing-spring-web-autoconfigure
0.3.0
com.uber.jaeger
jaeger-core
0.26.0
2、注入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 采样策略:
ConstSampler,全量采集
ProbabilisticSampler ,概率采集,默认万份之一
RateLimitingSampler ,限速采集,每秒只能采集一定量的数据
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(); }
说的再多,不过给你一个例子:
Springmvc接入jaeger client
Springmvc 接入jaeger github地址如下:
1、在spring mvc的项目pom.xml添加依赖
注意和springboot的区别
想在spring mvc引入tracing功能,配置中是必须添加TracingFilter
and TracingHandlerInterceptor
,这两个类 是必须的,你可以通过手动注入或者CDI的方式注入
具体代码示例如下:
@EnableWebMvc
@Configuration
@Import({TracingBeansConfiguration.class})
public class SpringMVCConfiguration extends WebMvcConfigurerAdapter implements ServletContextListener {
@Autowired
private List
}
}
这段代码:implements ServletContextListener
所以我们需要在web.xml里面讲这个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
此class的作用就是初始化两个bean,Tracer bean和HandlerInterceptorSpanDecorator bean,以供SpringMVCConfiguration使用,
其中Tracer bean作用和配置和我们使用spring boot相同,详细配置请参考前文。
另外还需要把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地址如下:
下载源码执行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定时执行脚本。