使用时间轮实现批量定时任务的分片执行

背景:项目需要使用定时任务实现每隔N天定时重启一批设备,但由于硬件及现场资源限制,无法同时重启所有设备,需要每隔指定时间间隔重启其中几个设备(相当于实现一个任务分片的效果)。

        基于以上需求,开始设计后端实现方案,最开始想到的就是队列+timer的方案,任务都存到队列中,然后通过一个定时器每隔指定时间间隔从队列中获取指定数量的任务进行执行,思前想后总是觉得有点麻烦,于是又苦思冥想有没有更好的方案,在了解了时间轮算法的特点后觉得完美契合需求。

1.时间轮算法概述

        关于时间轮算法的概述可参考时间轮算法(TimingWheel)-CSDN博客

2.实现篇

2.1定义时间轮槽位(TimeSlot)

        每个槽位中存储一个任务列表,在指定时刻任务列表中的任务会被取出执行。

import java.util.ArrayList;
import java.util.List;

public class TimeSlot {
    private List<Runnable> tasks = new ArrayList<>();

    public void addTask(Runnable task) {
        tasks.add(task);
    }

    public List<Runnable> getTasks() {
        return tasks;
    }

    public void clearTasks() {
        tasks.clear();
    }
}

2.2 定义时间轮(TimeWheel)

        时间轮由一定数量的槽位组成,并负责任务的调度和执行:

public class TimeWheel {

    /**
     * 总认为个数
     */
    private final int totalTaskCount;

    /**
    * 已执行任务的个数
    */
    private int executedTaskCount = 0;

    /**
    * 每隔刻度的时间间隔,单位为毫秒
    */
    private final int tickDuration;

    /**
    * 时间轮的大小,即槽位数量
    */
    private final int tickDuration;

    /**
    * 时间轮的槽位数组
    */
    private final int tickDuration;
    
    /**
    * 当前刻度位置
    */
    private int currentTick = 0; 

    /**
    * 执行任务的线程池
    */
    private final ScheduledExecutorService scheduler;
    

    public TimeWheel(int totalTaskCount, int tickDuration, int wheelSize) {
        this.totalTaskCount = totalTaskCount;
        this.tickDuration = tickDuration;
        this.wheelSize = wheelSize;
        this.slots = new TimeSlot[wheelSize];
        for (int i = 0; i < wheelSize; i++) {
            slots[i] = new TimeSlot();
        }
        scheduler = Executors.newSingleThreadScheduledExecutor();
        start();
    }

    private void start() {
        // initialDelay设置为500ms
        scheduler.scheduleAtFixedRate(this::tick, 500, tickDuration,                 TimeUnit.MILLISECONDS);
    }

    private void tick() {
        // 获取当前刻度的槽位
        TimeSlot slot = slots[currentTick];
        // 执行槽位中的所有任务
        for (Runnable task : slot.getTasks()) {
            // 任务执行
            task.run();
            // 如果已执行任务数等于总任务数则时间轮调度器自动退出
            executedTaskCount++;
            if (executedTaskCount == totalTaskCount){
                this.stop();
            }
        }
        // 清空当前槽位的任务
        slot.clearTasks();
        // 移动到下一个刻度
        currentTick = (currentTick + 1) % wheelSize;
    }

    public void addTask(Runnable task, long delay) {
        int ticks = (int) (delay / tickDuration);
        int slotIndex = (currentTick + ticks) % wheelSize;
        slots[slotIndex].addTask(task);
    }
    
    public void stop() {
        scheduler.shutdown();
    }


}

2.3使用样例

// 省略前面业务代码
boolean isEnableSharding = task.getEnablSharding();

if (isEnableSharding){

    // 处理开启了分片策略的任务
    
    List<List<NeInfo>> subNeInfoLists = new ArrayList<>();
    // 每次执行任务的个数,用户由前端页面设定
    int batchSize = task.getShardingThreshold(); 
    // 总任务个数
    int totalSize = neinfos.Size();

    for(int i = 0; i < totalSizel; i+=batchSize){
        int endIndex = Math.min(i + batchSize, totalSize);
        subNeInfoList.add(neinfos.subList(i, endIndex));
    }

    int wheelSize = 0;
    if (totalSize % batchSize == 0){
        wheelSize = totalSize / batchSize;
    }else{
        wheelSize = totalSize / batchSize + 1;
    }

    // 初始化时间轮,tickDuration为页面设定的指定时间间隔(单位是秒,所以这里需要转为毫秒)
    TimeWheel timeWheel = new TimeWheel(totalSize, task.getShardingTimeInterval*1000, wheelSize);

    for (int i = 0; i < subNeInfoLists.size(); i++){
        for(NeInfo neInfo : subNeInfoLists.get(i)){
            timeWheel.addTask(() -> {
                // 具体每个设备需要执行的任务
                bsService.doTask(neInfo);

            }, task.getShardingTimeInterval() * 1000 * i);
        }
    }


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/783456.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++第二弹 -- C++基础语法下(引用 内联函数 auto关键字 范围for 指针空值)

本篇大纲 前言一. 引用续讲1. 传值,传引用效率对比2. 类型转换和表达式传引用的注意事项3. 引用与指针 二. 内联函数1. 概念2. 特性3. 面试题 三. auto关键字(C11)1. 类型别名思考2. auto简介3. auto的使用细则4. auto不能推导的场景 四. 基于范围的for循环(C11)1. 范围for的语…

运维系列.Nginx:自定义错误页面

运维系列 Nginx&#xff1a;自定义错误页面 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/…

医院人员管理项目01_下午,css

文章目录 层叠样式表在html文件中引入css样式表&#xff1a;2种方法如何设置样式&#xff1a;3种css选择器继承权重 层叠样式表 引入html网页中的方式&#xff0c;共3种。 行内样式&#xff08;内联样式&#xff09;&#xff1a;直接在html中设置 内部样式&#xff1a;css代…

latex改写字体和字号

文章目录 字体使用宏包设置命令声明命令 字号例子设置特定字号 设置行间距用\setlength{\baselineskip}{24pt}设置\renewcommand{\baselinestretch}{2} \selectfont中文行距&#xff08;{ctex}&#xff09; 补充&#xff1a; 字体 使用宏包 \usepackage{ctex}设置命令 只对确…

【包邮送书】AIGC时代程序员的跃迁——编程高手的密码武器

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

20240708 Transformer

如何从浅入深理解transformer&#xff1f; - 知乎 1.出现了一些基于自监督的方法&#xff0c;这包括基于对比学习的方法如MoCo和SimCLR&#xff0c;和基于图像掩码的方法如MAE和BeiT 2、Transformer结构及其应用详解--GPT、BERT、MT-DNN、GPT-2 - 知乎 3. "Decoder-o…

掌握【Python异常处理】:打造健壮代码的现代编程指南

目录 ​编辑 1. 什么是异常&#xff1f; 知识点 示例 小李的理解 2. 常见的内置异常类型 知识点 示例 小李的理解 3. 异常机制的意义 知识点 示例 小李的理解 4. 如何处理异常 知识点 示例 小李的理解 5. 抛出异常 知识点 示例 小李的理解 6. Python内置…

从0到1搭建个性化推送引擎:百数教学带你掌握核心技术

百数低代码的推送提醒功能允许用户高度自定义提醒规则&#xff0c;支持多种提醒方式&#xff08;如钉钉、企业微信、微信、短信、语音、邮件等&#xff09;&#xff0c;以满足不同场景下的需求。 通过预设字段和模板&#xff0c;用户可以快速编辑提醒内容&#xff0c;减少重复…

ubuntu24.04按关键字卸载不需要的apt包

使用的时候发现一个imagemagic无法正常读取文件&#xff0c;试图卸载 man apt经过尝试后&#xff0c;发现list的一个神奇关键字&#xff0c;用来显示已安装的软件包 sudo apt list --installed | grep image按image关键字过滤&#xff1a; 之后按软件名卸载即可 sudo apt pu…

【VUE基础】VUE3第一节—vite创建vue3工程

什么是VUE Vue (发音为 /vjuː/&#xff0c;类似 view) 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助你高效地开发用户界面。无论是简单还是复杂的界面&#xff0…

MySQL性能优化 一、系统配置优化

数据库优化纬度有四个&#xff1a; 硬件升级、系统配置、表结构设计、SQL语句及索引。 优化选择&#xff1a; 优化成本&#xff1a;硬件升级 > 系统配置 > 表结构设计 > SQL语句及索引优化效果&#xff1a;硬件升级 < 系统配置 < 标结果设计 < SQL语句及索…

【C++】———— 继承

作者主页&#xff1a; 作者主页 本篇博客专栏&#xff1a;C 创作时间 &#xff1a;2024年7月5日 一、什么是继承&#xff1f; 继承的概念 定义&#xff1a; 继承机制就是面向对象设计中使代码可以复用的重要手段&#xff0c;它允许在程序员保持原有类特性的基础上进行扩展…

[工具教程]-31-解决mac扣盖后电池耗电快(谁在偷偷的用电池)

查看耗电模式 $ pmset -g查看 hibernatemode 这一行&#xff0c;如果 hibernatemode 后面的数字是 0 &#xff0c;那这种休眠模式下&#xff0c;掉电程度就是非常严重&#xff0c;如果 hibernatemode 后面的数字是 3 &#xff08;大部分人的电脑应该是这个休眠模式&#xff09…

LabVIEW中自定义Ring控件的图标

在LabVIEW中&#xff0c;自定义Ring控件的图标可以让用户界面更加直观和友好。以下是如何在LabVIEW中自定义Ring控件的图标的详细步骤&#xff1a; 步骤1&#xff1a;创建或获取图标 首先&#xff0c;你需要创建或获取你想要在Ring控件中使用的图标。你可以使用图像编辑软件&…

springboot+vue+mybatis图书销售管理系统+PPT+论文+讲解+售后

在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括图书销售管理系统的网络应用&#xff0c;在外国图书销售管理系统已经是很普遍的方式&#xff0c;不过国内的管理网站可能还处于起步阶段。图书销售管理系统具有网上图书信息管…

BaseServlet的封装

创建BaseServlet的必要性 如果不创建BaseServlet&#xff0c;现在我们只要实现一个功能&#xff0c;我们就需要创建一个servlet! 例如:用户模块(登录&#xff0c;注册&#xff0c;退出录&#xff0c;激活&#xff0c;发送邮件等等功能) 也就是说&#xff0c;我们必须要创建一…

ASO优化不仅仅是苹果商店,安卓商店同样不可忽视

大家一谈起ASO优化&#xff0c;不少人反应大多数都是IOS市场的优化&#xff0c;其实安卓也是不可分割的大市场&#xff0c;在国内手机应用市场&#xff0c;安卓的用户质量在稳步提高&#xff0c;因此开发者也越来越重视安卓市场的推广。做好安卓ASO也是非常必要的。 一、安卓市…

音视频质量评判标准

一、实时通信延时指标 通过图中表格可以看到&#xff0c;如果端到端延迟在200ms以内&#xff0c;说明整个通话是优质的&#xff0c;通话效果就像大家在同一个房间里聊天一样&#xff1b;300ms以内&#xff0c;大多数人很满意&#xff0c;400ms以内&#xff0c;有小部分人可以感…

五行、八卦、天干、地支

零基础入门&#xff1a;五行、八卦、天干、地支 给她家推荐一个小程序&#xff0c;感觉挺准的&#xff01;

PTrade常见问题系列5

回测失败&#xff1a;可用资源不足。 回测运行失败&#xff0c;错误码&#xff1a;2 错误信息&#xff1a;可用资源不足&#xff0c;请稍后在创建。 1、之前客户未限制客户容器使用内存和CPU&#xff0c;周末修改配置&#xff0c;限制了内存和CPU&#xff1b; 2、此报错是用户…