分布式系统调用链监控

一 背景

     分布式服务化框架下,由于分布式服务间存在相互依赖,彼此协同来完成各类业务场景。下图是一个典型的业务场景,从前端发起一个请求,到最后的业务完成,需要经过很多环节,这些环节可能都是分布式服务的方式提供,部署在不同的服务器上进行。而在这种复杂的分布式服务场景下,为了定位问题、性能瓶颈查询、异常日志跟踪等,如果没有服务追踪和分析工具的帮助,都是非常困难的。下图来自Google论文dapper。

二  设计方案

    感谢Google,中文论文如下:http://bigbully.github.io/Dapper-translation/

关键点:

  • TraceID:用来标识每一条业务请求链的唯一ID,TraceID需要在整个调用链路上传递
  • Span:请求链中的每一个环节为一个Span,每一个Span有一个SpanId来标识,前后Span间形成父子关系
  • Annotation:业务自定义的埋点信息,可以是sql、用户ID等关键信息

在Dapper跟踪树结构中,树节点是整个架构的基本单元,而每一个节点又是对span的引用。节点之间的连线表示的span和它的父span直接的关系。虽然span在日志文件中只是简单的代表span的开始和结束时间,他们在整个树形结构中却是相对独立的。

图2:5个span在Dapper跟踪树中的关联关系

上图中说明了span在一个大的跟踪过程中是什么样的。Dapper记录了span名称,以及每个span的ID和父ID,以重建在一次追踪过程中不同span之间的关系。如果一个span没有父ID被称为root span。所有span都挂在一个特定的跟踪上,也共用一个跟踪id(在图中未示出)。所有这些ID用全局唯一的64位整数标示。在一个典型的Dapper跟踪中,我们希望为每一个RPC对应到一个单一的span上,而且每一个额外的组件层都对应一个跟踪树型结构的层级。

图3:在图2中所示的一个单独的span的细节图

图3给出了一个更详细的典型的Dapper跟踪span的记录点的视图。在图2中这种某个span表述了两个“Helper.Call”的RPC(分别为server端和client端)。span的开始时间和结束时间,以及任何RPC的时间信息都通过Dapper在RPC组件库的植入记录下来。如果应用程序开发者选择在跟踪中增加他们自己的注释(如图中“foo”的注释)(业务数据),这些信息也会和其他span信息一样记录下来。

三 开源产品

    常见的开源框架,如Twitter的zipkin,其中zipkin是严格按照来Dapper论文来设计的,他提供了完整的跟踪记录收集、存储功能,以及查询API与界面。其存储支持多种数据库:MySql、ElasticSearch、Cassandra、Redis等等,收集API支持HTTP和Thrift。

美团CAT:  来源https://kefeng.wang/2018/07/06/tracing-cat/

基于Java开发的实时应用监控平台,包括实时应用监控,业务监控 。 集成方案是通过代码埋点的方式来实现监控,比如: 拦截器,注解,过滤器等。 缺点:对业务有入侵。

貌似最近美团突然从14年开始的1.X版本到3.0版本,没有看最新版本的特性。

四 到家自研方案

根据大会公开资料整理。

相关关键点:

spanID :是否根节点判断,根节点为0,非根节点依赖“.”进行追加区分树状关系。

             也需要设置parentID。

traceID: 简单的可以根据uuid生成,复杂的可以根据snowflake改造。

              因为接口调用频繁,对服务要求高,不推荐使用数据库自增大的方法。

             业务逻辑就是:有没有获取到的traceID,有就透传,没有就生成一个并返回。

tracedata在服务中传输策略:

        对于HTTP协议,可以在header报文头里面设置,考虑到线程安全性,保存应该使用ThreadLocal。但是如何将当前上下文中的信息进行临时存储,并保证线程安全呢?这一点可以借助ThreadLocal来完成,发起创建Trace信息时,往ThreadLocal中写入记录,当前请求过程中再发起新的请求时,从ThreadLocal中获取Trace信息继续往下传递,等信息可以提交归档的时候,从ThreadLocal读取,并清除ThreadLocal中的信息。

       对于dubbo或者自定义RPC框架,需要在协议内容增加tracedata。以便序列化反序列化网络传输后能正确获取tracedata。

埋点实现方案:

  • Client Send:客户端发起请求时,如果当前线程上下文已经有Trace信息,继续透传当前Trace信息,如果没有,表示一个信息的请求,生成信息的Trace信息进行传递。
  • Server Recieve: 服务端接收到请求时间点,此时从当前请求里获取Trace信息,并将当前信息存入线程上下文。
  • Server Send:服务端处理业务完成,准备返回响应时,标记业务处理完成,同时将当前Trace信息提交归档。
  • Client Receive:客户端接收到服务端响应时,标记服务调用完成,同时将当前Trace信息提交归档。

      推荐使用filter,大概业务逻辑:requestFilter-->doinvoke(业务逻辑)-->responseFilter.

      在业务调用前后启动,关闭。只需要配置filter,对具体业务无入侵。

这块主要是到家自研的RPC(DSF),web(DWF) 框架,没有开源出来,所以不方便贴代码。

大致思路如此。

五 日志输出

    MDC 介绍 
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

   

如果想在日志中同样显示用户名和会话ID,我们需要使用 MDC.put() 方法将这两个变量存储成键值对:

import java.io.FileReader;
import org.apache.Log4j.Logger;
import org.apache.Log4j.MDC;
...
MDC.put("username", "admin");
MDC.put("sessionID", "1234");
try {
  // tmpFile doesn't exist, causing an exception.
  FileReader fr = new FileReader("tmpFile");
}
catch (Exception ex) {
  logger.error("Unable to open file!");
}
finally {
  MDC.clear();
}

这里再一次强调,在不需要使用Context后,我们需要使用 MDC.clear() 方法将所有的键值对从MDC中移除,这样会降低内存的使用量,并阻止MDC在后面试图调用那些已经过期的数据。

在日志框架中访问MDC的值时,也稍微有些区别。对于存储在上下文中的任何键,我们可以使用%X(键)的方式来访问对应的值。这样,我们可以使用 %X(username) 和 %X(sessionID) 来获取对应的用户名和会话ID:

1

2

<PatternLayout pattern="%X{username} %X{sessionID} %-5p - %m%n" />

"admin 1234 ERROR – Unable to open file!"

如果我们没有指定任何键,那么MDC上下文就会被以 {(key, value),(key, value)} 的方式传递给Appender。

    我们项目中使用的是log4j2,将MDC和NDC合并到一个单独的组件中,这个组件被称为线程上下文。线程上下文是针对MDC和NDC的进化,它分别用线程上下文Map映射线程上下文栈来表示MDC和NDC。我们可以通过ThreadContext静态类来管理线程上下文,这个类在实现上类似于Log4j版本1中的MDC和NDC。本文上面介绍了traceID的使用,本节简单说明下它在日志文件的输出,因为log4j支持MDC的配置输出。项目代码中可以将traceID对放入其中,然后使用指定方式取出打印即可。

ThreadContext.put("traceId",traceId);   //使用

ThreadContext.remove("traceId");  //清除

所以配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
        <Appenders>
                <Console name="myConsole" target="SYSTEM_OUT">
                        <PatternLayout pattern="[%d{MM-dd HH:mm:ss,SSS} %-5p] [%t] [%X{traceId}] %c{2\} - %m%n%ex" />
                </Console>
                <RollingFile name="activexAppender" fileName="../log/payment_service/payment_service.log"
                             filePattern="../log/yy/xx.log.%d{yyyy-MM-dd}.log">
                        <PatternLayout>
                                <Pattern>[%d{MM-dd HH:mm:ss SSS} %-5level] [%t] [%X{traceId}] %c{3} - %m%n%ex</Pattern>
                        </PatternLayout>
                        <Policies>
                                <TimeBasedTriggeringPolicy />
                        </Policies>
                </RollingFile>

输出效果:

六 日志采集系统设计

  

实现结果

效果图

七 总结:

  设计具体的日志跟踪采集系统,需要考虑系统的高可用,尽量减少对业务入侵,大量日志数据的上传,如何采样,报警等等。有许多值得去不断探索优化的地方。

 

参考:http://sharecore.net/post/rpc%E6%9C%8D%E5%8A%A1%E8%BF%BD%E8%B8%AA%E7%9A%84%E5%8E%9F%E7%90%86%E4%B8%8E%E5%AE%9E%E8%B7%B5/

https://kefeng.wang/2018/07/06/tracing-cat/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值