定时任务优化: 用简单的轮询算法+Rddisssion实现类似时间轮定时任务

在定时任务开始执行的时候,CPU都会忽然飙高,但是平时的时候定时任务的资源呦没有被用到,为了更好的发挥我们的性能,我们就把每个任务里不同的groupId下的任务平摊到这个时间段里去执行,分散在执行任务那一下资源占用的问题。

轮询算法:

/**
     * 把groupId平均分到区间每个时段
     * @param groupIds
     * @return Pair<Map<Integer, List<Integer>>, Integer> key:平均分的grouId,value:时间轮大小(单位:s, 最小间隔60s)
     */
    public Pair<Map<Integer, List<Long>>, Integer> getAvgSegmentGroupInfo(List<Long> groupIds, String beanName) {
        
        int groupCount = groupIds.size();
        Integer jobRange = xxxConfig.getJobRangeTime().get(beanName);
        // 时间轮大小, 单位:s, 最小间隔60s
        int wheelSize = groupIdCount > jobRange ? 60 : (jobRange * 60 / groupIdCount);
        // 时间轮数量
        int wheelCount = jobRange * 60 / wheelSize;
        // 每次执行数量
        int rangeCount = groupIdCount < wheelCount ? 1 : groupIdCount / wheelCount;
        // 平均分
        Map<Integer, List<Long>> groupMap = new HashMap<>();
        if (groupIdCount <= wheelCount) {
            for (int i = 0; i < groupIdCount; i ++) {
                groupMap.put(i, Arrays.asList(groups.get(i)));
            }
        } else {
            Integer index = 0;
            for (int i = 0; i < wheelCount; i ++) {
                groupMap.put(i, groupIds.subList(index, index + rangeCount));
                index = index + rangeCount;
            }
            if (index < groupIdCount) {
                groupMap.put(wheelCount - 1, groupIdss.subList(index, groupIdCount));
            }
        }
        log.info("定时任务 {},groupIdCount {}, wheelSize:{}, size:{}, rangeCount:{}", beanName, groupIdCount,
                wheelSize, groupMap.size(), rangeCount);
        return new Pair<>(groupMap, wheelSize);
    }

redission实现延迟队列
用redis实现延迟队列是为了持久化,用kafka消息队列其实也可以。



import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static com.task.constent.xxx.HANDLE_REPORT_DELAY_QUEUE;

/**
 * redisson延迟队列
 */
@Slf4j
@Component
public class RedissonDelayMessageHelper implements InitializingBean, DisposableBean {
    private static RedissonClient redissonClient;

    private static RBlockingQueue<DelayMessage> blockingQueue;          // 阻塞队列

    private static RDelayedQueue<DelayMessage> delayedQueue;           // 延迟队列

    @Resource
    private Map<String, xxxService> xxxServiceMap = new ConcurrentHashMap<>(xxxTypeEnum.values().length << 1);


    @Autowired
    public void setRedissonClient(RedissonClient redissonClient) {
        RedissonDelayMessageHelper.redissonClient = redissonClient;
    }


    @PostConstruct
    public void setQueue() {
        blockingQueue = redissonClient.getBlockingDeque(HANDLE_REPORT_DELAY_QUEUE);
        delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
    }

    /**
     * 加入延迟队列
     * @param delayMessageBody
     * @param delayMessageType
     * @param delayTime 单位:s
     * @param <T>
     */
    public static <T> void add(T delayMessageBody, DelayMessageType delayMessageType, Long delayTime) {
        DelayMessage delayMessage = new DelayMessage<>();
        delayMessage.setType(delayMessageType);
        delayMessage.setBody(delayMessageBody);
        delayedQueue.offer(delayMessage, delayTime, TimeUnit.SECONDS);
    }

    public static void deleteOldQueue() {
        rBlockingQueue.delete();
        rDelayedQueue.delete();
    }

    /**
     * 移除队列
     * @param message
     * @return
     */
    public static boolean remove(DelayHandleReportMessage message) {
        return rDelayedQueue.remove(message);
    }

    /**
     * 销毁队列
     */
    @Override
    public void destroy() {
        rDelayedQueue.destroy();
    }

    /**
     * 延迟队列执行
     */
    @Override
    public void afterPropertiesSet() {
        Thread thread = new Thread(() -> {
            DelayMessage delayMessage = null;
            while (true) {
                try {

                    delayMessage = blockingQueue.take();
                    if (delayMessage.getRetryCount() >= 5) {
                        continue;
                    }

                    switch (delayMessage.getType()) {
                        case xxx:
                            DelayHandleTaskMessage delayHandleTaskMessage =  (DelayHandleTaskMessage)delayMessage.getBody();
                            xxxServiceMap.get(delayHandleTaskMessage.getTypeEnum().getBeanName()).handle(delayHandleTaskMessage);
                            break;
                        case ...:
                          ...
                            break;
                        default:
                            break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    ThreadUtil.safeSleep(1000);
                    afterPropertiesSet();
                } catch (RedisException e){
                    // 重新加入队列
                    log.info("延时任务执行报错,任务重新加入队列,重试次数:{}", delayMessage.getRetryCount());
                    delayMessage.setRetryCount(delayMessage.getRetryCount() + 1);
                    delayedQueue.offer(delayMessage, 5, TimeUnit.SECONDS);
                } catch (Exception e) {
                    log.error("延迟队列任务执行报错, 参数: {}", delayMessage, e);
                }
            }
        }, "延迟队列线程");
        thread.setDaemon(true);
        thread.start();
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值