# dynamic-thread-pool **Repository Path**: i-dont-recognize-you/dynamic-thread-pool ## Basic Information - **Project Name**: dynamic-thread-pool - **Description**: 可以通过配置中心去动态修改线程池 - **Primary Language**: Unknown - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-12-18 - **Last Updated**: 2025-12-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 动态线程池+kafka自定义拒绝策略,做到任务不丢失 ## 前言 > 开发环境为 jdk8 本文用到了 nacos、kafka 这两个第三方中间件,文章分为两个部分 > > 第一部分:动态线程池 (nacos 必须要,kafka 不强求)(拒绝策略为默认报错) > > 第二部分:动态线程池 + kafka 自定义拒绝策略(nacos和kafka都需要,kafka是保证任务不丢失的关键) 项目地址:[dynamic-thread-pool: 动态线程池+kafka自定义拒绝策略,做到任务不丢失](https://gitee.com/i-dont-recognize-you/dynamic-thread-pool) 线程池执行过程(也是我的文章,这对后面测试用例有指导意义):[线程池详解与线程池调优](https://blog.csdn.net/qq_57581439/article/details/127407479?spm=1001.2014.3001.5501) ## 技术实现选型 ### 第三方中间件 | 工具 | 版本 | jdk版本 | 备注 | | ----- | ------------------ | ------- | ------------------------------------------ | | nacos | nacos-server-2.0.4 | 8 | 不能比这个低 | | kafka | kafka_2.13-4.1.1 | 17 | 主要是4以上的版本不需要zookeeper,其他也行 | ### maven 主要版本(开发环境jdk8) | jar包 | 版本 | 备注 | | -------------------- | ---------- | ---------------------------------- | | spring-cloud-alibaba | 2021.0.6.2 | 不能比这个低 | | spring-cloud | 2021.0.8 | 根据 spring-cloud-alibaba 版本选配 | | spring-boot | 2.6.13 | 根据 spring-cloud-alibaba 版本选配 | ### maven ```xml 4.0.0 com.zhao dynamicPool 0.0.1-SNAPSHOT dynamicPool dynamicPool org.springframework.boot spring-boot-starter-parent 2.6.13 1.8 UTF-8 UTF-8 2.6.13 2021.0.8 2021.0.6.2 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test com.alibaba fastjson 2.0.42 cn.hutool hutool-all 5.8.26 org.apache.commons commons-lang3 3.18.0 com.google.guava guava 32.0.1-android org.projectlombok lombok org.springframework.boot spring-boot-starter-actuator com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-bootstrap org.springframework.kafka spring-kafka dynamicPool org.apache.maven.plugins maven-compiler-plugin 3.8.1 1.8 1.8 UTF-8 org.springframework.boot spring-boot-maven-plugin ${spring-boot.version} com.zhao.dynamicPool.DynamicPoolApplication org.projectlombok lombok repackage repackage ``` # 一、nacos配置(默认测试配置,测试完后归位) ### bootstrap.yaml ```yaml spring: application: name: dynamic-pool-service # 应用名称,也是Nacos中的服务名 cloud: nacos: discovery: server-addr: 127.0.0.1:8848 # Nacos Config Server地址 # 配置中心配置 config: server-addr: 127.0.0.1:8848 # Nacos Config Server地址 file-extension: yaml # 配置文件扩展名(yaml, yml, properties等) refresh-enabled: true # 必须为true ``` ### 配置如下 > 核心线程数:1,最大线程数:2,队列最大容量:2 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/120a0b7aedd247e2a4c5b2be907be567.png) # 二、动态线程池 > spring 提供了 ThreadPoolTaskExecutor 线程池 > > 他可以通过编码实现动态的改变线程池的核心线程数、最大线程数、超时时间,但是不能动态的调整阻塞队列的队列长度 > > 因为 ThreadPoolTaskExecutor 使用的是 LinkedBlockingQueue 的阻塞队列 > > LinkedBlockingQueue 的 capacity 参数是 final 字段的,不可以修改 > > 所以本章的核心是实现拥有可变的 capacity 参数的阻塞队列 VariableLinkedBlockingQueue + @NacosConfigKeysListener 注解实现 nacos 参数监听,通过配置文件修改线程池 ## 可变阻塞队列:VariableLinkedBlockingQueue.java(不需要修改) > 该 VariableLinkedBlockingQueue 队列是 dromara 团队的代码,dromara 团队是参考 rabbitmq 团队的代码,并实现了 LinkedBlockingQueue 的方法 > > dromara 团队代码如下:[VariableLinkedBlockingQueue.java](https://gitee.com/dromara/dynamic-tp/blob/master/common/src/main/java/org/dromara/dynamictp/common/queue/VariableLinkedBlockingQueue.java) > > rabbitmq 团队的代码如下:[VariableLinkedBlockingQueue.java](https://github.com/rabbitmq/rabbitmq-java-client/blob/main/src/main/java/com/rabbitmq/client/impl/VariableLinkedBlockingQueue.java) ```java public class VariableLinkedBlockingQueue extends AbstractQueue implements BlockingQueue, java.io.Serializable { private static final long serialVersionUID = -6903933977591709194L; static class Node { E item; Node next; Node(E x) { item = x; } } private int capacity; private final AtomicInteger count = new AtomicInteger(); transient Node head; private transient Node last; private final ReentrantLock takeLock = new ReentrantLock(); private final Condition notEmpty = takeLock.newCondition(); private final ReentrantLock putLock = new ReentrantLock(); private final Condition notFull = putLock.newCondition(); private void signalNotEmpty() { final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { notEmpty.signal(); } finally { takeLock.unlock(); } } private void signalNotFull() { final ReentrantLock putLock = this.putLock; putLock.lock(); try { notFull.signal(); } finally { putLock.unlock(); } } private void enqueue(Node node) { // assert putLock.isHeldByCurrentThread(); // assert last.next == null; last = last.next = node; } private E dequeue() { // assert takeLock.isHeldByCurrentThread(); // assert head.item == null; Node h = head; Node first = h.next; h.next = h; // help GC head = first; E x = first.item; first.item = null; return x; } void fullyLock() { putLock.lock(); takeLock.lock(); } void fullyUnlock() { takeLock.unlock(); putLock.unlock(); } public VariableLinkedBlockingQueue() { this(Integer.MAX_VALUE); } public VariableLinkedBlockingQueue(int capacity) { if (capacity <= 0) { throw new IllegalArgumentException(); } this.capacity = capacity; last = head = new Node(null); } public VariableLinkedBlockingQueue(Collection c) { this(Integer.MAX_VALUE); final ReentrantLock putLock = this.putLock; putLock.lock(); // Never contended, but necessary for visibility try { int n = 0; for (E e : c) { if (e == null) { throw new NullPointerException(); } if (n == capacity) { throw new IllegalStateException("Queue full"); } enqueue(new Node(e)); ++n; } count.set(n); } finally { putLock.unlock(); } } @Override public int size() { return count.get(); } public void setCapacity(int capacity) { final int oldCapacity = this.capacity; this.capacity = capacity; final int size = count.get(); if (capacity > size && size >= oldCapacity) { signalNotFull(); } } @Override public int remainingCapacity() { return capacity - count.get(); } @Override public void put(E e) throws InterruptedException { if (e == null) { throw new NullPointerException(); } int c = -1; Node node = new Node(e); final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { while (count.get() >= capacity) { notFull.await(); } enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) { notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) { signalNotEmpty(); } } @Override public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { if (e == null) { throw new NullPointerException(); } long nanos = unit.toNanos(timeout); int c = -1; final ReentrantLock putLock = this.putLock; final AtomicInteger count = this.count; putLock.lockInterruptibly(); try { while (count.get() >= capacity) { if (nanos <= 0) { return false; } nanos = notFull.awaitNanos(nanos); } enqueue(new Node(e)); c = count.getAndIncrement(); if (c + 1 < capacity) { notFull.signal(); } } finally { putLock.unlock(); } if (c == 0) { signalNotEmpty(); } return true; } @Override public boolean offer(E e) { if (e == null) { throw new NullPointerException(); } final AtomicInteger count = this.count; if (count.get() >= capacity) { return false; } int c = -1; Node node = new Node(e); final ReentrantLock putLock = this.putLock; putLock.lock(); try { if (count.get() < capacity) { enqueue(node); c = count.getAndIncrement(); if (c + 1 < capacity) { notFull.signal(); } } } finally { putLock.unlock(); } if (c == 0) { signalNotEmpty(); } return c >= 0; } @Override public E take() throws InterruptedException { E x; int c = -1; final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { notEmpty.await(); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) { notEmpty.signal(); } } finally { takeLock.unlock(); } if (c >= capacity) { signalNotFull(); } return x; } @Override public E poll(long timeout, TimeUnit unit) throws InterruptedException { E x = null; int c = -1; long nanos = unit.toNanos(timeout); final AtomicInteger count = this.count; final ReentrantLock takeLock = this.takeLock; takeLock.lockInterruptibly(); try { while (count.get() == 0) { if (nanos <= 0) { return null; } nanos = notEmpty.awaitNanos(nanos); } x = dequeue(); c = count.getAndDecrement(); if (c > 1) { notEmpty.signal(); } } finally { takeLock.unlock(); } if (c >= capacity) { signalNotFull(); } return x; } @Override public E poll() { final AtomicInteger count = this.count; if (count.get() == 0) { return null; } E x = null; int c = -1; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { if (count.get() > 0) { x = dequeue(); c = count.getAndDecrement(); if (c > 1) { notEmpty.signal(); } } } finally { takeLock.unlock(); } if (c >= capacity) { signalNotFull(); } return x; } @Override public E peek() { if (count.get() == 0) { return null; } final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { Node first = head.next; if (first == null) { return null; } else { return first.item; } } finally { takeLock.unlock(); } } void unlink(Node p, Node trail) { // assert isFullyLocked(); // p.next is not changed, to allow iterators that are // traversing p to maintain their weak-consistency guarantee. p.item = null; trail.next = p.next; if (last == p) { last = trail; } if (count.getAndDecrement() >= capacity) { notFull.signal(); } } @Override public boolean remove(Object o) { if (o == null) { return false; } fullyLock(); try { for (Node trail = head, p = trail.next; p != null; trail = p, p = p.next) { if (o.equals(p.item)) { unlink(p, trail); return true; } } return false; } finally { fullyUnlock(); } } @Override public boolean contains(Object o) { if (o == null) { return false; } fullyLock(); try { for (Node p = head.next; p != null; p = p.next) { if (o.equals(p.item)) { return true; } } return false; } finally { fullyUnlock(); } } @Override public Object[] toArray() { fullyLock(); try { int size = count.get(); Object[] a = new Object[size]; int k = 0; for (Node p = head.next; p != null; p = p.next) { a[k++] = p.item; } return a; } finally { fullyUnlock(); } } @Override @SuppressWarnings("unchecked") public T[] toArray(T[] a) { fullyLock(); try { int size = count.get(); if (a.length < size) { a = (T[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size); } int k = 0; for (Node p = head.next; p != null; p = p.next) { a[k++] = (T) p.item; } if (a.length > k) { a[k] = null; } return a; } finally { fullyUnlock(); } } @Override public String toString() { fullyLock(); try { Node p = head.next; if (p == null) { return "[]"; } StringBuilder sb = new StringBuilder(); sb.append('['); for (;;) { E e = p.item; sb.append(e == this ? "(this Collection)" : e); p = p.next; if (p == null) { return sb.append(']').toString(); } sb.append(',').append(' '); } } finally { fullyUnlock(); } } @Override public void clear() { fullyLock(); try { for (Node p, h = head; (p = h.next) != null; h = p) { h.next = h; p.item = null; } head = last; // assert head.item == null && head.next == null; if (count.getAndSet(0) >= capacity) { notFull.signal(); } } finally { fullyUnlock(); } } @Override public int drainTo(Collection c) { return drainTo(c, Integer.MAX_VALUE); } @Override public int drainTo(Collection c, int maxElements) { if (c == null) { throw new NullPointerException(); } if (c == this) { throw new IllegalArgumentException(); } if (maxElements <= 0) { return 0; } boolean signalNotFull = false; final ReentrantLock takeLock = this.takeLock; takeLock.lock(); try { int n = Math.min(maxElements, count.get()); // count.get provides visibility to first n Nodes Node h = head; int i = 0; try { while (i < n) { Node p = h.next; c.add(p.item); p.item = null; h.next = h; h = p; ++i; } return n; } finally { // Restore invariants even if c.add() threw if (i > 0) { // assert h.item == null; head = h; signalNotFull = (count.getAndAdd(-i) >= capacity); } } } finally { takeLock.unlock(); if (signalNotFull) { signalNotFull(); } } } @Override public Iterator iterator() { return new Itr(); } private class Itr implements Iterator { private Node current; private Node lastRet; private E currentElement; Itr() { fullyLock(); try { current = head.next; if (current != null) { currentElement = current.item; } } finally { fullyUnlock(); } } @Override public boolean hasNext() { return current != null; } private Node nextNode(Node p) { for (;;) { Node s = p.next; if (s == p) { return head.next; } if (s == null || s.item != null) { return s; } p = s; } } @Override public E next() { fullyLock(); try { if (current == null) { throw new NoSuchElementException(); } E x = currentElement; lastRet = current; current = nextNode(current); currentElement = (current == null) ? null : current.item; return x; } finally { fullyUnlock(); } } @Override public void remove() { if (lastRet == null) { throw new IllegalStateException(); } fullyLock(); try { Node node = lastRet; lastRet = null; for (Node trail = head, p = trail.next; p != null; trail = p, p = p.next) { if (p == node) { unlink(p, trail); break; } } } finally { fullyUnlock(); } } } static final class LBQSpliterator implements Spliterator { static final int MAX_BATCH = 1 << 25; // max batch array size; final VariableLinkedBlockingQueue queue; Node current; // current node; null until initialized int batch; // batch size for splits boolean exhausted; // true when no more nodes long est; // size estimate LBQSpliterator(VariableLinkedBlockingQueue queue) { this.queue = queue; this.est = queue.size(); } @Override public long estimateSize() { return est; } @Override public Spliterator trySplit() { Node h; final VariableLinkedBlockingQueue q = this.queue; int b = batch; int n = (b <= 0) ? 1 : (b >= MAX_BATCH) ? MAX_BATCH : b + 1; if (!exhausted && ((h = current) != null || (h = q.head.next) != null) && h.next != null) { Object[] a = new Object[n]; int i = 0; Node p = current; q.fullyLock(); try { if (p != null || (p = q.head.next) != null) { do { if ((a[i] = p.item) != null) { ++i; } } while ((p = p.next) != null && i < n); } } finally { q.fullyUnlock(); } if ((current = p) == null) { est = 0L; exhausted = true; } else if ((est -= i) < 0L) { est = 0L; } if (i > 0) { batch = i; return Spliterators.spliterator(a, 0, i, Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.CONCURRENT); } } return null; } @Override public void forEachRemaining(Consumer action) { if (action == null) { throw new NullPointerException(); } final VariableLinkedBlockingQueue q = this.queue; if (!exhausted) { exhausted = true; Node p = current; do { E e = null; q.fullyLock(); try { if (p == null) { p = q.head.next; } while (p != null) { e = p.item; p = p.next; if (e != null) { break; } } } finally { q.fullyUnlock(); } if (e != null) { action.accept(e); } } while (p != null); } } @Override public boolean tryAdvance(Consumer action) { if (action == null) { throw new NullPointerException(); } final VariableLinkedBlockingQueue q = this.queue; if (!exhausted) { E e = null; q.fullyLock(); try { if (current == null) { current = q.head.next; } while (current != null) { e = current.item; current = current.next; if (e != null) { break; } } } finally { q.fullyUnlock(); } if (current == null) { exhausted = true; } if (e != null) { action.accept(e); return true; } } return false; } @Override public int characteristics() { return Spliterator.ORDERED | Spliterator.NONNULL | Spliterator.CONCURRENT; } } @Override public Spliterator spliterator() { return new LBQSpliterator(this); } private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { fullyLock(); try { // Write out any hidden stuff, plus capacity s.defaultWriteObject(); // Write out all elements in the proper order. for (Node p = head.next; p != null; p = p.next) { s.writeObject(p.item); } // Use trailing null as sentinel s.writeObject(null); } finally { fullyUnlock(); } } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // Read in capacity, and any hidden stuff s.defaultReadObject(); count.set(0); last = head = new Node(null); // Read in all elements and place in queue for (;;) { @SuppressWarnings("unchecked") E item = (E) s.readObject(); if (item == null) { break; } add(item); } } } ``` ## 线程池配置:DynamicThreadPoolConfig.java(需要根据自己业务把使用参数名进行修改即可(我用todo标记了)) > 该类重点有三个 > > 第一个是,实现 DynamicThreadPoolTaskExecutor 类用于继承 ThreadPoolTaskExecutor,重写 createQueue 方法,让该线程池使用容量可变的阻塞队列 > > 第二个是,使用 @NacosConfigKeysListener 注解监听配置中心的线程池参数,实现线程池的动态修改,这里注意要使用 spring-cloud-alibaba 2021.0.6.2 及以上的版本,说明如下[版本说明](https://nacos.io/en/blog/nacos-gvr7dx_awbbpb_mmufdmayp5dfozci/?spm=5238cd80.cff869d.0.0.237f7e849GXM35&source=blog#nacosconfiglistener%E6%B3%A8%E8%A7%A3%E7%94%A8%E6%B3%95%E4%BB%8B%E7%BB%8D) > > ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/72e9e12e0065492aa4bc23c90715a29d.png) > > 第三个是,使用 getThreadPoolStatus 方法,获取到线程池运行参数作为监控 ```java /** * 动态线程池 * * 加 todo 的 就是可以选择性修改的 */ @Configuration @RefreshScope // 支持配置热更新 @EnableAsync // 启用异步支持 @Slf4j public class DynamicThreadPoolConfig { /** * 核心参数 默认使用io密集型 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.core-size:0}") private int coreSize; /** * 最大参数 默认使用io密集型 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.max-size:0}") private int maxSize; /** * 队列容量 默认 1w 的队列 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.queue-capacity:10000}") private int queueCapacity; /** * 超时时间 默认 60s 超时 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.keep-alive-seconds:60}") private int keepAliveSeconds; /** * 动态线程池 * todo 根据业务情况自行修改 Bean 的值 */ @Bean("dynamicThreadPool") DynamicThreadPoolTaskExecutor dynamicThreadPool() { int cpuCores = Runtime.getRuntime().availableProcessors(); if (coreSize <= 0) { coreSize = cpuCores * 2; } if (maxSize <= 0) { maxSize = cpuCores * 4; } DynamicThreadPoolTaskExecutor executor = new DynamicThreadPoolTaskExecutor(); // 核心线程数(默认线程数) executor.setCorePoolSize(coreSize); // 最大线程数(队列满后扩容的最大值) executor.setMaxPoolSize(maxSize); // 线程空闲时间(秒) executor.setKeepAliveSeconds(keepAliveSeconds); // 队列容量(超过核心线程数时,任务进入队列) executor.setQueueCapacity(queueCapacity); // 线程名前缀 executor.setThreadNamePrefix("dynamic-thread-"); // 拒绝策略(此处使用报错执行) executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy()); // 初始化 executor.initialize(); return executor; } /** * 动态线程池 * todo 如果你改了 Bean 的值 记得改 Qualifier 中的值 */ @Autowired @Qualifier("dynamicThreadPool") @Lazy private ThreadPoolTaskExecutor dynamicThreadPool; /** * 公平读写锁 */ private final static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock(true); /** * 动态调整线程池参数 * * @param corePoolSize 核心线程数 * @param maxPoolSize 最大线程数 * @param keepAliveSeconds 超时时间(秒) * @param queueCapacity 队列容量 */ public void adjustThreadPool(Integer corePoolSize, Integer maxPoolSize, Integer keepAliveSeconds, Integer queueCapacity) { ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock(); try { writeLock.lock(); // 调整核心线程数 if (corePoolSize != null && corePoolSize != dynamicThreadPool.getCorePoolSize()) { dynamicThreadPool.setCorePoolSize(corePoolSize); } // 调整最大线程数 if (maxPoolSize != null && maxPoolSize != dynamicThreadPool.getMaxPoolSize()) { dynamicThreadPool.setMaxPoolSize(maxPoolSize); } // 调整超时时间 if (keepAliveSeconds != null && keepAliveSeconds != dynamicThreadPool.getKeepAliveSeconds()) { dynamicThreadPool.setKeepAliveSeconds(keepAliveSeconds); } // 调整队列容量(需重置队列) if (queueCapacity != null && queueCapacity != dynamicThreadPool.getQueueCapacity()) { VariableLinkedBlockingQueue queue = (VariableLinkedBlockingQueue) dynamicThreadPool.getThreadPoolExecutor().getQueue(); if (queueCapacity < queue.remainingCapacity() + queue.size()) { throw new UnsupportedOperationException("线程池 不支持缩容"); } // 需要自定义可调整队列容量 queue.setCapacity(queueCapacity); // 重置线程池容量参数(这里只是改参数,用于查询时与队列保持同步,并不能调整队列大小) dynamicThreadPool.setQueueCapacity(queueCapacity); } } finally { writeLock.unlock(); } } /** * 获取线程池状态 */ public ThreadPoolStatus getThreadPoolStatus() { ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock(); try { readLock.lock(); ThreadPoolExecutor threadPoolExecutor = dynamicThreadPool.getThreadPoolExecutor(); BlockingQueue queue = threadPoolExecutor.getQueue(); int useQueueCapacity = queue.size(); int remainingCapacity = queue.remainingCapacity(); return new ThreadPoolStatus() .setCorePoolSize(dynamicThreadPool.getCorePoolSize()) .setMaxPoolSize(dynamicThreadPool.getMaxPoolSize()) .setActiveThreads(dynamicThreadPool.getActiveCount()) .setKeepAliveSeconds(dynamicThreadPool.getKeepAliveSeconds()) .setQueueMaxCapacity(useQueueCapacity + remainingCapacity) .setQueueUseCapacity(useQueueCapacity) .setQueueRemainingCapacity(remainingCapacity); } finally { readLock.unlock(); } } /** * 监听配置文件变更 * todo 监听配置文件变更 */ @NacosConfigKeysListener(dataId = "dynamic-pool-service.yaml", group = "DEFAULT_GROUP", interestedKeyPrefixes = "dynamic.thread-pool.") private void onConfigChanged(ConfigChangeEvent changeEvent) { log.info("onConfigChanged 监听到配置变化:{}", JSONObject.toJSONString(changeEvent)); DynamicPoolConfig config = new DynamicPoolConfig(); try { Class aClass = config.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { declaredField.setAccessible(true); if (declaredField.isAnnotationPresent(ConfigChangeEventToBean.class)) { ConfigChangeEventToBean annotation = declaredField.getAnnotation(ConfigChangeEventToBean.class); String value = annotation.value(); ConfigChangeItem changeItem = changeEvent.getChangeItem(value); // 处理配置变更 if (changeItem != null && PropertyChangeType.MODIFIED.equals(changeItem.getType())){ declaredField.set(config, changeItem.getNewValue()); } } } log.info("onConfigChanged 配置转换结果:{}", config); this.adjustThreadPool( Optional.ofNullable(config.getCoreSize()).map(Integer::parseInt).orElse(null), Optional.ofNullable(config.getMaxSize()).map(Integer::parseInt).orElse(null), Optional.ofNullable(config.getKeepAliveSeconds()).map(Integer::parseInt).orElse(null), Optional.ofNullable(config.getQueueCapacity()).map(Integer::parseInt).orElse(null) ); } catch (Exception e) { log.error("onConfigChanged 配置转换异常", e); } } /** * 线程池状态 */ @Data @Accessors(chain = true) public static class ThreadPoolStatus { /** * 核心线程数 */ private int corePoolSize; /** * 最大线程数 */ private int maxPoolSize; /** * 线程使用数 */ private int activeThreads; /** * 线程空闲时间(秒) */ private int keepAliveSeconds; /** * 队列最大容量 */ private int queueMaxCapacity; /** * 队列使用容量 */ private int queueUseCapacity; /** * 队列剩余 */ private int queueRemainingCapacity; } /** * 用于解决配置转换问题 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) private @interface ConfigChangeEventToBean{ String value(); } /** * 动态线程池配置 * todo 需要自己改一下 注解的 value 属性 */ @Data @Accessors(chain = true) static class DynamicPoolConfig { /** * 核心线程数 */ @ConfigChangeEventToBean("dynamic.thread-pool.core-size") private String coreSize; /** * 最大线程数 */ @ConfigChangeEventToBean("dynamic.thread-pool.max-size") private String maxSize; /** * 队列容量 */ @ConfigChangeEventToBean("dynamic.thread-pool.queue-capacity") private String queueCapacity; /** * 超时时间(秒) */ @ConfigChangeEventToBean("dynamic.thread-pool.keep-alive-seconds") private String keepAliveSeconds; } /** * 自定义线程池 实现自定义可变队列 */ private static class DynamicThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { @Override @NonNull protected BlockingQueue createQueue(int queueCapacity) { return queueCapacity > 0 ? new VariableLinkedBlockingQueue<>(queueCapacity) : new VariableLinkedBlockingQueue<>(); } } } ``` ## 测试案例:PoolTestController.java ```java @RestController @Slf4j @RequestMapping("/poolTest") public class PoolTestController { @Autowired @Qualifier("dynamicThreadPool") private ThreadPoolTaskExecutor dynamicThreadPool; @Autowired private DynamicThreadPoolConfig dynamicThreadPoolConfig; /** * 添加任务 测试 就让他等着 看拒绝情况 */ @GetMapping("/addTask") public Results addTask() { dynamicThreadPool.submit(() -> { log.info("addTask 线程开始执行 {}", Thread.currentThread().getName()); while (true) { try { Thread.sleep(10000); } catch (InterruptedException e) { log.error("addTask 线程执行异常", e); } } }); return Results.success(); } /** * 获取参数 */ @GetMapping("/getParameter") public Results getParameter() { DynamicThreadPoolConfig.ThreadPoolStatus threadPoolStatus = dynamicThreadPoolConfig.getThreadPoolStatus(); return Results.success(threadPoolStatus); } } ``` ## 测试结果: ### 1、默认配置 > 根据默认配置的 核心线程数:1,最大线程数:2,队列长度:2 > > 那么一共可以执行 4 个任务 > > 按照理论,第五次调用 /poolTest/addTask 接口就会报错 > > 控制台会显示两个任务执行,一个任务被拒绝,我们试一下 > > 下面可以看见我们调用了五次接口,有一个报错 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/57e391e855ee4d81b0481db826f6272c.png) > 我们查一下线程池状态 > > 有两个任务交由两个线程执行,有两个任务在队列里面,所以第五次请求失败 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/30159caafadc48d4898fadf7f5d2eb2d.png) ### 2、修改配置 > 修改配置为 核心线程数:2,最大线程数:3,队列长度:3 > > 那么一共可以执行的任务数量为 6,修改为后看下线程池配置 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/12083b17a71a4440bc40ddf106b3892c.png) > 我们之前使用了 2 个线程 2 个队列 > > 按照理论,我们再调用 /poolTest/addTask 接口三次会报错 > > 我们会在控制台看见,有一个新线程开始任务,有一个新的报错,我们试一下 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/43c6f4b0c5f64210be5f9965c0312fa1.png) > 我们查线程状态时,队列里面也会多一个任务 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4c48bbde80574cd9a16b51e062b595b6.png) # 三、动态线程池+kafka自定义拒绝策略 > 这里就不讲动态线程池的部分了,主要将 kafka 自定义拒绝策略的实现 > > 因为这里也用到了动态线程池(有部分差异),所以得和上面二章节理论一起看 > > 但测试时我就不展示动态线程部分了,就使用默认的 核心线程数:1,最大线程数:2,最大队列容量:2 ## kafka 配置:application.yaml(也可以放到nacos里面) ```yaml spring: kafka: bootstrap-servers: localhost:9092 consumer: group-id: ${spring.application.name} enable-auto-commit: false # 关闭自动提交 auto-offset-reset: earliest key-deserializer: org.apache.kafka.common.serialization.StringDeserializer value-deserializer: org.apache.kafka.common.serialization.StringDeserializer producer: acks: -1 key-serializer: org.apache.kafka.common.serialization.StringSerializer value-serializer: org.apache.kafka.common.serialization.StringSerializer template: default-topic: test-topic listener: ack-mode: manual # 监听,手动提交 ``` ## 工具类:SpringBootBeanUtil.java > 主要用于下面 kafka 拒绝策略获取到配置的应用名称 ```java @Component @SuppressWarnings("unused") public class SpringBootBeanUtil implements ApplicationContextAware, EnvironmentAware { private static final Logger log = LoggerFactory.getLogger(SpringBootBeanUtil.class); private static ApplicationContext applicationContext; private static Environment environment; @Override public void setEnvironment(final Environment environment) { if (SpringBootBeanUtil.applicationContext == null) { SpringBootBeanUtil.environment = environment; } } @Override public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException { if (SpringBootBeanUtil.applicationContext == null) { SpringBootBeanUtil.applicationContext = applicationContext; } } /** * 获取applicationContext */ public static ApplicationContext getApplicationContext() { return applicationContext; } /** * 获取applicationContext */ public static Environment getEnvironment() { return environment; } /** * 通过name获取 Bean. */ public static Object getBean(String name) { return getApplicationContext().getBean(name); } /** * 通过class获取Bean. */ public static T getBean(Class clazz) { return getApplicationContext().getBean(clazz); } /** * 通过name,以及Clazz返回指定的Bean */ public static T getBean(String name, Class clazz) { return getApplicationContext().getBean(name, clazz); } } ``` ## 启动器:DynamicPoolApplication.java > 优先把 SpringBootBeanUtil 注入到容器里面,不然后面初始化 kafka 的拒绝策略会报错(因为我们需要从配置里面获取应用名(spring.application.name)) ```java @SpringBootApplication public class DynamicPoolApplication { public static void main(String[] args) { SpringApplication app = new SpringApplication(DynamicPoolApplication.class); app.addInitializers(new PriorityRegistrationClass()); app.run(args); } /** * 优先注册 */ static class PriorityRegistrationClass implements ApplicationContextInitializer { @Override public void initialize(GenericApplicationContext applicationContext) { // 你可以注册多个Bean applicationContext.registerBean(SpringBootBeanUtil.class); } } } ``` ## kafka 生产者:KafkaProducerService.java ```java /** * kafka 生产者 */ @Service @Slf4j public class KafkaProducerService { @Autowired private KafkaTemplate kafkaTemplate; /** * 同步发送消息 * @param topic 主体 * @param msg 消息 */ public void sendMessageSync(String topic, Object msg) throws ExecutionException, InterruptedException { try { kafkaTemplate.send(topic, msg).get(); log.info("sendMessageSync 同步消息发送 成功 topic={} msg={}", topic, msg); } catch (Exception e) { log.error("sendMessageSync 同步消息发送 失败", e); throw e; } } /** * 异步发送消息 * @param topic 主体 * @param msg 消息 */ public void sendMessageAsync(String topic, Object msg) { kafkaTemplate.send(topic, msg).addCallback(success -> { log.info("sendMessageAsync 异步消息发送 成功 topic={} msg={}", topic, msg); }, failure -> { log.error("sendMessageAsync 异步消息发送 失败", failure); }); } } ``` ## kafka 消费者:KafkaConsumerService.java ```java /** * kafka 消费者 */ @Service @Slf4j public class KafkaConsumerService { /** * 监听Kafka消息 手动提交 * todo 测试topic 需修改 * * @param msg 消息内容 */ @KafkaListener(topics = "test-reject", groupId = "dynamic-pool-service") public void consume(String msg, Acknowledgment ack) { log.info("kafka 开始消费 msg: {}", msg); try { // 这里是通过反射 进行任务执行 KafkaRejectPolicy.RejectedTaskMessage.taskStart(msg); ack.acknowledge(); } catch (Exception e) { log.info("kafka 消费异常", e); } } } ``` ## kafka 拒绝策略 > 这里面重要的就是 KafkaTaskInfoRunnable 这个抽象类 > > 我们的任务都需要继承这个类来实现 kafka 异步任务来做到任务不丢失 > > 逻辑是这样的(后面测试案例会讲到,这里只是给大家理一下逻辑) > > 我们先实现一个 A 类继承 KafkaTaskInfoRunnable 这个抽象类(A.java extends KafkaTaskInfoRunnable.java) > > KafkaTaskInfoRunnable 实现了 Runnable 接口,所以 A 类需要实现 run 方法,这样就可以通过线程池进行异步任务 > > 我们线程池满了之后,任务被拒绝了,会给 kafka 发送一个消息(json字符串) > > json 里面包括了实现任务的类(A.java)(Class)、入参(具体值)、入参类型(Class)等(这三个比较关键) > > kafka 监听后会从消息(json)里面获取到任务的类(Class)、入参(具体值)、入参类型(Class) > > 然后通过反射同步的调用 A.java 的 run 方法,达到消峰(因为kafka会一个任务执行完再接着执行)和任务不丢失 > > 由于要使用反射获取 A.java 的具体数据,所以 A.java 是不能使用匿名内部类的 > > ps:发送警告我没有处理,有兴趣大家可以实现以下,这个也简单,如果警告频繁,说明 QPS 过大,我们就需要自己调整线程池或者就是加机器了 ```java /** * Kafka拒绝策略 - 将被拒绝的任务发送到Kafka队列 */ @Slf4j public class KafkaRejectPolicy implements RejectedExecutionHandler { private static final AtomicLong rejectCount = new AtomicLong(0); private final String topic; /** * 应用名称(用于消息标识) */ private final String applicationName; /** * 构造函数 * * @param topic 目标主题 */ public KafkaRejectPolicy(String topic) { this.topic = topic; // 这里得先在启动器配置优先加载 SpringBootBeanUtil 的 bean 对象 String applicationName = SpringBootBeanUtil.getEnvironment().getProperty("spring.application.name"); if (applicationName == null || applicationName.isEmpty()) { throw new IllegalArgumentException("配置项 spring.application.name 不能为空"); } this.applicationName = applicationName; } /** * 获取ip 地址 */ private static String getIpAddress() throws UnknownHostException { InetAddress localHost = InetAddress.getLocalHost(); return localHost.getHostAddress(); } @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { try { // 检查任务类型 if (!(r instanceof KafkaTaskInfoRunnable)) { // 使用 submit 方法时会走到 AbstractExecutorService 的 submit 方法,会封装一层 FutureTask // 如果是 FutureTask,尝试提取内部的 Runnable if (r instanceof FutureTask) { try { // 通过反射获取 FutureTask 内部的 callable Field callableField = FutureTask.class.getDeclaredField("callable"); callableField.setAccessible(true); Object callable = callableField.get(r); // 如果是 RunnableAdapter(Executor.callable 返回的内部类) if (callable != null && callable.getClass().getName().contains("RunnableAdapter")) { Field taskField = callable.getClass().getDeclaredField("task"); taskField.setAccessible(true); Object originalTask = taskField.get(callable); if (originalTask instanceof KafkaTaskInfoRunnable) { r = (Runnable) originalTask; } } } catch (Exception e) { log.error("提取 FutureTask 内部任务失败", e); } } } // 最终检查 if (!(r instanceof KafkaTaskInfoRunnable)) { throw new IllegalArgumentException("该任务无法由 Kafka 执行,必须使用 KafkaTaskInfoRunnable 类型"); } KafkaTaskInfoRunnable KafkaTaskInfoRunnable = (KafkaTaskInfoRunnable) r; long count = rejectCount.incrementAndGet(); // 获取线程池状态信息 String poolInfo = getThreadPoolInfo(executor); // 构建拒绝任务的消息 RejectedTaskMessage taskMessage = new RejectedTaskMessage() .setTaskId(generateTaskId()) .setApplicationName(applicationName) .setIpAddress(getIpAddress()) .setThreadPoolInfo(poolInfo) .setRejectTime(System.currentTimeMillis()) .setRejectCount(count) .setTaskDataJson(KafkaTaskInfoRunnable.getTaskDataJson()) .setTaskClass(KafkaTaskInfoRunnable.getTaskClass()) .setTaskInputParameterClass(KafkaTaskInfoRunnable.getTaskInputParameterClass()); // 发送到Kafka String msg = JSONObject.toJSONString(taskMessage); // 使用 kafka 生产者发送消息 KafkaProducerService kafkaProducer = SpringBootBeanUtil.getBean(KafkaProducerService.class); // 同步发送并处理回调 kafkaProducer.sendMessageSync(topic, msg); log.info("拒绝任务已发送到Kafka队列。拒绝次数: {}, 线程池状态: {}", count, poolInfo); } catch (Exception e) { log.error(" kafka 拒绝策略 发送消息异常", e); // 降级处理(让当前线程自己处理) if (!executor.isShutdown()) { r.run(); } } } /** * 获取线程池状态信息 *

* 包含以下关键指标: * - 活跃线程数:当前正在执行任务的线程数量 * - 线程池大小:线程池中当前的线程数量(包括空闲线程) * - 核心线程数:线程池保持的最小线程数量 * - 最大线程数:线程池允许创建的最大线程数量 * - 队列大小:任务队列中等待执行的任务数量 * - 队列容量:任务队列的最大容量 */ private String getThreadPoolInfo(ThreadPoolExecutor executor) { BlockingQueue queue = executor.getQueue(); return String.format( "活跃线程: %d, 池中线程: %d, 核心线程: %d, 最大线程: %d, 队列大小/队列容量: %d/%d", // 正在执行任务的线程数 executor.getActiveCount(), // 线程池中当前的线程总数 executor.getPoolSize(), // 核心线程数 executor.getCorePoolSize(), // 最大线程数 executor.getMaximumPoolSize(), // 队列中等待的任务数 queue.size(), // 队列总容量 queue.size() + queue.remainingCapacity() ); } /** * 生成任务ID */ private String generateTaskId() { return UUID.randomUUID().toString(); } /** * 发送告警 */ private void sendAlert(RejectedTaskMessage message) { // 实现告警逻辑(邮件、短信、钉钉等) log.error("任务处理失败!任务ID: {}", message.getTaskId()); } /** * 任务信息接口(不能使用匿名内部类) * 实现此接口的任务可以传递更多信息到拒绝策略 */ public static abstract class KafkaTaskInfoRunnable implements Runnable { private final T taskData; public KafkaTaskInfoRunnable(T taskData) { // 运行时检查 if (this.getClass().isAnonymousClass()) { throw new IllegalStateException("TaskInfoRunnable 类 不能使用匿名内部类"); } this.taskData = taskData; } protected String getTaskDataJson() { return JSONObject.toJSONString(taskData); } protected T getTaskData() { return this.taskData; } /** * 任务执行类名(全类名) */ protected Class> getTaskClass() { return (Class>) this.getClass(); } /** * 任务入参类名(全类名) */ protected Class getTaskInputParameterClass() { return (Class) taskData.getClass(); } } /** * 拒绝任务消息实体 */ @Data @Accessors(chain = true) public static class RejectedTaskMessage implements Serializable { private static final long serialVersionUID = 1L; /** * 任务ID(唯一ID) */ private String taskId; /** * 应用名称 */ private String applicationName; /** * ip地址 */ private String ipAddress; /** * 线程池状态信息 */ private String threadPoolInfo; /** * 拒绝时间戳 */ private long rejectTime; /** * 拒绝计数 */ private long rejectCount; /** * 任务执行类名(全类名) */ private Class taskClass; /** * 任务入参类名(全类名) */ private Class taskInputParameterClass; /** * 任务入参数据(json) */ private String taskDataJson; public static void taskStart(String msgJson) throws Exception { JSON.config(JSONReader.Feature.SupportClassForName); KafkaRejectPolicy.RejectedTaskMessage message = JSONObject.parseObject( msgJson, KafkaRejectPolicy.RejectedTaskMessage.class ); String dataJson = message.getTaskDataJson(); Class inputParameterClass = message.getTaskInputParameterClass(); Class> taskClass = (Class>) message.getTaskClass(); Object o = JSONObject.parseObject(dataJson, inputParameterClass); Constructor> constructor = taskClass.getDeclaredConstructor(inputParameterClass); constructor.setAccessible(true); KafkaTaskInfoRunnable infoRunnable = constructor.newInstance(o); infoRunnable.run(); } } } ``` ## 动态线程池:DynamicKafkaRejectThreadPoolConfig.java > 跟上面的动态线程池类似,但有细微差距 > > 最主要的是 ThreadPoolKafkaTaskExecutor 类 > > 该类也继承了 ThreadPoolTaskExecutor 类,用于可以动态改变线程池的参数(核心、最大线程、队列容量(也是通过VariableLinkedBlockingQueue 实现的)) > > 这里比较重要的就是重写线程池的 execute 和 submit 方法,只允许继承了 KafkaTaskInfoRunnable 的任务入队 ```java /** * 动态线程池 (kafka 拒绝策略版本) *

* 加 todo 的 就是可以选择性修改的 */ @Configuration @RefreshScope // 支持配置热更新 @EnableAsync // 启用异步支持 @Slf4j @Lazy public class DynamicKafkaRejectThreadPoolConfig { /** * 核心参数 默认使用io密集型 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.core-size:0}") private int coreSize; /** * 最大参数 默认使用io密集型 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.max-size:0}") private int maxSize; /** * 队列容量 默认 1w 的队列 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.queue-capacity:10000}") private int queueCapacity; /** * 超时时间 默认 60s 超时 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.keep-alive-seconds:60}") private int keepAliveSeconds; /** * 超时时间 默认 60s 超时 * todo 根据业务情况自行修改 */ @Value("${dynamic.thread-pool.kafka-reject-policy-topic:test-reject}") private String kafkaRejectPolicyTopic; /** * 动态线程池 * todo 根据业务情况自行修改 Bean 的值 */ @Bean("dynamicKafkaRejectThreadPool") ThreadPoolKafkaTaskExecutor dynamicKafkaRejectThreadPool() { int cpuCores = Runtime.getRuntime().availableProcessors(); if (coreSize <= 0) { coreSize = cpuCores * 2; } if (maxSize <= 0) { maxSize = cpuCores * 4; } ThreadPoolKafkaTaskExecutor executor = new ThreadPoolKafkaTaskExecutor(); // 核心线程数(默认线程数) executor.setCorePoolSize(coreSize); // 最大线程数(队列满后扩容的最大值) executor.setMaxPoolSize(maxSize); // 线程空闲时间(秒) executor.setKeepAliveSeconds(keepAliveSeconds); // 队列容量(超过核心线程数时,任务进入队列) executor.setQueueCapacity(queueCapacity); // 线程名前缀 executor.setThreadNamePrefix("dynamic-thread-"); // 拒绝策略(此处使用报错执行) executor.setRejectedExecutionHandler(new KafkaRejectPolicy(kafkaRejectPolicyTopic)); // 初始化 executor.initialize(); return executor; } /** * 动态线程池 * todo 如果你改了 Bean 的值 记得改 Qualifier 中的值 */ @Autowired @Qualifier("dynamicKafkaRejectThreadPool") @Lazy private ThreadPoolTaskExecutor dynamicKafkaRejectThreadPool; /** * 公平读写锁 */ private final static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock(true); /** * 动态调整线程池参数 * * @param corePoolSize 核心线程数 * @param maxPoolSize 最大线程数 * @param keepAliveSeconds 超时时间(秒) * @param queueCapacity 队列容量 */ public void adjustThreadPool(Integer corePoolSize, Integer maxPoolSize, Integer keepAliveSeconds, Integer queueCapacity) { ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock(); try { writeLock.lock(); // 调整核心线程数 if (corePoolSize != null && corePoolSize != dynamicKafkaRejectThreadPool.getCorePoolSize()) { dynamicKafkaRejectThreadPool.setCorePoolSize(corePoolSize); } // 调整最大线程数 if (maxPoolSize != null && maxPoolSize != dynamicKafkaRejectThreadPool.getMaxPoolSize()) { dynamicKafkaRejectThreadPool.setMaxPoolSize(maxPoolSize); } // 调整超时时间 if (keepAliveSeconds != null && keepAliveSeconds != dynamicKafkaRejectThreadPool.getKeepAliveSeconds()) { dynamicKafkaRejectThreadPool.setKeepAliveSeconds(keepAliveSeconds); } // 调整队列容量(需重置队列) if (queueCapacity != null && queueCapacity != dynamicKafkaRejectThreadPool.getQueueCapacity()) { VariableLinkedBlockingQueue queue = (VariableLinkedBlockingQueue) dynamicKafkaRejectThreadPool.getThreadPoolExecutor().getQueue(); if (queueCapacity < queue.remainingCapacity() + queue.size()) { throw new UnsupportedOperationException("线程池 不支持缩容"); } // 需要自定义可调整队列容量 queue.setCapacity(queueCapacity); // 重置线程池容量参数(这里只是改参数,用于查询时与队列保持同步,并不能调整队列大小) dynamicKafkaRejectThreadPool.setQueueCapacity(queueCapacity); } } finally { writeLock.unlock(); } } /** * 获取线程池状态 */ public ThreadPoolStatus getThreadPoolStatus() { ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock(); try { readLock.lock(); ThreadPoolExecutor threadPoolExecutor = dynamicKafkaRejectThreadPool.getThreadPoolExecutor(); BlockingQueue queue = threadPoolExecutor.getQueue(); int useQueueCapacity = queue.size(); int remainingCapacity = queue.remainingCapacity(); return new ThreadPoolStatus() .setCorePoolSize(dynamicKafkaRejectThreadPool.getCorePoolSize()) .setMaxPoolSize(dynamicKafkaRejectThreadPool.getMaxPoolSize()) .setActiveThreads(dynamicKafkaRejectThreadPool.getActiveCount()) .setKeepAliveSeconds(dynamicKafkaRejectThreadPool.getKeepAliveSeconds()) .setQueueMaxCapacity(useQueueCapacity + remainingCapacity) .setQueueUseCapacity(useQueueCapacity) .setQueueRemainingCapacity(remainingCapacity); } finally { readLock.unlock(); } } /** * 监听配置文件变更 * todo 监听配置文件变更 */ @NacosConfigKeysListener(dataId = "dynamic-pool-service.yaml", group = "DEFAULT_GROUP", interestedKeyPrefixes = "dynamic.thread-pool.") private void onConfigChanged(ConfigChangeEvent changeEvent) { log.info("onConfigChanged 监听到配置变化:{}", JSONObject.toJSONString(changeEvent)); DynamicPoolConfig config = new DynamicPoolConfig(); try { Class aClass = config.getClass(); Field[] declaredFields = aClass.getDeclaredFields(); for (Field declaredField : declaredFields) { declaredField.setAccessible(true); if (declaredField.isAnnotationPresent(ConfigChangeEventToBean.class)) { ConfigChangeEventToBean annotation = declaredField.getAnnotation(ConfigChangeEventToBean.class); String value = annotation.value(); ConfigChangeItem changeItem = changeEvent.getChangeItem(value); // 处理配置变更 if (changeItem != null && PropertyChangeType.MODIFIED.equals(changeItem.getType())) { declaredField.set(config, changeItem.getNewValue()); } } } log.info("onConfigChanged 配置转换结果:{}", config); this.adjustThreadPool( Optional.ofNullable(config.getCoreSize()).map(Integer::parseInt).orElse(null), Optional.ofNullable(config.getMaxSize()).map(Integer::parseInt).orElse(null), Optional.ofNullable(config.getKeepAliveSeconds()).map(Integer::parseInt).orElse(null), Optional.ofNullable(config.getQueueCapacity()).map(Integer::parseInt).orElse(null) ); } catch (Exception e) { log.error("onConfigChanged 配置转换异常", e); } } /** * 线程池状态 */ @Data @Accessors(chain = true) public static class ThreadPoolStatus { /** * 核心线程数 */ private int corePoolSize; /** * 最大线程数 */ private int maxPoolSize; /** * 线程使用数 */ private int activeThreads; /** * 线程空闲时间(秒) */ private int keepAliveSeconds; /** * 队列最大容量 */ private int queueMaxCapacity; /** * 队列使用容量 */ private int queueUseCapacity; /** * 队列剩余 */ private int queueRemainingCapacity; } /** * 用于解决配置转换问题 */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD}) private @interface ConfigChangeEventToBean { String value(); } /** * 动态线程池配置 * todo 需要自己改一下 注解的 value 属性 */ @Data @Accessors(chain = true) static class DynamicPoolConfig { /** * 核心线程数 */ @ConfigChangeEventToBean("dynamic.thread-pool.core-size") private String coreSize; /** * 最大线程数 */ @ConfigChangeEventToBean("dynamic.thread-pool.max-size") private String maxSize; /** * 队列容量 */ @ConfigChangeEventToBean("dynamic.thread-pool.queue-capacity") private String queueCapacity; /** * 超时时间(秒) */ @ConfigChangeEventToBean("dynamic.thread-pool.keep-alive-seconds") private String keepAliveSeconds; } /** * 自定义线程池 实现自定义可变队列 */ public static class ThreadPoolKafkaTaskExecutor extends ThreadPoolTaskExecutor { public > void execute(T task) { super.execute(task); } public > Future submit(T task) { return super.submit(task); } @Override @NonNull protected BlockingQueue createQueue(int queueCapacity) { return queueCapacity > 0 ? new VariableLinkedBlockingQueue<>(queueCapacity) : new VariableLinkedBlockingQueue<>(); } } } ``` ## 任务执行:UserModifyNameHandler.java > 这个方法就是用于执行异步任务(不能是匿名内部类,不然反射获取不到对应的类就执行 kafka 的任务了) > > User.java 就是有两个成员变量 name 和 age 用于测试,这里就不展示了,可以看我 gitee 中的代码(链接再最上面的前言部分) ```java @Slf4j public class UserModifyNameHandler extends KafkaRejectPolicy.KafkaTaskInfoRunnable{ public UserModifyNameHandler(User taskData) { super(taskData); } @Override public void run() { User taskData = super.getTaskData(); taskData.setName(taskData.getName() + "--修改了名字"); // 睡两秒钟模仿耗时 try { Thread.sleep(2_000); } catch (InterruptedException ignored) { } log.info("用户修改名字 任务开始执行 {}", taskData); } } ``` ## 测试案例:KafkaPoolTestController.java > 这个测试案例里面,我们只需要调用异常 /kafkaPoolTest/addTask 方法 > > 会执行 10 条任务,我们再任务 UserModifyNameHandler 里面睡了两秒钟, > > 所以我们先设置的线程池参数(核心线程数:1,最大线程数:2,最大队列容量:2),只会执行 4 个任务 > > 其他的就会被拒绝掉,然后放进 kafka 的队列里面,由 kafka 的消费者消费(KafkaConsumerService) > > 得到的效果就是线程池执行了 4 条任务(异步), kafka 执行了 6 条任务(同步) ```java @RestController @Slf4j @RequestMapping("/kafkaPoolTest") public class KafkaPoolTestController { @Autowired @Qualifier("dynamicKafkaRejectThreadPool") private DynamicKafkaRejectThreadPoolConfig.ThreadPoolKafkaTaskExecutor dynamicThreadPool; @Autowired private DynamicThreadPoolConfig dynamicThreadPoolConfig; /** * 记录生成了多少用户 */ private static final AtomicLong userCount = new AtomicLong(0); /** * 添加任务 测试 就让他等着 看拒绝情况 */ @GetMapping("/addTask") public Results addTask() { // 一次性生成十个数据 List users = mockUsers(); // 线程池参数 核心线程:1 最大线程:2 队列容量:2 // 那么一次调用只能有 4 个任务执行,其他的都会被拒绝 // 那么拒绝的任务都进入了 kafka 的队列,由 KafkaConsumerService 处理 users.forEach(user -> { Future submit = dynamicThreadPool.submit(new UserModifyNameHandler(user)); }); return Results.success(); } /** * 获取参数 */ @GetMapping("/getParameter") public Results getParameter() { DynamicThreadPoolConfig.ThreadPoolStatus threadPoolStatus = dynamicThreadPoolConfig.getThreadPoolStatus(); return Results.success(threadPoolStatus); } /** * 模拟生成用户 */ public synchronized static List mockUsers() { List<@Nullable User> list = Lists.newArrayList(); for (int i = 0; i < 10; i++) { long count = userCount.incrementAndGet(); User user = new User().setName("用户:" + count).setAge(18); list.add(user); } return list; } } ``` ## 测试结果: > 由于打印的消息有点多,所以我就截取一部分进行展示 > > 分别是 > > 线程池异步进行了 4 条任务,用户 4、1、3、2 > > kafka 同步执行了 6 条任务,用户 5、6、7、8、9、10(这里有可能也不是这个顺序执行的,这要看 kafka 入队顺序了,因为本来线程池就是异步的执行任务,kafka 为什么我要写成同步呢?因为本来进入 kafka 队列就是因为服务器资源紧张,线程池不够用了,再异步执行的话,跟线程池设置超多的最大线程数或者最大队列数有什么区别呢,这样会导致 OOM 的,所以这里使用同步,只让一个线程执行任务,就会减少服务器的压力,从而做到消峰+任务不丢失) ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/3028b42ddc8045e188eae5c63f8d4a69.png)