上周参加了在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的非侵入性方式。
目标
- 允许为进入应用程序的初始请求生成关联ID
- 启用相关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