在定时任务开始执行的时候,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();
}
}