前言
通过@DynamicTp注解JUC线程,会享受到监控功能。
围绕注册、获取、刷新这三个核心api去跟代码。核心类是DtpRegistry。
模块
本节引用:dynamictp.cn/guide/intro…
- adapter 模块:主要是适配一些第三方组件的线程池管理,目前已经实现的有 SpringBoot 内置的三大 web 容器(Tomcat、Jetty、Undertow)、Dubbo、RocketMq、Hystrix、Grpc 的线程池管理, 后续会接入其他常用组件的线程池管理。
- common 模块:主要是一些各个模板都会用到的类,解耦依赖,复用代码,大家日常开发中可能也经常会这样做。
- core 模块:该框架的核心代码都在这个模块里,包括动态调整参数,监控报警,以及串联整个项目流程都在此。
- example 模块:提供一个简单使用示例,方便使用者参照
- extension 模块:放一些扩展功能实现,比如基于 redis 的流控扩展、邮件发送扩展、skywalking 上下文传递扩展等
- logging 模块:用于配置框架内部日志的输出,目前主要用于输出线程池监控指标数据到指定文件
- starter模块:提供独立功能模块的依赖封装、自动配置等相关。
DtpRegistry
实现了ApplicationRunner接口,所以它在应用一启动的时候就会执行run(),该类保存着所有已经注册的线程池对象。
@Override
public void run(ApplicationArguments args) {
Set<String> remoteExecutors = Collections.emptySet();
// 我们在配置文件里面配置的线程池
if (CollectionUtils.isNotEmpty(dtpProperties.getExecutors())) {
remoteExecutors = dtpProperties.getExecutors().stream()
.map(ThreadPoolProperties::getThreadPoolName)
.collect(Collectors.toSet());
}
val registeredDtpExecutors = Sets.newHashSet(DTP_REGISTRY.keySet());
// 找出本地的线程池,就是所有线程池中没有在配置文件中配置的线程池
val localDtpExecutors = CollectionUtils.subtract(registeredDtpExecutors, remoteExecutors);
log.info("DtpRegistry initialization is complete, remote dtpExecutors: {}, local dtpExecutors: {}, local commonExecutors: {}",
remoteExecutors, localDtpExecutors, COMMON_REGISTRY.keySet());
}
上面我们还有一个问题,就是从DTP_REGISTRY、COMMON_REGISTRY这两个map中遍历所有线程池,那么这两个map是什么时候被填充的呢?
我们在registerDtp,registerCommon这两个方法中看到了填充map,顺着调用链路,我们来看看是哪里调用这两个方法。
通过方法的调用链路,我们发现了在DtpPostProcessor类的postProcessAfterInitialization方法中会调用上面两个方法。我们会在DtpPostProcessor中来看。
DtpPostProcessor
这个类是真正拦截我们的线程池对象的一个bean的后处理器,它是关键,我们后面会重点来介绍它的实现,它的初始化依赖于ApplicationContextHolder,DtpPostProcessor这个类又在哪里初始化?答,在配置中心的starter模块中的starter-common中的BaseBeanAutoConfiguration里,所以我们需要引入具体的配置中心模块或者只需要享受监控的话引入starter-common模块。
BeanPostProcessor对Spring工厂所创建的对象,进行再加工
public class DtpPostProcessor implements BeanPostProcessor {
这个类对我们应用中定义的线程池进行拦截,并填充相关的map,以实现一些增强逻辑。
@Override
public Object postProcessAfterInitialization(@NonNull Object bean, @NonNull String beanName) throws BeansException {
if (!(bean instanceof ThreadPoolExecutor) && !(bean instanceof ThreadPoolTaskExecutor)) {
return bean;
}
if (bean instanceof DtpExecutor) {
DtpExecutor dtpExecutor = (DtpExecutor) bean;
if (bean instanceof EagerDtpExecutor) {
((TaskQueue) dtpExecutor.getQueue()).setExecutor((EagerDtpExecutor) dtpExecutor);
}
registerDtp(dtpExecutor);
return dtpExecutor;
}
ApplicationContext applicationContext = ApplicationContextHolder.getInstance();
String dtpAnnotationVal;
try {
DynamicTp dynamicTp = applicationContext.findAnnotationOnBean(beanName, DynamicTp.class);
if (Objects.nonNull(dynamicTp)) {
dtpAnnotationVal = dynamicTp.value();
} else {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) applicationContext;
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) registry.getBeanDefinition(beanName);
MethodMetadata methodMetadata = (MethodMetadata) annotatedBeanDefinition.getSource();
if (Objects.isNull(methodMetadata) || !methodMetadata.isAnnotated(DynamicTp.class.getName())) {
return bean;
}
dtpAnnotationVal = Optional.ofNullable(methodMetadata.getAnnotationAttributes(DynamicTp.class.getName()))
.orElse(Collections.emptyMap())
.getOrDefault("value", "")
.toString();
}
} catch (NoSuchBeanDefinitionException e) {
log.error("There is no bean with the given name {}", beanName, e);
return bean;
}
String poolName = StringUtils.isNotBlank(dtpAnnotationVal) ? dtpAnnotationVal : beanName;
if (bean instanceof ThreadPoolTaskExecutor) {
ThreadPoolTaskExecutor taskExecutor = (ThreadPoolTaskExecutor) bean;
registerCommon(poolName, taskExecutor.getThreadPoolExecutor());
} else {
registerCommon(poolName, (ThreadPoolExecutor) bean);
}
return bean;
}
private void registerDtp(DtpExecutor executor) {
DtpRegistry.registerDtp(executor, "beanPostProcessor");
}
private void registerCommon(String poolName, ThreadPoolExecutor executor) {
ExecutorWrapper wrapper = new ExecutorWrapper(poolName, executor);
DtpRegistry.registerCommon(wrapper, "beanPostProcessor");
}
刷新
/**
* Refresh while the listening configuration changed.
* @param properties the main properties that maintain by config center
*/
public static void refresh(DtpProperties properties) {
if (Objects.isNull(properties) || CollectionUtils.isEmpty(properties.getExecutors())) {
log.warn("DynamicTp refresh, empty threadPoolProperties.");
return;
}
properties.getExecutors().forEach(x -> {
if (StringUtils.isBlank(x.getThreadPoolName())) {
log.warn("DynamicTp refresh, threadPoolName must not be empty.");
return;
}
val dtpExecutor = DTP_REGISTRY.get(x.getThreadPoolName());
if (Objects.isNull(dtpExecutor)) {
log.warn("DynamicTp refresh, cannot find specified dtpExecutor, name: {}.", x.getThreadPoolName());
return;
}
refresh(dtpExecutor, x);
});
}
private static void refresh(DtpExecutor executor, ThreadPoolProperties properties) {
if (properties.getCorePoolSize() < 0
|| properties.getMaximumPoolSize() <= 0
|| properties.getMaximumPoolSize() < properties.getCorePoolSize()
|| properties.getKeepAliveTime() < 0) {
log.error("DynamicTp refresh, invalid parameters exist, properties: {}", properties);
return;
}
DtpMainProp oldProp = ExecutorConverter.convert(executor);
doRefresh(executor, properties);
DtpMainProp newProp = ExecutorConverter.convert(executor);
if (oldProp.equals(newProp)) {
log.warn("DynamicTp refresh, main properties of [{}] have not changed.", executor.getThreadPoolName());
return;
}
List<FieldInfo> diffFields = EQUATOR.getDiffFields(oldProp, newProp);
List<String> diffKeys = diffFields.stream().map(FieldInfo::getFieldName).collect(toList());
// 线程池参数修改推送
NoticeManager.doNoticeAsync(new ExecutorWrapper(executor), oldProp, diffKeys);
log.info("DynamicTp refresh, name: [{}], changed keys: {}, corePoolSize: [{}], maxPoolSize: [{}], queueType: [{}], " +
"queueCapacity: [{}], keepAliveTime: [{}], rejectedType: [{}], allowsCoreThreadTimeOut: [{}]",
executor.getThreadPoolName(),
diffKeys,
String.format(PROPERTIES_CHANGE_SHOW_STYLE, oldProp.getCorePoolSize(), newProp.getCorePoolSize()),
String.format(PROPERTIES_CHANGE_SHOW_STYLE, oldProp.getMaxPoolSize(), newProp.getMaxPoolSize()),
String.format(PROPERTIES_CHANGE_SHOW_STYLE, oldProp.getQueueType(), newProp.getQueueType()),
String.format(PROPERTIES_CHANGE_SHOW_STYLE, oldProp.getQueueCapacity(), newProp.getQueueCapacity()),
String.format("%ss => %ss", oldProp.getKeepAliveTime(), newProp.getKeepAliveTime()),
String.format(PROPERTIES_CHANGE_SHOW_STYLE, oldProp.getRejectType(), newProp.getRejectType()),
String.format(PROPERTIES_CHANGE_SHOW_STYLE, oldProp.isAllowCoreThreadTimeOut(),
newProp.isAllowCoreThreadTimeOut()));
}
// 参数在这里进行生效
private static void doRefresh(DtpExecutor dtpExecutor, ThreadPoolProperties properties) {
// 当配置中心修改的最大线程数比之前的小,先修改核心线程再更新最大线程数;反之亦然、
// 如果先更新核心线程数,则可能出现新的核心线程数大于旧的最大线程数,因为新的最大线程数大于旧的最大线程数
// 可能是新的核心线程大于旧的最大线程数,所以如果新的最大线程数大于旧的最大线程数,就先更新最大线程数
doRefreshPoolSize(dtpExecutor, properties);
if (!Objects.equals(dtpExecutor.getKeepAliveTime(properties.getUnit()), properties.getKeepAliveTime())) {
dtpExecutor.setKeepAliveTime(properties.getKeepAliveTime(), properties.getUnit());
}
if (!Objects.equals(dtpExecutor.allowsCoreThreadTimeOut(), properties.isAllowCoreThreadTimeOut())) {
dtpExecutor.allowCoreThreadTimeOut(properties.isAllowCoreThreadTimeOut());
}
// update reject handler
if (!Objects.equals(dtpExecutor.getRejectHandlerName(), properties.getRejectedHandlerType())) {
dtpExecutor.setRejectedExecutionHandler(RejectHandlerGetter.getProxy(properties.getRejectedHandlerType()));
dtpExecutor.setRejectHandlerName(properties.getRejectedHandlerType());
}
// update Alias Name
if (!Objects.equals(dtpExecutor.getThreadPoolAliasName(), properties.getThreadPoolAliasName())) {
dtpExecutor.setThreadPoolAliasName(properties.getThreadPoolAliasName());
}
updateQueueProp(properties, dtpExecutor);
dtpExecutor.setWaitForTasksToCompleteOnShutdown(properties.isWaitForTasksToCompleteOnShutdown());
dtpExecutor.setAwaitTerminationSeconds(properties.getAwaitTerminationSeconds());
dtpExecutor.setPreStartAllCoreThreads(properties.isPreStartAllCoreThreads());
dtpExecutor.setRunTimeout(properties.getRunTimeout());
dtpExecutor.setQueueTimeout(properties.getQueueTimeout());
List<TaskWrapper> taskWrappers = TaskWrappers.getInstance().getByNames(properties.getTaskWrapperNames());
dtpExecutor.setTaskWrappers(taskWrappers);
// update notify items
val allNotifyItems = mergeAllNotifyItems(properties.getNotifyItems());
NotifyHelper.refreshNotify(dtpExecutor.getThreadPoolName(), dtpProperties.getPlatforms(),
dtpExecutor.getNotifyItems(), allNotifyItems);
dtpExecutor.setNotifyItems(allNotifyItems);
dtpExecutor.setNotifyEnabled(properties.isNotifyEnabled());
}
DtpExecutor
继承了DtpLifecycleSupport,该类定义了DtpExecutor的生命周期。
public abstract class DtpLifecycleSupport extends ThreadPoolExecutor implements
InitializingBean, DisposableBean {
Constructor > @PostConstruct > InitializingBean > init-method,在Bean生命周期结束前调用destory()方法做一些收尾工作,亦可以使用destory-method。
prestartAllCoreThreads设置项,可以在线程池创建,但还没有接收到任何任务的情况下,先行创建符合corePoolSize参数值的线程数:
DtpLifecycleSupport实现了InitializingBean, DisposableBean两个spring生命周期的回调接口,也就是当DtpExecutor初始化时会调用afterPropertiesSet(),调用afterPropertiesSet()方法中调用了initialize()方法,这个方法是抽象类,最终交给子类来实现,我们看到在DtpExecutor的实现中,是通过一个布尔变量preStartAllCoreThreads来控制是否需要提前启动核心线程,我们知道在线程池的默认线程池中,在线程池启动的时候是不会启动核心线程的,只有来了新的任务时才会启动线程。提前启动线程,让线程等待任务的到来:
public int prestartAllCoreThreads() {
int n = 0;
while (addWorker(null, true))
++n;
return n;
}
DtpMonitor
其本身也实现了ApplicationRunner,项目一启动就会调用run(),所以先看看run()。
@Override
public void run(ApplicationArguments args) {
MONITOR_EXECUTOR.scheduleWithFixedDelay(this::run,
0, dtpProperties.getMonitorInterval(), TimeUnit.SECONDS);
}
默认每五秒就去检查下线程池的配置。
private void run() {
List<String> dtpNames = DtpRegistry.listAllDtpNames();
// 拿到所有标有DynamicTp注解的线程池
List<String> commonNames = DtpRegistry.listAllCommonNames();
checkAlarm(dtpNames);
collect(dtpNames, commonNames);
}
private void collect(List<String> dtpNames, List<String> commonNames) {
if (!dtpProperties.isEnabledCollect()) {
return;
}
// 拿到所有的线程池对象,并获取到线程池的各种属性统计指标ThreadPoolStats
dtpNames.forEach(x -> {
DtpExecutor executor = DtpRegistry.getDtpExecutor(x);
ThreadPoolStats poolStats = MetricsConverter.convert(executor);
// 根据指标收集类型(logging,micrometer)进行指标的处理,默认是进行日志打印
doCollect(poolStats);
});
commonNames.forEach(x -> {
ExecutorWrapper wrapper = DtpRegistry.getCommonExecutor(x);
ThreadPoolStats poolStats = MetricsConverter.convert(wrapper);
doCollect(poolStats);
});
publishCollectEvent();
}
DtpEndPoint
这是基于springboot-actuator来实现的一个端点。
DtpAdapterListener
一个比较关键的类,实现了GenericApplicationListener接口,算是一个事件监听器,主要监听RefreshEvent、CollectEvent、AlarmCheckEvent这三个事件,在这里实现的第三方中间件线程池的参数更新操作和收集告警操作。
RefreshEvent
引入了dynamic-tp-spring-boot-starter-nacos这个starter么,在这个starter中有个自动装配类:DtpAutoConfiguration,在这个配置类中初始化了唯一的bean就是NacosRefresher,它负责配置更新时的刷新。最终调用DtpRegistry.refresh(),并发布RefreshEvent事件。