最近因工做須要在研究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定時執行腳本。