dubbo解析-详解监控Monitor的实现原理

本文基于dubbo 2.7.5版本代码

前一篇文章介绍了MonitorFilter实现,MonitorFilter可以拦截所有的请求和返回值,收集所有的统计信息。之后便将统计信息交给Monitor对象处理。在dubbo2.7.5版本里面,Monitor实现类只有DubboMonitor,因此监控中心只支持dubbo协议。本文将详细介绍DubboMonitor的实现原理。

一、DubboMonitor对象创建

DubboMonitor对象是在MonitorFilter的collect方法里面创建的,因为collect方法只有在服务请求时才会被访问,因此Monitor对象的创建属于懒加载,只有在使用的时候才会创建。创建Monitor的代码如下:

Monitor monitor = monitorFactory.getMonitor(monitorUrl);

monitorFactory使用ExtensionLoader.getAdaptiveExtension方法获得,getMonitor根据monitorUrl获得对应的MonitorFactory实现类。dubbo只提供了实现类DubboMonitorFactory。DubboMonitorFactory的getMonitor方法将Monitor对象的创建委托给DubboMonitorFactory的createMonitor方法。下面介绍createMonitor方法。

1、createMonitor方法

	protected Monitor createMonitor(URL url) {
		//这里的url是在客户端启动的时候调用loadMonitor方法构造的
		//例如:dubbo://127.0.0.1:2181/org.apache.dubbo.monitor.MonitorService?application=consumer&dubbo=2.0.2&interface=org.apache.dubbo.monitor.MonitorService&pid=108660&qos.enable=false&register.ip=192.168.56.1&release=2.7.5&timestamp=1589616815635
		//要了解这段url的组成,可以看一下ConfigValidationUtils.loadMonitor方法,url里面的ip地址、端口是使用dubbo.monitor.address设置的
        URLBuilder urlBuilder = URLBuilder.from(url);
        //设置协议,默认是dubbo
        urlBuilder.setProtocol(url.getParameter(PROTOCOL_KEY, DUBBO_PROTOCOL));
        if (StringUtils.isEmpty(url.getPath())) {
            urlBuilder.setPath(MonitorService.class.getName());
        }
        //filter表示访问监控中心需要经过的过滤器,默认没有过滤器
        String filter = url.getParameter(REFERENCE_FILTER_KEY);
        if (StringUtils.isEmpty(filter)) {
            filter = "";
        } else {
            filter = filter + ",";
        }
        urlBuilder.addParameters(CHECK_KEY, String.valueOf(false),
                REFERENCE_FILTER_KEY, filter + "-monitor");
        //引用远程服务,也就是远程的监控中心的服务,monitorInvoker相当于客户端,所有访问监控中心都是通过monitorInvoker完成。
        //这里也可以看到监控中心暴露的服务是MonitorService,所以入参url的path是org.apache.dubbo.monitor.MonitorService。
        //refer方法以后在介绍客户端启动的时候说明
        Invoker<MonitorService> monitorInvoker = protocol.refer(MonitorService.class, urlBuilder.build());
        //创建monitorInvoker的代理
        MonitorService monitorService = proxyFactory.getProxy(monitorInvoker);
        //创建DubboMonitor对象
        return new DubboMonitor(monitorInvoker, monitorService);
    }

2、DubboMonitor的构造方法

上一小节的代码最后调用了DubboMonitor的构造方法。我们看一下构造方法的内容。

//monitorInvoker可以简单理解为监控中心的客户端,这一版dubbo里面使用
//monitorService是monitorInvoker的代理对象
//这两个对象本质是一样,monitorService最终也是访问monitorInvoker。
//在DubboMonitor中访问监控中心使用的是monitorService
public DubboMonitor(Invoker<MonitorService> monitorInvoker, MonitorService monitorService) {
        this.monitorInvoker = monitorInvoker;
        this.monitorService = monitorService;
        //monitorInterval表示monitorInvoker每多长时间访问一次监控中心,默认是1分钟
        //该值可以通过dubbo.monitor.interval修改
        this.monitorInterval = monitorInvoker.getUrl().getPositiveParameter("interval", 60000);
        //创建定时任务执行器,这个定时器没有使用时间轮算法,而是使用java的ScheduledExecutorService
        //任务执行间隔是monitorInterval指定的
        sendFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> {
            try {
                // collect data
                send();//将统计的数据发送到监控中心
            } catch (Throwable t) {
                logger.error("Unexpected error occur at send statistic, cause: " + t.getMessage(), t);
            }
        }, monitorInterval, monitorInterval, TimeUnit.MILLISECONDS);
    }

二、MonitorService

监控中心发布的服务是MonitorService,客户端也是访问MonitorService的方法将数据发送至监控中心。DubboMonitor实现了MonitorService接口。

public interface MonitorService {
	//。。。一些常量定义删除
	
	//MonitorFilter收集完信息后调用collect方法,
	//该方法的作用是,其他对象访问完该方法可以认为统计信息已经发送给了监控中心
    void collect(URL statistics);
    //查询监控中心数据,dubbo提供了多个维度查询,在dubbo2.7.5版本中没有对该方法的调用
    //下面是dubbo里面对该方法的注释,解释了入参query查询规则
    /*
    * 1. support lookup by day: count://host/interface?application=foo&method=foo&side=provider&view=chart&date=2012-07-03
     * 1.1 host,application,interface,group,version,method: query criteria for looking up by host, application, interface, method. When one criterion is not present, it means ALL will be accepted, but 0.0.0.0 is ALL for host
     * 1.2 side=consumer,provider: decide the data from which side, both provider and consumer are returned by default
     * 1.3 default value is view=summary, to return the summarized data for the whole day. view=chart will return the URL address showing the whole day trend which is convenient for embedding in other web page
     * 1.4 date=2012-07-03: specify the date to collect the data, today is the default value
     *
	*/
    List<URL> lookup(URL query);
}

三、重要方法详解

1、collect

从《dubbo解析-详解监控MonitorFilter的实现原理》文章中可以知道,在MonitorFilter的collect方法中会调用Monitor的collect方法。
访问完Monitor的collect方法,便可以认为统计信息已经发送给了监控中心。

//dubbo会将统计信息组装成URL对象作为该方法的入参,组装的内容可以参见
//文章《dubbo解析-详解监控MonitorFilter的实现原理》
public void collect(URL url) {
		//从url中获取一些统计信息
        int success = url.getParameter(MonitorService.SUCCESS, 0);//服务是否成功,用于统计成功次数
        int failure = url.getParameter(MonitorService.FAILURE, 0);//服务是否失败,用于统计失败次数
        int input = url.getParameter(MonitorService.INPUT, 0);//服务端收到请求信息的大小,以字节为单位
        int output = url.getParameter(MonitorService.OUTPUT, 0);//客户端收到返回信息的大小,以字节为单位
        int elapsed = url.getParameter(MonitorService.ELAPSED, 0);//访问服务耗时
        int concurrent = url.getParameter(MonitorService.CONCURRENT, 0);//访问服务的总次数
        //创建Statistics对象,该对象作为statisticsMap的key
        //statisticsMap是DubboMonitor的属性,类型是ConcurrentHashMap<Statistics, AtomicReference<long[]>>
        Statistics statistics = new Statistics(url);
        //reference保存各个统计信息,使用long数组存储,存储了10项信息
        AtomicReference<long[]> reference = statisticsMap.get(statistics);
        if (reference == null) {
            statisticsMap.putIfAbsent(statistics, new AtomicReference<long[]>());
            reference = statisticsMap.get(statistics);
        }
        long[] current;
        long[] update = new long[LENGTH];
        //下面的代码为更新reference数组的统计信息,使用循环,直到更新成功为止
        do {
            current = reference.get();
            if (current == null) {
                update[0] = success;
                update[1] = failure;
                update[2] = input;
                update[3] = output;
                update[4] = elapsed;
                update[5] = concurrent;
                update[6] = input;
                update[7] = output;
                update[8] = elapsed;
                update[9] = concurrent;
            } else {
                update[0] = current[0] + success;
                update[1] = current[1] + failure;
                update[2] = current[2] + input;
                update[3] = current[3] + output;
                update[4] = current[4] + elapsed;
                update[5] = (current[5] + concurrent) / 2;
                //下面四项信息,只统计最大值
                update[6] = current[6] > input ? current[6] : input;
                update[7] = current[7] > output ? current[7] : output;
                update[8] = current[8] > elapsed ? current[8] : elapsed;
                update[9] = current[9] > concurrent ? current[9] : concurrent;
            }
        } while (!reference.compareAndSet(current, update));
    }

DubboMonitor的collect方法只是将统计信息保存在了statisticsMap中,也就是保存在本地内存中。
statisticsMap的value使用的是AtomicReference对象,使用原子类可以避免并发访问时加锁。
在DubboMonitor的构造方法中创建了定时任务,每过一段时间,会将statisticsMap的内容发送到监控中心。定时任务调用的是send方法。

2、send

send方法做了三件事:

  1. 将统计信息构造为URL对象;
  2. 将URL对象发送给监控中心;
  3. 将部分统计数据清零。
public void send() {
        String timestamp = String.valueOf(System.currentTimeMillis());
        //遍历statisticsMap
        for (Map.Entry<Statistics, AtomicReference<long[]>> entry : statisticsMap.entrySet()) {
            Statistics statistics = entry.getKey();
            AtomicReference<long[]> reference = entry.getValue();
            long[] numbers = reference.get();
            long success = numbers[0];
            long failure = numbers[1];
            long input = numbers[2];
            long output = numbers[3];
            long elapsed = numbers[4];
            long concurrent = numbers[5];
            long maxInput = numbers[6];
            long maxOutput = numbers[7];
            long maxElapsed = numbers[8];
            long maxConcurrent = numbers[9];
            String protocol = getUrl().getParameter(DEFAULT_PROTOCOL);
            //将10项统计信息构造为URL对象
            URL url = statistics.getUrl()
                    .addParameters(MonitorService.TIMESTAMP, timestamp,
                            MonitorService.SUCCESS, String.valueOf(success),
                            MonitorService.FAILURE, String.valueOf(failure),
                            MonitorService.INPUT, String.valueOf(input),
                            MonitorService.OUTPUT, String.valueOf(output),
                            MonitorService.ELAPSED, String.valueOf(elapsed),
                            MonitorService.CONCURRENT, String.valueOf(concurrent),
                            MonitorService.MAX_INPUT, String.valueOf(maxInput),
                            MonitorService.MAX_OUTPUT, String.valueOf(maxOutput),
                            MonitorService.MAX_ELAPSED, String.valueOf(maxElapsed),
                            MonitorService.MAX_CONCURRENT, String.valueOf(maxConcurrent),
                            DEFAULT_PROTOCOL, protocol
                    );
            //将统计信息发送到监控中心,monitorService可以认为是监控中心的客户端,
            //是监控中心在本地的代理对象
            //monitorService内部会访问网络,将数据使用dubbo协议传送给监控中心
            monitorService.collect(url);
            //重置6项数据,这六项数据的统计时间范围是上次发送给监控中心
            //到这次发送给监控中心为止,每发送给监控中心后,这六项数据清零
            //6项数据为:1、服务成功次数;2、服务失败次数;3、服务端收到请求信息总大小;
            //4、客户端收到返回信息的总大小;5、服务耗时总时间;6、作用未知
            long[] current;
            long[] update = new long[LENGTH];
            do {
                current = reference.get();
                if (current == null) {
                    update[0] = 0;
                    update[1] = 0;
                    update[2] = 0;
                    update[3] = 0;
                    update[4] = 0;
                    update[5] = 0;
                } else {
                    update[0] = current[0] - success;
                    update[1] = current[1] - failure;
                    update[2] = current[2] - input;
                    update[3] = current[3] - output;
                    update[4] = current[4] - elapsed;
                    update[5] = current[5] - concurrent;
                }
            } while (!reference.compareAndSet(current, update));
        }
    }

send方法将10项数据发送给监控中心:

  1. 一段时间内服务成功次数;
  2. 一段时间内服务失败次数;
  3. 一段时间内服务端收到请求的总大小
  4. 一段时间内客户端收到返回值的总大小
  5. 一段时间内服务的总耗时
  6. 作用未知,有博客介绍该数据为一次收集周期的平均TPS
  7. 最大请求信息大小
  8. 最大响应信息大小
  9. 最大响应时间
  10. 一段时间内总调用次数

这里的一段时间指的是上次访问监控中心到这次访问监控中心为止。

四、总结

DubboMonitor的作用总起来说是收集10项统计信息,然后使用定时任务将信息发送给监控中心。
dubbo只提供了DubboMonitor实现,监控中心必须使用dubbo协议。如果收集的信息不符合要求,需要重新实现MonitorFactory和Monitor接口,并且在配置参数dubbo.monitor.address中指定协议。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值