以管理倒计时为例:

package com.rongyi.platform.biotherm.web.websocket.activity.timers;

import com.rongyi.platform.biotherm.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @desc: 周期性任务管理器。 是单实例的管理。 取消任务,需要通过MQ广播 或 调用者记录所在的实例
 * @author: 毛会懂
 * @create: 2019-10-13 10:35:00
 **/
@Slf4j
@Service
public class ThreadExecutorManager {
    // 周期任务线程池
    private static ScheduledThreadPoolExecutor executorService = (ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(50);
    // 管理定时任务的future
    private static Map<String, Future> futureHashMap = new ConcurrentHashMap<>();

    @PostConstruct
    private void init(){
        executorService.setRemoveOnCancelPolicy(true);
    }

    /**
     * 添加定时器任务
     */
    public static void addTimeTask(String timerName,Runnable runnable,Integer activityId,long period){
        if(StringUtils.isEmpty(timerName) || activityId == null || runnable == null ||  period <= 0){
            return ;
        }
        String realRankTaskName = realTaskName(timerName,activityId);
        if(futureHashMap.get(realRankTaskName) != null){
           // 在分布式系统中,并不能保证realRankTaskName 只有一个。需要调用者保证。
            log.info("不应该执行到这里,同一个realRankTaskName只能有一个,添加失败");
            return;
        }
        Future future = executorService.scheduleAtFixedRate(runnable, 0, period, TimeUnit.SECONDS);
        futureHashMap.put(realRankTaskName, future);
        log.info("添加定时器任务成功,taskName={}",realRankTaskName);
    }

    /**
     * 删除定时器任务
     */
    public static Boolean removeTimeTask(String timerName,Integer activityId){
        if(StringUtils.isEmpty(timerName) || activityId == null){
            return Boolean.TRUE;
        }
        String realRankTaskName = realTaskName(timerName,activityId);
        Future removeFuture = futureHashMap.remove(realRankTaskName);
        log.info("移除的定时任务名称:{}-{}",timerName,removeFuture);
        if(removeFuture != null) {
            boolean cancel = removeFuture.cancel(true);
            log.info("任务取消是否成功:{}",cancel);
            if(!cancel){
                futureHashMap.put(realRankTaskName,removeFuture);
                log.info("不应该执行到这里,任务取消失败,重新加入到futureHashMap中");
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;
    }


    private static String realTaskName(String taskName,Integer activityId){
        return taskName + "_" + activityId;
    }
}
/**
 * @desc: 向设备端推送倒计时
 * @author: 毛会懂
 * @create: 2019-10-13 09:46:00
 **/
@Slf4j
public class CountDownExecutorService implements Runnable {

    // 任务名
    private String taskName;

    // 活动id
    private Integer activityId;

    // 时长,递减到0,自动关闭
    private Integer timeLength;

    // screen不为空时,向大屏推送倒计时
    private IDeviceScreen screen;

    // mobile不为空时,向手机端推送倒计时
    private IDeviceMobile mobile;

    // 活动状态
    private DeviceActivityStatusEnum activityStatus;

    public CountDownExecutorService(String taskName, Integer activityId, Integer timeLength,
                                    IDeviceScreen screen,
                                    IDeviceMobile mobile,
                                    IDeviceFoot foot,
                                    DeviceActivityStatusEnum activityStatus){
        this.taskName = taskName;
        this.activityId = activityId;
        this.timeLength = timeLength;
        this.screen = screen;
        this.mobile = mobile;
        this.foot = foot;
        this.activityStatus = activityStatus;
    }

    @Override
    public void run() {
        log.debug("倒计时开始,活动id:{},timeLength:{}",activityId,timeLength);
        if(Thread.currentThread().isInterrupted()){
            log.info("不应该执行到这里,已经检查到中断");
            throw new RuntimeException();
        }
        try {
            // 推送倒计时
            pushCountDown();

            //游戏结束,取消实时排行推送
            if(timeLength <= 0){
                ThreadExecutorManager.removeTimeTask(taskName,activityId);
                return;
            }
        }catch (Exception e){
            log.error("倒计时异常,活动id:{},{}",activityId,e);
        }
        //计算剩余时间
        timeLength--;
        if(timeLength < 0){
            log.info("不应该执行到这里,倒计时倒计到负数了");
            throw new RuntimeException();
        }
    }

    /**
     * 向客户端推送倒计时。 可根据活动状态判断是否发送。但有可能引起性能问题。
     */
    private void pushCountDown(){
        if(screen != null){
            screen.pushCountDown(activityId,timeLength,activityStatus);
        }
        if(mobile != null){
            mobile.pushCountDown(activityId,timeLength,activityStatus);
        }
    }
}

 

调用方:

// 启动倒计时的定时器(用户已参与,准备中的倒计时)
Runnable realRankTask = new CountDownExecutorService(TASK_NAME_PRE,
activityId,
timeLength,
deviceScreen,
deviceMobile,
deviceFoot,
DeviceActivityStatusEnum.STATUS_PRE_MODE);
ThreadExecutorManager.addTimeTask(TASK_NAME_PRE,realRankTask,activityId,1);

// 取消定时任务:
webSocketSender.removeTimeTask(TASK_NAME_INIT_HIDDEN_QR,activityId);
    /**
     * 取消定时任务
     */
    public Boolean removeTimeTask(String timerName,Integer activityId){
        Map<String,Object> map = new HashMap<>();
        map.put("timerName",timerName);
        map.put("activityId",activityId);
        rabbitTemplate.convertAndSend(RabbitMqConfig.PLATFORM_GAME_FANOUT_EXCHANGE_TASK,
                RabbitMqConfig.PLATFORM_GAME_WEB_SOCKET_TIMER_QUEUE_KEY,
                map);
        log.info("取消定时任务的MQ发送成功:{},{}",timerName,activityId);
        return Boolean.TRUE;
    }

 

取消定时任务MQ的接收:

  @RabbitHandler
    @RabbitListener(queues = "#{webSocketCancelTimerTask.name}")
    public void consumerCancelTimerTask(Map<String,Object> map, Message message, Channel channel) {
        MDC.put(Constant.TRACE_ID_NAME, UuidUtil.nextAsTextWithoutHyphen16());
        final long deliveryTag = message.getMessageProperties().getDeliveryTag();
        log.info("consumerCancelTimerTask,收到的信息:{}", JSON.toJSON(map));

        try {
            if(map == null){
                channel.basicAck(deliveryTag, false);
                return;
            }
            String timerName = (String) map.get("timerName");
            Integer activityId = (Integer) map.get("activityId");
            Boolean result = ThreadExecutorManager.removeTimeTask(timerName, activityId);
            if (!result) {
                throw new RuntimeException("移除倒计时定时器失败了");
            }
            // 通知 MQ 消息已被成功消费,可以ACK了
            channel.basicAck(deliveryTag, false);
        }  catch (Exception e) {
            log.error("consumerCancelTimerTask 异常:", e);
            try {
                // 处理失败,重新压入MQ
                channel.basicRecover();
                Thread.sleep(100);
            } catch (IOException | InterruptedException e1) {
                log.error(e1.getMessage());
            }
        }
    }

 

posted on 2023-02-07 15:24  毛会懂  阅读(18)  评论(0编辑  收藏  举报