feign线程隔离策略链路传递
一、MDC机制
1、配置文件增加 %X{traceId}
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{traceId}] %-5level %logger{50} %msg%n"/>
2、web拦截器,增加链路追踪id
@Component public class LoginInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { MDC.put("traceId", Idutil.uuid()); return true; } public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { MDC.clear(); } }
3、添加transmittable-thread-local的jar包
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.12.4</version> </dependency>
4、重写MDC适配,一定要在org.slf4j包下
package org.slf4j; import com.alibaba.ttl.TransmittableThreadLocal; import org.slf4j.spi.MDCAdapter; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; public class TtlMDCAdapter implements MDCAdapter { private final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new TransmittableThreadLocal<>(); private static final int WRITE_OPERATION = 1; private static final int MAP_COPY_OPERATION = 2; static TtlMDCAdapter mtcMDCAdapter; static { mtcMDCAdapter = new TtlMDCAdapter(); // 替换MDC的MDCAdapter MDC.mdcAdapter = mtcMDCAdapter; } public static MDCAdapter getInstance() { return mtcMDCAdapter; } final ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>(); private Integer getAndSetLastOperation(int op) { Integer lastOp = lastOperation.get(); lastOperation.set(op); return lastOp; } private boolean wasLastOpReadOrNull(Integer lastOp) { return lastOp == null || lastOp.intValue() == MAP_COPY_OPERATION; } private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) { Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>()); if (oldMap != null) { // we don't want the parent thread modifying oldMap while we are // iterating over it synchronized (oldMap) { newMap.putAll(oldMap); } } copyOnInheritThreadLocal.set(newMap); return newMap; } /** * Put a context value (the <code>val</code> parameter) as identified with the * <code>key</code> parameter into the current thread's context map. Note that * contrary to log4j, the <code>val</code> parameter can be null. * <p/> * <p/> * If the current thread does not have a context map it is created as a side * effect of this call. * * @throws IllegalArgumentException in case the "key" parameter is null */ @Override public void put(String key, String val) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } Map<String, String> oldMap = copyOnInheritThreadLocal.get(); Integer lastOp = getAndSetLastOperation(WRITE_OPERATION); if (wasLastOpReadOrNull(lastOp) || oldMap == null) { Map<String, String> newMap = duplicateAndInsertNewMap(oldMap); newMap.put(key, val); } else { oldMap.put(key, val); } } /** * Remove the the context identified by the <code>key</code> parameter. * <p/> */ @Override public void remove(String key) { if (key == null) { return; } Map<String, String> oldMap = copyOnInheritThreadLocal.get(); if (oldMap == null) return; Integer lastOp = getAndSetLastOperation(WRITE_OPERATION); if (wasLastOpReadOrNull(lastOp)) { Map<String, String> newMap = duplicateAndInsertNewMap(oldMap); newMap.remove(key); } else { oldMap.remove(key); } } /** * Clear all entries in the MDC. */ @Override public void clear() { lastOperation.set(WRITE_OPERATION); copyOnInheritThreadLocal.remove(); } /** * Get the context identified by the <code>key</code> parameter. * <p/> */ @Override public String get(String key) { final Map<String, String> map = copyOnInheritThreadLocal.get(); if ((map != null) && (key != null)) { return map.get(key); } else { return null; } } /** * Get the current thread's MDC as a map. This method is intended to be used * internally. */ public Map<String, String> getPropertyMap() { lastOperation.set(MAP_COPY_OPERATION); return copyOnInheritThreadLocal.get(); } /** * Returns the keys in the MDC as a {@link Set}. The returned value can be * null. */ public Set<String> getKeys() { Map<String, String> map = getPropertyMap(); if (map != null) { return map.keySet(); } else { return null; } } /** * Return a copy of the current thread's context map. Returned value may be * null. */ @Override public Map<String, String> getCopyOfContextMap() { Map<String, String> hashMap = copyOnInheritThreadLocal.get(); if (hashMap == null) { return null; } else { return new HashMap<String, String>(hashMap); } } @Override public void setContextMap(Map<String, String> contextMap) { lastOperation.set(WRITE_OPERATION); Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>()); newMap.putAll(contextMap); // the newMap replaces the old one for serialisation's sake copyOnInheritThreadLocal.set(newMap); } }
5、加载MDC适配器
package org.slf4j; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; public class TtlMDCAdapterInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext configurableApplicationContext) { // 加载自定义的MDCAdapter TtlMDCAdapter.getInstance(); } }
在spring.factories文件添加自定义初始化器
#SpringBoot程序添加自定义ApplicationContextInitializer,用以支持异步程序日志链路追踪
org.springframework.context.ApplicationContextInitializer=org.slf4j.TtlMDCAdapterInitializer
二、自定义线程池实现
JDK自带ThreadPoolExecutor
package com.ren.user.common.feign; import com.alibaba.ttl.TtlCallable; import com.alibaba.ttl.TtlRunnable; import java.util.Collection; import java.util.List; import java.util.concurrent.*; public class TtlThreadPoolExecutor extends ThreadPoolExecutor { public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); } public TtlThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } public TtlThreadPoolExecutor(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) { Runnable runnable = TtlRunnable.get(command); super.execute(runnable); } @Override public <T> Future<T> submit(Runnable task, T result) { Runnable runnable = TtlRunnable.get(task); return super.submit(runnable, result); } @Override public Future<?> submit(Runnable task) { Runnable runnable = TtlRunnable.get(task); return super.submit(runnable); } @Override public <T> Future<T> submit(Callable<T> task) { Callable command = TtlCallable.get(task); return super.submit(command); } @Override protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { Runnable command = TtlRunnable.get(runnable); return super.newTaskFor(command, value); } @Override protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { Callable command = TtlCallable.get(callable); return super.newTaskFor(command); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException { Collection<? extends Callable<T>> callables = TtlCallable.gets(tasks); return super.invokeAny(callables); } @Override public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { Collection<? extends Callable<T>> callables = TtlCallable.gets(tasks); return super.invokeAny(callables, timeout, unit); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException { Collection<? extends Callable<T>> callables = TtlCallable.gets(tasks); return super.invokeAll(callables); } @Override public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException { Collection<? extends Callable<T>> callables = TtlCallable.gets(tasks); return super.invokeAll(callables, timeout, unit); } }
Spring提供ThreadPoolTaskExecutor
package com.ren.user.common.feign; import com.alibaba.ttl.TtlCallable; import com.alibaba.ttl.TtlRunnable; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.concurrent.ListenableFuture; import java.util.concurrent.Callable; import java.util.concurrent.Future; public class TtlThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { @Override public void execute(Runnable command) { Runnable ttlRunnable = TtlRunnable.get(command); super.execute(ttlRunnable); } @Override public <T> Future<T> submit(Callable<T> task) { Callable ttCallable = TtlCallable.get(task); return super.submit(ttCallable); } @Override public Future<?> submit(Runnable task) { Runnable ttlRunnable = TtlRunnable.get(task); return super.submit(ttlRunnable); } @Override public ListenableFuture<?> submitListenable(Runnable task) { Runnable ttlRunnable = TtlRunnable.get(task); return super.submitListenable(ttlRunnable); } @Override public <T> ListenableFuture<T> submitListenable(Callable<T> task) { Callable ttlCallable = TtlCallable.get(task); return super.submitListenable(ttlCallable); } }
三、自定义feign插件
1、自定义策略
package com.ren.user.common.feign; import com.alibaba.ttl.TtlCallable; import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.*; @Slf4j public class TtlHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy { private static TtlHystrixConcurrencyStrategy INSTANCE = new TtlHystrixConcurrencyStrategy(); public static TtlHystrixConcurrencyStrategy getInstance() { return INSTANCE; } private TtlHystrixConcurrencyStrategy() { } @Override public <T> Callable<T> wrapCallable(Callable<T> callable) { return TtlCallable.get(callable); } }
2、初始化策略
@Configuration(proxyBeanMethods = false) public class FeignConfig{ @PostConstruct public void init(){ // 重置之前的策略 HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance().getEventNotifier(); HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance().getMetricsPublisher(); HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance().getPropertiesStrategy(); HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance().getCommandExecutionHook(); HystrixPlugins.reset(); // 换成最新的 HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); // HystrixPlugins.getInstance().registerConcurrencyStrategy(HystrixConcurrencyStrategyDefault.getInstance()); HystrixPlugins.getInstance().registerConcurrencyStrategy(TtlHystrixConcurrencyStrategy.getInstance()); HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); } @Bean public RequestInterceptor feignRequestInterceptor() { return requestTemplate -> { requestTemplate.header("traceId", MDC.get("traceId")); }; } }