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 的用法,有时候我们使用了某个库、某个框后,自然的以为都封装好了,不需要二次封装。其实不是,可以说大部分的库和框架,都需要基于任务进行二次封装,有利于程序的可扩展性。

相关推荐
love530love6 小时前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
星辰徐哥6 小时前
Spring Boot 微服务架构设计与实现
spring boot·后端·微服务
星辰徐哥6 小时前
Spring Boot 数据导入导出与报表生成
spring boot·后端·ui
明夜之约6 小时前
Spring Boot 自动装配源码
java·spring boot·后端
Leaton Lee6 小时前
Spring Boot分层架构详解:从Controller到Service再到Mapper的完整流程
java·spring boot·后端·架构
Micro麦可乐6 小时前
Spring Boot 实战:从零设计一个短链系统(含完整代码与数据库设计)
数据库·spring boot·后端·哈希算法·雪花算法·短链系统
Jinkxs6 小时前
Resilience4j- 与 Spring Boot 快速集成:自动配置与基础注解使用
java·spring boot·后端
毕设源码_郑学姐6 小时前
计算机毕业设计springboot网络相册设计与实现 基于Spring Boot框架的在线相册管理系统开发与应用 Spring Boot驱动的网络影集设计与实践
spring boot·后端·课程设计
辣机小司6 小时前
【踩坑记录:Spring Boot 配置文件读取值不一致?警惕 YAML 的“八进制陷阱”与 SnakeYAML 版本之谜】
java·spring boot·后端·yaml·踩坑记录
码农阿豪6 小时前
从零到一:Spring Boot快速接入金仓数据库实战
数据库·spring boot·后端