Java中的OpenTracing使用實(shí)例
構(gòu)件組織
OpenTracing API的Java構(gòu)件如下:
- opentracing-api:主要的API,無其他依賴。
- opentracing-noop:為主要API提供無意義實(shí)現(xiàn)(NoopTracer),依賴于opentracing-api。
- opentracing-util:工具類,例如GlobalTracer和默認(rèn)的基于ThreadLocal存儲(chǔ)的ScopeManager實(shí)現(xiàn),依賴于上面所有的構(gòu)件。
- opentracing-mock:用于測(cè)試的mock層。包含MockTracer,簡(jiǎn)單的將Span存儲(chǔ)在內(nèi)存中,依賴于opentracing-api和opentracing-noop。
安裝(Maven)
<dependency> <groupId>io.opentracing</groupId> <artifactId>opentracing-api</artifactId> <version>VERSION</version> </dependency>
也可以使用opentracing-noop,opentracing-mock,opentracing-util來安裝其他的構(gòu)件,如果安裝多個(gè)構(gòu)件,需要提供一致的VERSION。
主要API
主要的OpenTracing API將所有主要組件聲明為接口以及輔助類,例如Tracer,Span,SpanContext,Scope,ScopeManager,F(xiàn)ormat(用映射定義通用的SpanContext注入和提取格式)。
OpenTracing 社區(qū)貢獻(xiàn)
除了官方的API,也有一些苦在opentracing-contribe,保管通用的輔助類像TracerResolver和框架工具庫
例如 Java Web Servlet Filter and Spring Cloud,可以用于在使用這些框架工具的項(xiàng)目中方便的集成OpenTracing。
Quick Start
下面使用opentracing-mock中的MockTracer來進(jìn)行示例:
import java.util.Map; import io.opentracing.mock.MockTracer; import io.opentracing.mock.MockSpan; import io.opentracing.tags.Tags; // Initialize MockTracer with the default values. MockTracer tracer = new MockTracer(); // Create a new Span, representing an operation. MockSpan span = tracer.buildSpan("foo").start(); // Add a tag to the Span. span.setTag(Tags.COMPONENT, "my-own-application"); // do something for business logic // Finish the Span. span.finish(); // Analyze the saved Span. System.out.println("Operation name = " + span.operationName()); System.out.println("Start = " + span.startMicros()); System.out.println("Finish = " + span.finishMicros()); // Inspect the Span's tags. Map<String, Object> tags = span.tags();
使用Span
在任何時(shí)間點(diǎn),OpenTracing Java API僅允許同一個(gè)線程中只存在一個(gè)活躍的Span。但是在同一個(gè)線程中允許同時(shí)存在符合下述條件的Span:
- Started,新建的Span,但是沒有在任何作用域(Scope)中激活
- Not Finished,調(diào)用finish方法之前均處于該狀態(tài)
- Not Active,未被激活
同一個(gè)線程上可能有多個(gè)Span,如果它們:
- 正在等待I/O操作完成
- 被子Span阻塞
- 或被溢出關(guān)鍵路徑
人工地將活躍的Span從一個(gè)函數(shù)傳遞到另一個(gè)函數(shù)是極為不便的,所以O(shè)penTracing要求每個(gè)Tracer包含一個(gè)作用域管理器(ScopeManager)。ScopeManager可以通過Scope來方法激活的Span,Scope來管理Span的激活與失活。ScopeManager API運(yùn)行將Span傳到到另一個(gè)線程或回調(diào),而不是傳遞Scope。
開發(fā)這在創(chuàng)建新的Span時(shí),如果當(dāng)前線程的Scope中已經(jīng)存在活躍的Span,則該活躍Span則會(huì)成為新創(chuàng)建Span的父親,除非開發(fā)者在buildSpan()時(shí)調(diào)用ignoreActiveSpan()或者明確指定父上下文(parent context)。
訪問活躍的Span
開發(fā)者可以通Scope對(duì)象訪問活躍的Span
io.opentracing.Tracer tracer = ...; ... Scope scope = tracer.scopeManager().active(); if (scope != null) { scope.span().log("..."); }
在線程間移動(dòng)Span
使用OpenTracing API,開發(fā)者可以在多個(gè)不同的線程間傳輸Span。
一個(gè)Span的生命周期可以在一個(gè)線程中開始在另一個(gè)線程中結(jié)束。
不支持傳遞Scope到另一個(gè)線程或回調(diào)。Span的內(nèi)部時(shí)序細(xì)節(jié)看來如下:
[ ServiceHandlerSpan ] | FunctionA | waiting on an RPC | FunctionB | ---------------------------------------------------------> time
當(dāng)執(zhí)行FunctionA和FunctionB時(shí)ServiceHandlerSpan是活躍的,但是在等待RPC調(diào)用的過程中是失活的。RPC可能有自己的Span,但我們現(xiàn)在只關(guān)注ServerHandlerSpan如何從FunctionA傳播到FunctionB。使用ScopeManager API可以在FunctionA中獲取Span,RPC結(jié)束后在FunctionB中重新獲取Span。步驟如下:
- 通過startManager或startActive(false)方法創(chuàng)建一個(gè)Span以阻止Scope失活時(shí)令Span終止。
- 在回調(diào)代碼(閉包/Runnable/Future)中調(diào)用tracer.scopeManager().active(span,false)來重新激活Span獲取一個(gè)新的Scope,當(dāng)Span不再活躍時(shí)關(guān)閉Scope(或者使用try-with-resources以簡(jiǎn)化代碼)
- 在回調(diào)代碼末尾,調(diào)用tracer.scopeManager().active(span,true)來重新激活Span并得到一個(gè)自動(dòng)關(guān)閉的Scope。
代碼如下:
io.opentracing.Tracer tracer = ...;//通過具體的實(shí)現(xiàn)來創(chuàng)建tracer對(duì)象 ... // STEP 1 ABOVE: 開啟新的Span和Scope try (Scope scope = tracer.buildSpan("ServiceHandlerSpan").startActive(false)) { // Span在Scope中被激活 final Span span = scope.span(); doAsyncWork(new Runnable() { @Override public void run() { // STEP 2 ABOVE: 重新激活Span // 如果需要自動(dòng)終止激活的Span,傳遞true給active方法 try (Scope scope = tracer.scopeManager().activate(span, true)) { ... } } }); }
通過框架的攔截器能力實(shí)現(xiàn)HTTP請(qǐng)求追蹤
通過上文中的代碼,我們知道了如何使用Tracer對(duì)象構(gòu)建Span,如何在線程中激活Span,以及如何在異步環(huán)境的不同線程間傳遞Span。
在實(shí)際的業(yè)務(wù)開發(fā)中,我們很難使用這種侵入的方式來實(shí)現(xiàn)追蹤,更多的是利用各種框架提供的攔截器機(jī)制,來對(duì)各種業(yè)務(wù)調(diào)用進(jìn)行自動(dòng)追蹤,比如Spring AOP,Servlet Filter,等等。下面一段代碼展示了如何通過Servlet Filter來進(jìn)行服務(wù)端的HTTP請(qǐng)求追蹤。
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpResponse = (HttpServletResponse) servletResponse; // 從Http Headers中提取上下文 SpanContext extractedContext = tracer.extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(httpRequest)); // 創(chuàng)建并激活一個(gè)新的Span,如果前面提取到的上下文不為null,則作為父SpanContext final Scope scope = tracer.buildSpan(httpRequest.getMethod()) .asChildOf(extractedContext) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER) .startActive(false); final Span span = scope.span(); // 追蹤請(qǐng)求的地址 span.setTag(Tags.HTTP_URL, httpRequest.getRequestURI()); try { // 實(shí)際執(zhí)行過濾器鏈處理請(qǐng)求 chain.doFilter(servletRequest, servletResponse); } finally { if (httpRequest.isAsyncStarted()) { // 如果請(qǐng)求是異步的,那么需要將Span對(duì)象傳遞到異步回調(diào)中 httpRequest.getAsyncContext() .addListener(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { // 回調(diào)是在異步線程中執(zhí)行的 // 需要使用Scope在異步先線程中激活Span try(Scope sc = tracer.scopeManager().activite(span, true)){ HttpServletResponse httpResponse = (HttpServletResponse) event.getSuppliedResponse(); // 追蹤響應(yīng)狀態(tài) sc.span().setTag(Tags.HTTP_STATUS, httpResponse.getStatus()); } } @Override public void onTimeout(AsyncEvent event) throws IOException { try(Scope sc = tracer.scopeManager().activite(span, true)){ // 記錄錯(cuò)誤 sc.span().setTag(Tags.ERROR, true) sc.span().log(Maps.of(Fields.EVENT, event, Fields.ERROR_KIND, "TIMEOUT")) } } @Override public void onError(AsyncEvent event) throws IOException { try(Scope sc = tracer.scopeManager().activite(span, true)){ // 記錄錯(cuò)誤 sc.span().setTag(Tags.ERROR, true) sc.span().log( Maps.of(Fields.EVENT, event, Fields.ERROR_KIND, event.getThrowable().getClass())) } } @Override public void onStartAsync(AsyncEvent event) throws IOException { } }); } else { // 如果是同步請(qǐng)求,直接終止Span scope.span().finish(); } // 釋放當(dāng)前線程中的Span scope.close(); } }
利用這個(gè)過濾器,在Servlet應(yīng)用中,用于追蹤請(qǐng)求代碼與業(yè)務(wù)代碼解耦,并且僅需要一次編寫,下面來看客戶端如何追蹤請(qǐng)求并向處理請(qǐng)求的服務(wù)端傳遞上下文,以Spring RestTemplate為例:
可以通過RestTemplate.setInterceptors注冊(cè)攔截器。
@Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] body, ClientHttpRequestExecution execution) throws IOException { // 創(chuàng)建新的Span,以當(dāng)前線程中的SpanContext為父,如沒有則自己成為根Span try (Scope scope = tracer.buildSpan(httpRequest.getMethod().toString()) .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_CLIENT).startActive(true)) { // 追蹤請(qǐng)求地址 scope.span().setTag(Tags.HTTP_URL, httpRequest.getURI().toString()) // 將SpanContext注入到請(qǐng)求頭中 // 看前文中的代碼可以知道,服務(wù)端通過Tracer.extract可以從請(qǐng)求頭中提取出SpanContext tracer.inject(scope.span().context(), Format.Builtin.HTTP_HEADERS, new HttpHeadersCarrier(httpRequest.getHeaders())); // 實(shí)際執(zhí)行請(qǐng)求 return execution.execute(httpRequest, body); } }
使用opentracing-spring-cloud
上文中通過代碼示例了,如何通過框架工具提供的攔截器能力來實(shí)現(xiàn)請(qǐng)求追蹤,由于Spring MVC,RestTemplate,Servlet……這些開源工具是的用戶相當(dāng)廣泛,所以在opentracing-contrib項(xiàng)目中提供了非常多針對(duì)這些被廣泛使用的開源工具的集成支持包。
其中java-spring-cloud子項(xiàng)目,為spring-cloud項(xiàng)目提供了opentracing-spring-cloud-starter,這個(gè)starter通過依賴了很多其他的opentrcing集成支持庫,來為基于spring-cloud架構(gòu)的應(yīng)用提供一站式opentracing集成方案,其中包括如下組件:
利用SpringBoot的AutoConfiguration機(jī)制為用戶提供了幾乎無須手動(dòng)配置的集成方案。
- Spring Web (RestControllers, RestTemplates, WebAsyncTask, WebClient, WebFlux)
- @Async, @Scheduled, Executors
- WebSocket STOMP
- Feign, HystrixFeign
- Hystrix
- JMS
- JDBC
- Mongo
- Zuul
- Reactor
- RxJava
- Redis
- Standard logging - logs are added to active span
- Spring Messaging - trace messages being sent through Messaging Channels
- RabbitMQ
使用SpringCloud的開發(fā)者,可以簡(jiǎn)單的將opentracing-spring-cloud-starter添加到自己項(xiàng)目的依賴中,來體驗(yàn)它帶來的opentracing集成。
如果不使用SpringCloud也可以其為起點(diǎn),按自己的需求從其依賴中挑選自己需要的部分,或者瀏覽opentracing-contrib項(xiàng)目來尋找自己需要的支持庫。
使用Jaeger
前文中描述的API以及中間件集成方案,都是對(duì)OpenTracing API的集成,仔細(xì)看代碼中缺少一個(gè)必要的構(gòu)建Tracer對(duì)象的步驟。在實(shí)際場(chǎng)景中,我們需要一種具體的OpenTracing實(shí)現(xiàn),來創(chuàng)建Tracer對(duì)象。
Jaeger是由Uber開源的OpenTracing實(shí)現(xiàn)項(xiàng)目,它提供了追蹤數(shù)據(jù)上報(bào)服務(wù)以及數(shù)據(jù)的視圖,來幫助開發(fā)者解決分布式系統(tǒng)中的如下問題:
- 分布式事務(wù)監(jiān)控
- 性能和延遲優(yōu)化
- 分析故障源頭
- 服務(wù)以來分析
- 分布式上下文傳播
以來jaeger-client-java可以利用如下代碼創(chuàng)建一個(gè)Tracer對(duì)象:
Configuration config = new io.jaegertracing.Configuration("服務(wù)名稱"); // 設(shè)置數(shù)據(jù)發(fā)送方式 Configuration.SenderConfiguration sender = new Configuration.SenderConfiguration(); sender.withEndpoint("<endpoint>"); // endpoint可以是在阿里云上購買的鏈路追蹤服務(wù)或者自己使用Jaeger搭建的服務(wù) // 設(shè)置采樣方式 config.withSampler(new Configuration.SamplerConfiguration().withType("const").withParam(1)); // 設(shè)置數(shù)據(jù)上報(bào)方式 config.withReporter(new Configuration.ReporterConfiguration().withSender(sender).withMaxQueueSize(10000)); Tracer tracer = config.getTracer();
Configuration類提供了非常多的配置功能,有興趣的開發(fā)者可以閱讀其API文檔來了解更多的自定義選項(xiàng),甚至擴(kuò)展Jaeger的功能。
到此這篇關(guān)于Java中的OpenTracing使用實(shí)例的文章就介紹到這了,更多相關(guān)OpenTracing使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 數(shù)據(jù)結(jié)構(gòu)七大排序使用分析
這篇文章主要介紹了Java常用的排序算法及代碼實(shí)現(xiàn),在Java開發(fā)中,對(duì)排序的應(yīng)用需要熟練的掌握,這樣才能夠確保Java學(xué)習(xí)時(shí)候能夠有扎實(shí)的基礎(chǔ)能力。那Java有哪些排序算法呢?本文小編就來詳細(xì)說說Java常見的排序算法,需要的朋友可以參考一下2022-04-04Java中使用MyBatis-Plus操作數(shù)據(jù)庫的實(shí)例
本文主要介紹了Java中使用MyBatis-Plus操作數(shù)據(jù)庫的實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02Jenkins環(huán)境搭建實(shí)現(xiàn)過程圖解
這篇文章主要介紹了Jenkins環(huán)境搭建實(shí)現(xiàn)過程圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證
這篇文章主要介紹了Springboot如何實(shí)現(xiàn)Web系統(tǒng)License授權(quán)認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05如何在springMVC的controller中獲取request
這篇文章主要介紹了如何在springMVC的controller中獲取request,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12