SOFA source code analysis - the principle of custom thread pool

foreword

In the official introduction of SOFA-RPC, a custom thread pool is introduced, which can set up an independent business thread pool for a specified service, which is isolated from SOFARPC's own business thread pool. Multiple services can share an independent thread pool.

The API usage is as follows:

UserThreadPool threadPool = new UserThreadPool();
threadPool.setCorePoolSize(10);
threadPool.setMaximumPoolSize(100);
threadPool.setKeepAliveTime(200);
threadPool.setPrestartAllCoreThreads(false);
threadPool.setAllowCoreThreadTimeOut(false);
threadPool.setQueueSize(200);

UserThreadPoolManager.registerUserThread("com.alipay.sofa.rpc.quickstart.HelloService", threadPool);

A custom thread pool is set up for the HelloService service as above.

Used in SOFABoot as follows:

<bean id="customExcutor" class="com.alipay.sofa.rpc.server.UserThreadPool" init-method="init">
    <property name="corePoolSize" value="10" />
    <property name="maximumPoolSize" value="10" />
    <property name="queueSize" value="0" />
</bean>

<bean id="helloService" class="com.alipay.sofa.rpc.quickstart.HelloService"/>

<sofa:service ref="helloService" interface="XXXService">
    <sofa:binding.bolt>
        <sofa:global-attrs thread-pool-ref="customExcutor"/>
    </sofa:binding.bolt>
</sofa:service>

So what is the realization principle?

Take a look.

Source code analysis

Key code:


UserThreadPoolManager.
        registerUserThread("com.alipay.sofa.rpc.quickstart.HelloService", threadPool);

UserThreadPoolManager is a user-defined thread pool manager. It contains a Map, the key is the interface name, and the value is the thread pool (a UserThreadPool object).

Take a look at this UserThreadPool.

A very simple class that encapsulates the JDK thread pool. And initialized some thread pool parameters, such as:

  • corePoolSize = 10
  • maximumPoolSize = 100
  • keepAliveTime = 300000 (thread recycling time (ms))
  • queueSize = 0
  • threadPoolName = "SofaUserProcessor" thread name
  • boolean allowCoreThreadTimeOut whether to close the core thread pool
  • boolean prestartAllCoreThreads Whether to initialize the core thread pool
  • volatile ThreadPoolExecutor executor

During initialization, the default parameters remain unchanged, the number of core threads is 10, the maximum is 100, the core thread pool is not closed by default, and the thread pool is not initialized by default. The default is the SynchronousQueue queue, which has the highest performance, and can also be set to a blocking queue or a priority queue. Of course, these can all be changed.

When will this thread pool work?

Let's talk about the conclusion first: when Netty reads the data (channelRead method), it will call the process method of the RpcRequestProcessor class through layer-by-layer calls. This method will get the UserProcessor object of the context (for bolts, the implementation class is BoltServerProcessor). UserProcessor has an internal interface ExecutorSelector, a thread pool selector, which defines a select method and returns a thread pool. If there is a thread pool, it will return to the custom thread pool (method: UserThreadPoolManager.getUserThread(service)), if not, return to the system thread pool.

Let's take a look at the specific code.

We are familiar with RpcHandler, which is Netty's handler. In the ChannelRead method, the handleCommand method of RpcCommandHandler will be called, and this method will be submitted to the thread pool for execution. The content of the task is to execute the process method.

By calling, the process method of RpcRequestProcessor will be executed finally. The call stack is as follows:

Line 105 will have the following judgment:

// to check whether get executor using executor selector
if (null == userProcessor.getExecutorSelector()) {
    executor = userProcessor.getExecutor();
} else {
    // in case haven't deserialized in io thread
    // it need to deserialize clazz and header before using executor dispath strategy
    if (!deserializeRequestCommand(ctx, cmd, RpcDeserializeLevel.DESERIALIZE_HEADER)) {
        return;
    }
    //try get executor with strategy
    executor = userProcessor.getExecutorSelector().select(cmd.getRequestClass(),
        cmd.getRequestHeader());
}

Try to get the thread pool selector. If it is null, use the system thread pool. If it is not null, call the select method of the selector to get the thread pool, and then use this thread to perform tasks.

// Till now, if executor still null, then try default
if (executor == null) {
    executor = (this.getExecutor() == null ? defaultExecutor : this.getExecutor());
}

// use the final executor dispatch process task
executor.execute(new ProcessTask(ctx, cmd));

So how is this select method implemented? Currently there is only one implementation, the inner class UserThreadPoolSelector of BoltServerProcessor. The logic of this method is as follows:
Get the service name from the Header, and call it according to the service name UserThreadPoolManager.getUserThread(service). If the return value is not null, it means that the user has set a custom thread pool, and the thread pool is returned. If it is null, return the system thread pool.

The BoltServerProcessor's getExecutorSelector judgment rules are as follows:

    @Override
    public ExecutorSelector getExecutorSelector() {
        return UserThreadPoolManager.hasUserThread() ? executorSelector : null;
    }

    public static boolean hasUserThread() {
        return userThreadMap != null && userThreadMap.size() > 0;
    }

    public BoltServerProcessor(BoltServer boltServer) {
        this.boltServer = boltServer;
        this.executorSelector = new UserThreadPoolSelector(); // 支持自定义业务线程池
    }

It can be seen that BoltServerProcessor will create an inner class object by default. As long as the Map in UserThreadPoolManager is not empty, it will try to call the select method. If the custom thread pool in the cache is found through the service name, it will return directly. very perfect.

It should be noted that there is only one system thread pool, the default core thread pool size is 20, and the maximum is 200. It seems that this is also the default configuration of tomcat. Therefore, when the concurrency is high, the user may need to use a custom thread pool, which can significantly increase the concurrency.

Summarize

Well, the principle of the custom thread pool is almost explored. This function is very useful. When the system concurrency is very high, or a certain service is very slow, and the service cannot affect other services, you can use the custom thread. pool to isolate these slow services from other services.

The principle is to interact with Server through UserThreadPoolManager. When Server executes a task, it will find the thread pool corresponding to the calling service from the current context. If there is one, it will return to the thread pool managed by UserThreadPoolManager. If not, return Framework thread pool.

The specific judgment code is in com.alipay.remoting.rpc.protocol.RpcRequestProcessorthe .

bye!!!

Guess you like

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