javascript
在Spring Boot中实现相关ID(用于SOA /微服务中的分布式跟踪)
上周參加了在Geecon上Sam Newman的微服務討論后,我開始思考更多有關用于監視,報告和診斷的面向服務/微服務平臺最可能的基本功能:相關ID。 關聯ID允許在面向服務的復雜平臺中進行分布式跟蹤,在該平臺中,對單個應用程序的請求通常可以由多個下游服務處理。 如果沒有關聯下游服務請求的能力,那么很難理解平臺中如何處理請求。
我已經在我最近從事的幾個SOA項目中看到了關聯ID的好處,但是正如Sam在他的演講中提到的那樣,通常很容易想到在構建應用程序的初始版本時不需要這種類型的跟蹤。 ,但是當您確實意識到好處(和需求!)時,很難將其改造到應用程序中。 我還沒有找到在基于Java / Spring的應用程序中實現相關ID的完美方法,但是在通過電子郵件與Sam聊天后,他提出了一些建議,我現在將其變成一個使用Spring Boot的簡單項目,以演示如何做到這一點。被實施。
為什么?
在兩次Sam的Geecon談話中,他都提到,根據他的經驗,關聯ID對于診斷目的非常有用。 關聯ID本質上是一個生成的ID,它與單個(通常是用戶驅動的)請求一起進入應用程序,該請求通過堆棧向下傳遞到相關服務。 在SOA或微服務平臺中,這種類型的ID非常有用,因為進入應用程序的請求通常被“散布”或由多個下游服務處理,而關聯ID允許所有下游請求(從請求的初始點)到根據ID進行關聯或分組。 然后,可以通過關聯所有下游服務日志并匹配所需的ID來使用相關ID來執行所謂的“分布式跟蹤”,以在整個應用程序堆棧中查看請求的跟蹤(如果使用集中式日志記錄,這非常容易框架,例如logstash )。
面向服務領域的主要參與者一直在討論分布式跟蹤和關聯請求的需求,因此Twitter創建了他們的開源Zipkin框架 (通常將其插入RPC框架Finagle )和Netflix。已將其Karyon網絡/微服務框架開源,這兩個框架均提供分布式跟蹤。 當然在該領域有商業產品,其中一個產品是AppDynamics ,雖然很酷,但價格卻很高。
在Spring Boot中創建概念驗證
與Zipkin和Karyon一樣,它們都具有相對侵入性,因為您必須在(通常是自以為是的)框架之上構建服務。 對于某些用例,這可能很好,但對于其他用例而言卻沒什么用,特別是在構建微服務時。 我最近一直在嘗試使用Spring Boot ,并且該框架通過提供許多預配置的明智默認值而建立在廣為人知和喜愛(至少對我而言!)的Spring框架上。 這使您可以快速構建微服務(尤其是通過RESTful接口進行通信的微服務)。 本博客pos的其余部分說明了我如何(希望)實現一種實現關聯ID的非侵入性方式。
目標
實作
我在GitHub上創建了兩個項目, 一個包含一個實現,其中所有請求都以同步方式處理 (即,在單個線程上處理所有請求處理的傳統Spring方法),還有一個用于異步(非阻塞)時的實現。 )正在使用的通信方式(即,結合使用Servlet 3異步支持和Spring的DeferredResult和Java的Futures / Callables)。 本文的大部分內容描述了異步實現,因為這更有趣:
- Spring Boot異步(DeferredResult + Futures)通信相關ID Github回購
 
這兩個代碼庫中的主要工作都是由CorrelationHeaderFilter承擔的,CorrelationHeaderFilter是一個標準的Java EE篩選器,它檢查HttpServletRequest標頭中是否存在correlationId。 如果找到一個,則在RequestCorrelation類中設置一個ThreadLocal變量(稍后討論)。 如果未找到相關標識,則生成一個相關標識并將其添加到RequestCorrelation類中:
public class CorrelationHeaderFilter implements Filter {//...@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)throws IOException, ServletException {final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;String currentCorrId = httpServletRequest.getHeader(RequestCorrelation.CORRELATION_ID_HEADER);if (!currentRequestIsAsyncDispatcher(httpServletRequest)) {if (currentCorrId == null) {currentCorrId = UUID.randomUUID().toString();LOGGER.info("No correlationId found in Header. Generated : " + currentCorrId);} else {LOGGER.info("Found correlationId in Header : " + currentCorrId);}RequestCorrelation.setId(currentCorrId);}filterChain.doFilter(httpServletRequest, servletResponse);}//...private boolean currentRequestIsAsyncDispatcher(HttpServletRequest httpServletRequest) {return httpServletRequest.getDispatcherType().equals(DispatcherType.ASYNC);}此代碼中唯一可能不會立即顯而易見的是條件檢查currentRequestIsAsyncDispatcher(httpServletRequest) ,但這是為了防止當Async Dispatcher線程運行以返回結果時正在執行的相關ID代碼(請注意,因為我最初并不希望Async Dispatcher再次觸發執行過濾器!)。
這是RequestCorrelation類,其中包含一個簡單的ThreadLocal <String>靜態變量,用于保存當前執行線程的相關ID(通過上面的CorrelationHeaderFilter設置):
public class RequestCorrelation {public static final String CORRELATION_ID = "correlationId";private static final ThreadLocal<String> id = new ThreadLocal<String>();public static String getId() { return id.get(); }public static void setId(String correlationId) { id.set(correlationId); } }一旦將關聯ID存儲在RequestCorrelation類中,就可以通過調用RequestCorrelation中的靜態getId()方法,將其檢索并添加到下游服務請求(或數據存儲訪問等)中。 將這種行為封裝在應用程序服務之外可能是一個好主意,并且您可以在我創建的RestClient類中看到如何執行此操作的示例,該類構成Spring的RestTemplate并處理標頭中相關性ID的設置從調用類透明地顯示。
@Component public class CorrelatingRestClient implements RestClient {private RestTemplate restTemplate = new RestTemplate();@Overridepublic String getForString(String uri) {String correlationId = RequestCorrelation.getId();HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.set(RequestCorrelation.CORRELATION_ID, correlationId);LOGGER.info("start REST request to {} with correlationId {}", uri, correlationId);//TODO: error-handling and fault-tolerance in productionResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET,new HttpEntity<String>(httpHeaders), String.class);LOGGER.info("completed REST request to {} with correlationId {}", uri, correlationId);return response.getBody();} }//... calling Class public String exampleMethod() {RestClient restClient = new CorrelatingRestClient();return restClient.getForString(URI_LOCATION); //correlation id handling completely abstracted to RestClient impl }使這項工作適用于異步請求…
當您同步處理所有請求時,上面包含的代碼可以很好地工作,但是在SOA /微服務平臺中以非阻塞異步方式處理請求通常是個好主意。 在Spring中,可以通過將DeferredResult類與Servlet 3異步支持結合使用來實現。 在異步方法中使用ThreadLocal變量的問題在于,最初處理請求(并創建DeferredResult / Future)的線程將不是執行實際處理的線程。
因此,需要一點膠水代碼以確保相關性id跨線程傳播。 這可以通過使用所需功能擴展Callable來實現:(不要擔心示例調用類代碼看起來不直觀-在Spring中,DeferredResults和Futures之間的這種適應是必然的,在整個過程中,包括樣板ListenableFutureAdapter的完整代碼為在我的GitHub存儲庫中):
public class CorrelationCallable<V> implements Callable<V> {private String correlationId;private Callable<V> callable;public CorrelationCallable(Callable<V> targetCallable) {correlationId = RequestCorrelation.getId();callable = targetCallable;}@Overridepublic V call() throws Exception {RequestCorrelation.setId(correlationId);return callable.call();} }//... Calling Class@RequestMapping("externalNews") public DeferredResult<String> externalNews() {return new ListenableFutureAdapter<>(service.submit(new CorrelationCallable<>(externalNewsService::getNews))); }這樣就可以了-無論處理的同步/異步性質如何,相關ID的傳播!
您可以克隆包含我的異步示例的Github報告,并通過在命令行上運行mvn spring-boot:run來執行應用程序。 如果您在瀏覽器中(或通過curl)訪問http:// localhost:8080 / externalNews ,您將在Spring Boot控制臺中看到類似于以下內容的內容,該內容清楚地表明了在初始請求中生成的關聯ID,然后被傳播到模擬外部調用(請查看ExternalNewsServiceRest類以了解如何實現):
[nio-8080-exec-1] u.c.t.e.c.w.f.CorrelationHeaderFilter : No correlationId found in Header. Generated : d205991b-c613-4acd-97b8-97112b2b2ad0 [pool-1-thread-1] u.c.t.e.c.w.c.CorrelatingRestClient : start REST request to http://localhost:8080/news with correlationId d205991b-c613-4acd-97b8-97112b2b2ad0 [nio-8080-exec-2] u.c.t.e.c.w.f.CorrelationHeaderFilter : Found correlationId in Header : d205991b-c613-4acd-97b8-97112b2b2ad0 [pool-1-thread-1] u.c.t.e.c.w.c.CorrelatingRestClient : completed REST request to http://localhost:8080/news with correlationId d205991b-c613-4acd-97b8-97112b2b2ad0結論
我對這個簡單的原型感到非常滿意,它確實滿足了我上面列出的兩個目標。 未來的工作將包括為此代碼編寫一些測試(不要為TDDing而感到羞恥!),并將此功能擴展到更實際的示例。
我要非常感謝Sam,不僅是因為在Geecon的精彩演講中分享了他的知識,還感謝我抽出時間來回復我的電子郵件。 如果您對微服務和相關工作感興趣,我強烈建議您參閱Sam's Microservice書籍,該書可在O'Reilly的Early Access中獲得 。 我很喜歡閱讀當前可用的章節,并且最近實施了許多SOA項目,我可以從中獲得很多不錯的建議。 我將以極大的興趣關注本書的發展!
資源資源
我多次使用Tomasz Nurkiewicz的優秀博客來學習如何最好地在Spring中連接所有DeferredResult / Future代碼:
http://www.nurkiewicz.com/2013/03/deferredresult-asynchronous-processing.html
翻譯自: https://www.javacodegeeks.com/2014/05/implementing-correlation-ids-in-spring-boot-for-distributed-tracing-in-soamicroservices.html
總結
以上是生活随笔為你收集整理的在Spring Boot中实现相关ID(用于SOA /微服务中的分布式跟踪)的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 煎牛排用什么锅 煎牛排用的锅
 - 下一篇: 顺反异构的条件是什么 顺反异构产生的条件