背景
? ? ? ? 無論你的系統(tǒng)是龐大的單應(yīng)用架構(gòu)蛮粮,還是采用的微服務(wù)架構(gòu)溅固,只要是調(diào)用過程非常復(fù)雜有缆,都會(huì)存在如何追蹤各個(gè)方法或者服務(wù)間調(diào)用異常如何被開發(fā)和運(yùn)維人員快速定位的問題名秀,為了解決這個(gè)問題励负,你勢(shì)必需要記錄大量的日志。
? ? ? ?舉個(gè)例子匕得,對(duì)于一個(gè)大流量的Web應(yīng)用通常以Stateless方式設(shè)計(jì)继榆,這樣可以更方便的進(jìn)行水平擴(kuò)容。但是隨著應(yīng)用實(shí)例數(shù)量越來越多汁掠,我們查詢?nèi)罩揪驮絹碓嚼щy裕照。在沒有日志系統(tǒng)的情況下,首先我們需要定位到請(qǐng)求的服務(wù)器地址调塌,如果每臺(tái)服務(wù)器都部署了多個(gè)應(yīng)用實(shí)例晋南,我們則需要去每個(gè)應(yīng)用實(shí)例的日志目錄下去找日志文件。每個(gè)服務(wù)可能還會(huì)設(shè)置日志滾動(dòng)策略(如:每200M一個(gè)文件)羔砾,還有日志壓縮歸檔策略负间。
? ? ? ? 如此,我們查詢一條出錯(cuò)信息就要在茫茫多的日志文件里去找到它姜凄,于是使出我們的十八般武藝head less tail grep wc awk count cut政溃,但是如果需要統(tǒng)計(jì)最近3天的某個(gè)接口的異常次數(shù),或者超時(shí)次數(shù)态秧。董虱。。申鱼。除了上面出現(xiàn)的狀況我們還需要考慮:日志量太大如何歸檔愤诱、文本搜索太慢怎么辦?
? ? ? ? 但是對(duì)于故障排查肯定是希望能夠快速的進(jìn)行日志查詢捐友、定位淫半、解決問題,對(duì)于實(shí)時(shí)性要求非常高匣砖。為了解決這個(gè)問題科吭,日志追蹤系統(tǒng)應(yīng)運(yùn)而生。
概念
? ??????先介紹一個(gè)概念:分布式跟蹤猴鲫,或分布式追蹤对人。
????????電商平臺(tái)由數(shù)以百計(jì)的分布式服務(wù)構(gòu)成,每一個(gè)請(qǐng)求路由過來后拂共,會(huì)經(jīng)過多個(gè)業(yè)務(wù)系統(tǒng)并留下足跡牺弄,并產(chǎn)生對(duì)各種Cache或DB的訪問,但是這些分散的數(shù)據(jù)對(duì)于問題排查匣缘,或是流程優(yōu)化都幫助有限猖闪。對(duì)于這么一個(gè)跨進(jìn)程/跨線程的場(chǎng)景鲜棠,匯總收集并分析海量日志就顯得尤為重要。要能做到追蹤每個(gè)請(qǐng)求的完整調(diào)用鏈路培慌,收集調(diào)用鏈路上每個(gè)服務(wù)的性能數(shù)據(jù)豁陆,計(jì)算性能數(shù)據(jù)和比對(duì)性能指標(biāo)(SLA),甚至在更遠(yuǎn)的未來能夠再反饋到服務(wù)治理中吵护,那么這就是分布式跟蹤的目標(biāo)了盒音。在業(yè)界,Twitter 的 zipkin 和淘寶的鷹眼就是類似的系統(tǒng)馅而,它們都起源于 Google Dapper 論文祥诽。
????????整理一下,Google叫Dapper瓮恭,淘寶叫鷹眼雄坪,Twitter叫ZipKin,京東商城叫Hydra屯蹦,eBay叫Centralized Activity Logging (CAL)维哈,大眾點(diǎn)評(píng)網(wǎng)叫CAT,其底層實(shí)現(xiàn)的追蹤邏輯登澜,幾乎都是一樣的阔挠。
分布式追蹤系統(tǒng)的設(shè)計(jì)理念
(1)低侵入性——作為非業(yè)務(wù)組件,應(yīng)當(dāng)盡可能少侵入或者無侵入其他業(yè)務(wù)系統(tǒng)脑蠕,對(duì)于使用方透明购撼,減少開發(fā)人員的負(fù)擔(dān);
(2)靈活的應(yīng)用策略——可以(最好隨時(shí))決定所收集數(shù)據(jù)的范圍和粒度谴仙;
(3)時(shí)效性——從數(shù)據(jù)的收集和產(chǎn)生迂求,到數(shù)據(jù)計(jì)算和處理,再到最終展現(xiàn)狞甚,都要求盡可能快锁摔;
(4)決策支持——這些數(shù)據(jù)是否能在決策支持層面發(fā)揮作用,特別是從 DevOps 的角度哼审;
(5)可視化才是王道。
Zipkin出場(chǎng)
? ? ? ? 在眾多的追蹤系統(tǒng)中孕豹,本文只是對(duì)Zipkin的應(yīng)用做說明涩盾。
? ? ? ? 最初接觸Zipkin,是在《Spring Cloud 微服務(wù)實(shí)戰(zhàn)》這本書中的 Spring Cloud Sleuth章節(jié)励背,按照這個(gè)章節(jié)寫的例子學(xué)習(xí)下來春霍,踩了無數(shù)個(gè)坑,弄的我是云里霧里叶眉。索性放棄書上的例子址儒,直接看 Zipkin官網(wǎng)(https://zipkin.io)和Spinrg官網(wǎng)中關(guān)于Cloud Sleuth章節(jié)(https://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/2.1.2.RELEASE/multi/multi_spring-cloud-sleuth.html)芹枷,同時(shí)查閱了多個(gè)網(wǎng)絡(luò)上的文章,才發(fā)現(xiàn)隨著Spring boot 2.X的問世莲趣,Spring Cloud Sleuth關(guān)于Zipkin的實(shí)現(xiàn)方式發(fā)生了非常大的變化鸳慈。(在寫這篇文章時(shí),Zipkin的版本是V2.14.0)
? ??????Spring Cloud Sleuth 在Spring Boot 1.x時(shí)代喧伞,是對(duì)Zipkin做一個(gè)完全整合走芋,不僅實(shí)現(xiàn)了以 HTTP 的方式收集跟蹤信息,還實(shí)現(xiàn)了通過消息中間件來對(duì)跟蹤信息進(jìn)行異步收集的封裝潘鲫。就連Zipkin服務(wù)器翁逞,也做了一層封裝。而到了 Spring Boot 2.0 之后?Zipkin 不再推薦我們?cè)僮远x Server 端了溉仑,Sleuth專注于對(duì)Dapper 中的算法進(jìn)行封裝挖函,spring-cloud-starter-zipkin?只是對(duì)Zipkin客戶端的封裝,對(duì)于Sleuth在工程中如何使用浊竟,Spring官網(wǎng)上是這樣寫的:
Only Sleuth (log correlation):如果你只想使用Sleuth功能怨喘,而不想與Zipkin做集成的話,那么你只需要引入spring-cloud-starter-sleuth就可以了逐沙。我覺得哲思,這種方式,對(duì)于普通使用者是很少會(huì)這樣用的吩案。是否是給那些牛到自己開發(fā)追蹤服務(wù)的人準(zhǔn)備的棚赔?誰知道呢!
Sleuth with Zipkin via HTTP :不重復(fù)發(fā)明輪子徘郭,是本人的一貫主張靠益。如果你想使用Sleuth并通過HTTP方式集成ZipkinServer,你只需要引入spring-cloud-starter-zipkin 就可以了残揉。你可能會(huì)問:spring-cloud-starter-sleuth不需要引入了嗎? 我的回答是:需要胧后,但它會(huì)被spring-cloud-starter-zipkin間接依賴而自動(dòng)引入的。
Sleuth with Zipkin over RabbitMQ or Kafka :最后一種使用方式是抱环,如果你不想通過HTPP方式集成ZipkinServer壳快,而是通過RabbitMQ或者Kafka這些消息中間件做異步消息處理的話(筆者推薦這種用法),你除了需要引入spring-cloud-starter-zipkin镇草,還需要引入spring-rabbit(本文以Rabbit為例)眶痰。還有一點(diǎn)就是:如果使用了消息中間件傳遞消息,那么Zipkin的服務(wù)端也需要做相應(yīng)的配置梯啤,才能監(jiān)聽處理消息竖伯。
RabbitMQ相關(guān)參數(shù)配置
spring:
? rabbitmq:
? ? host: 10.10.10.10
? ? port: 5672
? ? username: guest
? ? password: guest
同時(shí),在啟動(dòng)Zipkin服務(wù)端的時(shí)候,添加以上相同的Rabiit配置
D:\>java -jar zipkin.jar --zipkin.collector.rabbitmq.addresses=10.10.10.10?
端口如果不寫默認(rèn)是5672七婴,用戶名密碼也是采用的默認(rèn)值祟偷,如果你的配置不是采用默認(rèn)值,需要維護(hù)相應(yīng)參數(shù)打厘。
啟動(dòng)服務(wù)
啟動(dòng)集成了Sleuth的服務(wù)和Zipkin 服務(wù)端修肠,進(jìn)行訪問帶多級(jí)調(diào)用的服務(wù)(省略具體操作),打開Zipkin的UI界面http://localhost:9411/zipkin/? 婚惫,如下圖:
可以看到氛赐,剛才的調(diào)用鏈已經(jīng)可以查詢到了。點(diǎn)擊某條記錄先舷,進(jìn)入調(diào)用詳細(xì)頁面艰管。
可以看到每個(gè)服務(wù)的調(diào)用時(shí)間,時(shí)長蒋川,是否異常等信息牲芋。這樣,你就可以快速定位那些響應(yīng)時(shí)間超長的請(qǐng)求或者發(fā)生異常的請(qǐng)求捺球。
存儲(chǔ)
Zipkin最初是為在Cassandra上存儲(chǔ)數(shù)據(jù)而構(gòu)建的缸浦,因?yàn)镃assandra是可擴(kuò)展的,有一個(gè)靈活的模式氮兵,并且在Twitter中廣泛使用裂逐。然而,我們使這個(gè)組件可插拔泣栈。除了Cassandra之外卜高,Zipkin的存儲(chǔ)還支持ElasticSearch和MySQL。
要想把記錄的信息存儲(chǔ)到Mysqls南片,你需要在啟動(dòng)Zipkin Server的時(shí)候添加如下的參數(shù):
D:\>java -jar zipkin.jar --zipkin.collector.rabbitmq.addresses=10.10.10.10 --STORAGE_TYPE=mysql --MYSQL_DB=zipkin --MYSQL_HOST=10.10.1.10 --MYSQL_TCP_PORT=3306 --MYSQL_USER=root --MYSQL_PASS=123456
并且掺涛,要確保在zipkin庫下,已經(jīng)存在了需要用到的表疼进。建表語句需要與Zipkin Server的版本相對(duì)應(yīng)薪缆,避免未知錯(cuò)誤:
CREATETABLEIFNOTEXISTSzipkin_spans (
`trace_id_high`BIGINTNOTNULLDEFAULT0COMMENT'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id`BIGINTNOTNULL,
`id`BIGINTNOTNULL,
`name`VARCHAR(255)NOTNULL,
`remote_service_name`VARCHAR(255),
`parent_id`BIGINT,
`debug`BIT(1),
`start_ts`BIGINTCOMMENT'Span.timestamp(): epoch micros used for endTs query and to implement TTL',
`duration`BIGINTCOMMENT'Span.duration(): micros used for minDuration and maxDuration query',
PRIMARYKEY(`trace_id_high`,`trace_id`,`id`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSEDCHARACTERSET=utf8 COLLATE utf8_general_ci;
ALTERTABLEzipkin_spans ADD INDEX(`trace_id_high`,`trace_id`) COMMENT'for getTracesByIds';
ALTERTABLEzipkin_spans ADD INDEX(`name`) COMMENT'for getTraces and getSpanNames';
ALTERTABLEzipkin_spans ADD INDEX(`remote_service_name`) COMMENT'for getTraces and getRemoteServiceNames';
ALTERTABLEzipkin_spans ADD INDEX(`start_ts`) COMMENT'for getTraces ordering and range';
CREATETABLEIFNOTEXISTSzipkin_annotations (
`trace_id_high`BIGINTNOTNULLDEFAULT0COMMENT'If non zero, this means the trace uses 128 bit traceIds instead of 64 bit',
`trace_id`BIGINTNOTNULLCOMMENT'coincides with zipkin_spans.trace_id',
`span_id`BIGINTNOTNULLCOMMENT'coincides with zipkin_spans.id',
`a_key`VARCHAR(255)NOTNULLCOMMENT'BinaryAnnotation.key or Annotation.value if type == -1',
`a_value`BLOB COMMENT'BinaryAnnotation.value(), which must be smaller than 64KB',
`a_type`INTNOTNULLCOMMENT'BinaryAnnotation.type() or -1 if Annotation',
`a_timestamp`BIGINTCOMMENT'Used to implement TTL; Annotation.timestamp or zipkin_spans.timestamp',
`endpoint_ipv4`INTCOMMENT'Null when Binary/Annotation.endpoint is null',
`endpoint_ipv6`BINARY(16) COMMENT'Null when Binary/Annotation.endpoint is null, or no IPv6 address',
`endpoint_port`SMALLINTCOMMENT'Null when Binary/Annotation.endpoint is null',
`endpoint_service_name`VARCHAR(255) COMMENT'Null when Binary/Annotation.endpoint is null'
) ENGINE=InnoDB ROW_FORMAT=COMPRESSEDCHARACTERSET=utf8 COLLATE utf8_general_ci;
ALTERTABLEzipkin_annotations ADD UNIQUEKEY(`trace_id_high`,`trace_id`,`span_id`,`a_key`,`a_timestamp`) COMMENT'Ignore insert on duplicate';
ALTERTABLEzipkin_annotations ADD INDEX(`trace_id_high`,`trace_id`,`span_id`) COMMENT'for joining with zipkin_spans';
ALTERTABLEzipkin_annotations ADD INDEX(`trace_id_high`,`trace_id`) COMMENT'for getTraces/ByIds';
ALTERTABLEzipkin_annotations ADD INDEX(`endpoint_service_name`) COMMENT'for getTraces and getServiceNames';
ALTERTABLEzipkin_annotations ADD INDEX(`a_type`) COMMENT'for getTraces and autocomplete values';
ALTERTABLEzipkin_annotations ADD INDEX(`a_key`) COMMENT'for getTraces and autocomplete values';
ALTERTABLEzipkin_annotations ADD INDEX(`trace_id`,`span_id`,`a_key`) COMMENT'for dependencies job';
CREATETABLEIFNOTEXISTSzipkin_dependencies (
`day`DATENOTNULL,
`parent`VARCHAR(255)NOTNULL,
`child`VARCHAR(255)NOTNULL,
`call_count`BIGINT,
`error_count`BIGINT,
PRIMARYKEY(`day`,`parent`,`child`)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSEDCHARACTERSET=utf8 COLLATE utf8_general_ci;