微服务透传日志traceId

问题

在微服务架构中,一次业务执行完可能需要跨多个服务,这个时候,我们想看到业务完整的日志信息,就要从各个服务中获取,即便是使用了ELK把日志收集到一起,但如果不做处理,也是无法完整把一次业务请求的日志完整链路串联起来。有人说,可以在日志中加入某个业务参数,比如订单id等,但是不可能所有业务都是有这样的参数给你用的。

解决方案

在打印日志的时候加入一个可以贯穿整个业务请求的唯一标识,我们把它称traceId。下面是在一个物联网项目的微服务架构中,使用dubbo时的基本流程:

拿到traceId,该怎么使用呢?答案也很简单,用过logback的都知道,是可以在日志信息里配置一些公共参数的,比如方法名,类名,线程名等,我们把traceId也设置进去就可以了。

logback配置如下:

xml 复制代码
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <pattern>%date{yyyy-MM-dd HH:mm:ss} [%-5level]  [%X{traceId}]  [%thread] [%logger{80}] [%file:%line] [%method]>>>>>> %msg%n</pattern>
            <charset>utf-8</charset>
        </encoder>

代码中设置:

web过滤器,针对的是HTTP请求,

java 复制代码
@Configuration
public class TraceFilter extends OncePerRequestFilter {


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws IOException, ServletException {
        try {
            String uuid = UUID.randomUUID().toString();
            MDC.put("traceId",uuid);
            filterChain.doFilter(request, response);
        } finally {
            // 必须清除
            MDC.clear();
        }
    }
}

跨服务调用走dubbo:

消费者过滤器:

java 复制代码
@Activate(group = {CommonConstants.CONSUMER})
public class ConsumerDubboFilter implements org.apache.dubbo.rpc.Filter{

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        Map<String, Object> attachments = invocation.getObjectAttachments();
        // 获取traceId
        String traceId = MDC.get("traceId");
        // 传递日志追踪信息
        attachments.put("traceId",traceId);
        return invoker.invoke(invocation);
    }
}

生产者过滤器:

java 复制代码
@Activate(group = {CommonConstants.PROVIDER})
public class ProviderDubboFilter implements org.apache.dubbo.rpc.Filter{

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        try {
            // 获取到traceId
            String traceId = (String)invocation.getObjectAttachment("traceId");
            // 设置traceId
            MDC.put("traceId",traceId);
            return invoker.invoke(invocation);
        }finally {
            // 必须清除
            MDC.clear();
        }
    }
}

有聪明的小伙伴,此时已经想到了一个问题,那就是如果在子线程中打印日志依然是拿不到traceId的,没关系,照样可以解决,子线程传递上下文信息,第一个想到的是什么?对,阿里的TTL,

在pom中引入:

xml 复制代码
        <dependency>
            <groupId>com.ofpay</groupId>
            <artifactId>logback-mdc-ttl</artifactId>
            <version>1.0.2</version>
        </dependency>

logback的xml配置中加入一行代码:

xml 复制代码
<contextListener class="com.ofpay.logback.TtlMdcListener"/>

还有伙伴会问,如果用的是OpenFeign呢,其实原理一样,使用OpenFeign的拦截器,在跨服务调用之前拦截,拿到traceId,再放入HTTP请求头中,下游服务在过滤器中再从请求头中获取即可。这里代码就不再展示,直接搜feign的拦截器使用就行了。

扩展

既然traceId可以这样透传,那么其他的一些公共的信息当然也可以透传,比如分页信息,用户信息,ThreadLocal中上下文信息,都可以使用这样的方式去透传,让开发人员感觉还是在单服务中一样简便。

下一篇:MQTT+Disruptor提高并发

相关推荐
特立独行的猫A2 小时前
用仓颉语言 cj-tauri 框架,五分钟给娃做了个鸿蒙形近字游戏
java·后端·python
真上帝的左手5 小时前
30. 技术专题-‌应用框架组件-分布式架构
分布式·架构·系统架构·分布式架构
budingxiaomoli8 小时前
Spring统一功能处理
java·后端·spring
Decay__9 小时前
SQLServer 索引与 SQL 优化 —— 聚集索引、非聚集索引、覆盖索引、碎片维护与执行计划
运维·数据库·sql·mysql·架构
罗小爬EX9 小时前
Helm 从概念到实战:以一个普通 Web 应用为例
云原生·helm
Flynn Liang10 小时前
可观测性与成本治理:一次对话的 17 个观测点
java·langchain·可观测性·micrometer·langchain4j·token 成本·llm 监控
Dawson Zhu10 小时前
【分布式训练④】张量并行与序列并行:把单层参数切碎分到多张卡
人工智能·语言模型·架构·aigc·agi
金銀銅鐵10 小时前
带您看懂字节码指令(三):同一行加法,为什么有四种指令
java·jvm
yuyuxun112 小时前
协同过滤的校园二手物品智能推荐系统的设计与实现 毕业设计-附源码40082
javascript·vue.js·spring boot·python·数据分析·django·课程设计
码客-ygluu13 小时前
一个通用游戏数据服务器
c++·架构