参考nacos配置中心机制,实现前后端业务长轮询功能

思路

  1. 基于订阅发布,实现事件监听。创建通知中心,在通知中心注册事件推送者,绑定事件监听者与事件推送者
  2. 创建数据变更推送者线程,在线程中推送发布事件,放入队列,在线程启动后自旋获取队列中的事件,获取事件后通知监听者执行事件回调;如果队列满了,直接通知当前事件的所有监听者
  3. 创建数据变更监听器,传递事件监听事件,在事件中会包含业务数据
  4. 创建长连接调度线程池,核心线程数为1即可,线程内逻辑执行速度很快,没有必要创建更多线程
  5. 长连接上下文处理器单例化,只要用来存储当前等待的客户端长连接
  6. 客户端请求服务端,服务端hold住线程,可以基于Servlet的AsyncContext或者spring的DeferredResult。将客户端线程构造成长连接线程,调度线程池启动线程
  7. 在线程中做两件事,一是将当前线程对象放入长连接队列,二是再次通过调度线程池在延迟之后单次执行调度任务,当到达延迟时间之后,默认响应给客户端,比如nacos中默认客户端与服务端的请求间隔是30s,那么服务端如果没有事件变更,在29.5s的时候会响应给客户端
  8. 当有事件变更的时候,事件监听器接收到事件,会通过长连接上下文处理器获取所有客户端长连接线程任务,判断线程任务的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);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值