Dubbo source code analysis seven: a problem using the executes attribute

We know that in Dubbo , the size of the thread pool can be configured for the Provider to control the maximum parallelism of the service provided by the system. The default is 200. If we want to configure it to 500 , we can configure it as follows:

 

<dubbo:provider token="true" threads="500"/>

 

When we want to limit the maximum number of threads used by a dubbo service, dubbo provides the attribute executes to provide this function. For example, if we want to limit the maximum number of threads in the thread pool to be used by an interface at the same time , we can configure as follows :

 

<dubbo:service interface="com.manzhizhen.service.MyLoverService" executes="100" />

 

Let's take a look at how the internal executes of dubbo are implemented, so we have to move to ExecuteLimitFilter , let's take a look at its implementation directly:

@Activate(group = Constants.PROVIDER, value = Constants.EXECUTES_KEY)

public class ExecuteLimitFilter implements Filter {

 

    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {

        URL url = invoker.getUrl();

        String methodName = invocation.getMethodName();

        int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0);

        // If the interface / method has executes set and the value is greater than 0

        if (max > 0) {

            // Take out the counter corresponding to the interface / method

            RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName());

            // If the number of threads currently in use is greater than or equal to the set threshold, throw an exception directly

            if (count.getActive() >= max) {

                throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited.");

            }

        }

        long begin = System.currentTimeMillis();

        boolean isException = false;

        // counter +1

        RpcStatus.beginCount(url, methodName);

        try {

            Result result = invoker.invoke(invocation);

            return result;

        } catch (Throwable t) {

            isException = true;

            if (t instanceof RuntimeException) {

                throw (RuntimeException) t;

            } else {

                throw new RpcException("unexpected exception when ExecuteLimitFilter", t);

            }

        } finally {

            // Counter -1 in finally

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

        }

    }

}

 

看上面的代码,可以得知基本步骤就是(黄底的部分代码):计数器当前值和阈值比较 > 计数器+1 > 计数器-1。这种方式在高并发时会出现静态条件问题的,比如当前该接口已经使用了99个线程,这是时候有两个请求同时到达都发现count.getActive()是小于max的,于是该接口使用的线程数就有可能达到了101个。

 

那么,我们能不能把RpcStatus.beginCount(url, methodName);放到count.getActive() >= max的前面去执行?仔细想想后也不行,这样做的话有可能在高并发时请求被count.getActive() >= max卡死,因为大量请求将计数器+1+1的速度远大于原有请求执行完将计数器-1的速度),导致一段时间内计数器一直大于阈值但实际上该接口使用的线程数却是0

 

于是,为了将比较和+1做成原子的,我们想到了Semaphore信号量Semaphore维护了一组许可,用来管理有限的资源,比如这里的线程数。使用Semaphore的有两点需要注意的地方,第一个就是说如果使用不当会导致Semaphore中的许可数多于最初设置的值,或者变为负数,也就是说,Semaphore并没有对非正常使用作出任何保护措施。还有一点是Semaphore没有方法能直接修改许可数量,但我们可以通过间接方法(比如多release几次以增加许可数量),但毕竟不优雅。

 

<!--StartFragment--> <!--EndFragment-->

Semaphore可以解决上述问题,但当需要修改线程数时,我们应该新建一个Semaphore对象,当采用这种替换Semaphore对象来应对许可数的变化时,一定要确保在仍和情况下acquire和release操作应该在一个Semaphore对象上。这也是我们在设计那些控制并发的小工具时需要注意的地方。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326330928&siteId=291194637