статья, написанная ранеестатья, восторженный пользователь сети [Кидзо Кельвин] прокомментировал, что он все еще может быть запутанным в многопоточности, и рекомендуется печатать traceId через MDC, чтобы отследить вызов полной ссылки. В «Наггетс» все талантливы и красиво говорят, они мне очень нравятся. Nuggets помогают мне прогрессировать, а восторженные пользователи сети всегда могут внести предложения по улучшению.
написать впереди
В этой статье вы узнаете, что такое MDC, проблемы, существующие в приложении MDC, и как решить существующие проблемы.
Введение в MDC
Введение:
MDC (сопоставленный диагностический контекст) — это функция, предоставляемая log4j, logback и log4j2 для облегчения ведения журнала в многопоточных условиях.MDCможно рассматривать какХэш-таблица привязана к текущему потоку, вы можете добавить к нему пары ключ-значение. Контент, включенный в MDC, можетДоступ к коду, выполняемому в том же потоке.当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。 MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据
Описание API:
- clear() => удаляет все MDC
- get (String key) => Получить значение указанного ключа в MDC текущего потока
- getContext() => Получить MDC текущего потока MDC
- PUT (STRING Key, Object O) => Сохраняет указанное значение ключа в MDC текущего потока
- Удалить (String Cook) => Удаляет пару значение ключа, указанную в текущем потоке MDC
преимущество:
- Код лаконичен, стиль лога унифицирован, нет необходимости вручную прописывать traceId при печати лога, то есть LOGGER.info("traceId:{} ", traceId)
Я могу думать только об этом сейчас
использование MDC
-
добавить перехватчик
public class LogInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //如果有上层调用就用上层的ID String traceId = request.getHeader(Constants.TRACE_ID); if (traceId == null) { traceId = TraceIdUtil.getTraceId(); } MDC.put(Constants.TRACE_ID, traceId); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //调用结束后删除 MDC.remove(Constants.TRACE_ID); } }
-
Изменить формат журнала
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
Точка% x {traceid}, traceid такой же, как имя ключа в MDC
Простота в использовании настолько проста, но в некоторых случаях traceId не будет получен
Проблемы с МДЦ
-
Журнал печати в дочернем потоке теряет traceId
-
HTTP-вызов теряет traceId
......Если traceId утерян, решите его один за другим и никогда не оптимизируйте его заранее.
Решить проблемы МДК
Отсутствует traceId в журнале дочернего потока
traceId дочернего потока будет утерян в процессе печати лога.Решение состоит в том, чтобы переписать пул потоков.В случае непосредственного создания нового потока это не рассматривается [такого использования следует избегать в практических приложениях] .Переписать пул потоков не более чем задача.один пакет
-
Класс пакета пула потоков: ThreadPoolExecutorMdcWrapper.java
public class ThreadPoolExecutorMdcWrapper extends ThreadPoolExecutor { public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); } public ThreadPoolExecutorMdcWrapper(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } public ThreadPoolExecutorMdcWrapper(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 task) { super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } @Override public <T> Future<T> submit(Runnable task, T result) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()), result); } @Override public <T> Future<T> submit(Callable<T> task) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } @Override public Future<?> submit(Runnable task) { return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap())); } }
инструкция:
- Наследование класса ThreadPoolExecutor, метод повторного выполнения задачи
- Оберните задачу один раз с помощью ThreadMdcUtil
-
Класс инструмента пакета thread traceId: ThreadMdcUtil.java
public class ThreadMdcUtil { public static void setTraceIdIfAbsent() { if (MDC.get(Constants.TRACE_ID) == null) { MDC.put(Constants.TRACE_ID, TraceIdUtil.getTraceId()); } } public static <T> Callable<T> wrap(final Callable<T> callable, final Map<String, String> context) { return () -> { if (context == null) { MDC.clear(); } else { MDC.setContextMap(context); } setTraceIdIfAbsent(); try { return callable.call(); } finally { MDC.clear(); } }; } public static Runnable wrap(final Runnable runnable, final Map<String, String> context) { return () -> { if (context == null) { MDC.clear(); } else { MDC.setContextMap(context); } setTraceIdIfAbsent(); try { runnable.run(); } finally { MDC.clear(); } }; } }
Описание [В качестве примера возьмем инкапсуляцию Runnable]:
- Анализируя этот поток, соответствующий наличию или отсутствию MDC Map там устанавливается
- Установите значение traceId в MDC. Если оно не существует, оно будет создано заново. В случае, если это не подпоток, если это подпоток, traceId в MDC не равен нулю.
- Выполнить метод запуска
Код эквивалентен следующему способу написания, который будет более интуитивным
public static Runnable wrap(final Runnable runnable, final Map<String, String> context) { return new Runnable() { @Override public void run() { if (context == null) { MDC.clear(); } else { MDC.setContextMap(context); } setTraceIdIfAbsent(); try { runnable.run(); } finally { MDC.clear(); } } }; }
Возвращается упакованный Runnable.Перед выполнением задачи [runnable.run()] сначала устанавливает Map основного потока в текущий поток [т.е. MDC.setContextMap(context)], так что подпоток соответствует в основной поток MDC Карта такая же
HTTP-вызов теряет traceId
При использовании HTTP для вызова интерфейса обслуживания стороннего сервиса, TraceID будет потерян, и инструмент вызова HTTP должен быть изменен. При отправке добавьте TraceID на заголовок запроса, и добавьте перехватчик на более низкий уровень Callee на Получите TraceID в заголовке и добавьте его в MDC.
Существует множество способов выполнения HTTP-вызовов, наиболее распространенными являются HttpClient, OKHttp, RestTemplate, поэтому приводятся решения только для этих HTTP-вызовов.
HTTP-клиент:
- Реализовать перехватчик HttpClient
public class HttpClientTraceIdInterceptor implements HttpRequestInterceptor {
@Override
public void process(HttpRequest httpRequest, HttpContext httpContext) throws HttpException, IOException {
String traceId = MDC.get(Constants.TRACE_ID);
//当前线程调用中有traceId,则将该traceId进行透传
if (traceId != null) {
//添加请求体
httpRequest.addHeader(Constants.TRACE_ID, traceId);
}
}
}
Реализуйте интерфейс HttpRequestInterceptor и переопределите метод процесса.
Если вызывающий поток содержит traceId, полученный traceId должен быть прозрачно передан через заголовок в запросе.
-
Добавить перехватчик для HttpClient
private static CloseableHttpClient httpClient = HttpClientBuilder.create() .addInterceptorFirst(new HttpClientTraceIdInterceptor()) .build();
Добавьте перехватчик на httpClient через метод AddinterectorFirst
Okhttp:
-
Реализовать перехватчик OKHttp
public class OkHttpTraceIdInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { String traceId = MDC.get(Constants.TRACE_ID); Request request = null; if (traceId != null) { //添加请求体 request = chain.request().newBuilder().addHeader(Constants.TRACE_ID, traceId).build(); } Response originResponse = chain.proceed(request); return originResponse; } }
Реализовать перехватчик Interceptor и переписать метод перехватчика.Логика реализации аналогична HttpClient.Если удается получить traceId текущего потока, он будет прозрачно передан вниз.
-
Добавить перехватчик для OkHttp
private static OkHttpClient client = new OkHttpClient.Builder() .addNetworkInterceptor(new OkHttpTraceIdInterceptor()) .build();
Вызовите метод addNetworkInterceptor, чтобы добавить перехватчик.
Шаблон отдыха:
-
Реализовать перехватчик RestTemplate
public class RestTemplateTraceIdInterceptor implements ClientHttpRequestInterceptor { @Override public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException { String traceId = MDC.get(Constants.TRACE_ID); if (traceId != null) { httpRequest.getHeaders().add(Constants.TRACE_ID, traceId); } return clientHttpRequestExecution.execute(httpRequest, bytes); } }
Реализовать интерфейс ClientHttpRequestInterceptor и переписать метод перехвата, остальная логика такая же и повторяться не будет.
-
Добавить перехватчик для RestTemplate
restTemplate.setInterceptors(Arrays.asList(new RestTemplateTraceIdInterceptor()));
Вызовите метод setInterceptors, чтобы добавить перехватчик
Перехватчики сторонних сервисов:
Полный процесс traceId HTTP-вызова к интерфейсу сторонней службы требует сотрудничества со сторонней службой. Сторонней службе необходимо добавить перехватчик, чтобы получить traceId в заголовке запроса и добавить его в MDC. .
public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//如果有上层调用就用上层的ID
String traceId = request.getHeader(Constants.TRACE_ID);
if (traceId == null) {
traceId = TraceIdUtils.getTraceId();
}
MDC.put("traceId", traceId);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
MDC.remove(Constants.TRACE_ID);
}
}
инструкция:
- Сначала получите TraceID из заголовка запроса
- Если traceId не может быть получен из заголовка запроса, это означает, что это не сторонний вызов, а непосредственно генерируется новый traceId
- Сохраните сгенерированный traceId в MDC
В дополнение к добавлению перехватчиков вам также необходимо добавить печать traceId в формат журнала следующим образом:
<property name="pattern">[TRACEID:%X{traceId}] %d{HH:mm:ss.SSS} %-5level %class{-1}.%M()/%L - %msg%xEx%n</property>
Нужно добавить %X{traceId}
Наконец прикрепил:код проекта,Добро пожаловатьforkиstar, поднимите звездочку, смиренно попрошайничая
Рекомендуемые статьи в прошлом
1. Пишите лог на запрос аспекта, удобнее скидывать фронт и бэкенды
2. Почему Alibaba отключает Executors для создания пулов потоков?
Справочная статья:
1. Используйте traceId для отслеживания всего журнала обработки запроса в проекте Java.
2. Введение в MDC — многопоточная практика управления журналами