本文只講原理,不講框架。
咱們一一來解答:java
關於多線程傳遞,分爲兩種狀況:spring
好比,上面這個系統,系統入口在A處,A調用B的服務,B裏面又起了一個線程B1去訪問D的服務,B自己又去訪問C服務。segmentfault
咱們就能夠這麼來跟蹤日誌:瀏覽器
咱們模擬A到B這兩個服務來實現一個日誌跟蹤系統。多線程
爲了簡單起見,咱們使用SpringBoot,它默認使用的日誌框架是logback,並且Slf4j提供了一個包裝了InheritableThreadLocal的類叫MDC,咱們只要把traceId放在MDC中,打印日誌的時候統一打印就能夠了,不用顯式地打印traceId。app
咱們分紅三個模塊:框架
前置過濾器,用攔截器實現也是同樣的。dom
從請求頭中獲取traceId,若是不存在就生成一個,並放入MDC中。異步
@Slf4j @WebFilter("/**") @Component public class TraceFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; // 從請求頭中獲取traceId String traceId = request.getHeader("traceId"); // 不存在就生成一個 if (traceId == null || "".equals(traceId)) { traceId = UUID.randomUUID().toString(); } // 放入MDC中,本文來源於工從號彤哥讀源碼 MDC.put("traceId", traceId); chain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }
改造線程池,提交任務的時候進行包裝。async
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); } @Override public void execute(Runnable command) { // 提交者的本地變量 Map<String, String> contextMap = MDC.getCopyOfContextMap(); super.execute(()->{ if (contextMap != null) { // 若是提交者有本地變量,任務執行以前放入當前任務所在的線程的本地變量中 MDC.setContextMap(contextMap); } try { command.run(); } finally { // 任務執行完,清除本地變量,以防對後續任務有影響 MDC.clear(); } }); } }
改造Spring的異步線程池,包裝提交的任務。
@Slf4j @Component public class TraceAsyncConfigurer implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(8); executor.setMaxPoolSize(16); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-pool-"); executor.setTaskDecorator(new MdcTaskDecorator()); executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); return executor; } @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (throwable, method, params) -> log.error("asyc execute error, method={}, params={}", method.getName(), Arrays.toString(params)); } public static class MdcTaskDecorator implements TaskDecorator { @Override public Runnable decorate(Runnable runnable) { Map<String, String> contextMap = MDC.getCopyOfContextMap(); return () -> { if (contextMap != null) { MDC.setContextMap(contextMap); } try { runnable.run(); } finally { MDC.clear(); } }; } } }
封裝Http工具類,把traceId加入頭中,帶到下一個服務。
@Slf4j public class HttpUtils { public static String get(String url) throws URISyntaxException { RestTemplate restTemplate = new RestTemplate(); MultiValueMap<String, String> headers = new HttpHeaders(); headers.add("traceId", MDC.get("traceId")); URI uri = new URI(url); RequestEntity<?> requestEntity = new RequestEntity<>(headers, HttpMethod.GET, uri); ResponseEntity<String> exchange = restTemplate.exchange(requestEntity, String.class); if (exchange.getStatusCode().equals(HttpStatus.OK)) { log.info("send http request success"); } return exchange.getBody(); } }
A服務經過Http調用B服務。
@Slf4j @RestController public class AController { @RequestMapping("a") public String a(String name) { log.info("Hello, " + name); try { // A中調用B return HttpUtils.get("http://localhost:8002/b"); } catch (Exception e) { log.error("call b error", e); } return "fail"; } }
A服務的日誌輸出格式:
中間加了[%X{traceId}]
一串表示輸出traceId。
# 本文來源於工從號彤哥讀源碼 logging: pattern: console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx'
B服務內部有兩種跨線程調用:
BController.java
@Slf4j @RestController public class BController { @Autowired private BService bService; @RequestMapping("b") public String b() { log.info("Hello, b receive request from a"); bService.sendMsgBySpring(); bService.sendMsgByThreadPool(); return "ok"; } }
BService.java
@Slf4j @Service public class BService { public static final TraceThreadPoolExecutor threadPool = new TraceThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100)); @Async public void sendMsgBySpring() { log.info("send msg by spring success"); } public void sendMsgByThreadPool() { threadPool.execute(()->log.info("send msg by thread pool success")); } }
B服務的日誌輸出格式:
中間加了[%X{traceId}]
一串表示輸出traceId。
logging: pattern: console: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr([%X{traceId}]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx'
打開瀏覽器,輸入http://localhost:8001/a?name=andy
。
A服務輸出日誌:
2019-12-26 21:36:29.132 INFO 5132 --- [nio-8001-exec-2] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.a.AController : Hello, andy 2019-12-26 21:36:35.380 INFO 5132 --- [nio-8001-exec-2] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.common.HttpUtils : send http request success
B服務輸出日誌:
2019-12-26 21:36:29.244 INFO 2368 --- [nio-8002-exec-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.b.BController : Hello, b receive request from a 2019-12-26 21:36:29.247 INFO 2368 --- [nio-8002-exec-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 2019-12-26 21:36:35.279 INFO 2368 --- [ async-pool-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.b.BService : send msg by spring success 2019-12-26 21:36:35.283 INFO 2368 --- [pool-1-thread-1] [8a59cb96-bbc8-42a9-aa62-df7a52875447] com.alan.trace.b.BService : send msg by thread pool success
能夠看到,A服務成功生成了traceId,而且傳遞給了B服務,且B服務線程間能夠保證同一個請求的traceId是能夠傳遞的。