上周参加了在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的非侵入性方式。

目标

  1. 允许为进入应用程序的初始请求生成关联ID
  2. 启用相关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 /微服务中的分布式跟踪)相关推荐

  1. 什么是Spring Boot以及为什么它是用于创建微服务的首选框架

    为什么要使用Spring Boot创建微服务? Spring Boot是Java领域众所周知的首选框架,用于创建Micro Services. 使用Spring引导框架,可以非常轻松地创建Java应用 ...

  2. Spring Boot与Docker(一):微服务架构和容器化概述

    本文讲的是Spring Boot与Docker(一):微服务架构和容器化概述,[编者的话]本篇是<使用Spring Boot和Docker构建微服务架构>系列四部曲的第一篇,本篇将会对我们 ...

  3. 攻克微服务中的最大难点:用户数据

    今天 数人云与大家分享的文章将探讨微服务架构的创建与开发工作当中最为困难的部分--用户数据. 只有我们摆脱自己的依赖时微服务才能起作用,换言之,存在于单一数据库上的多任务进程并不是真正的微服务.使用S ...

  4. 微服务中的面向切面编程和更多模式

    目录 介绍 如何建立这篇文章? 在Java中使用代码(11 +,Spring boot 2.2 +,Spring Boot AOP,AspectJ) 在C#中使用代码(7,.NET MVC Core ...

  5. 【Spring Boot官方文档原文理解翻译-持续更新中】

    [Spring Boot官方文档原文理解翻译-持续更新中] 文章目录 [Spring Boot官方文档原文理解翻译-持续更新中] Chapter 4. Getting Started 4.1. Int ...

  6. spring boot 集成 mongodb 通过id查询问题

    spring boot 集成 mongodb 通过id查询问题 java 连接 mongodb 查询时通过id 查询不到数据,但其他字段是可以的,现在请各位大佬看看会是什么原因 通过id为参数查询 具 ...

  7. java event sourcing_使用Spring Cloud和Reactor在微服务中实现EventSourcing -解道Jdon

    使用Spring Cloud和Reactor在微服务中实现Event Sourcing 当在微服务架构中构建应用时,状态管理成为分布式系统的问题,相比于传统monolithic应用,将状态管理通过事务 ...

  8. STS创建Spring Boot项目实战(Rest接口、数据库、用户认证、分布式Token JWT、Redis操作、日志和统一异常处理)

    STS创建Spring Boot项目实战(Rest接口.数据库.用户认证.分布式Token JWT.Redis操作.日志和统一异常处理) 1.项目创建 1.新建工程 2.选择打包方式,这边可以选择为打 ...

  9. java s.charat_Java中s.charAt(index)用于提取字符串s中的特定字符操作

    charAt(int index)方法是一个能够用来检索特定索引下的字符的String实例的方法. charAt()方法返回指定索引位置的char值.索引范围为0~length()-1. 如: str ...

最新文章

  1. 区块链+数字经济发展白皮书,45页pdf
  2. 独家 | 如何改善你的训练数据集?(附案例)
  3. ThinkPHP使用分组详细介绍(十七)
  4. ASP.NET Core 介绍和项目解读
  5. Linux centos 6.7设置MySQL为开机启动
  6. [JavaWeb-HTML]HTML概念介绍和快速入门
  7. continue 结束本次循环,继续下一次循环
  8. 2013.07.10《播音主持之绕口令训练…
  9. 【引用】我国一、二级学科目录
  10. Visual Studio 2015(C#)编写实现TCP调试助手(服务端+客户端一体)-新手
  11. no properties discovered to create BeanSerializer 问题解决
  12. 银河麒麟安装docker-compose体验
  13. notification 发送通知后顶栏的小图标不对,为纯白色而不是设置的smallIcon 的原因
  14. 弄明白了清华校训“自强不息 厚德载物”的来龙去脉
  15. unity3d api 中文文档_unity3D游戏开发工程师完整简历范文
  16. 西安交通大学计算机学院保研面试,西安交通大学电子与信息工程学院(专业学位)计算机技术保研细则...
  17. 国产化系统改造实践(未完)
  18. 算法[第四版]-图灵程序设计丛书-笔记
  19. CCF2020企业非法集资风险预测-季军方案
  20. linux 查看声卡设备并测试录音 (ALSA 音频工具)

热门文章

  1. java实现验证码3秒刷新一次
  2. vmware启动多个虚拟机
  3. eclipse 创建ssm spring+springmvc+mybatis 实现登录注册
  4. aria2c rpc php,aria2c 的基本配置,附带傻瓜式源码
  5. java运行环境变量及自定义变量
  6. HDU1864(01背包)
  7. java面试常见面试问题_Java面试准备:15个Java面试问题
  8. 只读事务上下文_我可以/应该在事务上下文中使用并行流吗?
  9. orm提取指定列_使用ORM提取数据很容易! 是吗?
  10. starter_您是否尝试过MicroProfile Starter?