Recently saw the Meituan technical team’s dynamic thread pool analysis article: https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html and a corresponding open source project: https://github.com/dromara/dynamic-tp
A little interesting, but also feel the utility of the dynamic thread pool in the work, through this article to analyze the core implementation principle of the dynamic thread pool, this article refers to the Meituan article and the implementation of the open source project, hereby thanks.
Dynamic thread pool refers to the fact that the parameters in the thread pool can be dynamically modified and take effect, such as corePoolSize, maximumPoolSize, etc.
In work, parameters such as the number of core threads and the maximum number of threads in the thread pool are difficult to estimate and fix, and it is necessary to dynamically adjust them as the application runs.
1. Directly based on SpringBoot
2. Support Nacos Configuration Center configuration
Core configuration items
I hope that a dynamic thread pool can be configured with the above configuration:
1. dtp: Indicates dynamic thread pool, an abbreviation for dynamic thread pool
2. enable: Indicates whether to use the dynamic thread pool, defaults to true
3. core-pool-size: Indicates the number of core threads for dtp
4. maximum-pool-size: Indicates the maximum number of threads for dtp
5. For other parameters of the thread pool, you can expand them later
In addition, I hope that if there is a dtp configuration in the project’s configuration, and the enable is not equal to false, it means that to open the dynamic thread pool, you need to add a thread pool object to the Spring container as a bean object, so that other beans can use the dynamic thread pool through dependency injection.
Also, for the above configuration, we would better configure it in nacos so that it can be modified dynamically.
Start by creating two projects:
1. dtp-autoconfiguration: The autoconfiguration module that represents the dynamic thread pool stores some related autoconfiguration classes
2. user: Indicates a business application that uses a dynamic thread pool
Then rewrite user as a SpringBoot application:
Create a new startup class and controller:
Now, I like to be able to use the dynamic thread pool in ZhouyuController, like this:
For this section to work, there must be several conditions:
1. The Spring container must have a ThreadPoolExecutor type bean
2. And this ThreadPoolExecutor object has to be what we call a dynamic thread pool object
This raises the question, how exactly do we represent a dynamic thread pool, the difference between the dynamic thread pool and the normal thread pool is that the dynamic thread pool can support the modification of its parameters through nacos.
So do we need to define a new class to represent the dynamic thread pool? The answer I gave was yes, because if you don’t define a new one, then for the above code, if I have multiple Bean objects of type ThreadPoolExecutor in the Spring container, how do I find the dynamic thread pool? You can only use the property name, for example, the property name is dynamicThreadPoolExecutor, which requires us to register the dynamic thread pool object with the Spring container, and the beanName must be dynamicThreadPoolExecutor.
And if we define a new class (dtp-aucotoncifuration project):
So it’s convenient if we want to use the dynamic thread pool:
This way, the code looks more explicit.
Note that user adds dependencies:
Next, let’s create the DtpExecutor object and add it to the Spring container, which is a very important step.
If the application wants to turn on the dynamic thread pool, then it needs to do a step, otherwise it does not need to do this step, and when creating the DtpExecutor object, it has to use the configured parameters, and it has to support Nacos, and it has to be put into the Spring container.
SpringBoot’s auto-configuration class can be used here.
First add the spring-boot dependency to the dtp-autoconfiguration:
And create a new auto-configuration class:
Indicates that this configuration class will only take effect when dtp.anable=true, and will not take effect without this configuration item or false.
Then we can define a bean for DtpExecutor in DtpAutoConfiguration, and the first thing we think of is to take advantage of @Bean, such as:
However, there is no parameterless construction method in DtpExecutor, that is, when constructing a DtpExecutor object, we need to be able to get the configuration item parameters, so how to take it?
Students who are familiar with Spring may think of using the Enviroment object, because whether we have a configuration item in the application.yaml local to the application or a configuration item in nacos, it is ultimately placed in the Environment object, such as the following code:
Let’s test it first, noting that for the DtpAutoConfiguration autoconfiguration class to work, spring.factories is also required:
Because we have limitations, we also have to configure dtp in user:
And the code of ZhouyuController is also roughly changed:
This tells us if we can use the dynamic thread pool and if it is a parameter that we have configured.
Start the User app and visit localhost:8080/test, the result is:
Found, the results are normal. If you have questions, you can follow me and discuss them together:
Also, we can try configuring in nacos, then we need to introduce nacos-client in user:
Then configure nacos:
Then configure dtp.yaml in nacos:
Launch the user app and visit localhost:8080/test:
The results are also normal.
That is, the code is written here, and we are done:
1. Can read the configuration in nacos
2. And create the DtpExecutor object
3. And put it in the Spring container
So the core problem has not been solved: if the configuration is modified in nacos during the operation of the application, how does it take effect?
This requires that in the user application can find out whether the configuration content in the nacos has been modified, which requires the use of the nacos listener mechanism, we in the auto-configuration module to define a nacos listener, which requires auto-configuratino also depends on nacos-client, We directly transfer the dependency of nacos-client from the user module to the auto-configuration module, so that there is no impact on the user, because the user depends on the auto-configuration module, and thus indirectly depends on the nacos-client.
Let’s build a new Nacos listener, NacosRefresher:
Also define NacosRefresher as a bean in DtpAutoConfiguration:
It is also possible to use @Import to import NacosRefresher:
Once NacosRefresher arrives at the data change event, then it is possible to update DtpExecutor, so here we want to solve two problems:
1. Parse content, because content is String, we need to parse and get the configuration item content
2. Update the DtpExecutor object in the Spring container, then you need to be able to get the DtpExecutor object
Let’s start the application and modify the configuration of the nacos to see what the context looks like:
I just modified the core-pool-size, but how content dtp.yaml content, then let’s parse:
The result is:
In this way, we parse content into Properties format, which makes it easier to get configuration items.
Next, as long as we can get the DtpExecutor object in the Spring container, then how to get it, here refer to the open source project dynamic-tp, using the BeanPostProcessor to store it in a static map.
Start by creating a new DtpUtil:
Then create a new BeanPostProcessor that will store the DtpExecutor object into DtpUtil:
Also import DtpBeanPostProcessor in DtpAutoConfiguration.
Then in NacosRefresher, we can use DtpUtil to get the DtpExecutor object, and we can modify the corresponding parameters:
This completes the parameter modification of DtpExecutor, at this point, a simple dynamic thread pool is completed, you can test it yourself, modify the Nacos configuration, see if the controller can get the latest corePoolSize in real time, I test is no problem.
For implementing a dynamic thread pool, the core points are:
1. The dynamic thread pool is a bean object
2. Real-time discovery of configuration changes
3. But the core is the source design of the thread pool itself
For example, setCorePoolSize, for ThreadPoolExecutor:
It will judge:
1. If the current worker thread is larger than the latest corePoolSize, then the idle thread will be interrupted, and only the corePoolSize idle thread will be protected eventually
2. If the number of core threads increases, the addWorker(null, true) method is called to add new core threads
The so-called dynamic thread pool is actually to dynamically modify the number of threads in the thread pool, less to increase, more to break.
I hope that everyone can gain something, and if you have any questions, you can pay attention to me and discuss it together: