Rust 中实现定时任务管理

2025 年马上就要过去了,我们要站好牛马最后一班岗,我们要年终奖.....嗯,老板说我们要继续做梦,继续做任务做主线升级...这不任务就来了吗?写一个定时任务:

监控公司里每个人每天都浏览了哪些网页......

需求分析

这个需求其实特别简单,查询监控系统中的网页浏览记录表就行,每分钟查询一次。最简单的做法就是写一个死循环:

css 复制代码
fn main() {
	loop {
		sleep(Duration::from_secs(60));
		// todo: 查询数据表
	}
}

当我把这个方案提交给老板的后,老板来了一句:要是那么简单,我要养你干嘛?!我之后要监控更多的内容呢?你也这样写吗?!

我心里默默地想:既然不简单,你把需求说清楚啊,总是一句话需求做一个"淘宝"!不过也就在心里吐槽,年底了,我不敢和他硬刚。

这时候,我就面临一个选择,是自己写一个定时任务库,还是选择一个别人已经写好的库了。会想过去那么多年,自己含辛茹苦抚养公司长大,最终年年年终年年空,一时间悲上心头!嗯,又不按照代码行数给我算工资,少些点,直接用 tokio-cron-scheduler 这个库。

实现定时任务

首先安装这个库,这个在 Rust 中并不是难事:

toml 复制代码
[package]
name = "beacon"
version = "0.1.0"
edition = "2024"

[dependencies]
tokio = { version = "1.48.0", features = ["full"] }
tokio-cron-scheduler = "0.15.1"

然后,先写一个 Hello World 吧,每分钟打印一次 Hello World :

rust 复制代码
use tokio_cron_scheduler::{Job, JobScheduler};

#[tokio::main]
async fn main() {
    let scheduler = JobScheduler::new().await.unwrap();

    scheduler
        .add(
            Job::new("* * * * * *", |_uuid, _l| {
                println!("Hello World");
            }).unwrap(),
        )
        .await
        .unwrap();

    scheduler.start().await.unwrap();
		tokio::signal::ctrl_c().await.unwrap();
}

任务插件化

很快我提交了代码,给老板 Review。虽然老板不懂技术,但是他可以用 AI 来评审啊!很快老板发了我一长段 AI 的输出,重点就一句话:如果有很多任务,你也这样直接写在 main() 函数里吗?

好吧,继续改。怎么改了?思来想去,那就把任务做成插件化,然后在 main 函数中读取配置,自动注册任务。

首先,创建一个 trait ,编写 src/task.rs ,定义任务的名称、cron 表达式以及具体执行逻辑:

rust 复制代码
use async_trait::async_trait;

#[async_trait]
pub trait Task: Send + Sync {
    fn name(&self) -> &str;
    fn cron(&self) -> &str;
    async fn execute(&self);
}

接着,来修改 main 函数,新增一个注册任务的方法:

rust 复制代码
mod task;
mod tasks;

use std::sync::Arc;
use task::Task;
use tasks::{DataSyncTask, ReportGeneratorTask};

fn register_tasks() -> Vec<Arc<dyn Task>> {
    vec![
        Arc::new(DataSyncTask) as Arc<dyn Task>,
        Arc::new(ReportGeneratorTask) as Arc<dyn Task>,
        // 在这里添加更多任务...
    ]
}

上面使用了 Arc 来包装 Task,使得 Task 可以安全的在线程之间传递,采用引用计数。

然后修改 main 函数:

rust 复制代码
use tokio_cron_scheduler::{Job, JobScheduler};

#[tokio::main]
async fn main() {
    let scheduler = JobScheduler::new().await.unwrap();
    // 获取注册的任务
    let tasks = register_tasks();
		// 遍历所有任务统一注册
    for task in tasks {
        let task_clone = Arc::clone(&task);
        let cron = task.cron().to_string();
        let name = task.name().to_string();

        let job = Job::new_async(cron.as_str(), move |_uuid, _l| {
            let task = Arc::clone(&task_clone);
            Box::pin(async move {
                task.execute().await;
            })
        })
        .unwrap();

        scheduler.add(job).await.unwrap();
        println!("✓ Registered task: {} ({})", name, task.cron());
    }

    scheduler.start().await.unwrap();
    println!("\n🚀 Scheduler started. Press Ctrl+C to stop.");
    tokio::signal::ctrl_c().await.unwrap();
    println!("\n👋 Shutting down...");
}

需要说明的是,上面之所以使用 Box::pin 是因为 async 代码块生成的 Future 可能包含自引用,如果 Future 在内存中移动,自引用指针会失效。而 Box::pin 则将其固定在堆上,这样自引用指针在程序的生命周期内就会一直有效。

至此,这个程序就可以支持更多的任务了,只需要实现 Task 这个 trait 就行,例如:

rust 复制代码
use crate::task::Task;
use async_trait::async_trait;

pub struct ReportGeneratorTask;

#[async_trait]
impl Task for ReportGeneratorTask {
    fn name(&self) -> &str {
        "Report Generator"
    }

    fn cron(&self) -> &str {
        "0 0 9 * * *" // 每天早上 9 点
    }

    async fn execute(&self) {
        println!("[{}] Generating daily report...", self.name());
        
        match self.generate_report().await {
            Ok(path) => println!("[{}] ✓ Report saved to: {}", self.name(), path),
            Err(e) => eprintln!("[{}] ✗ Error: {}", self.name(), e),
        }
    }
}

impl ReportGeneratorTask {
    async fn generate_report(&self) -> Result<String, String> {
        // 复杂的报表生成逻辑
        tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
        Ok("/tmp/report_2025_12_29.pdf".to_string())
    }
}

总结

这篇文章只是简单了描述了 tokio-cron-scheduler 的用法,有时候我们使用了某个库、某个框后,自然的以为都封装好了,不需要二次封装。其实不是,可以说大部分的库和框架,都需要基于任务进行二次封装,有利于程序的可扩展性。

相关推荐
代码游侠2 小时前
复习——ARM Cortex-A 裸机开发深度解析
arm开发·笔记·嵌入式硬件·学习·架构
努力搬砖的咸鱼3 小时前
Kubernetes 核心对象详解:Pod、Deployment、Service
微服务·云原生·容器·架构·kubernetes
套码汉子3 小时前
从 “重复造轮子” 到 “搭积木式开发”:活动系统架构如何支撑业务高效迭代
架构·系统架构·游戏开发·组件化
短剑重铸之日3 小时前
《7天学会Redis》特别篇: Redis分布式锁
java·redis·分布式·后端·缓存·redission·看门狗机制
小北方城市网4 小时前
SpringBoot 全局异常处理与接口规范实战:打造健壮可维护接口
java·spring boot·redis·后端·python·spring·缓存
Chan164 小时前
【 微服务SpringCloud | 方案设计 】
java·spring boot·微服务·云原生·架构·intellij-idea
hanqunfeng4 小时前
(三十三)Redisson 实战
java·spring boot·后端
哈__5 小时前
2026 年国产时序数据库技术深度解析:多模态融合架构与工程实践
数据库·架构·时序数据库
亲爱的非洲野猪5 小时前
Apigee Hybrid 数据存储架构详解:Redis与数据库的精确分工
数据库·redis·架构
码灵5 小时前
全品类 CPU 架构选型手册
架构