mirror of https://github.com/longtai-cn/hippo4j
parent
3791c6e7de
commit
ded7622277
@ -0,0 +1,28 @@
|
||||
package io.dynamict.hreadpool.common.executor;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
* Executor Factory.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:35
|
||||
*/
|
||||
public class ExecutorFactory {
|
||||
|
||||
|
||||
public static final class Managed {
|
||||
|
||||
private static final String DEFAULT_NAMESPACE = "dynamic.thread-pool";
|
||||
|
||||
private static final ThreadPoolManager THREAD_POOL_MANAGER = ThreadPoolManager.getInstance();
|
||||
|
||||
public static ScheduledExecutorService newSingleScheduledExecutorService(String group, ThreadFactory threadFactory) {
|
||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1, threadFactory);
|
||||
THREAD_POOL_MANAGER.register(DEFAULT_NAMESPACE, group, executorService);
|
||||
return executorService;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package io.dynamict.hreadpool.common.executor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
/**
|
||||
* Thread Pool Manager.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:36
|
||||
*/
|
||||
public class ThreadPoolManager {
|
||||
|
||||
private Map<String, Map<String, Set<ExecutorService>>> resourcesManager;
|
||||
|
||||
private Map<String, Object> lockers = new ConcurrentHashMap(8);
|
||||
|
||||
private static final ThreadPoolManager INSTANCE = new ThreadPoolManager();
|
||||
|
||||
public static ThreadPoolManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void register(String namespace, String group, ExecutorService executor) {
|
||||
if (!resourcesManager.containsKey(namespace)) {
|
||||
synchronized (this) {
|
||||
lockers.put(namespace, new Object());
|
||||
}
|
||||
}
|
||||
final Object monitor = lockers.get(namespace);
|
||||
synchronized (monitor) {
|
||||
Map<String, Set<ExecutorService>> map = resourcesManager.get(namespace);
|
||||
if (map == null) {
|
||||
map = new HashMap(8);
|
||||
map.put(group, new HashSet());
|
||||
map.get(group).add(executor);
|
||||
resourcesManager.put(namespace, map);
|
||||
return;
|
||||
}
|
||||
if (!map.containsKey(group)) {
|
||||
map.put(group, new HashSet());
|
||||
}
|
||||
map.get(group).add(executor);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
package io.dynamict.hreadpool.common.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 远程调用通用参数
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 21:08
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
public class GlobalRemotePoolInfo implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 5447003335557127308L;
|
||||
|
||||
/**
|
||||
* 命名空间
|
||||
*/
|
||||
private String namespace;
|
||||
|
||||
/**
|
||||
* 项目 ID
|
||||
*/
|
||||
private String itemId;
|
||||
|
||||
/**
|
||||
* 线程池标识
|
||||
*/
|
||||
private String tpId;
|
||||
|
||||
/**
|
||||
* 核心线程数
|
||||
*/
|
||||
private Integer coreSize;
|
||||
|
||||
/**
|
||||
* 最大线程数
|
||||
*/
|
||||
private Integer maxSize;
|
||||
|
||||
/**
|
||||
* 队列类型
|
||||
*/
|
||||
private Integer queueType;
|
||||
|
||||
/**
|
||||
* 队列长度
|
||||
*/
|
||||
private Integer capacity;
|
||||
|
||||
/**
|
||||
* 线程存活时长
|
||||
*/
|
||||
private Integer keepAliveTime;
|
||||
|
||||
/**
|
||||
* 是否告警
|
||||
*/
|
||||
private Integer isAlarm;
|
||||
|
||||
/**
|
||||
* 容量告警
|
||||
*/
|
||||
private Integer capacityAlarm;
|
||||
|
||||
/**
|
||||
* 活跃度告警
|
||||
*/
|
||||
private Integer livenessAlarm;
|
||||
|
||||
/**
|
||||
* MD5
|
||||
*/
|
||||
private String md5;
|
||||
|
||||
/**
|
||||
* 内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package io.dynamict.hreadpool.common.web.base;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 全局返回对象
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/3/19 16:12
|
||||
*/
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
public class Result<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -4408341719434417427L;
|
||||
|
||||
public static final String SUCCESS_CODE = "0";
|
||||
|
||||
private String code;
|
||||
|
||||
private String message;
|
||||
|
||||
private T data;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return SUCCESS_CODE.equals(code);
|
||||
}
|
||||
|
||||
public boolean isFail() {
|
||||
return !isSuccess();
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package io.dynamict.hreadpool.common.web.base;
|
||||
|
||||
import io.dynamict.hreadpool.common.web.exception.ErrorCode;
|
||||
import io.dynamict.hreadpool.common.web.exception.ServiceException;
|
||||
|
||||
/**
|
||||
* Result 工具类
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/3/19 16:12
|
||||
*/
|
||||
public final class Results {
|
||||
|
||||
public static Result<Void> success() {
|
||||
return new Result<Void>()
|
||||
.setCode(Result.SUCCESS_CODE);
|
||||
}
|
||||
|
||||
public static <T> Result<T> success(T data) {
|
||||
return new Result<T>()
|
||||
.setCode(Result.SUCCESS_CODE)
|
||||
.setData(data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> failure(ServiceException serviceException) {
|
||||
return new Result<T>().setCode(ErrorCode.SERVICE_ERROR.getCode())
|
||||
.setMessage(serviceException.getMessage());
|
||||
}
|
||||
|
||||
public static <T> Result<T> failure(String code, String message) {
|
||||
return new Result<T>()
|
||||
.setCode(code)
|
||||
.setMessage(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package io.dynamict.hreadpool.common.web.exception;
|
||||
|
||||
/**
|
||||
* 异常码
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/3/19 16:07
|
||||
*/
|
||||
public enum ErrorCode {
|
||||
|
||||
UNKNOWN_ERROR {
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "未知错误";
|
||||
}
|
||||
},
|
||||
|
||||
VALIDATION_ERROR {
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "2";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "参数错误";
|
||||
}
|
||||
},
|
||||
|
||||
SERVICE_ERROR {
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "3";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return "服务异常";
|
||||
}
|
||||
};
|
||||
|
||||
public abstract String getCode();
|
||||
|
||||
public abstract String getMessage();
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package io.dynamict.hreadpool.common.web.exception;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 业务系统业务逻辑相关异常
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/3/19 16:14
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ServiceException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = -8667394300356618037L;
|
||||
|
||||
private String detail;
|
||||
|
||||
public ServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ServiceException(String message, String detail) {
|
||||
super(message);
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
public ServiceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.detail = cause.getMessage();
|
||||
}
|
||||
|
||||
public ServiceException(String message, String detail, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.detail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ServiceException{" +
|
||||
"message='" + getMessage() + "'," +
|
||||
"detail='" + getDetail() + "'" +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package io.dynamic.threadpool.starter.http;
|
||||
|
||||
import io.dynamic.threadpool.starter.config.DynamicThreadPoolProperties;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Server Http Agent.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 20:50
|
||||
*/
|
||||
public class ServerHttpAgent implements HttpAgent {
|
||||
|
||||
private final DynamicThreadPoolProperties dynamicThreadPoolProperties;
|
||||
|
||||
public ServerHttpAgent(DynamicThreadPoolProperties properties) {
|
||||
this.dynamicThreadPoolProperties = properties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String httpGet(String path, Map<String, String> headers, Map<String, String> paramValues, String encoding, long readTimeoutMs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String httpPost(String path, Map<String, String> headers, Map<String, String> paramValues, String encoding, long readTimeoutMs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String httpDelete(String path, Map<String, String> headers, Map<String, String> paramValues, String encoding, long readTimeoutMs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameSpace() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEncode() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package io.dynamic.threadpool.starter.http;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Server List Manager.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 20:42
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServerListManager {
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
package io.dynamic.threadpool.starter.listener;
|
||||
|
||||
/**
|
||||
* 长轮询执行
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/20 18:37
|
||||
*/
|
||||
public class LongPollingRunnable implements Runnable {
|
||||
|
||||
private final int taskId;
|
||||
|
||||
public LongPollingRunnable(Integer taskId) {
|
||||
this.taskId = taskId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package io.dynamic.threadpool.starter.remote;
|
||||
|
||||
import io.dynamic.threadpool.starter.config.ApplicationContextHolder;
|
||||
import io.dynamic.threadpool.starter.config.DynamicThreadPoolProperties;
|
||||
import io.dynamic.threadpool.starter.toolkit.HttpClientUtil;
|
||||
import io.dynamict.hreadpool.common.web.base.Result;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Server Http Agent.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 20:50
|
||||
*/
|
||||
public class ServerHttpAgent implements HttpAgent {
|
||||
|
||||
private final DynamicThreadPoolProperties dynamicThreadPoolProperties;
|
||||
|
||||
private final ServerListManager serverListManager;
|
||||
|
||||
private HttpClientUtil httpClientUtil = ApplicationContextHolder.getBean(HttpClientUtil.class);
|
||||
|
||||
public ServerHttpAgent(DynamicThreadPoolProperties properties) {
|
||||
this.dynamicThreadPoolProperties = properties;
|
||||
this.serverListManager = new ServerListManager(dynamicThreadPoolProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result httpGet(String path, Map<String, String> headers, Map<String, String> paramValues, long readTimeoutMs) {
|
||||
return httpClientUtil.restApiGetByThreadPool(buildUrl(path), headers, paramValues, readTimeoutMs, Result.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result httpPost(String path, Map<String, String> headers, Map<String, String> paramValues, long readTimeoutMs) {
|
||||
return httpClientUtil.restApiPostByThreadPool(buildUrl(path), headers, paramValues, readTimeoutMs, Result.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result httpDelete(String path, Map<String, String> headers, Map<String, String> paramValues, long readTimeoutMs) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getNameSpace() {
|
||||
return dynamicThreadPoolProperties.getNamespace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEncode() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private String buildUrl(String path) {
|
||||
return serverListManager.getCurrentServerAddr() + path;
|
||||
}
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
package io.dynamic.threadpool.starter.remote;
|
||||
|
||||
import io.dynamic.threadpool.starter.config.DynamicThreadPoolProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Server List Manager.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 20:42
|
||||
*/
|
||||
@Slf4j
|
||||
public class ServerListManager {
|
||||
|
||||
private static final String HTTPS = "https://";
|
||||
|
||||
private static final String HTTP = "http://";
|
||||
|
||||
private String serverAddrsStr;
|
||||
|
||||
volatile List<String> serverUrls = new ArrayList();
|
||||
|
||||
private volatile String currentServerAddr;
|
||||
|
||||
private Iterator<String> iterator;
|
||||
|
||||
private final DynamicThreadPoolProperties properties;
|
||||
|
||||
public ServerListManager(DynamicThreadPoolProperties dynamicThreadPoolProperties) {
|
||||
this.properties = dynamicThreadPoolProperties;
|
||||
serverAddrsStr = properties.getServerAddr();
|
||||
|
||||
if (!StringUtils.isEmpty(serverAddrsStr)) {
|
||||
List<String> serverAddrs = new ArrayList();
|
||||
String[] serverAddrsArr = this.serverAddrsStr.split(",");
|
||||
|
||||
for (String serverAddr : serverAddrsArr) {
|
||||
if (serverAddr.startsWith(HTTPS) || serverAddr.startsWith(HTTP)) {
|
||||
// TODO 固定写,后面优化
|
||||
currentServerAddr = serverAddr;
|
||||
serverAddrs.add(serverAddr);
|
||||
}
|
||||
}
|
||||
|
||||
this.serverUrls = serverAddrs;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCurrentServerAddr() {
|
||||
if (StringUtils.isEmpty(currentServerAddr)) {
|
||||
iterator = iterator();
|
||||
currentServerAddr = iterator.next();
|
||||
}
|
||||
return currentServerAddr;
|
||||
}
|
||||
|
||||
Iterator<String> iterator() {
|
||||
if (serverUrls.isEmpty()) {
|
||||
log.error("[iterator-serverlist] No server address defined!");
|
||||
}
|
||||
return new ServerAddressIterator(serverUrls);
|
||||
}
|
||||
|
||||
private static class ServerAddressIterator implements Iterator<String> {
|
||||
|
||||
final List<RandomizedServerAddress> sorted;
|
||||
|
||||
final Iterator<RandomizedServerAddress> iter;
|
||||
|
||||
public ServerAddressIterator(List<String> source) {
|
||||
sorted = new ArrayList<RandomizedServerAddress>();
|
||||
for (String address : source) {
|
||||
sorted.add(new RandomizedServerAddress(address));
|
||||
}
|
||||
Collections.sort(sorted);
|
||||
iter = sorted.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return null;
|
||||
}
|
||||
|
||||
static class RandomizedServerAddress implements Comparable<RandomizedServerAddress> {
|
||||
|
||||
static Random random = new Random();
|
||||
|
||||
String serverIp;
|
||||
|
||||
int priority = 0;
|
||||
|
||||
int seed;
|
||||
|
||||
public RandomizedServerAddress(String ip) {
|
||||
try {
|
||||
this.serverIp = ip;
|
||||
/*
|
||||
change random scope from 32 to Integer.MAX_VALUE to fix load balance issue
|
||||
*/
|
||||
this.seed = random.nextInt(Integer.MAX_VALUE);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RandomizedServerAddress other) {
|
||||
if (this.priority != other.priority) {
|
||||
return other.priority - this.priority;
|
||||
} else {
|
||||
return other.seed - this.seed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.dynamic.threadpool.starter.config.CommonConfiguration, \
|
||||
io.dynamic.threadpool.starter.config.OkHttpClientConfig
|
||||
io.dynamic.threadpool.starter.config.OkHttpClientConfig,\
|
||||
io.dynamic.threadpool.starter.config.DynamicThreadPoolAutoConfiguration
|
@ -0,0 +1,12 @@
|
||||
package io.dynamic.threadpool.server.event;
|
||||
|
||||
import io.dynamic.threadpool.server.notify.Event;
|
||||
|
||||
/**
|
||||
* Local Data Change Event.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:13
|
||||
*/
|
||||
public class LocalDataChangeEvent extends Event {
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.dynamic.threadpool.server.notify;
|
||||
|
||||
import cn.hutool.core.collection.ConcurrentHashSet;
|
||||
import io.dynamic.threadpool.server.notify.listener.Subscriber;
|
||||
|
||||
/**
|
||||
* The default event publisher implementation.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:06
|
||||
*/
|
||||
public class DefaultPublisher extends Thread implements EventPublisher {
|
||||
|
||||
protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet();
|
||||
|
||||
@Override
|
||||
public void addSubscriber(Subscriber subscriber) {
|
||||
subscribers.add(subscriber);
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package io.dynamic.threadpool.server.notify;
|
||||
|
||||
import cn.hutool.core.collection.ConcurrentHashSet;
|
||||
import io.dynamic.threadpool.server.notify.listener.Subscriber;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* Default Share Publisher.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:05
|
||||
*/
|
||||
public class DefaultSharePublisher {
|
||||
|
||||
private final Map<Class<? extends SlowEvent>, Set<Subscriber>> subMappings = new ConcurrentHashMap();
|
||||
|
||||
protected final ConcurrentHashSet<Subscriber> subscribers = new ConcurrentHashSet();
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
|
||||
public void addSubscriber(Subscriber subscriber, Class<? extends Event> subscribeType) {
|
||||
Class<? extends SlowEvent> subSlowEventType = (Class<? extends SlowEvent>) subscribeType;
|
||||
subscribers.add(subscriber);
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
Set<Subscriber> sets = subMappings.get(subSlowEventType);
|
||||
if (sets == null) {
|
||||
Set<Subscriber> newSet = new ConcurrentHashSet<Subscriber>();
|
||||
newSet.add(subscriber);
|
||||
subMappings.put(subSlowEventType, newSet);
|
||||
return;
|
||||
}
|
||||
sets.add(subscriber);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package io.dynamic.threadpool.server.notify;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* An abstract class for event.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:59
|
||||
*/
|
||||
public abstract class Event implements Serializable {
|
||||
|
||||
private static final AtomicLong SEQUENCE = new AtomicLong(0);
|
||||
|
||||
private final long sequence = SEQUENCE.getAndIncrement();
|
||||
|
||||
/**
|
||||
* Event sequence number, which can be used to handle the sequence of events.
|
||||
*
|
||||
* @return sequence num, It's best to make sure it's monotone.
|
||||
*/
|
||||
public long sequence() {
|
||||
return sequence;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package io.dynamic.threadpool.server.notify;
|
||||
|
||||
import io.dynamic.threadpool.server.notify.listener.Subscriber;
|
||||
|
||||
/**
|
||||
* Event publisher.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:58
|
||||
*/
|
||||
public interface EventPublisher {
|
||||
|
||||
void addSubscriber(Subscriber subscriber);
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package io.dynamic.threadpool.server.notify;
|
||||
|
||||
import io.dynamic.threadpool.server.notify.listener.SmartSubscriber;
|
||||
import io.dynamic.threadpool.server.notify.listener.Subscriber;
|
||||
import io.dynamic.threadpool.server.toolkit.ClassUtil;
|
||||
import io.dynamic.threadpool.server.toolkit.MapUtil;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Unified Event Notify Center.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:58
|
||||
*/
|
||||
public class NotifyCenter {
|
||||
|
||||
private static final NotifyCenter INSTANCE = new NotifyCenter();
|
||||
|
||||
public static int ringBufferSize = 16384;
|
||||
|
||||
private DefaultSharePublisher sharePublisher;
|
||||
|
||||
private static BiFunction<Class<? extends Event>, Integer, EventPublisher> publisherFactory = null;
|
||||
|
||||
private final Map<String, EventPublisher> publisherMap = new ConcurrentHashMap(16);
|
||||
|
||||
public static <T> void registerSubscriber(final Subscriber consumer) {
|
||||
if (consumer instanceof SmartSubscriber) {
|
||||
for (Class<? extends Event> subscribeType : ((SmartSubscriber) consumer).subscribeTypes()) {
|
||||
if (ClassUtil.isAssignableFrom(SlowEvent.class, subscribeType)) {
|
||||
INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);
|
||||
} else {
|
||||
addSubscriber(consumer, subscribeType);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final Class<? extends Event> subscribeType = consumer.subscribeType();
|
||||
if (ClassUtil.isAssignableFrom(SlowEvent.class, subscribeType)) {
|
||||
INSTANCE.sharePublisher.addSubscriber(consumer, subscribeType);
|
||||
return;
|
||||
}
|
||||
|
||||
addSubscriber(consumer, subscribeType);
|
||||
}
|
||||
|
||||
private static void addSubscriber(final Subscriber consumer, Class<? extends Event> subscribeType) {
|
||||
|
||||
final String topic = ClassUtil.getCanonicalName(subscribeType);
|
||||
synchronized (NotifyCenter.class) {
|
||||
// MapUtils.computeIfAbsent is a unsafe method.
|
||||
MapUtil.computeIfAbsent(INSTANCE.publisherMap, topic, publisherFactory, subscribeType, ringBufferSize);
|
||||
}
|
||||
EventPublisher publisher = INSTANCE.publisherMap.get(topic);
|
||||
publisher.addSubscriber(consumer);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.dynamic.threadpool.server.notify;
|
||||
|
||||
/**
|
||||
* Slow Event.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:05
|
||||
*/
|
||||
public abstract class SlowEvent extends Event {
|
||||
@Override
|
||||
public long sequence() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package io.dynamic.threadpool.server.notify.listener;
|
||||
|
||||
import io.dynamic.threadpool.server.notify.Event;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Subscribers to multiple events can be listened to.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:02
|
||||
*/
|
||||
public abstract class SmartSubscriber extends Subscriber {
|
||||
|
||||
public abstract List<Class<? extends Event>> subscribeTypes();
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package io.dynamic.threadpool.server.notify.listener;
|
||||
|
||||
import io.dynamic.threadpool.server.notify.Event;
|
||||
|
||||
/**
|
||||
* An abstract subscriber class for subscriber interface.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:02
|
||||
*/
|
||||
public abstract class Subscriber<T extends Event> {
|
||||
|
||||
/**
|
||||
* Event callback.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
public abstract void onEvent(T event);
|
||||
|
||||
/**
|
||||
* Type of this subscriber's subscription.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract Class<? extends Event> subscribeType();
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package io.dynamic.threadpool.server.service;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Config Servlet Inner.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 23:13
|
||||
*/
|
||||
@Service
|
||||
public class ConfigServletInner {
|
||||
|
||||
@Autowired
|
||||
private LongPollingService longPollingService;
|
||||
|
||||
public String doPollingConfig(HttpServletRequest request, HttpServletResponse response, Map<String, String> clientMd5Map, int probeRequestSize) {
|
||||
if (LongPollingService.isSupportLongPolling(request)) {
|
||||
longPollingService.addLongPollingClient(request, response, clientMd5Map, probeRequestSize);
|
||||
return HttpServletResponse.SC_OK + "";
|
||||
}
|
||||
return HttpServletResponse.SC_OK + "";
|
||||
}
|
||||
}
|
@ -0,0 +1,184 @@
|
||||
package io.dynamic.threadpool.server.service;
|
||||
|
||||
import io.dynamic.threadpool.server.event.LocalDataChangeEvent;
|
||||
import io.dynamic.threadpool.server.notify.Event;
|
||||
import io.dynamic.threadpool.server.notify.NotifyCenter;
|
||||
import io.dynamic.threadpool.server.notify.listener.Subscriber;
|
||||
import io.dynamic.threadpool.server.toolkit.ConfigExecutor;
|
||||
import io.dynamic.threadpool.server.toolkit.RequestUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 长轮询服务
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/22 23:14
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class LongPollingService {
|
||||
|
||||
private static final int FIXED_POLLING_INTERVAL_MS = 10000;
|
||||
|
||||
public static final String LONG_POLLING_HEADER = "Long-Pulling-Timeout";
|
||||
|
||||
public static final String LONG_POLLING_NO_HANG_UP_HEADER = "Long-Pulling-Timeout-No-Hangup";
|
||||
|
||||
public static final String CLIENT_APPNAME_HEADER = "Client-AppName";
|
||||
|
||||
private Map<String, Long> retainIps = new ConcurrentHashMap();
|
||||
|
||||
public LongPollingService() {
|
||||
allSubs = new ConcurrentLinkedQueue();
|
||||
|
||||
ConfigExecutor.scheduleLongPolling(new StatTask(), 0L, 10L, TimeUnit.SECONDS);
|
||||
|
||||
NotifyCenter.registerSubscriber(new Subscriber() {
|
||||
|
||||
@Override
|
||||
public void onEvent(Event event) {
|
||||
if (isFixedPolling()) {
|
||||
// Ignore.
|
||||
} else {
|
||||
if (event instanceof LocalDataChangeEvent) {
|
||||
LocalDataChangeEvent evt = (LocalDataChangeEvent) event;
|
||||
// ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Event> subscribeType() {
|
||||
return LocalDataChangeEvent.class;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class StatTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
log.info("[long-pulling] client count " + allSubs.size());
|
||||
}
|
||||
}
|
||||
|
||||
class DataChangeTask implements Runnable {
|
||||
|
||||
final String groupKey;
|
||||
|
||||
final long changeTime = System.currentTimeMillis();
|
||||
|
||||
final boolean isBeta;
|
||||
|
||||
final List<String> betaIps;
|
||||
|
||||
DataChangeTask(String groupKey, boolean isBeta, List<String> betaIps) {
|
||||
this.groupKey = groupKey;
|
||||
this.isBeta = isBeta;
|
||||
this.betaIps = betaIps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean isSupportLongPolling(HttpServletRequest req) {
|
||||
return null != req.getHeader(LONG_POLLING_HEADER);
|
||||
}
|
||||
|
||||
private static boolean isFixedPolling() {
|
||||
return SwitchService.getSwitchBoolean(SwitchService.FIXED_POLLING, false);
|
||||
}
|
||||
|
||||
private static int getFixedPollingInterval() {
|
||||
return SwitchService.getSwitchInteger(SwitchService.FIXED_POLLING_INTERVAL, FIXED_POLLING_INTERVAL_MS);
|
||||
}
|
||||
|
||||
public void addLongPollingClient(HttpServletRequest req, HttpServletResponse rsp, Map<String, String> clientMd5Map,
|
||||
int probeRequestSize) {
|
||||
String str = req.getHeader(LONG_POLLING_HEADER);
|
||||
String noHangUpFlag = req.getHeader(LONG_POLLING_NO_HANG_UP_HEADER);
|
||||
String appName = req.getHeader(CLIENT_APPNAME_HEADER);
|
||||
int delayTime = SwitchService.getSwitchInteger(SwitchService.FIXED_DELAY_TIME, 500);
|
||||
|
||||
long timeout = Math.max(10000, Long.parseLong(str) - delayTime);
|
||||
if (isFixedPolling()) {
|
||||
timeout = Math.max(10000, getFixedPollingInterval());
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
String ip = RequestUtil.getRemoteIp(req);
|
||||
|
||||
final AsyncContext asyncContext = req.startAsync();
|
||||
asyncContext.setTimeout(0L);
|
||||
|
||||
ConfigExecutor.executeLongPolling(new ClientLongPolling(asyncContext, clientMd5Map, ip, probeRequestSize, timeout, appName));
|
||||
}
|
||||
|
||||
final Queue<ClientLongPolling> allSubs;
|
||||
|
||||
class ClientLongPolling implements Runnable {
|
||||
|
||||
final AsyncContext asyncContext;
|
||||
|
||||
final Map<String, String> clientMd5Map;
|
||||
|
||||
final long createTime;
|
||||
|
||||
final String ip;
|
||||
|
||||
final String appName;
|
||||
|
||||
final int probeRequestSize;
|
||||
|
||||
final long timeoutTime;
|
||||
|
||||
Future<?> asyncTimeoutFuture;
|
||||
|
||||
public ClientLongPolling(AsyncContext asyncContext, Map<String, String> clientMd5Map, String ip, int probeRequestSize, long timeout, String appName) {
|
||||
this.asyncContext = asyncContext;
|
||||
this.clientMd5Map = clientMd5Map;
|
||||
this.ip = ip;
|
||||
this.probeRequestSize = probeRequestSize;
|
||||
this.timeoutTime = timeout;
|
||||
this.appName = appName;
|
||||
this.createTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
asyncTimeoutFuture = ConfigExecutor.scheduleLongPolling(() -> {
|
||||
getRetainIps().put(ClientLongPolling.this.ip, System.currentTimeMillis());
|
||||
allSubs.remove(ClientLongPolling.this);
|
||||
|
||||
if (isFixedPolling()) {
|
||||
|
||||
}
|
||||
|
||||
}, timeoutTime, TimeUnit.MILLISECONDS);
|
||||
|
||||
allSubs.add(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Map<String, Long> getRetainIps() {
|
||||
return retainIps;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package io.dynamic.threadpool.server.service;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* SwitchService.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:23
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SwitchService {
|
||||
|
||||
public static final String FIXED_DELAY_TIME = "fixedDelayTime";
|
||||
|
||||
public static final String FIXED_POLLING = "isFixedPolling";
|
||||
|
||||
public static final String FIXED_POLLING_INTERVAL = "fixedPollingInertval";
|
||||
|
||||
private static volatile Map<String, String> switches = new HashMap(16);
|
||||
|
||||
public static int getSwitchInteger(String key, int defaultValue) {
|
||||
int rtn = defaultValue;
|
||||
try {
|
||||
String status = switches.get(key);
|
||||
rtn = status != null ? Integer.parseInt(status) : defaultValue;
|
||||
} catch (Exception e) {
|
||||
rtn = defaultValue;
|
||||
log.error("corrupt switch value {}, {}", key, switches.get(key));
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
|
||||
public static boolean getSwitchBoolean(String key, boolean defaultValue) {
|
||||
boolean rtn = defaultValue;
|
||||
try {
|
||||
String value = switches.get(key);
|
||||
rtn = value != null ? Boolean.parseBoolean(value) : defaultValue;
|
||||
} catch (Exception e) {
|
||||
rtn = defaultValue;
|
||||
log.error("corrupt switch value {}, {}", key, switches.get(key));
|
||||
}
|
||||
return rtn;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.dynamic.threadpool.server.toolkit;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class Util.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:03
|
||||
*/
|
||||
public class ClassUtil {
|
||||
|
||||
public static boolean isAssignableFrom(Class clazz, Class cls) {
|
||||
Objects.requireNonNull(cls, "cls");
|
||||
return clazz.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
public static String getCanonicalName(Class cls) {
|
||||
Objects.requireNonNull(cls, "cls");
|
||||
return cls.getCanonicalName();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package io.dynamic.threadpool.server.toolkit;
|
||||
|
||||
import cn.hutool.core.thread.ThreadFactoryBuilder;
|
||||
import io.dynamict.hreadpool.common.executor.ExecutorFactory;
|
||||
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Config Executor.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:33
|
||||
*/
|
||||
public class ConfigExecutor {
|
||||
|
||||
private static final ScheduledExecutorService LONG_POLLING_EXECUTOR = ExecutorFactory.Managed
|
||||
.newSingleScheduledExecutorService("default group",
|
||||
ThreadFactoryBuilder.create()
|
||||
.setNamePrefix("io.dynamic.threadPool.config.LongPolling")
|
||||
.build());
|
||||
|
||||
public static void executeLongPolling(Runnable runnable) {
|
||||
LONG_POLLING_EXECUTOR.execute(runnable);
|
||||
}
|
||||
|
||||
public static ScheduledFuture<?> scheduleLongPolling(Runnable runnable, long period, TimeUnit unit) {
|
||||
return LONG_POLLING_EXECUTOR.schedule(runnable, period, unit);
|
||||
}
|
||||
|
||||
public static void scheduleLongPolling(Runnable runnable, long initialDelay, long period, TimeUnit unit) {
|
||||
LONG_POLLING_EXECUTOR.scheduleWithFixedDelay(runnable, initialDelay, period, unit);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package io.dynamic.threadpool.server.toolkit;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Map Util.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 19:09
|
||||
*/
|
||||
public class MapUtil {
|
||||
|
||||
public static Object computeIfAbsent(Map target, Object key, BiFunction mappingFunction, Object param1, Object param2) {
|
||||
Objects.requireNonNull(target, "target");
|
||||
Objects.requireNonNull(key, "key");
|
||||
Objects.requireNonNull(mappingFunction, "mappingFunction");
|
||||
Objects.requireNonNull(param1, "param1");
|
||||
Objects.requireNonNull(param2, "param2");
|
||||
|
||||
Object val = target.get(key);
|
||||
if (val == null) {
|
||||
Object ret = mappingFunction.apply(param1, param2);
|
||||
target.put(key, ret);
|
||||
return ret;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
package io.dynamic.threadpool.server.toolkit;
|
||||
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Request Util.
|
||||
*
|
||||
* @author chen.ma
|
||||
* @date 2021/6/23 18:28
|
||||
*/
|
||||
public class RequestUtil {
|
||||
|
||||
private static final String X_REAL_IP = "X-Real-IP";
|
||||
|
||||
private static final String X_FORWARDED_FOR = "X-Forwarded-For";
|
||||
|
||||
private static final String X_FORWARDED_FOR_SPLIT_SYMBOL = ",";
|
||||
|
||||
public static final String CLIENT_APPNAME_HEADER = "Client-AppName";
|
||||
|
||||
public static String getRemoteIp(HttpServletRequest request) {
|
||||
String xForwardedFor = request.getHeader(X_FORWARDED_FOR);
|
||||
if (!StringUtils.isEmpty(xForwardedFor)) {
|
||||
return xForwardedFor.split(X_FORWARDED_FOR_SPLIT_SYMBOL)[0].trim();
|
||||
}
|
||||
String nginxHeader = request.getHeader(X_REAL_IP);
|
||||
return StringUtils.isEmpty(nginxHeader) ? request.getRemoteAddr() : nginxHeader;
|
||||
}
|
||||
}
|
Loading…
Reference in new issue