思路
- 基于订阅发布,实现事件监听。创建通知中心,在通知中心注册事件推送者,绑定事件监听者与事件推送者
- 创建数据变更推送者线程,在线程中推送发布事件,放入队列,在线程启动后自旋获取队列中的事件,获取事件后通知监听者执行事件回调;如果队列满了,直接通知当前事件的所有监听者
- 创建数据变更监听器,传递事件监听事件,在事件中会包含业务数据
- 创建长连接调度线程池,核心线程数为1即可,线程内逻辑执行速度很快,没有必要创建更多线程
- 长连接上下文处理器单例化,只要用来存储当前等待的客户端长连接
- 客户端请求服务端,服务端hold住线程,可以基于Servlet的AsyncContext或者spring的DeferredResult。将客户端线程构造成长连接线程,调度线程池启动线程
- 在线程中做两件事,一是将当前线程对象放入长连接队列,二是再次通过调度线程池在延迟之后单次执行调度任务,当到达延迟时间之后,默认响应给客户端,比如nacos中默认客户端与服务端的请求间隔是30s,那么服务端如果没有事件变更,在29.5s的时候会响应给客户端
- 当有事件变更的时候,事件监听器接收到事件,会通过长连接上下文处理器获取所有客户端长连接线程任务,判断线程任务的Topic和接收到的事件topic是否相同,如果相同的话,基于长连接任务完成AsyncContext响应
代码
通知中心
public class NotifyCenter {
private static Map<String, EventPusher> pusherMap = new ConcurrentHashMap<>();
public static int DEFAULT_QUEUE_SIZE = 16384;
private static EventPusherFactory DEFAULT_EVENT_PUSHER_FACTORY = (event, queueMaxSize) -> {
EventPusher push = new ThreadEventPusher();
push.init(event, queueMaxSize);
return push;
};
public static void registerPublish(final Class<? extends Event> event, EventPusher eventPusher) {
synchronized (NotifyCenter.class) {
pusherMap.putIfAbsent(event.getCanonicalName(), eventPusher);
}
}
public static EventPusher registerPublish(final Class<? extends Event> event) {
return registerPublish(event, DEFAULT_EVENT_PUSHER_FACTORY, DEFAULT_QUEUE_SIZE);
}
public static EventPusher registerPublish(final Class<? extends Event> event, int queueMaxSize) {
return registerPublish(event, DEFAULT_EVENT_PUSHER_FACTORY, queueMaxSize);
}
public static EventPusher registerPublish(final Class<? extends Event> event, EventPusherFactory eventPusherFactory, int queueMaxSize) {
synchronized (NotifyCenter.class) {
MapUtil.computeIfAbsent(pusherMap, event.getCanonicalName(), eventPusherFactory, event, queueMaxSize);
}
return pusherMap.get(event.getCanonicalName());
}
public static void registerSubscribe(final EventSubscriber subscriber, Class<? extends Event> eventType) {
EventPusher eventPusher = registerPublish(eventType, DEFAULT_QUEUE_SIZE);
eventPusher.addSubscriber(subscriber);
}
}
抽象事件
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;
}
}
数据改变事件
public class LocalDataChangeEvent extends Event {
private TopicContext topicContext;
public LocalDataChangeEvent(TopicContext topicContext) {
this.topicContext = topicContext;
}
public TopicContext getTopicContext() {
return topicContext;
}
}
事件推送者接口
public interface EventPusher extends Closeable {
/**
* Initializes the event publisher.
*
* @param type {@link Event >}
* @param bufferSize Message staging queue size
*/
void init(Class<? extends Event> type, int bufferSize);
/**
* The number of currently staged events.
*
* @return event size
*/
long currentEventSize();
/**
* Add listener.
*
* @param subscriber {@link EventSubscriber}
* @return
*/
void addSubscriber(EventSubscriber subscriber);
/**
* Remove listener.
*
* @param subscriber {@link EventSubscriber}
*/
void removeSubscriber(EventSubscriber subscriber);
/**
* publish event.
*
* @param event {@link Event}
* @return publish event is success
*/
boolean publish(Event event);
/**
* Notify listener.
*
* @param subscriber {@link EventSubscriber}
* @param event {@link Event}
*/
void notifySubscriber(EventSubscriber subscriber, Event event);
}
public interface Closeable {
/**
* Shutdown the Resources, such as Thread Pool.
*
*/
void shutdown();
}
事件推送者工厂
用于生成事件推送者
public interface EventPusherFactory extends BiFunction<Class<? extends Event>, Integer, EventPusher> {
}
事件推送者线程
@Slf4j
public class ThreadEventPusher extends Thread implements EventPusher {
private volatile boolean shutdown = false;
private volatile boolean initialized = false;
private Class<? extends Event> eventType;
private int queueMaxSize = -1;
protected final ConcurrentHashSet<EventSubscriber> subscribers = new ConcurrentHashSet<>();
private BlockingQueue<Event> queue;
@Override
public void init(Class<? extends Event> type, int bufferSize) {
setDaemon(true);
setName("Event Publisher <" + type.getSimpleName() + ">");
this.eventType = type;
this.queueMaxSize = bufferSize;
this.queue = new ArrayBlockingQueue<>(bufferSize);
start();
}
@Override
public synchronized void start() {
if (!initialized) {
// start just called once
super.start();
initialized = true;
log.info("Event Pusher {} , 绑定 Event <{}> 初始化", this.getClass().getSimpleName(), eventType.getSimpleName());
}
}
@Override
public long currentEventSize() {
return 0;
}
@Override
public void addSubscriber(EventSubscriber subscriber) {
subscribers.add(subscriber);
}
@Override
public void removeSubscriber(EventSubscriber subscriber) {
subscribers.remove(subscriber);
}
@Override
public boolean publish(Event event) {
checkIsStart();
if (!event.getClass().isAssignableFrom(eventType)) {
throw new RuntimeException("发布失败,event类型不匹配");
}
boolean success = this.queue.offer(event);
if (!success) {
log.warn("Unable to plug in due to interruption, synchronize sending time, event : {}", event);
log.warn("Event [{}] , 队列长度超过限制", event);
receiveEvent(event);
return true;
}
return true;
}
private void checkIsStart() {
if (!initialized) {
throw new IllegalStateException("Publisher does not start");
}
}
@Override
public void notifySubscriber(EventSubscriber subscriber, Event event) {
subscriber.onEvent(event);
}
@Override
public void shutdown() {
shutdown = true;
}
@Override
public void run() {
openEventHandler();
}
void openEventHandler() {
try {
// This variable is defined to resolve the problem which message overstock in the queue.
int waitTimes = 60;
// To ensure that messages are not lost, enable EventHandler when
// waiting for the first Subscriber to register
for (; ; ) {
if (shutdown || hasSubscriber() || waitTimes <= 0) {
break;
}
ThreadUtil.sleep(1000L);
waitTimes--;
}
for (; ; ) {
if (shutdown) {
break;
}
final Event event = queue.take();
log.info("event - [{}] receive , thread - [{}]", eventType.getSimpleName(), Thread.currentThread().getName());
receiveEvent(event);
}
} catch (Throwable ex) {
log.error("Event listener exception : ", ex);
}
}
private void receiveEvent(Event event) {
for (EventSubscriber subscriber : subscribers) {
notifySubscriber(subscriber, event);
}
}
private boolean hasSubscriber() {
return CollectionUtil.isNotEmpty(subscribers);
}
}
事件监听器接口
public interface EventSubscriber<T extends Event> {
void onEvent(T t);
}
数据改变事件监听器
public class LocalDataChangeSubscriber implements EventSubscriber<LocalDataChangeEvent> {
private final LongPollingHandler longPollingService;
public LocalDataChangeSubscriber(LongPollingHandler longPollingService) {
this.longPollingService = longPollingService;
}
@Override
public void onEvent(LocalDataChangeEvent localDataChangeEvent) {
Queue<ClientLongPollingContext.ClientLongPollingTask> allClientTask = ClientLongPollingContext.getInstance().getAllClientTask();
allClientTask.iterator().forEachRemaining(n -> {
TopicContext topicContext = localDataChangeEvent.getTopicContext();
if (StrUtil.equals(topicContext.topic(), n.getTopicContext().topic())) {
n.sendResponse(false);
}
});
}
}
客户端长轮询上下文
@Slf4j
public class ClientLongPollingContext {
/**
* 当前长连接线程
*/
private final Queue<ClientLongPollingTask> allClientTask;
/**
* 长连接超时时间
*/
private final int overtime;
private ClientLongPollingContext() {
allClientTask = new ConcurrentLinkedQueue<>();
overtime = 15;
}
private static class Singleton {
private static final ClientLongPollingContext INSTANCE = new ClientLongPollingContext();
}
public static ClientLongPollingContext getInstance() {
return Singleton.INSTANCE;
}
public ClientLongPollingTask instanceTask(TopicContext topicContext, AsyncContext asyncContext, LongPollingHandler longPollingHandler) {
return new ClientLongPollingTask(topicContext, asyncContext, longPollingHandler);
}
public Queue<ClientLongPollingTask> getAllClientTask() {
return allClientTask;
}
public class ClientLongPollingTask implements Runnable {
final TopicContext topicContext;
final AsyncContext asyncContext;
final LongPollingHandler longPollingHandler;
Future<?> asyncTimeoutFuture;
public ClientLongPollingTask(TopicContext topicContext, AsyncContext asyncContext, LongPollingHandler longPollingHandler) {
this.topicContext = topicContext;
this.asyncContext = asyncContext;
this.longPollingHandler = longPollingHandler;
}
@Override
public void run() {
asyncTimeoutFuture = ExecutorFactory.scheduleLongPolling(() -> {
System.out.println("超时回调");
sendResponse(true);
}, overtime, TimeUnit.SECONDS);
allClientTask.add(this);
}
public void sendResponse(boolean isTimeout) {
// 取消超时任务
if (null != asyncTimeoutFuture) {
asyncTimeoutFuture.cancel(false);
}
generateResponse(isTimeout);
allClientTask.remove(this);
}
private void generateResponse(boolean isTimeout) {
HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
try {
// Disable cache.
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-cache,no-store");
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(topicContext.responseContentType());
String responseContext;
if(isTimeout){
responseContext = topicContext.timeoutResponseContext();
}else{
responseContext = topicContext.responseContext();
}
response.getWriter().println(responseContext);
} catch (Exception ex) {
log.error(ex.toString(), ex);
} finally {
longPollingHandler.removeTopicCache(topicContext.topic());
asyncContext.complete();
}
}
public AsyncContext getAsyncContext() {
return asyncContext;
}
public TopicContext getTopicContext() {
return topicContext;
}
}
}
状态线程
用于监听当前连接数
public class StatTask implements Runnable {
private final LongPollingHandler longPollingService;
public StatTask(LongPollingHandler longPollingService) {
this.longPollingService = longPollingService;
}
@Override
public void run() {
System.out.println("[long-polling] client count " + ClientLongPollingContext.getInstance().getAllClientTask().size());
}
}
抽象业务主题
public abstract class TopicContext {
/**
* 业务主题,唯一
*
* @return
*/
public abstract String topic();
/**
* 正确响应内容
*
* @return
*/
public abstract String responseContext();
/**
* 超时响应内容
*
* @return
*/
public abstract String timeoutResponseContext();
/**
* 响应ContentType
*
* @return
*/
public String responseContentType() {
return MediaType.APPLICATION_JSON_UTF8_VALUE;
}
private String mdc;
public TopicContext() {
mdc = MDC.get(LogConstant.TRACE_ID);
}
public String getMdc() {
return mdc;
}
}
长轮询处理器
public class LongPollingHandler {
public Map<String, TopicContext> topicContextCache = new ConcurrentHashMap<>();
public LongPollingHandler() {
ExecutorFactory.scheduleLongPolling(new StatTask(this), 0L, 10L, TimeUnit.SECONDS);
NotifyCenter.registerPublish(LocalDataChangeEvent.class);
NotifyCenter.registerSubscribe(new LocalDataChangeSubscriber(this), LocalDataChangeEvent.class);
}
public void executeLongPolling(TopicContext topicContext) {
HttpServletRequest request = WebUtil.getRequest();
AsyncContext asyncContext = request.startAsync();
topicContextCache.putIfAbsent(topicContext.topic(), topicContext);
ExecutorFactory.executeLongPolling(ClientLongPollingContext.getInstance().instanceTask(topicContext, asyncContext, this));
}
public void publishDataChangeEvent(String topic) {
TopicContext topicContext = topicContextCache.get(topic);
if (topicContext == null) {
return;
}
LocalDataChangeEvent localDataChangeEvent = new LocalDataChangeEvent(topicContext);
EventPusher eventPusher = NotifyCenter.registerPublish(LocalDataChangeEvent.class);
eventPusher.publish(localDataChangeEvent);
}
public void removeTopicCache(String topic) {
topicContextCache.remove(topic);
}
}
线程工厂
public class ExecutorFactory {
private static final ScheduledExecutorService LONG_POLLING_EXECUTOR = Executors.newScheduledThreadPool(1, new NameThreadFactory("LongPollingTest"));
public static void executeLongPolling(Runnable runnable) {
LONG_POLLING_EXECUTOR.execute(runnable);
}
public static void scheduleLongPolling(Runnable runnable, long initialDelay, long period, TimeUnit unit) {
LONG_POLLING_EXECUTOR.scheduleWithFixedDelay(runnable, initialDelay, period, unit);
}
public static ScheduledFuture<?> scheduleLongPolling(Runnable runnable, long period, TimeUnit unit) {
return LONG_POLLING_EXECUTOR.schedule(runnable, period, unit);
}
}
public class NameThreadFactory implements ThreadFactory {
private final AtomicInteger id = new AtomicInteger(0);
private String name;
public NameThreadFactory(String name) {
if (!name.endsWith(StrUtil.DOT)) {
name += StrUtil.DOT;
}
this.name = name;
}
@Override
public Thread newThread(Runnable r) {
String threadName = name + id.getAndIncrement();
Thread thread = new Thread(r, threadName);
thread.setDaemon(true);
return thread;
}
}
调用controller
@RestController
@RequestMapping("long-polling")
public class LongPollingController {
@Autowired
private LongPollingHandler longPollingHandler;
@RequestMapping(value = "/start")
public void start(@RequestParam String id) {
TopicContext topicContext = new TopicContext() {
@Override
public String topic() {
return id;
}
@Override
public String responseContext() {
return "ok";
}
@Override
public String timeoutResponseContext() {
return "timeout";
}
};
longPollingHandler.executeLongPolling(topicContext);
}
@RequestMapping(value = "/change")
public void change(@RequestParam String id) {
longPollingHandler.publishDataChangeEvent(id);
}
}