日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

拦截器如何获取@requestbody_分布式系统中如何优雅地追踪日志(原理篇)

發布時間:2023/12/4 windows 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 拦截器如何获取@requestbody_分布式系统中如何优雅地追踪日志(原理篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

分布式系統中日志追蹤需要考慮的幾個點?

  • 需要一個全服務唯一的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,本文來源于工從號彤哥讀源碼;
  • 改造線程池,在提交的時候包裝任務,這個工作量比較大,因為服務內部可能依賴其它框架,這些框架的線程池有可能也需要修改;
  • 實現

    我們模擬A到B這兩個服務來實現一個日志跟蹤系統。

    為了簡單起見,我們使用SpringBoot,它默認使用的日志框架是logback,而且Slf4j提供了一個包裝了InheritableThreadLocal的類叫MDC,我們只要把traceId放在MDC中,打印日志的時候統一打印就可以了,不用顯式地打印traceId。

    我們分成三個模塊:

  • 公共包:封裝攔截器,traceId的生成,服務內傳遞,請求頭的傳遞等;
  • A服務:只依賴于公共包,并提供一個接口接收外部請求;
  • B服務:依賴于公共包,并內部起一個線程池,用于發送B1->D的請求,當然我們這里不發送請求,只在線程池中簡單地打印一條日志;
  • 公共包

  • TraceFilter.java
  • 前置過濾器,用攔截器實現也是一樣的。

    從請求頭中獲取traceId,如果不存在就生成一個,并放入MDC中。

    @Slf4j @WebFilter("/**") @Component public 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();}});} }
  • TraceAsyncConfigurer.java
  • 改造Spring的異步線程池,包裝提交的任務。

    @Slf4j @Component public class TraceAsyncConfigurer implements AsyncConfigurer {@Overridepublic 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;}@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (throwable, method, params) -> log.error("asyc execute error, method={}, params={}", method.getName(), Arrays.toString(params));}public static class MdcTaskDecorator implements TaskDecorator {@Overridepublic Runnable decorate(Runnable runnable) {Map<string, string> contextMap = MDC.getCopyOfContextMap();return () -> {if (contextMap != null) {MDC.setContextMap(contextMap);}try {runnable.run();} finally {MDC.clear();}};}}}
  • HttpUtils.java
  • 封裝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服務

    A服務通過Http調用B服務。

    @Slf4j @RestController public class AController {@RequestMapping("a")public String a(String name) {log.info("Hello, " + name);try {// A中調用Breturn 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服務

    B服務內部有兩種跨線程調用:

    • 利用Spring的異步線程池
    • 使用自己的線程池

    BController.java

    @Slf4j @RestController public class BController {@Autowiredprivate 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));@Asyncpublic 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是可以傳遞的。

    文章來源:https://my.oschina.net/u/4108008/blog/3152201

    關注我了解更多程序員資訊技術,領取豐富架構資料。

    總結

    以上是生活随笔為你收集整理的拦截器如何获取@requestbody_分布式系统中如何优雅地追踪日志(原理篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。