mirror of https://github.com/longtai-cn/hippo4j
parent
e12b34c2e2
commit
da0c9318c9
@ -0,0 +1,33 @@
|
||||
HELP.md
|
||||
target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
build/
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.dynamic-threadpool</groupId>
|
||||
<artifactId>parent</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>common</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>common</name>
|
||||
<description>Demo project for Spring Boot</description>
|
||||
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
@ -0,0 +1,56 @@
|
||||
package io.dynamict.hreadpool.common.toolkit;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* MD5 Util.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 17:55
|
||||
*/
|
||||
public class Md5Util {
|
||||
|
||||
private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
private static final ThreadLocal<MessageDigest> MESSAGE_DIGEST_LOCAL = ThreadLocal.withInitial(() -> {
|
||||
try {
|
||||
return MessageDigest.getInstance("MD5");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
public static String md5Hex(byte[] bytes) throws NoSuchAlgorithmException {
|
||||
try {
|
||||
MessageDigest messageDigest = MESSAGE_DIGEST_LOCAL.get();
|
||||
if (messageDigest != null) {
|
||||
return encodeHexString(messageDigest.digest(bytes));
|
||||
}
|
||||
throw new NoSuchAlgorithmException("MessageDigest get MD5 instance error");
|
||||
} finally {
|
||||
MESSAGE_DIGEST_LOCAL.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public static String md5Hex(String value, String encode) {
|
||||
try {
|
||||
return md5Hex(value.getBytes(encode));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String encodeHexString(byte[] bytes) {
|
||||
int l = bytes.length;
|
||||
|
||||
char[] out = new char[l << 1];
|
||||
|
||||
for (int i = 0, j = 0; i < l; i++) {
|
||||
out[j++] = DIGITS_LOWER[(0xF0 & bytes[i]) >>> 4];
|
||||
out[j++] = DIGITS_LOWER[0x0F & bytes[i]];
|
||||
}
|
||||
|
||||
return new String(out);
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.dynamic.threadpool.starter.adapter;
|
||||
|
||||
import io.dynamic.threadpool.starter.core.ThreadPoolDynamicRefresh;
|
||||
|
||||
/**
|
||||
* ConfigAdapter.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 21:29
|
||||
*/
|
||||
public class ConfigAdapter {
|
||||
|
||||
/**
|
||||
* 回调修改线程池配置
|
||||
*
|
||||
* @param tpId
|
||||
* @param config
|
||||
*/
|
||||
public void callbackConfig(String tpId, String config) {
|
||||
ThreadPoolDynamicRefresh.refreshDynamicPool(tpId, config);
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package io.dynamic.threadpool.starter.adapter;
|
||||
|
||||
import cn.hutool.core.thread.ThreadFactoryBuilder;
|
||||
import io.dynamic.threadpool.starter.operation.ThreadPoolOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 动态线程池配置适配器
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 20:17
|
||||
*/
|
||||
public class ThreadPoolConfigAdapter extends ConfigAdapter {
|
||||
|
||||
@Autowired
|
||||
private ThreadPoolOperation threadPoolOperation;
|
||||
|
||||
private ExecutorService executorService = new ThreadPoolExecutor(
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
TimeUnit.MILLISECONDS,
|
||||
new ArrayBlockingQueue(1),
|
||||
new ThreadFactoryBuilder().setNamePrefix("threadPool-config").build(),
|
||||
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||
|
||||
public void subscribeConfig(List<String> tpIds) {
|
||||
tpIds.forEach(each -> threadPoolOperation.subscribeConfig(each, executorService, (tpId, config) -> callbackConfig(tpId, config)));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package io.dynamic.threadpool.starter.config;
|
||||
|
||||
import io.dynamic.threadpool.starter.adapter.ThreadPoolConfigAdapter;
|
||||
import io.dynamic.threadpool.starter.core.ConfigService;
|
||||
import io.dynamic.threadpool.starter.core.ThreadPoolConfigService;
|
||||
import io.dynamic.threadpool.starter.core.ThreadPoolRunListener;
|
||||
import io.dynamic.threadpool.starter.operation.ThreadPoolOperation;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 动态线程池自动装配类
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 09:20
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
@AllArgsConstructor
|
||||
@EnableConfigurationProperties(DynamicThreadPoolProperties.class)
|
||||
@ConditionalOnProperty(prefix = DynamicThreadPoolProperties.PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
|
||||
public class DynamicThreadPoolAutoConfiguration {
|
||||
|
||||
private final DynamicThreadPoolProperties properties;
|
||||
|
||||
@Bean
|
||||
public ConfigService configService() {
|
||||
return new ThreadPoolConfigService();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ThreadPoolRunListener threadPoolRunListener() {
|
||||
return new ThreadPoolRunListener();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ThreadPoolConfigAdapter threadPoolConfigAdapter() {
|
||||
return new ThreadPoolConfigAdapter();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ThreadPoolOperation threadPoolOperation() {
|
||||
return new ThreadPoolOperation();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package io.dynamic.threadpool.starter.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* 动态线程池配置
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 09:14
|
||||
*/
|
||||
@Slf4j
|
||||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties(prefix = DynamicThreadPoolProperties.PREFIX)
|
||||
public class DynamicThreadPoolProperties {
|
||||
|
||||
public static final String PREFIX = "spring.threadpool.dynamic";
|
||||
|
||||
/**
|
||||
* 命名空间
|
||||
*/
|
||||
private String namespace;
|
||||
|
||||
/**
|
||||
* 项目 Id
|
||||
*/
|
||||
private String itemId;
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package io.dynamic.threadpool.starter.core;
|
||||
|
||||
import io.dynamic.threadpool.starter.listener.Listener;
|
||||
|
||||
/**
|
||||
* 配置服务
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/21 21:49
|
||||
*/
|
||||
public interface ConfigService {
|
||||
|
||||
/**
|
||||
* 添加监听器, 如果服务端发生变更, 客户端会使用监听器进行回调
|
||||
*
|
||||
* @param tpId
|
||||
* @param listener
|
||||
*/
|
||||
void addListener(String tpId, Listener listener);
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.dynamic.threadpool.starter.core;
|
||||
|
||||
import io.dynamic.threadpool.starter.listener.ClientWorker;
|
||||
import io.dynamic.threadpool.starter.listener.Listener;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 线程池配置服务
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/21 21:50
|
||||
*/
|
||||
public class ThreadPoolConfigService implements ConfigService {
|
||||
|
||||
private final ClientWorker clientWorker;
|
||||
|
||||
public ThreadPoolConfigService() {
|
||||
clientWorker = new ClientWorker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(String tpId, Listener listener) {
|
||||
clientWorker.addTenantListeners(tpId, Arrays.asList(listener));
|
||||
}
|
||||
}
|
@ -1,10 +1,162 @@
|
||||
package io.dynamic.threadpool.starter.listener;
|
||||
|
||||
import io.dynamic.threadpool.starter.core.CacheData;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 客户端监听
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/20 18:34
|
||||
*/
|
||||
@Slf4j
|
||||
public class ClientWorker {
|
||||
|
||||
private double currentLongingTaskCount = 0;
|
||||
|
||||
private final ScheduledExecutorService executor;
|
||||
|
||||
private final ScheduledExecutorService executorService;
|
||||
|
||||
private final ConcurrentHashMap<String, CacheData> cacheMap = new ConcurrentHashMap(16);
|
||||
|
||||
@SuppressWarnings("all")
|
||||
public ClientWorker() {
|
||||
this.executor = Executors.newScheduledThreadPool(1, r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("io.dynamic.threadPool.client.Worker.executor");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
int threadSize = Runtime.getRuntime().availableProcessors();
|
||||
this.executorService = Executors.newScheduledThreadPool(threadSize, r -> {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("io.dynamic.threadPool.client.Worker.longPolling.executor");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
this.executor.scheduleWithFixedDelay(() -> {
|
||||
try {
|
||||
checkConfigInfo();
|
||||
} catch (Throwable e) {
|
||||
log.error("[sub-check] rotate check error", e);
|
||||
}
|
||||
}, 1L, 10L, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查配置信息
|
||||
*/
|
||||
public void checkConfigInfo() {
|
||||
int listenerSize = cacheMap.size();
|
||||
double perTaskConfigSize = 3000D;
|
||||
int longingTaskCount = (int) Math.ceil(listenerSize / perTaskConfigSize);
|
||||
|
||||
if (longingTaskCount > currentLongingTaskCount) {
|
||||
for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
|
||||
executorService.execute(new LongPollingRunnable(i));
|
||||
}
|
||||
currentLongingTaskCount = longingTaskCount;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 长轮训任务
|
||||
*/
|
||||
class LongPollingRunnable implements Runnable {
|
||||
|
||||
private final int taskId;
|
||||
|
||||
public LongPollingRunnable(int taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
List<CacheData> cacheDataList = new ArrayList();
|
||||
|
||||
List<String> changedTpIds = checkUpdateTpIds(cacheDataList);
|
||||
if (!CollectionUtils.isEmpty(cacheDataList)) {
|
||||
log.info("[dynamic threadPool] tpIds changed :: {}", changedTpIds);
|
||||
}
|
||||
|
||||
for (String each : changedTpIds) {
|
||||
String[] keys = each.split(",");
|
||||
String namespace = keys[0];
|
||||
String itemId = keys[1];
|
||||
String tpId = keys[2];
|
||||
|
||||
try {
|
||||
String content = getServerConfig(namespace, itemId, tpId, 3000L);
|
||||
CacheData cacheData = cacheMap.get(tpId);
|
||||
cacheData.setContent(content);
|
||||
cacheDataList.add(cacheData);
|
||||
log.info("[data-received] namespace :: {}, itemId :: {}, tpId :: {}, md5 :: {}, content :: {}",
|
||||
namespace, itemId, tpId, cacheData.getMd5(), content);
|
||||
} catch (Exception ex) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
for (CacheData each : cacheDataList) {
|
||||
each.checkListenerMd5();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查修改的线程池 ID
|
||||
*
|
||||
* @param cacheDataList
|
||||
* @return
|
||||
*/
|
||||
public List<String> checkUpdateTpIds(List<CacheData> cacheDataList) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getServerConfig(String namespace, String itemId, String tpId, long readTimeout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* CacheData 添加 Listener
|
||||
*
|
||||
* @param tpId
|
||||
* @param listeners
|
||||
*/
|
||||
public void addTenantListeners(String tpId, List<? extends Listener> listeners) {
|
||||
CacheData cacheData = addCacheDataIfAbsent(tpId);
|
||||
for (Listener listener : listeners) {
|
||||
cacheData.addListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CacheData 不存在则添加
|
||||
*
|
||||
* @param tpId
|
||||
* @return
|
||||
*/
|
||||
public CacheData addCacheDataIfAbsent(String tpId) {
|
||||
CacheData cacheData = cacheMap.get(tpId);
|
||||
if (cacheData != null) {
|
||||
return cacheData;
|
||||
}
|
||||
|
||||
cacheData = new CacheData(tpId);
|
||||
CacheData lastCacheData = cacheMap.putIfAbsent(tpId, cacheData);
|
||||
|
||||
return lastCacheData;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
package io.dynamic.threadpool.starter.listener;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* 监听器
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 20:20
|
||||
*/
|
||||
public interface Listener {
|
||||
|
||||
/**
|
||||
* 获取执行器
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Executor getExecutor();
|
||||
|
||||
/**
|
||||
* 接受配置信息
|
||||
*
|
||||
* @param configInfo
|
||||
*/
|
||||
void receiveConfigInfo(String configInfo);
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package io.dynamic.threadpool.starter.operation;
|
||||
|
||||
import io.dynamic.threadpool.starter.core.ConfigService;
|
||||
import io.dynamic.threadpool.starter.listener.Listener;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* ThreadPoolOperation.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 20:25
|
||||
*/
|
||||
public class ThreadPoolOperation {
|
||||
|
||||
@Autowired
|
||||
private ConfigService configService;
|
||||
|
||||
public Listener subscribeConfig(String tpId, Executor executor, ThreadPoolSubscribeCallback threadPoolSubscribeCallback) {
|
||||
Listener configListener = new Listener() {
|
||||
@Override
|
||||
public void receiveConfigInfo(String config) {
|
||||
threadPoolSubscribeCallback.callback(tpId, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
}
|
||||
};
|
||||
|
||||
configService.addListener(tpId, configListener);
|
||||
|
||||
return configListener;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.dynamic.threadpool.starter.operation;
|
||||
|
||||
/**
|
||||
* ThreadPoolSubscribeCallback.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 20:26
|
||||
*/
|
||||
public interface ThreadPoolSubscribeCallback {
|
||||
|
||||
/**
|
||||
* 回调函数
|
||||
*
|
||||
* @param tpId
|
||||
* @param config
|
||||
*/
|
||||
void callback(String tpId, String config);
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package io.dynamic.threadpool.starter.wrap;
|
||||
|
||||
import io.dynamic.threadpool.starter.listener.Listener;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
/**
|
||||
* 监听包装
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 17:47
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class ManagerListenerWrap {
|
||||
|
||||
final Listener listener;
|
||||
|
||||
String lastCallMd5;
|
||||
|
||||
public ManagerListenerWrap(String md5, Listener listener) {
|
||||
this.lastCallMd5 = md5;
|
||||
this.listener = listener;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
|
@ -0,0 +1,5 @@
|
||||
spring:
|
||||
threadpool:
|
||||
dynamic:
|
||||
namespace: common
|
||||
itemId: message-center
|
Loading…
Reference in new issue