本文只講原理,不講框架。java
咱們一一來解答:spring
好比,上面這個系統,系統入口在A處,A調用B的服務,B裏面又起了一個線程B1去訪問D的服務,B自己又去訪問C服務。瀏覽器
咱們就能夠這麼來跟蹤日誌:多線程
咱們模擬A到B這兩個服務來實現一個日誌跟蹤系統。app
爲了簡單起見,咱們使用SpringBoot,它默認使用的日誌框架是logback,並且Slf4j提供了一個包裝了InheritableThreadLocal的類叫MDC,咱們只要把traceId放在MDC中,打印日誌的時候統一打印就能夠了,不用顯式地打印traceId。框架
咱們分紅三個模塊:dom
前置過濾器,用攔截器實現也是同樣的。異步
從請求頭中獲取traceId,若是不存在就生成一個,並放入MDC中。async
@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() {
}
}複製代碼
改造線程池,提交任務的時候進行包裝。分佈式
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是能夠傳遞的。