最近因工做须要在研究traing系统,最后选了jaeger,下面是一些总结,同时摘抄了网上的一些资料,并结合本身实践过程当中遇到的一些什么问题,欢迎指正,如你也在使用jaeger,或者想使用jaeger,途中遇到什么困难,可发邮件交流:honglouleiyan@163.comhtml
随着公司的发展,业务不断的增长,模块的不断拆分,系统间业务调用就变得越复杂,对定位线上故障带来很大困难。整个调用链不透明,犹如系统被蒙上一块黑纱,当线上遇到故障时,整个技术部就陷入痛苦的漩涡。这时候分布式追踪系统应运而生,如揭开了黑纱,让阳光照进黑暗。java
官方文档node
OpenTracing语义规范(中文版)python
OpenTracing语义惯例linux
Jaeger是Uber开发的一套分布式追踪系统,已在Uber大规模使用。并在2017-9-13 加入CNCF 开源组织。使用Jaeger能够很是直观的展现整个分布式系统的调用链,由此能够很好发现和解决问题:github
https://www.jaegertracing.io/docs/web
https://github.com/jaegertracing/jaegerspring
使用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!
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
首先,你安装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
若你安装的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地址,
同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界面了,固然数据确定是空空如也。
根据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就能够吧数据传输完成,以免单点故障
以上,使用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官方支持如下客户端:
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的东西;
Jaeger tracing收集数据原理是第一个应用被调用的时候生成一个traceId,而后这个traceId会放到HTTP请求头里面将其传给下一个链路,而后每个链路里面登陆带有这个traceId,最后在elasticsearch/Cassandra里面讲采集到数据聚合成一个调用链路;
因此,jaeger应用场景为HTTP调用链相关的场景,对于dubbo这种RPC调用我的认为是不适用的。
以现有技术体系,目前成熟的框架有springmvc、springboot、springcloud,其中springboot、springcloud基本相同,本文只讲springmvc、springboot,由于两者有一些差异,须要特别处理;
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 采样策略:
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 github地址以下:
一、在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之后,你应该能够在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定时执行脚本。