实现原理
属性值更新
nacos配置值变更后可以自动修改上下文中使用到的该配置值,在 spring-cloud-starter-alibaba-nacos-config中主要借助 RefreshEventListener 和 ContextRefresher 实现。
上面的属性刷新调用时序图,我们可以比较好介入的点就是 RefreshListener 和 ContextRefresher中的处理流程,一种解耦的方式就是利用spring的事件机制,在刷新属性方法执行结束后发布一个属性更新完成事件,并把本次更新的key嵌在事件中一起发布,方便外部感知。而这恰好在 ContextRefreshe.refreshEnvironment已经有相关逻辑实现了,源码如下:
public synchronized Set<String> refreshEnvironment() {
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());
addConfigFilesToEnvironment();
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();
//发布了环境变更事件
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
return keys;
}
但是很重要得一点是,销毁bean然后重建bean以及赋值属性是在 RefreshScope.refreshAll()中进行的,所以监听这个EnvironmentChangeEvent事件行不通,那监听 RefreshScope中发布的RefreshScopeRefreshedEvent 呢?还是不太现实,因为他发布事件时没有发布被更新的key我们无法利用这些被更新的key做文章。那要实现发布属性,而且能访问被更新后的值只能在RefreshListener 和 ContextRefresher上做文章。
为什么要选择ContextRefresher呢?
@Configuration
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED, matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {
......
@Bean
@ConditionalOnMissingBean
public ContextRefresher contextRefresher(ConfigurableApplicationContext context,
RefreshScope scope) {
return new ContextRefresher(context, scope);
}
@Bean
public RefreshEventListener refreshEventListener(ContextRefresher contextRefresher) {
return new RefreshEventListener(contextRefresher);
}
......
}
RefreshAutoConfiguration这个配置类中做了一些Bean托管定义,ContextRefresher上有@ConditionalOnMissingBean修饰,告诉我们可以自己自定义ContextRefresher来代替框架中定义好的(当然没有标注@ConditionalOnMissingBean我们也可以强制替换,即定义相同Bean 加上@Primary即可 )。实现如下:
public class ContextRefresherDecorator extends ContextRefresher {
private ApplicationContext context;
public ContextRefresherDecorator(ConfigurableApplicationContext context,
RefreshScope scope) {
super(context, scope);
this.context = context;
}
@Override
public synchronized Set<String> refresh() {
Set<String> refresh = super.refresh();
context.publishEvent(new RefreshCompleteEvent(refresh));
return refresh;
}
}
public class RefreshCompleteEvent extends ApplicationEvent {
/**
* 被刷新的key集合
*/
private final Set<String> refreshedKeySet;
/**
* Create a new {@code ApplicationEvent}.
*
* @param source the object on which the event initially occurred or with
* which the event is associated (never {@code null})
*/
public RefreshCompleteEvent(Object source) {
super(source);
this.refreshedKeySet = (Set<String>) source;
}
public Set<String> getRefreshedKeySet() {
return refreshedKeySet;
}
}
线程池参数更新
涉及到线程,任何临界区操作必须考虑的问题是数据竞争和线程安全。线程池的配置参数是典型的共享变量,线程池中的线程都会在各自的执行流程上使用到,下面是ThreadPoolExecutor关键参数在代码中的定义:
public class ThreadPoolExecutor extends AbstractExecutorService {
private volatile ThreadFactory threadFactory;
/**
* Handler called when saturated or shutdown in execute.
*/
private volatile RejectedExecutionHandler handler;
/**
* Timeout in nanoseconds for idle threads waiting for work.
* Threads use this timeout when there are more than corePoolSize
* present or if allowCoreThreadTimeOut. Otherwise they wait
* forever for new work.
*/
private volatile long keepAliveTime;
/**
* Core pool size is the minimum number of workers to keep alive
* (and not allow to time out etc) unless allowCoreThreadTimeOut
* is set, in which case the minimum is zero.
*/
private volatile int corePoolSize;
/**
* Maximum pool size. Note that the actual maximum is internally
* bounded by CAPACITY.
*/
private volatile int maximumPoolSize;
}
上面是线程池核心参数的定义,我们发现核心参数都是由volatile修饰,保证线程间的修改的可见性。而我们使用的场景也是
nacos配置修改->一个线程轮训到变化值修改线程池核心参数->线程池中的线程感知到线程池核心参数变化
这是典型的一写多度场景,所以对于corePoolSize maximumPoolSize RejectedExecutionHandler keepAliveTime我们直接调用线程池的属性set方法修改值,就能实现加锁修改的效果。
工作队列调整
工作队列的修改就是调整队列大大小,扩缩容问题。以LinkedBlockingQueue为例讲解,它定义的容量capacity是使用final修饰的,直接是无法修改的,当然可以使用反射去修改,但是缺乏可见性保证,存在竞态条件不可取。
Java中提供了并发量控制工具Semaphore,它完美适配容量控制。
public class ResizableSemaphore extends Semaphore {
public ResizableSemaphore(int permits, boolean fair) {
super(permits, fair);
}
public ResizableSemaphore(int permits) {
super(permits);
}
@Override
protected void reducePermits(int reduction) {
super.reducePermits(reduction);
}
public void increasePermits(int delta) {
//Aqs中这里会 将 当前值+delta 设置未新的permit
this.release(delta);
}
}
新的可resize的队列实现
public class ResizeableLinkedBlockingQueue<E> extends LinkedBlockingQueue<E> {
private int queueSize;
private final ResizableSemaphore availablePlaces;
public ResizeableLinkedBlockingQueue() {
super();
this.queueSize = Integer.MAX_VALUE;
availablePlaces = new ResizableSemaphore(queueSize, true);
}
public ResizeableLinkedBlockingQueue(Collection<? extends E> collection) {
super(collection);
this.queueSize = Integer.MAX_VALUE;
availablePlaces = new ResizableSemaphore(queueSize, true);
}
public ResizeableLinkedBlockingQueue(int initialCapacity) {
super();
this.queueSize = initialCapacity;
availablePlaces = new ResizableSemaphore(initialCapacity, true);
}
@Override
public int size() {
return queueSize;
}
/**
* 调整队列大小
*
* @param newSize
* @throws IllegalArgumentException
*/
public synchronized void resizeQueue(int newSize) throws IllegalArgumentException {
if (newSize < 0) {
throw new IllegalArgumentException("Cannot set queue size to a value below zero.");
}
int difference;
if (newSize < queueSize) {
difference = queueSize - newSize;
availablePlaces.reducePermits(difference);
} else if (newSize > queueSize) {
difference = newSize - queueSize;
availablePlaces.increasePermits(difference);
}
queueSize = newSize;
//Nothing to do if newSize == queueSize.
}
@Override
public boolean offer(E e) {
if (Objects.isNull(e)) {
throw new IllegalArgumentException("Element to offer cannot be null.");
}
boolean returnValue;
if (availablePlaces.tryAcquire()) {
if (super.offer(e)) {
returnValue = true;
} else {
returnValue = false;
//If the queue.offer(e) fails, the availablePlaces.tryAcquire() will still have occurred, so we'll have
//to release the acquired permit.
availablePlaces.release();
}
} else {
returnValue = false;
}
return returnValue;
}
@Override
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
if (Objects.isNull(e)) {
throw new IllegalArgumentException("Element to offer cannot be null.");
}
boolean returnValue;
if (availablePlaces.tryAcquire(timeout, unit)) {
if (super.offer(e)) {
returnValue = true;
} else {
returnValue = false;
//If the queue.offer(e) fails, the availablePlaces.tryAcquire(timeout, unit) will still have occurred,
//so we'll have to release the acquired permit.
availablePlaces.release();
}
} else {
returnValue = false;
}
return returnValue;
}
@Override
public E poll() {
E returnValue = super.poll();
if (returnValue != null) {
availablePlaces.release();
}
return returnValue;
}
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E resultValue = super.poll(timeout, unit);
if (resultValue != null) {
availablePlaces.release();
}
return resultValue;
}
@Override
public void put(E e) throws InterruptedException {
availablePlaces.acquire();
super.put(e);
}
@Override
public int remainingCapacity() {
return availablePlaces.availablePermits();
}
@Override
public boolean remove(Object o) {
if (super.remove(o)) {
availablePlaces.release();
return true;
}
return false;
}
@Override
public E take() throws InterruptedException {
E result = super.take();
availablePlaces.release();
return result;
}
}
代码实现
代码结构
关键实现
可调整参数的线程池
@Slf4j
public class ResizeableThreadPoolExecutor extends ThreadPoolExecutor implements Refreshable {
private IPoolProperty iPoolProperty;
private ResizeableLinkedBlockingQueue<Runnable> queue;
public ResizeableThreadPoolExecutor(IPoolProperty properties, ResizeableLinkedBlockingQueue<Runnable> queue) {
super(properties.getCorePoolSize(), properties.getMaxPoolSize(),
properties.getKeepLiveTime(),
TimeUnit.SECONDS,
queue);
this.queue = queue;
this.iPoolProperty = properties;
log.info("created {}", pintExecutorInfo());
}
@Override
public void refresh() {
if (iPoolProperty.getCorePoolSize() > iPoolProperty.getMaxPoolSize()) {
log.error("illegal config for thread pool:{}", iPoolProperty);
}
log.info("before update:{}", pintExecutorInfo());
this.setCorePoolSize(iPoolProperty.getCorePoolSize());
this.setMaximumPoolSize(iPoolProperty.getMaxPoolSize());
this.setKeepAliveTime(iPoolProperty.getKeepLiveTime(), TimeUnit.SECONDS);
queue.resizeQueue(iPoolProperty.getQueueSize());
log.info("after update:{}", pintExecutorInfo());
}
/**
* 打印线程池的信息
*
* @return 线程池描述信息字符串
*/
private String pintExecutorInfo() {
FormattingTuple formattingTuple = MessageFormatter.arrayFormat("Thread executor:core_size={}," +
"thread_cur_size={},max_size={}," +
"queue_cur_size={},total_task_count={}", new Object[]{this.getCorePoolSize(),
this.getActiveCount(),
this.getMaximumPoolSize(),
this.getQueue().size(),
this.getTaskCount()});
return formattingTuple.getMessage();
}
}
线程池和配置绑定注册类
@Slf4j
public class DynamicThreadPoolRegistryImpl implements DynamicThreadPoolRegistry {
private ConcurrentHashMap<String, Refreshable> containerStorage = new ConcurrentHashMap<>(8);
private final IPoolProperty property;
public DynamicThreadPoolRegistryImpl(IPoolProperty property) {
this.property = property;
}
/**
* 注册容器
*
* @param propertyClass 属性类 属性类必须被 {@link org.springframework.boot.context.properties.ConfigurationProperties} 修饰
* @param refreshable 可更新容器
*/
@Override
public void register(Class<? extends IPoolProperty> propertyClass, Refreshable refreshable) {
ConfigurationProperties annotation = AnnotationUtils.getAnnotation(propertyClass, ConfigurationProperties.class);
if (Objects.isNull(annotation)) {
throw new IllegalArgumentException("propertyClass must be annotated by @ConfigurationProperties");
}
if (Objects.isNull(refreshable)) {
throw new IllegalArgumentException("refreshable container must not be null");
}
String prefix = annotation.prefix();
containerStorage.put(prefix, refreshable);
}
@Override
public void trigger(RefreshCompleteEvent event) {
Set<String> refreshedKeySet = event.getRefreshedKeySet();
log.info("property value of context:{} have been updated", refreshedKeySet);
Set<String> keyPrefixSet = refreshedKeySet.stream().map(key -> {
String[] keySegmentArray = StringUtils.split(key, ".");
return String.join(".", Arrays.copyOf(keySegmentArray, keySegmentArray.length - 1));
}).filter(StringUtils::isNotBlank).collect(Collectors.toSet());
keyPrefixSet.stream().map(containerStorage::get).filter(Objects::nonNull).forEach(Refreshable::refresh);
}
配置类
@Configuration
public class DynamicThreadPoolConfigure {
private static final String THREAD_CONFIG_FILE_NAME = "dynamic-thread-pool";
@Bean
public DynamicThreadPoolRegistry dynamicThreadPoolRegistry(@Qualifier("RESIZEABLE_EXECUTOR") Executor resizableExecutor, IPoolProperty property) {
DynamicThreadPoolRegistry dynamicThreadPoolRegistry = new DynamicThreadPoolRegistryImpl(property);
Refreshable refreshable = (Refreshable) resizableExecutor;
dynamicThreadPoolRegistry.register(ApiExecuteThreadPoolProperty.class, refreshable);
return dynamicThreadPoolRegistry;
}
@Bean("RESIZEABLE_EXECUTOR")
public Executor resizableExecutor(IPoolProperty properties) {
ResizeableLinkedBlockingQueue<Runnable> queue = new ResizeableLinkedBlockingQueue<>(properties.getQueueSize());
ResizeableThreadPoolExecutor executor = new ResizeableThreadPoolExecutor(properties, queue);
return executor;
}
@Bean
public ContextRefresher contextRefresherDecorator(ConfigurableApplicationContext context, RefreshScope scope) {
return new ContextRefresherDecorator(context, scope);
}
备注
资料来源:如何实现可调整的线程池