深度解析dubbo过滤器之MonitorFilter

本文基于dubbo v2.6.x

1. Monitor与MonitorFilter的关系

首先介绍下Monitor,Monitor字面是监控的意思,在dubbo中可以通过Monitor实时监控(不能算是实时,你要是将上报周期缩小可以接近实时)到服务的调用情况(粒度为方法级别),包括请求成功数,请求失败数,吞吐量,当前并发数,响应时长等等。
而Monitor这些调用统计指标是MonitorFilter 来采集的,MonitorFilter就相当于咱们的source。当我们配置了monitor,这个MonitorFilter就会在服务调用端或者服务提供端进行数据采集,然后上报给对应的monitor对象。
xml配置示例:

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

2.MonitorFilter源码解析

我们先来看下MonitorFilter的class定义:

@Activate(group = {Constants.PROVIDER, Constants.CONSUMER})
public class MonitorFilter implements Filter {...}

我们看到MonitorFilter 适应与服务调用者端与服务提供者端,接下来看下它的成员变量:

// 缓存着 key=接口名.方法名       value= 计数器
private final ConcurrentMap<String, AtomicInteger> concurrents = new ConcurrentHashMap<String, AtomicInteger>();
private MonitorFactory monitorFactory;

concurrents 缓存着 方法与某时刻调用次数(这里可以理解为某时刻的并发数)的对应关系,monitorFactory 是monitor的工厂,通过它可以获取具体的monitor,monitorFactory 是dubbo spi自动注入的。
接下来看下invoke方法:

 @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (invoker.getUrl().hasParameter(Constants.MONITOR_KEY)) {// 判断是否有monitor参数
            RpcContext context = RpcContext.getContext(); // provider must fetch context before invoke() gets called
            // 获取远端host
            String remoteHost = context.getRemoteHost();
            long start = System.currentTimeMillis(); // record start timestamp
            // 计数器+1
            getConcurrent(invoker, invocation).incrementAndGet(); // count up 自增加1
            try {
                // 进行调用
                Result result = invoker.invoke(invocation); // proceed invocation chain

                collect(invoker, invocation, result, remoteHost, start, false);// 进行收集
                return result;
            } catch (RpcException e) {
                collect(invoker, invocation, null, remoteHost, start, true);
                throw e;
            } finally {
                getConcurrent(invoker, invocation).decrementAndGet(); // count down 自减1
            }
        } else {// 没有monitor参数就放行
            return invoker.invoke(invocation);
        }
    }

这个invoke方法还是很简单,首先判断下是否有monitor参数,如果没有就正常调用,否则获取remoteHost与开始时间start,接着调用getConcurrent(invoker, invocation)方法获取对应的计数器然后自增+1 操作,看下这个getConcurrent 方法:
在这里插入图片描述
可以看到就是根据调用信息的接口+方法为key从concurrents缓存中查找对应的计数器返回,如果没有就创建塞到缓存中。
自增1表示当前并发数+1,接着就是进行真正调用了,之后调用collect 收集方法,该方法最后一个参数 表示是否异常,正常调用的话就是false,出现异常的话就是true。最后调用getConcurrent方法获取计数器自减1,因为这时当前这次调用就结束了,并发数也就是减1。
接下来我们看下collect这个收集方法。

// collect info
private void collect(Invoker<?> invoker, Invocation invocation, Result result, String remoteHost, long start, boolean error) {
        try {
            // ---- service statistics ----
            long elapsed = System.currentTimeMillis() - start; // invocation cost  调用耗时
            int concurrent = getConcurrent(invoker, invocation).get(); // current concurrent count  获取当前的并发数
            String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY);  // application
            String service = invoker.getInterface().getName(); // service name    //接口名
            String method = RpcUtils.getMethodName(invocation); // method name   // 方法名
            String group = invoker.getUrl().getParameter(Constants.GROUP_KEY);   // 分组
            String version = invoker.getUrl().getParameter(Constants.VERSION_KEY);  // version
            
            URL url = invoker.getUrl().getUrlParameter(Constants.MONITOR_KEY);  // monitorUrl
            // 根据monitorUrl 从监控工厂中获取对应的监控对象
            Monitor monitor = monitorFactory.getMonitor(url);// 获取Monitor
            if (monitor == null) {
                return;
            }
            int localPort;
            String remoteKey;
            String remoteValue;
            if (Constants.CONSUMER_SIDE.equals(invoker.getUrl().getParameter(Constants.SIDE_KEY))) {// 如果是服务调用端
                // ---- for service consumer ----
                localPort = 0;
                remoteKey = MonitorService.PROVIDER;
                remoteValue = invoker.getUrl().getAddress();// 远端地址,在这里就是服务提供者方的地址
            } else {// 如果是服务提供者端
                // ---- for service provider ----
                localPort = invoker.getUrl().getPort();// 获取服务提供者端的port
                remoteKey = MonitorService.CONSUMER;
                remoteValue = remoteHost;// 远端地址,这里是服务提供者方的地址
            }
            String input = "", output = "";
            // 这两个会在Serialize层添加上
            // 获取input
            if (invocation.getAttachment(Constants.INPUT_KEY) != null) {// input
                input = invocation.getAttachment(Constants.INPUT_KEY);
            }
            // 获取output
            if (result != null && result.getAttachment(Constants.OUTPUT_KEY) != null) {// output
                output = result.getAttachment(Constants.OUTPUT_KEY);
            }
            //-----------------------monitor收集-------------------------
            monitor.collect(new URL(Constants.COUNT_PROTOCOL,// count
                    NetUtils.getLocalHost(), localPort,
                    service + "/" + method,// path
                    // 下面这一堆被安排在了 parameters中
                    MonitorService.APPLICATION, application,
                    MonitorService.INTERFACE, service,
                    MonitorService.METHOD, method,
                    remoteKey, remoteValue,

                    // 是否是错误(这里变动的是key)
                    error ? MonitorService.FAILURE : MonitorService.SUCCESS, "1",
                    MonitorService.ELAPSED, String.valueOf(elapsed),// 耗时
                    MonitorService.CONCURRENT, String.valueOf(concurrent),// 当前的一个并发数
                    Constants.INPUT_KEY, input,
                    Constants.OUTPUT_KEY, output,
                    Constants.GROUP_KEY, group,
                    Constants.VERSION_KEY, version));
        } catch (Throwable t) {
            logger.error("Failed to monitor count service " + invoker.getUrl() + ", cause: " + t.getMessage(), t);
        }
    }

先解释下这几个参数:
invoker是服务提供者
invocation调用信息,里面有接口,方法,一些附加参数等,
result调用返回结果,
remoteHost远端host ,
start 调用的开始时间戳,
error调用是否出现异常,false表示正常调用,true表示要调用发生异常。
这个collect方法看起来比较长,实则内容比较简单,我把它分为了两部分,上面主要是一些调用信息与统计指标的收集工作,下面就是拼装这些调用信息url,然后调用对应monitor的collect 方法进行收集。
先看下上面收集调用信息与指标统计工作:通过start 算出调用耗时elapsed, 通过getConcurrent方法获取当前方法一个并发数concurrent,下面获取接口名,方法名我就不解释了,后面通过url获取monitorUrl,接着使用监控工厂获取monitorUrl对应的monitor对象,在往后就是根据端来获取远端key与远端地址,有个获取input与output参数值需要说下,这两个属性值其实是在序列化层(在DubboCountCodec中)设置进去的。在这里插入图片描述
下面这部分其实就是一行monitor.collect(url) ,使用monitor对象进行收集,只不过这个url是在这现拼装成的。可以看出来设置比较常规的参数外还设置了成功或失败,调用耗时,input,output,并发数这些调用统计指标。

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