深度解析dubbo monitor默认实现

本文基于dubbo v2.6.x

1. DubboMonitor原理

在《深度解析dubbo monitor(api)》一文中我们解析了dubbo Monitor 与MonitorFactory 的抽象接口,以及继承关系。在本文中我们将讲解下在当前版本的默认实现(也是唯一实现),就是DubboMonitor。 DubboMonitor 的原理就是将收集到的调用指标信息以dubbo远程调用的方法上报给对应的服务提供者(这个服务提供者理解成收集监控指标的服务会更好)
在这里插入图片描述
上图中这个Monitor服务是一个单独的服务,然后服务提供者与服务调用者的DubboMonitor收集了监控指标会定期调用这个Monitor服务上报自己的监控指标,这里这个调用是使用dubbo 远程调用的方式调用的。接下来我们DubboMonitor的实现就能理解了。

2. DubboMonitorFactory源码

DubboMonitorFactory是DubboMonitor的工厂,需要通过它来获取具体的DubboMonitor对象。

public class DubboMonitorFactory extends AbstractMonitorFactory {...}

我们可以看出DubboMonitorFactory 继承AbstractMonitorFactory 抽象工厂,就需要实现createMonitor 方法

 @Override
    protected Monitor createMonitor(URL url) {
        url = url.setProtocol(url.getParameter(Constants.PROTOCOL_KEY, "dubbo"));
        // 判断path
        if (url.getPath() == null || url.getPath().length() == 0) {
            url = url.setPath(MonitorService.class.getName());
        }
        String filter = url.getParameter(Constants.REFERENCE_FILTER_KEY);
        if (filter == null || filter.length() == 0) {
            filter = "";
        } else {
            filter = filter + ",";
        }
        url = url.addParameters(Constants.CLUSTER_KEY, "failsafe", Constants.CHECK_KEY, String.valueOf(false),
                Constants.REFERENCE_FILTER_KEY, filter + "-monitor");
        Invoker<MonitorService> monitorInvoker = protocol.refer(MonitorService.class, url);
        // dubbo 的MonitorService 引用
        MonitorService monitorService = proxyFactory.getProxy(monitorInvoker);
        // 创建DubboMonitor对象
        return new DubboMonitor(monitorInvoker, monitorService);
    }

在createMonitor 方法中,首先是处理一下url,设置protocol是参数protocol的值,缺省是dubbo,如果path是空的话就设置成MonitorService的全类名,接着就是找filter的配置。下面这几行就是比较重要的了,就是调用Protocol的refer方法进行服务引用获得invoker,然后根据invoker生成MonitorService 接口的代理,这里就跟咱们普通的服务调用者端服务引用差不多,不过这里并没有经过注册中心,而是直连的,直接连你配置的那个monitor address:
在这里插入图片描述
再往后就是创建DubboMonitor 对象返回了,这个方法最需要注意的就是创建MonitorService接口引用,因为在DubboMonitor 会调用该服务
的collect 方法上报监控信息

3. DubboMonitor源码

DubboMonitor实现Monitor接口,我们先看下成员信息

// 定时调度线程池
private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3, new NamedThreadFactory("DubboMonitorSendTimer", true));
// 定时上报监控信息任务
private final ScheduledFuture<?> sendFuture;
// Monitor的invoker
private final Invoker<MonitorService> monitorInvoker;
// Monitor MonitorService接口代理
private final MonitorService monitorService;
//监控间隔,缺省1min
private final long monitorInterval;
// 缓存 某个接口方法 与  监控指标值的对应关系
private final ConcurrentMap<Statistics, AtomicReference<long[]>> statisticsMap = new ConcurrentHashMap<Statistics, AtomicReference<long[]>>();

接下来再来看下构造方法:

  public DubboMonitor(Invoker<MonitorService> monitorInvoker, MonitorService monitorService) {
        this.monitorInvoker = monitorInvoker;
        this.monitorService = monitorService;

        // 获取interval  ,缺省是60s
        this.monitorInterval = monitorInvoker.getUrl().getPositiveParameter("interval", 60000);
        // collect timer for collecting statistics data
        sendFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                // collect data
                try {

                    /// 发送data
                    send();
                } catch (Throwable t) {
                    logger.error("Unexpected error occur at send statistic, cause: " + t.getMessage(), t);
                }
            }
        }, monitorInterval, monitorInterval, TimeUnit.MILLISECONDS);
    }

获取monitorInterval监控间隔,缺省是60s,创建定时上报指标任务sendFuture,然后任务里面就是调用send方法进行发送上报。
接下来就看下collect 收集方法:

 @Override
    public void collect(URL url) {
        // data to collect from 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);
        // init atomic reference
        Statistics statistics = new Statistics(url);
        AtomicReference<long[]> reference = statisticsMap.get(statistics);
        if (reference == null) {
            statisticsMap.putIfAbsent(statistics, new AtomicReference<long[]>());
            reference = statisticsMap.get(statistics);
        }
        // use CompareAndSet to sum
        long[] current;
        long[] update = new long[LENGTH];
        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;
                //concurrent
                update[5] = (current[5] + concurrent) / 2;

                // max
                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));
    }

collect 方法可以分为三部分,第一部分是收集监控指标值,接着就是拼装对应的Statistics对象,根据该对象从缓存statisticsMap中获取对应的缓存监控指标值,最后就是通过cas方式进行处理监控指标值了。
先来看下第一部分收集监控指标值,建议先看下《深度解析dubbo过滤器之MonitorFilter》,该方法的监控数据就是MonitorFilter 收集的。解释下这几个监控指标的意思:success 成功调用的次数,failure 失败调用的次数,input是服务提供者端接收到的数据量,output是服务调用者端收到响应数据,elapsed是调用耗时,concurrent是当前的并发数。
第二部分就是根据url生成Statistics 对象,根据该对象从缓存中获取对应的监控指标值,如果没有就创建塞到缓存中。我们简单看下Statistics 类的成员
在这里插入图片描述
怎样的算是同一个Statistics 对象,其实equals 方法就是比对这堆属性值,hashCode 也是分别以这些属性值的hashcode算的。
再来看下第三部分,缓存的值是个AtomicReference ,引用的是个long数组,如果这个数组是null也就是第一次的时候,会初始化这堆指标值,到这里可以看到有10个指标了,先说下索引位置出分别对应的啥指标:
0:监控周期总调用成功次数
1:监控周期总调用失败次数
2:监控周期总请求字节数
3:监控周期总响应字节数
4:监控周期总响应时间
5:监控周期均并发数
6:监控周期最大请求字节数
7:监控周期最大响应字节数
8:监控周期最大响应时间
9:监控周期最大并发数
这个监控周期就是上报数据定时任务的周期,也就是monitorInterval 变量的值。想必解释完指标含义,下面的更新操作就很清楚了,最后是通过cas将long数组替换一下。
接下来我们再看下定时上报的send方法:

public void send() {
        logger.debug("Send statistics to monitor " + getUrl());
        // 时间
        String timestamp = String.valueOf(System.currentTimeMillis());
        for (Map.Entry<Statistics, AtomicReference<long[]>> entry : statisticsMap.entrySet()) {
            // get statistics data
            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 version = getUrl().getParameter(Constants.DEFAULT_PROTOCOL);
            // send statistics data
            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),
                            Constants.DEFAULT_PROTOCOL, version
                    );
            /// 远程调用发送
            monitorService.collect(url);
            // reset
            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;
                }
                // cas 替换监控指标值数组
            } while (!reference.compareAndSet(current, update));
        }
    }

在send方法中,会遍历statisticsMap这个缓存map,在循环中会拼接对应statistics的监控指标url,然后调用monitorService.collect(url)进行远程发送,这monitorService就是你配置那个monitor的远程引用,最后就是重置对应监控指标值数组里面的值,使用cas 替换long数组。

4. 自己实现一个监控服务

在这里简单实现一个监控服务,这个监控服务收集其他服务上报过来的监控指标值,然后进行展示。
要想实现DubboMonitor监控收集服务,只需要实现MonitorService 这个接口,实现里面的collect收集方法,然后在需要监控的服务里面配置监控信息就可以了。

4.1 服务提供者

这里提供一个IHelloProviderService 接口,并提供它的实现类IHelloProviderServiceImpl进行服务暴露

public interface IHelloProviderService {
    String getName(Integer id);
}
@org.springframework.stereotype.Service
@Service()
public class IHelloProviderServiceImpl  implements IHelloProviderService {

    @Override
    public String getName(Integer id) {
        return "test";
    }
}

在配置文件xml中配置关于monitor的配置:

 <dubbo:monitor protocol="dubbo" address="127.0.0.1:18109"></dubbo:monitor>

这里protocol写dubbo就可以了,因为当前版本官方就提供了一个默认实现,就是DubboMonitor
address 就是需要 监控服务的地址信息(ip+port)
你也可以这样写(都是一个意思):

<dubbo:monitor  address="dubbo://127.0.0.1:18109"></dubbo:monitor>

这里我贴出来其他配置:

<dubbo:application name="dubbo-provider">
        <dubbo:parameter key="qos.enable" value="true"/>
        <dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
        <dubbo:parameter key="qos.port" value="8109"/>
</dubbo:application>
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181"/>
<dubbo:protocol accesslog="true" name="dubbo" port="18109" />

4.2 服务调用者

服务调用者 只需要注入IHelloProviderService 服务的引用就可以了。这里为了看出效果,我浏览器请求一次,进行一千次的服务调用。

@RestController
public class TestController {
    @Reference(check = false)
    private IHelloProviderService iHelloProviderService;
    ExecutorService threadPool =
            Executors.newFixedThreadPool(10);
    @RequestMapping("/tests")
    public String test(){
        for (int i=0;i<1000;++i) {
            threadPool.submit(new Runnable() {
                                  @Override
                                  public void run() {
                                      iHelloProviderService.getName(1);
                                  }
                              }
            );
        }
        return null;
    }
}

monitor配置方面与服务提供者一样就可以了,这里贴出来所有配置:

 <dubbo:application name="dubbo-consumer">
        <dubbo:parameter key="qos.enable" value="true"/>
        <dubbo:parameter key="qos.accept.foreign.ip" value="false"/>
        <dubbo:parameter key="qos.port" value="8108"/>
</dubbo:application>
<dubbo:monitor  address="dubbo://127.0.0.1:18109"></dubbo:monitor>
<dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" client="curator" >
    <dubbo:parameter key="save.file" value="true"></dubbo:parameter>
</dubbo:registry>
<dubbo:protocol accesslog="true" name="dubbo" port="18108" />

4.3 监控服务

我们上面说过,监控服务只需要实现MonitorService 接口,重写collect方法就可以了,这里为了方便,我将监控服务放到服务提供者服务里面(注意这个 监控服务本质也是个服务提供者,只不过是用来收集信息,展示信息而已,MonitorService的实现类也需要进行服务暴露)

@Service
@org.springframework.stereotype.Service
public class MonitorServiceImpl implements MonitorService {
    @Override
    public void collect(URL statistics) {
        if (statistics!=null){
            String application = statistics.getParameter(MonitorService.APPLICATION);
            String service = statistics.getParameter(MonitorService.INTERFACE);
            String method = statistics.getParameter(MonitorService.METHOD);
            String group = statistics.getParameter(MonitorService.GROUP);
            String version = statistics.getParameter(MonitorService.VERSION);
            String client = statistics.getParameter(MonitorService.CONSUMER);
            String server = statistics.getParameter(MonitorService.PROVIDER);
            String timestamp = statistics.getParameter(MonitorService.TIMESTAMP);
            String success = statistics.getParameter(MonitorService.SUCCESS);
            String failure = statistics.getParameter(MonitorService.FAILURE);
            String input = statistics.getParameter(MonitorService.INPUT);
            String output= statistics.getParameter( MonitorService.OUTPUT);
            String elapsed = statistics.getParameter(MonitorService.ELAPSED);
            String concurrent = statistics.getParameter(MonitorService.CONCURRENT);
            String maxInput = statistics.getParameter(MonitorService.MAX_INPUT);
            String maxOutput = statistics.getParameter(MonitorService.MAX_OUTPUT);
            String maxElapsed = statistics.getParameter(MonitorService.MAX_ELAPSED);
            String maxConcurrent = statistics.getParameter(MonitorService.MAX_CONCURRENT);
            System.out.println("application:"+application
                +",service:"+service+",method:"+method+",group:"+group+",version:"+version+",client:"+client+",server:"+server
                    +",timestamp:"+timestamp+",success:"+success+",failure:"+failure+",input:"+input+",output:"+output
                    +",elapsed:"+elapsed+",concurrent:"+concurrent+",maxInput:"+maxInput+",maxOutput:"+maxOutput+",maxElapsed:"+maxElapsed
                    +",maxConcurrent:"+maxConcurrent
            );


        }

    }

    @Override
    public List<URL> lookup(URL query) {
        return null;
    }
}

我实现的这个collect方法也没有干啥,就是将上报过来的监控信息打印出来。

4.4 调用

我这里请求几次那个http接口,进行几千次服务调用,来看下上报过来的信息是啥样子的

application:dubbo-consumer,service:com.xuzhaocai.dubbo.provider.IHelloProviderService,method:getName,group:null,version:null,client:null,server:192.168.1.106:18109,timestamp:1598709674608,success:1000,failure:0,input:0,output:36000,elapsed:842,concurrent:2,maxInput:0,maxOutput:36,maxElapsed:2,maxConcurrent:10

application:dubbo-provider,service:com.alibaba.dubbo.monitor.MonitorService,method:collect,group:null,version:null,client:192.168.1.106,server:null,timestamp:1598709674609,success:3,failure:0,input:2179,output:0,elapsed:0,concurrent:0,maxInput:730,maxOutput:0,maxElapsed:0,maxConcurrent:1

application:dubbo-provider,service:com.xuzhaocai.dubbo.provider.IHelloProviderService,method:getName,group:null,version:null,client:192.168.1.106,server:null,timestamp:1598709674609,success:1000,failure:0,input:244000,output:0,elapsed:1,concurrent:1,maxInput:244,maxOutput:0,maxElapsed:1,maxConcurrent:5

application:dubbo-consumer,service:com.xuzhaocai.dubbo.provider.IHelloProviderService,method:getName,group:null,version:null,client:null,server:192.168.1.106:18109,timestamp:1598709734611,success:4000,failure:0,input:0,output:144000,elapsed:3457,concurrent:2,maxInput:0,maxOutput:36,maxElapsed:7,maxConcurrent:10

application:dubbo-provider,service:com.alibaba.dubbo.monitor.MonitorService,method:collect,group:null,version:null,client:192.168.1.106,server:null,timestamp:1598709734614,success:4,failure:0,input:2947,output:0,elapsed:0,concurrent:0,maxInput:747,maxOutput:0,maxElapsed:0,maxConcurrent:1

application:dubbo-provider,service:com.xuzhaocai.dubbo.provider.IHelloProviderService,method:getName,group:null,version:null,client:192.168.1.106,server:null,timestamp:1598709734614,success:4000,failure:0,input:976000,output:0,elapsed:6,concurrent:1,maxInput:244,maxOutput:0,maxElapsed:1,maxConcurrent:6

可以看到既有服务调用者端上报的信息,也就服务提供者端上报的监控信息(毕竟我们两个服务都配置了监控服务)。我这里使用的默认监控周期,也就是1分钟一个周期,然后上报一次。
需要注意的是 这些监控指标值 是1分钟为周期的监控信息,这个周期大小可以通过interval这个参数值来配置。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页