分布式系统中日志追踪需要考虑的几个点?
- 需要一个全服务唯一的id,即traceId,如何保证?
- traceId如何在服务间传递?
- traceId如何在服务内部传递?
- traceId如何在多线程中传递?
- 全服务唯一的traceId,可以使用uuid生成,正常来说不会出现重复的;
- 关于服务间传递,对于调用者,在协议头加上traceId,对于被调用者,通过前置拦截器或者过滤器统一拦截;
- 关于服务内部传递,可以使用ThreadLocal传递traceId,一处放置,随处可用;
- 关于多线程传递,分为两种情况:子线程,可以使用InheritableThreadLocal线程池,需要改造线程池对提交的任务进行包装,把提交者的traceId包装到任务中
文章插图
比如,上面这个系统,系统入口在A处,A调用B的服务,B里面又起了一个线程B1去访问D的服务,B本身又去访问C服务 。
我们就可以这么来跟踪日志:
- 所有服务都需要一个全局的InheritableThreadLocal保存服务内部traceId的传递;
- 所有服务都需要一个前置拦截器或者过滤器,检测如果请求头没有traceId就生成一个,如果有就取出来,并把traceId放到全局的InheritableThreadLocal里面;
- 一个服务调用另一个服务的时候把traceId塞到请求头里,比如http header,本文来源于工从号彤哥读源码;
- 改造线程池,在提交的时候包装任务,这个工作量比较大,因为服务内部可能依赖其它框架,这些框架的线程池有可能也需要修改;
为了简单起见,我们使用SpringBoot,它默认使用的日志框架是logback,而且Slf4j提供了一个包装了InheritableThreadLocal的类叫MDC,我们只要把traceId放在MDC中,打印日志的时候统一打印就可以了,不用显式地打印traceId 。
我们分成三个模块:
- 公共包:封装拦截器,traceId的生成,服务内传递,请求头的传递等;
- A服务:只依赖于公共包,并提供一个接口接收外部请求;
- B服务:依赖于公共包,并内部起一个线程池,用于发送B1->D的请求,当然我们这里不发送请求,只在线程池中简单地打印一条日志;
- TraceFilter.JAVA
从请求头中获取traceId,如果不存在就生成一个,并放入MDC中 。
@Slf4j@WebFilter("/**")@Componentpublic class TraceFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;// 从请求头中获取traceIdString traceId = request.getHeader("traceId");// 不存在就生成一个if (traceId == null || "".equals(traceId)) {traceId = UUID.randomUUID().toString();}// 放入MDC中,本文来源于工从号彤哥读源码MDC.put("traceId", traceId);chain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {}}
- TraceThreadPoolExecutor.java
public class TraceThreadPoolExecutor extends ThreadPoolExecutor {public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);}public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);}public TraceThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);}@Overridepublic void execute(Runnable command) {// 提交者的本地变量Map<string, string> contextMap = MDC.getCopyOfContextMap();super.execute(()->{if (contextMap != null) {// 如果提交者有本地变量,任务执行之前放入当前任务所在的线程的本地变量中MDC.setContextMap(contextMap);}try {command.run();} finally {// 任务执行完,清除本地变量,以防对后续任务有影响MDC.clear();}});}}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 吃透移动端 H5 响应式布局 |深入原理到目前最佳实践方案
- 普洱茶发酵原理,普洱茶发酵中的有氧发酵与厌氧发酵
- 分布式系统ID的生成方法之UUID、数据库、算法、Redis、Leaf方案
- 分布式系统架构落地与瓶颈突破 进阶架构师必读,人人都是架构师
- 初中生半命题作文训练7篇 初中半命题作文
- 北京旅游攻略 一篇全解 太全了
- 刀片服务器宕机是什么意思?原理及危害有哪些?
- 君山银针三起三落原理,君山银针历史
- 致大学生的一篇励志文 大学生励志文章
- 高中生暑假社会实践报告精选范文5篇 高中社会实践报告