`
1028826685
  • 浏览: 939108 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类

dubbo+zipkin调用链监控

 
阅读更多

分布式环境下,对于线上出现问题往往比单体应用要复杂的多,原因是前端的一个请求可能对应后端多个系统的多个请求,错综复杂。

对于快速问题定位,我们一般希望是这样的:

  • 从下到下关键节点的日志,入参,出差,异常等。
  • 关键节点的响应时间
  • 关键节点依赖关系

而这些需求原来在单体应用中可以比较容易实现,但到了分布式环境,可能会出现:

  • 每个系统的技术栈不同
  • 有的系统有日志有的连日志都没有
  • 日志实现手段不相同

以上系统都是自治的,要想看整体的调用链非常困难。

分布式系统日志统一的手段有很多,比如常见的ELK,但这些日志都是文本,不太容易做分析。

更希望看到类似如下浏览器对于网络请求的分析:将分散的请求串联在一起

zipkin

这是推特的一个产品,通过API收集各系统的调用链信息然后做数据分析,展示调用链数据。

核心功能:

  • 搜索调用链信息
    此处不多说,无非就是从存储中按一定条件搜索请求信息。

zipkin默认是内存存储,也可以是其它的比如:mysq,elasticsearch

  • 查看某条请求的详细调用链

比如查询产品明细,除了产品的基本信息还需要展示对产品的所有评论。下图可以清晰的展示调用关系,product-dubbo-consumer调用product-dubbo-provider,product-dubbo-provider内部再调用comment-dubbo-provider。每步之间的时间也一目了然。

上面显示的时间默认是指调用端发起远程开始到从服务端接收到数据,其中包含网络连接以及数据传输的时间。

  • 查看服务之间的依赖关系

互联网项目目前微服务比较流行,微服务之间可能会存在循环引用形成一个网状关系。当项目规模越来越大后,微服务之间的依赖关系估计谁也理不清,现在可以从请求链中清楚查看依赖。

几个关键概念

  • traceId
    就是一个全局的跟踪ID,是跟踪的入口点,根据需求来决定在哪生成traceId。比如一个http请求,首先入口是web应用,一般看完整的调用链这里自然是traceId生成的起点,结束点在web请求返回点。

  • spanId
    这是下一层的请求跟踪ID,这个也根据自己的需求,比如认为一次rpc,一次sql执行等都可以是一个span。一个traceId包含一个以上的spanId。

  • parentId
    上一次请求跟踪ID,用来将前后的请求串联起来。

  • cs
    客户端发起请求的时间,比如dubbo调用端开始执行远程调用之前。

  • cr
    客户端收到处理完请求的时间。

  • ss
    服务端处理完逻辑的时间。

  • sr
    服务端收到调用端请求的时间。

客户端调用时间=cr-cs
服务端处理时间=sr-ss

优化考虑

默认系统是通过http请求将数据发送到zipkin,如果系统的调用量比较大,需要考虑如下这些问题:

  • 网络传输
    如果一次请求内部包含多次远程请求,那么对应span生成的数据会相对较大,可以考虑压缩之后再传输。

  • 阻塞
    调用链的功能只是辅助功能,不能影响现有业务系统(比如性能相比之前有下降,zipkin的稳定性影响现有业务等),所以在推送日志时最好采用异步+容错方式进行。

  • 数据丢失
    如果日志在后台积压,未处理完时服务器出现重启就会导致未来的急处理的日志数据会丢失,尽管这种调用数据可以容忍,但如果想做到极致的话,也是有办法的,比如用消息队列做缓冲。

dubbo zipkin

由于工作中一直用dubbo这个rpc框架实现微服务,以前我们基本都是在kibana平台上查询各自服务的日志然后分析,比较麻烦,特别是在分析性能瓶颈时。在dubbo中引入zipkin是非常方便的,因为无非就是写filter,在请求处理前后发送日志数据,让zipkin生成调用链数据。

调用链跟踪自动配置

由于我的项目环境是spring boot,所以附带做一个调用链追踪的自动配置。

  • 自动配置的注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableTraceAutoConfigurationProperties {
}
  • 自动配置的实现,主要是将特定配置节点的值读取到上下文对象中
@Configuration
@ConditionalOnBean(annotation = EnableTraceAutoConfigurationProperties.class)
@AutoConfigureAfter(SpringBootConfiguration.class)
@EnableConfigurationProperties(TraceConfig.class)
public class EnableTraceAutoConfiguration {

    @Autowired
    private TraceConfig traceConfig;

    @PostConstruct
    public void init() throws Exception {
        TraceContext.init(this.traceConfig);
    }
}
  • 配置类
@ConfigurationProperties(prefix = "dubbo.trace")
public class TraceConfig {

    private boolean enabled=true;

    private int connectTimeout;

    private int readTimeout;

    private int flushInterval=0;

    private boolean compressionEnabled=true;

    private String zipkinUrl;

    @Value("${server.port}")
    private int serverPort;

    @Value("${spring.application.name}")
    private String applicationName;

}
  • spring 配置
    按如下图配置才能实现自动加载功能。

  • 启动自动配置

最后在启动类中增加@EnableTraceAutoConfigurationProperties即可显示启动。

追踪上下文数据

因为一个请求内部会多次调用下级远程服务,所以会共享traceId以及spanId等,设计一个TraceContext用来方便访问这些共享数据。

这些上下文数据由于是请求级别,所以用ThreadLocal存储

public class TraceContext extends AbstractContext {

    private static ThreadLocal<Long> TRACE_ID = new InheritableThreadLocal<>();

    private static ThreadLocal<Long> SPAN_ID = new InheritableThreadLocal<>();

    private static ThreadLocal<List<Span>> SPAN_LIST = new InheritableThreadLocal<>();

    public static final String TRACE_ID_KEY = "traceId";

    public static final String SPAN_ID_KEY = "spanId";

    public static final String ANNO_CS = "cs";

    public static final String ANNO_CR = "cr";

    public static final String ANNO_SR = "sr";

    public static final String ANNO_SS = "ss";

    private static TraceConfig traceConfig;


    public static void clear(){
        TRACE_ID.remove();
        SPAN_ID.remove();
        SPAN_LIST.remove();
    }

    public static void init(TraceConfig traceConfig) {
        setTraceConfig(traceConfig);
    }

    public static void start(){
        clear();
        SPAN_LIST.set(new ArrayList<Span>());
    }

}

zipkin日志收集器

这里直接使用http发送数据,详细代码就不贴了,核心功能就是将数据通过http传送到zipkin,中间可以配合压缩等优化手段。

日志收集器代理

由于考虑到会扩展到多种日志收集器,所以用代理做封装。考虑到优化,可以结合线程池来异步执行日志发送,避免阻塞正常业务逻辑。

public class TraceAgent {
    private final AbstractSpanCollector collector;

    private final int THREAD_POOL_COUNT=5;

    private final ExecutorService executor =
            Executors.newFixedThreadPool(this.THREAD_POOL_COUNT, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread worker = new Thread(r);
                    worker.setName("TRACE-AGENT-WORKER");
                    worker.setDaemon(true);
                    return worker;
                }
            });

    public TraceAgent(String server) {

        SpanCollectorMetricsHandler metrics = new SimpleMetricsHandler();

        collector = HttpCollector.create(server, TraceContext.getTraceConfig(), metrics);
    }

    public void send(final List<Span> spans){
        if (spans != null && !spans.isEmpty()){
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    for (Span span : spans){
                        collector.collect(span);
                    }
                    collector.flush();
                }
            });
        }
    }
}

dubbo filter

上面做了那么的功能,都是为filter实现准备的。使用filter机制基本上可以认为对现有系统是无侵入性的,当然如果公司项目都直接引用dubbo原生包多少有些麻烦,最好的做法是公司对dubbo做一层包装,然后项目引用包装之后的包,这样就可以避免上面提到的问题,如此一来,调用端只涉及到修改配置文件。

  • 调用端filter
    调用端是调用链的入口,但需要判断是第一次调用还是内部多次调用。如果是第一次调用那么生成全新的traceId以及spanId。如果是内部多次调用,那么需要从TraceContext中获取traceId以及spanId。
private Span startTrace(Invoker<?> invoker, Invocation invocation) {

    Span consumerSpan = new Span();

    Long traceId=null;
    long id = IdUtils.get();
    consumerSpan.setId(id);
    if(null==TraceContext.getTraceId()){
        TraceContext.start();
        traceId=id;
    }
    else {
        traceId=TraceContext.getTraceId();
    }

    consumerSpan.setTrace_id(traceId);
    consumerSpan.setParent_id(TraceContext.getSpanId());
    consumerSpan.setName(TraceContext.getTraceConfig().getApplicationName());
    long timestamp = System.currentTimeMillis()*1000;
    consumerSpan.setTimestamp(timestamp);

    consumerSpan.addToAnnotations(
            Annotation.create(timestamp, TraceContext.ANNO_CS,
                    Endpoint.create(
                            TraceContext.getTraceConfig().getApplicationName(),
                            NetworkUtils.ip2Num(NetworkUtils.getSiteIp()),
                            TraceContext.getTraceConfig().getServerPort() )));

    Map<String, String> attaches = invocation.getAttachments();
    attaches.put(TraceContext.TRACE_ID_KEY, String.valueOf(consumerSpan.getTrace_id()));
    attaches.put(TraceContext.SPAN_ID_KEY, String.valueOf(consumerSpan.getId()));
    return consumerSpan;
}

private void endTrace(Span span, Stopwatch watch) {

    span.addToAnnotations(
            Annotation.create(System.currentTimeMillis()*1000, TraceContext.ANNO_CR,
                    Endpoint.create(
                            span.getName(),
                            NetworkUtils.ip2Num(NetworkUtils.getSiteIp()),
                            TraceContext.getTraceConfig().getServerPort())));

    span.setDuration(watch.stop().elapsed(TimeUnit.MICROSECONDS));
    TraceAgent traceAgent=new TraceAgent(TraceContext.getTraceConfig().getZipkinUrl());

    traceAgent.send(TraceContext.getSpans());

}

调用端需要通过Invocation的参数列表将生成的traceId以及spanId传递到下游系统中。

Map<String, String> attaches = invocation.getAttachments();
attaches.put(TraceContext.TRACE_ID_KEY, String.valueOf(consumerSpan.getTrace_id()));
attaches.put(TraceContext.SPAN_ID_KEY, String.valueOf(consumerSpan.getId()));
  • 服务端filter
    与调用端的逻辑类似,核心区别在于发送给zipkin的数据是服务端的。
private Span startTrace(Map<String, String> attaches) {

    Long traceId = Long.valueOf(attaches.get(TraceContext.TRACE_ID_KEY));
    Long parentSpanId = Long.valueOf(attaches.get(TraceContext.SPAN_ID_KEY));

    TraceContext.start();
    TraceContext.setTraceId(traceId);
    TraceContext.setSpanId(parentSpanId);

    Span providerSpan = new Span();

    long id = IdUtils.get();
    providerSpan.setId(id);
    providerSpan.setParent_id(parentSpanId);
    providerSpan.setTrace_id(traceId);
    providerSpan.setName(TraceContext.getTraceConfig().getApplicationName());
    long timestamp = System.currentTimeMillis()*1000;
    providerSpan.setTimestamp(timestamp);

    providerSpan.addToAnnotations(
            Annotation.create(timestamp, TraceContext.ANNO_SR,
                    Endpoint.create(
                            TraceContext.getTraceConfig().getApplicationName(),
                            NetworkUtils.ip2Num(NetworkUtils.getSiteIp()),
                            TraceContext.getTraceConfig().getServerPort() )));

    TraceContext.addSpan(providerSpan);
    return providerSpan;
}

private void endTrace(Span span, Stopwatch watch) {

    span.addToAnnotations(
            Annotation.create(System.currentTimeMillis()*1000, TraceContext.ANNO_SS,
                    Endpoint.create(
                            span.getName(),
                            NetworkUtils.ip2Num(NetworkUtils.getSiteIp()),
                            TraceContext.getTraceConfig().getServerPort())));

    span.setDuration(watch.stop().elapsed(TimeUnit.MICROSECONDS));
    TraceAgent traceAgent=new TraceAgent(TraceContext.getTraceConfig().getZipkinUrl());

    traceAgent.send(TraceContext.getSpans());

}

RPC之间的调用之所以能够串起来,主要是通过dubbo的Invocation所携带的参数来传递

filter应用

  • 调用端
<dubbo:consumer filter="traceConsumerFilter"></dubbo:consumer>
  • 服务端
<dubbo:provider filter="traceProviderFilter" />

埋点

要想生成调用链的数据,就需要确认关键节点,不限于远程调用,也有可能是本地的服务方法的调用,这就需要根据不同的需求来做埋点。

  • web 请求,通过filter机制,粗粒度。
  • rpc 请求,通过filter机制(一般rpc框架都有实现filter做扩展,如果没有就只能自己实现),粗粒度。
  • 内部服务,通过AOP机制,一般结合注解,类似于Spring Cache的使用,细粒度。
  • 数据库持久层,比如select,update这类,像mybatis都提供了拦截接口,与filter类似,细粒度。
    转载地址:https://github.com/liyong1028826685/jim-framework
分享到:
评论

相关推荐

    SpringBoot +Dubbo+zookeepper

    - 监控与日志:集成监控工具(如 Zipkin、Prometheus)进行服务跟踪,设置合适的日志级别,便于问题定位。 通过 SpringBoot、Dubbo 和 ZooKeeper 的整合,我们可以构建出高效、稳定且易于维护的分布式系统。对于...

    zipkin+dubbo整合示例

    这两天看了好几篇帖子,写zipkin与dubbo整合的内容都不...利用zipkin可以对dubbo进行调用链监控,可以查到调用链中的dubbo服务的性能,并且dubbo提供了SPI的接口,能很容易完成并自定义相应的filter去监控dubbo服务。

    zipkin分布式服务链接监控服务端

    - 使用`dubbo-zipkin`插件,使Dubbo服务调用信息能够上报到Zipkin服务器。 4. **Zipkin UI**: - Zipkin提供了直观的Web界面,用户可以通过搜索Trace ID来查看请求的完整链路和延迟信息。 - 可以通过服务名、...

    dubbo 链路跟踪

    5. **启动与查看**:启动Dubbo服务,当服务间发生调用时,Zipkin会收集到这些调用信息并展示在Web界面。你可以通过Zipkin的Web UI来查看和分析调用链路,找出性能瓶颈。 通过上述步骤,你就成功地在Dubbo中实现了...

    Dubbo源码+jar包

    - **调用链路监控**:查看Dubbo如何集成HTrace、Zipkin等工具,实现服务调用链路的追踪和分析。 - **过滤器和拦截器**:学习如何自定义过滤器,实现业务逻辑增强、日志记录、性能统计等功能。 - **负载均衡和容错...

    SpringBoot整合dubbo2.7.8+zookeeper3.4.14

    此外,为了更好地监控和管理服务,可以结合使用Dubbo的Admin工具或者集成Zipkin、Skywalking等分布式追踪系统。 总之,SpringBoot 2.4.2与Dubbo 2.7.8、Zookeeper 3.4.14的整合是一个常见的微服务架构实践,它简化...

    SpringBoot2.X版本优惠券实战整合Dubbo+Rocketmq+Redis

    此外,对于分布式环境中的监控和运维,可以借助Nginx进行负载均衡,通过Alibaba的SLF4J和Logback进行日志收集,使用Prometheus或Zipkin进行服务调用链路追踪和性能监控,以确保系统的稳定运行。 最后,理解并掌握...

    dubbo监控中心控制台

    Dubbo监控中心控制台是Java微服务架构中一个至关重要的组件,主要负责提供对Dubbo服务的实时监控和管理功能。Dubbo是一个高性能、轻量级的开源Java RPC框架,它提供了服务治理、集群容错、负载均衡等功能,广泛应用...

    Dubbo异步调用的优化共20页.pdf.zip

    5. **监控与问题排查**:介绍如何利用Dubbo提供的监控工具如HTrace、Zipkin等来追踪异步调用的执行情况,以及在遇到问题时如何进行有效的故障排查。 6. **实战案例**:分享实际项目中异步调用的使用场景和优化策略...

    BigBoot:基于Dubbo + SpringBoot 的分布式快速开发脚手架

    同时,使用Prometheus或Zipkin进行服务监控和调用链追踪,可以实时了解系统的运行状态。 六、暂停开发与转向SpringCloud 尽管BigBoot在分布式开发领域展现出了强大的实用性,但“暂停开发,拥抱SpringCloud去了”...

    SpringBoot&Dubbo.rar_DEMO_dubbo_springboot_springboot+dubbo_spri

    它允许服务提供者暴露服务给服务消费者,同时提供了监控和调用链跟踪等功能。在本示例中,`YzService`很可能包含了服务提供者的代码,实现了具体的业务逻辑,而`YzApi`可能是定义了服务接口的模块,供服务消费者调用...

    基于Dubbo埋点的分布式调用跟踪系统

    本系统是基于阿里巴巴的开源RPC框架Dubbo实现的,通过在Dubbo服务的调用链路上进行埋点,收集并分析调用数据,从而提供全链路监控能力。 首先,我们要了解Dubbo的核心概念。Dubbo是一个高性能、轻量级的Java RPC...

    dubbo项目源码

    在实际项目中,淘淘商城可能还涉及其他技术,如Zookeeper作为服务注册中心,MySQL和Redis作为数据库,Elasticsearch进行全文搜索,以及各种监控工具如Zipkin、Prometheus和Grafana等,这些组件共同构建了一个复杂的...

    管理系统系列--dubbo服务管理以及监控系统.zip

    监控系统通常指的是Dubbo内置的监控中心,如Dubbo Admin或者与其他监控工具(如Zipkin、Prometheus、Grafana等)集成,用于实时查看服务的运行状态,包括调用统计、延迟分布、依赖关系等,以便于及时发现和解决问题...

    基于SpingCloud、dubbo、nacos、zipkin、rabbitmq的微服务框架

    通过Zipkin,我们可以实时监控服务调用链路,理解请求的流转过程,从而优化系统性能。 **RabbitMQ** RabbitMQ是一个流行的开源消息代理和队列服务器,常用于实现微服务间的异步通信。在微服务架构中,RabbitMQ可以...

    dubbo-monitor-simple

    Monitor Simple是Dubbo监控体系中的基础实现,帮助开发者监控服务的调用情况,从而进行性能优化、故障排查和系统稳定性的保障。 在Dubbo的架构中,监控模块是不可或缺的一部分。`dubbo-monitor-simple`提供了基础的...

    基于Dubbo埋点的分布式调用跟踪系统.zip

    通过在每个服务调用中添加埋点,记录调用的上下文信息(如调用链ID、服务名、调用耗时等),可以形成一个完整的调用链路图,便于故障排查和性能分析。常见的分布式跟踪系统有Zipkin、Skywalking、Jaeger等,本项目...

    invocation-chain-monitor:RPC调用链监控

    例如,Dubbo支持与Zipkin、SkyWalking等流行的分布式追踪系统集成,通过收集调用链数据,实现微服务间的请求追踪。 "invocation-chain-monitor-master"这个项目可能包含了以下内容: 1. **核心库**:实现RPC调用链...

    dubbo-dubbo-2.7.8.zip

    3. **调用链跟踪**:结合Zipkin或Skywalking等工具,可以进行全链路监控,定位性能瓶颈。 4. **版本管理**:利用Dubbo的版本控制功能,可以实现服务的灰度发布和回滚。 总结,Dubbo 2.7.8作为一款成熟的分布式服务...

Global site tag (gtag.js) - Google Analytics