微服务TraceId设计(SpringCloud OpenFeign)[亲测有效]

微服务TraceId设计(SpringCloud OpenFeign)[亲测有效]微服务框架下traceId的生成和传递以及多线程环境下的一致性保证。

一、背景

     在微服务框架下,需要通过traceId来定位到整个链路中每个细节的运行情况,这里不引用外部组件,自己来进行实现。当然还保证在多线程环境下的一致性。

二、需求

1)接口入口侧如果有traceId,则获取;如果没有则生成;

2)定义生成traceId的规则;

3)traceId可以默认打印到log中,不需要自己显示定义;

4)通过feign传输到后端服务中

三、实现

3.1)TraceId生成算法

    参考:https://help.aliyun.com/document_detail/151840.html 

public static String genTraceId()  {

        try {
            StringBuilder sb = new StringBuilder();
            InetAddress addr = InetAddress.getLocalHost();
            if (null == addr) {
                return TRACE_ID_VALUE_DEFAULT;
            }
            String addrStr = addr.getHostAddress();
            if (null == addrStr) {
                return TRACE_ID_VALUE_DEFAULT;
            }
            String[] addrArr = addrStr.split("\\.");
            if (addrArr.length != 4) {
                return TRACE_ID_VALUE_DEFAULT;
            }

            sb.append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[0]))  , 2, "0"))
                    .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[1]))  , 2, "0"))
                    .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[2]))  , 2, "0"))
                    .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[3]))  , 2, "0"))
                    .append(System.currentTimeMillis())
                    .append(RandomUtil.randomNumbers(4))
                    .append(ProcessHandle.current().pid());

            return sb.toString();
        } catch (Exception e) {
            return TRACE_ID_VALUE_DEFAULT;
        }
    }

3.2)服务入口日志拦截

@EnableAspectJAutoProxy
@Component
@Aspect
@Slf4j
public class InterfaceLogAspect {

    @Around("execution(* com.springboot.k8s.demo.interfaces.*.*(..))")
    public Object interfaceAround(ProceedingJoinPoint joinPoint) {
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Parameter[] argNames = signature.getMethod().getParameters();
        StringBuilder sb = new StringBuilder(className + "." + joinPoint.getSignature().getName() + " -- ");

        //获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        TraceIdUtil.initTraceId(request);

        StopWatch clock = new StopWatch();
        clock.start();
        Object retVal = null;
        try {
            Map<String, Object> paramMap = new HashMap<>();
            for (int i = 0; i < argNames.length; i++) {
                paramMap.put(argNames[i].getName(), args[i]);
            }
            LoggerUtils.auditLogInfo(LogLabelEnum.REQUEST_IN.getLogLabel(), paramMap);
            retVal = joinPoint.proceed(joinPoint.getArgs());
        } catch (Throwable e) {
            //..
        } finally {
            //..
        }
        return retVal;
    }
}

3.3)FeignClient服务之间传递TraceId:

@Service
public class MyRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate template) {
        template.header(TraceIdUtil.TRACE_ID_KEY, TraceIdUtil.getTraceId());
    }
}

3.4)logback.xml

<appender name="consoleLog" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[%-5level][%date{yyyy-MM-dd HH:mm:ss:SSS}][%logger:%line] &#45;&#45;%mdc{client} %msg||traceId=%X{Trace-Id}%n</pattern>

        </layout>
    </appender>

备注:”%X{Trace-Id}”即将MDC中的以Trace-Id为key的值同步到日志中,也就是TraceIdUtil.TRACE_ID_KEY。

完整的TraceIdUtill为:

public class TraceIdUtil {

    public static final String TRACE_ID_KEY = "Trace-Id";

    public static final String TRACE_ID_VALUE_DEFAULT = "000000";

    public static void setTraceId(String traceId) {
        //如果参数为空,则设置默认traceId
        //将traceId放到MDC中
        MDC.put(TRACE_ID_KEY, traceId);
    }

    public static String getTraceId() {
        String traceId = MDC.get(TRACE_ID_KEY);

        return StringUtils.isNotBlank(traceId) ? traceId : TRACE_ID_VALUE_DEFAULT;
    }

    /**
     * generate traceId: https://help.aliyun.com/document_detail/151840.html
     */
    public static String genTraceId()  {

        try {
            StringBuilder sb = new StringBuilder();
            InetAddress addr = InetAddress.getLocalHost();
            if (null == addr) {
                return TRACE_ID_VALUE_DEFAULT;
            }
            String addrStr = addr.getHostAddress();
            if (null == addrStr) {
                return TRACE_ID_VALUE_DEFAULT;
            }
            String[] addrArr = addrStr.split("\\.");
            if (addrArr.length != 4) {
                return TRACE_ID_VALUE_DEFAULT;
            }

            sb.append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[0]))  , 2, "0"))
                    .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[1]))  , 2, "0"))
                    .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[2]))  , 2, "0"))
                    .append(StringUtils.leftPad(Integer.toHexString(Integer.valueOf(addrArr[3]))  , 2, "0"))
                    .append(System.currentTimeMillis())
                    .append(RandomUtil.randomNumbers(4))
                    .append(ProcessHandle.current().pid());

            return sb.toString();
        } catch (Exception e) {
            return TRACE_ID_VALUE_DEFAULT;
        }
    }

    public static void initTraceId(HttpServletRequest request) {
        String traceId = "";
        if (request != null) {
            //get traceId from request
            traceId = request.getHeader(TraceIdUtil.TRACE_ID_KEY);
        }
        //generate new traceId
        if (StringUtils.isBlank(traceId) || TRACE_ID_VALUE_DEFAULT.equals(traceId)){
            traceId = TraceIdUtil.genTraceId();
        }

        TraceIdUtil.setTraceId(traceId);
    }

    public static void main(String[] args) {
        System.out.println(TraceIdUtil.genTraceId());
    }
}

四、多线程TraceId复制

4.1)线程配置

@Configuration
public class ThreadPoolConfig {

    public static final String ASYNC_EXECUTOR_NAME = "threadPoolTaskExecutor";

    @Bean(name=ASYNC_EXECUTOR_NAME)
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // for passing in request scope context
        executor.setTaskDecorator(new ContextCopyingDecorator());
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setThreadNamePrefix("SeelAsyncThread-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        executor.initialize();
        return executor;
    }
}

4.2)定义TaskDecorator

public class ContextCopyingDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        Map<String, String> contextMap = MDC.getCopyOfContextMap();
        return  () -> {
            try {
                RequestContextHolder.setRequestAttributes(context);
                MDC.setContextMap(contextMap);
                runnable.run();
            } finally {
                RequestContextHolder.resetRequestAttributes();
            }
        };
    }
}

Author:忆之独秀

Email:leaguenew@qq.com

转载注明出处:

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/10853.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注