深度解析dubbo过滤器之ActiveLimitFilter

本文基于 dubbo v2.6.x

1. actives属性介绍

我们在调用者端配置dubbo:refrence的时候有一个dubbo调优的参数actives,可以看下官网对这个参数的说明(官方文档:链接):

属性对应URL参数类型是否必填缺省值作用描述
activesactivesint可选0 表示不做限制性能调优每服务消费者每服务每方法最大并发调用数

我这里直接从官网拿过来了,我们可以看下描述这一栏,每服务消费者每服务每方法最大并发调用数 这个可能不好理解,我画了张图大家可以理解下:
在这里插入图片描述
这个所谓的每服务消费者每服务每方法最大并发调用数 其实是 某个服务调用者调用 同一个服务提供者(同一个服务提供者实例,也就是上图三个箭头指向的同一个ip+port下的实例)的同一个接口的同一个方法的(可以看到上图三个箭头都调用的com.xxx.UserSerivce.getUserName() 方法)并发数(上图的actives =3 表示在同一时刻最多3个调用,然后多出来的调用只能等待)

2. 配置使用

我们看下这个actives属性的配置使用:

  1. 注解的使用:这个直接在@Reference()注解上配置actives属性就可以了,如:
    @Reference(check = false,actives = 1)
    private IHelloProviderService iHelloProviderService;
    
  2. xml配置的话在dubbo:refrence 标签添加 actives属性。如:
```xml
<dubbo:reference interface="com.xuzhaocai.dubbo.provider.IHelloProviderService" id="iHelloProviderService"  actives="1"></dubbo:reference> 
```

3. 源码解析

接下来我们就要进入重头戏了,解析下这个actives控制并发数的功能是怎样实现的,这里其实是通过服务调用者端的filter来实现并发数控制的,这个filter就是ActiveLimitFilter,我们先来看下它class 定义:

@Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
public class ActiveLimitFilter implements Filter {

可以看出来这个ActiveLimitFilter 只能用于服务调用者端,而且需要配置actives属性才能激活。
接着再看下invoke方法:

@Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 获取url
        URL url = invoker.getUrl();
        // 获取调用方法名
        String methodName = invocation.getMethodName();
        // 获取方法 actives 属性值 缺省是0 ,这actives 就是"每服务消费者每服务每方法最大并发调用数"
        int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0);
        // 获取对应url 对应method的一个RpcStatus
        RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());
		//------------------------------------
        if (max > 0) {
            // 获取timeout
            long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0);
            // 开始时间
            long start = System.currentTimeMillis();
            //剩余时间= timeout
            long remain = timeout;

            /// 获取一个active值
            int active = count.getActive();
            // 当active 大于max,说明调用的已经超了设置最大并发数
            if (active >= max) {
                synchronized (count) {
                    // 活跃着 的还是大于 最大并发数
                    while ((active = count.getActive()) >= max) {
                        try {
                            // 进行等待  等待超时时间
                            count.wait(remain);
                        } catch (InterruptedException e) {
                        }
                        // 计算消耗多少时间
                        long elapsed = System.currentTimeMillis() - start;
                        // 剩余时间  超时时间-消耗时间
                        remain = timeout - elapsed;
                        if (remain <= 0) {// 小于等于0,说明没有剩余时间了,也就是超时了 ,这里直接抛出超时
                            throw new RpcException("Waiting concurrent invoke timeout in client-side for service:  "
                                    + invoker.getInterface().getName() + ", method: "
                                    + invocation.getMethodName() + ", elapsed: " + elapsed
                                    + ", timeout: " + timeout + ". concurrent invokes: " + active
                                    + ". max concurrent invoke limit: " + max);
                        }
                    }
                }
            }
        }
		//----------------------------------------------------
        try {
            // 开始时间
            long begin = System.currentTimeMillis();
            // 对应的RpcStatus 的 actives+1
            RpcStatus.beginCount(url, methodName);
            try {
                // 进行调用
                Result result = invoker.invoke(invocation);

                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);
                return result;
            } catch (RuntimeException t) {

                // 失败情况
                RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);
                throw t;
            }
        } finally {
            // 执行完唤醒
            if (max > 0) {
                synchronized (count) {
                    count.notify();
                }
            }
        }
    }

为了阅读方便我把invoke方法源码分了三部分(我用‘-----’隔开了):
第一部分主要是:获取调用超时时间,获取用户配置并发数active,获取对应invoker对应方法的一个RpcStatus对象
第二部分主要是:对并发限制
第三部分主要是:调用前记录并发数信息,进行调用,调用结束的一些信息的记录。
我们先来看下第一部分,从代码可以看出,现获得了调用超时时间timeout,获取配置的active并发数,获取invoker对应调用方法的一个RpcStatus对象(这个对象里面其实就记录在这个服务调用者实例中当前这个 服务提供者实例中该接口中的该方法 一些调用信息,包括现在的一个并发数,总调用次数,总消耗时间等等 )
到这里可以先了解下 RpcStatus的成员变量(只罗列出了本文用到的):

 	private final AtomicInteger active = new AtomicInteger();// 记录当前调用并发的, 这个是会实时改的
    private final AtomicLong total = new AtomicLong(); // 总调用次数
    private final AtomicInteger failed = new AtomicInteger();// 失败次数
    private final AtomicLong totalElapsed = new AtomicLong();// 总消耗时间
    private final AtomicLong failedElapsed = new AtomicLong();// 失败消耗时间
    private final AtomicLong maxElapsed = new AtomicLong();// 最大消耗时间
    private final AtomicLong failedMaxElapsed = new AtomicLong();// 失败最大消耗时间
    private final AtomicLong succeededMaxElapsed = new AtomicLong();// 成功最大消耗时间

可以看到都是与调用有关的指标,而且都是使用Atomic 原子类。
我们看下是怎样获取当前这个调用的RpcStatus对象的,也就是源码中的这一句:

 RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName());

对应的RpcStatus的getStatus方法源码:

  /**
     * 获取 url对应缓存 method的RpcStatus
     * @param url
     * @param methodName
     * @return status
     */
    public static RpcStatus getStatus(URL url, String methodName) {
        String uri = url.toIdentityString();// 生成IdentityString dubbo://192.168.3.33:18109/com.xuzhaocai.dubbo.provider.IHelloProviderService
        // 使用IdentityString  获取
        // key是方法名,value是RpcStatus
        ConcurrentMap<String, RpcStatus> map = METHOD_STATISTICS.get(uri);
        if (map == null) {// 没有就创建塞进去
            METHOD_STATISTICS.putIfAbsent(uri, new ConcurrentHashMap<String, RpcStatus>());
            map = METHOD_STATISTICS.get(uri);
        }

        // 获取methodName 对应的RpcStatus
        RpcStatus status = map.get(methodName);

        // 没有就创建塞进去
        if (status == null) {
            map.putIfAbsent(methodName, new RpcStatus());
            status = map.get(methodName);
        }
        // 返回method 对应的 RpcStatus
        return status;
    }

我们可以看到这个getStatus 方法中首先根据这个invoker的url生成一个Identity (这个invoker对应的时候某个服务提供者的某个接口),这里有个生成的例子 “dubbo://192.168.3.33:18109/com.xuzhaocai.dubbo.provider.IHelloProviderService”,根据这个Identity 从METHOD_STATISTICS 缓存中获取 对应的value ,也是个map(这个map ,就是对应的 key 是某个方法 ,value就是RpcStatus对象),如果还没有这个的缓存,就创建一个塞进去,然后在从这个map 中获取对应调用方法的value(这个value就是RpcStatus) ,如果没有的话就创建RpcStatus 对象,塞到map中缓存起来,最后将对应RpcStatus 对象返回。

我们再来看下第二部分,该部分上面说过主要是做并发数的控制:
如果max>0 ,这个max是我们配置的那个actives属性,actives是0的话不对并发数做控制,也就不进去这个if代码块了,我们看下max>0的情况,先是获取对应的timeout,从对应RpcStatus中获取active值,也就是当前的一个并发数,记录开始时间start,剩余时间remain 为timeout。当active 大于max,说明调用的已经超了设置最大并发数,这时候需要对当前这个调用做限制,先是获取锁,有个while循环,条件是当active 大于max 的时候进入,接着就是当前调用wait方法进行等待,等待超时时间就是那个remain(第一次的时候也就是timeout那个时间)接着计算消耗时间 , 重新计算remain =timeout-消耗时间 ,如果remain 小于等于0 就抛出超时异常,其实这里如果这个wait一只没有被唤醒,循环一次就能超时,因为一开始 wait等待时间就是timeout,一直没有被唤醒的话,消耗时间也就是大于等于这个超时时间了,这时候这个调用就超时了。

我们再来看下第三部分,该部分主要是记录并发数,进行调用 ,收集记录一些调用信息。我们详细看下这个:
先是记录开始时间begin,接着就是调用RpcStatus.beginCount(url, methodName); 进行并发数+1操作,我们看下具体源码:

public static void beginCount(URL url, String methodName) {
        beginCount(getStatus(url));
        beginCount(getStatus(url, methodName));
}

分别又调用了beginCount(getStatus(url)); 与beginCount(getStatus(url, methodName)); ,先看下getStatus(url) 方法:

 public static RpcStatus getStatus(URL url) {
        // 生成IdentityString
        String uri = url.toIdentityString();//dubbo://192.168.3.33:18109/com.xuzhaocai.dubbo.provider.IHelloProviderService
        //获取IdentityString对应的缓存
        RpcStatus status = SERVICE_STATISTICS.get(uri);
        if (status == null) {// 没有就生成 塞到SERVICE_STATISTICS
            SERVICE_STATISTICS.putIfAbsent(uri, new RpcStatus());
            status = SERVICE_STATISTICS.get(uri);
        }
        return status;
    }

这个getStatus(URL url) 方法其实获取的是一个接口级别的RpcStatus ,可以看到也是生成Identity ,根据这个key从SERVICE_STATISTICS 这个map缓存获取对应的RpcStatus 对象,如果没有就新创建一个塞到缓存SERVICE_STATISTICS中,并将该RpcStatus对应对象返回。
这个getStatus(url, methodName) 方法我们上面解释过,我们这就不说了,接着看下beginCount 方法源码:

 private static void beginCount(RpcStatus status) {
        status.active.incrementAndGet();// active+1
 }

beginCount方法其实就对对应的RpcStatus对象 的avtive属性+1 操作,这里也就是并发数+1的意思。
好了RpcStatus.beginCount(url, methodName);这行代码我们解释清楚了,接着往下看,Result result = invoker.invoke(invocation);这个是进行调用方法,然后返回result 结果,如果没有异常的话 接着就是执行 下面这行

RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true);

如果异常了就执行下面这行:

RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false);

我们看到就一个参数不一样,来看下endCount 的具体源码:

public static void endCount(URL url, String methodName, long elapsed, boolean succeeded) {
        endCount(getStatus(url), elapsed, succeeded);
        endCount(getStatus(url, methodName), elapsed, succeeded);
}

getStatus(url)与getStatus(url, methodName) 上面解释了,我们不解释了,就是拿到对应的RpcStatus对象,我们看下endCount方法:

private static void endCount(RpcStatus status, long elapsed, boolean succeeded) {

        // actives -1
        status.active.decrementAndGet();
        // total +1
        status.total.incrementAndGet();
        // 累加 消耗时间
        status.totalElapsed.addAndGet(elapsed);
        // 记录最大消耗时间
        if (status.maxElapsed.get() < elapsed) {// 本次消耗时间 大于 之前记录的最大消耗时间
            status.maxElapsed.set(elapsed);// 记录最大消耗时间
        }
        if (succeeded) {//成功的话

            // 记录成功最大消耗时间
            if (status.succeededMaxElapsed.get() < elapsed) {
                status.succeededMaxElapsed.set(elapsed);
            }
        } else {// 失败的话

            // 失败次数+1
            status.failed.incrementAndGet();
            //  累加失败消耗时间
            status.failedElapsed.addAndGet(elapsed);
            // 记录失败最大消耗时间
            if (status.failedMaxElapsed.get() < elapsed) {
                status.failedMaxElapsed.set(elapsed);
            }
        }
    }

我们可以看到,因为这里是执行完了,不管异常还是成功都是执行完了,所以对active这个并发数属性-1操作,接着就是累加总调用次数,累加总调用消耗时间,记录最大调用消耗时间,如果成功的话,记录成功最大调用消耗时间,如果失败的话,累加总失败次数,累加失败消耗时间,记录失败最大消耗时间 ,我们可以看到统计的指标还是挺多的。
再回到invoke方法,最后一小块代码:

} finally {
    // 执行完唤醒
     if (max > 0) {
         synchronized (count) {
             count.notify();
         }
     }
}

最后 如果是设置了并发数限制 也就是max>0 就进行唤醒操作,因为我们在第二部分并发数限制那块 对超过并发数的调用进行wait,需要将在并发数统计里面的那几个线程再完成调用 进行对其他wait线程进行唤醒。
好了,以上便是对ActiveLimitFilter 的全部解析了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

$码出未来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值