一个带有REST API控制的简单任务调度器。
- 基于文件的任务配置
- 支持cron表达式
- 进程锁防止多个实例同时运行
- 变量替换功能(支持
${tasksDir}) - 任务配置热重载
- API主机和端口配置(默认:127.0.0.1:18081)
- 带有安全限制的命令执行端点
| 变量 | 默认值 | 说明 |
|---|---|---|
SCHEDULER_API_HOST |
127.0.0.1 |
API服务器绑定的主机IP(s)。支持多个IP,用逗号分隔。 |
SCHEDULER_API_PORT |
18081 |
API服务器端口号 |
SCHEDULER_WORKER_THREADS |
8 |
用于任务执行的工作线程数 |
SCHEDULER_MAX_TASKS |
64 |
可以执行的最大任务数 |
在服务器上执行一个shell命令。
请求体:
{
"cmd": "ls"
}响应:
200 OK- 命令成功执行403 Forbidden- 命令被限制(例如,危险命令如rm -rf /)408 请求超时- 命令执行在30秒后超时500 内部服务器错误- 命令执行因内部错误失败
示例请求:
curl -s -X POST http://localhost:18081/exec \
-H "Content-Type: application/json" \
-d '{"cmd":"ls"}' | jq示例响应:
{
"status": 0,
"stdout": "file1.txt\nfile2.txt",
"stderr": ""
}- "status": 0 表示成功,其它值表示失败。
- 接口超时时间为30秒,如果有执行命令超过30秒的需求,请使用task调度,异步通知方案。
为了安全起见,以下命令被完全禁止执行:
rebootshutdownrm -rf /dd if=/dev/zero of=/dev/sdamkfs.ext4 /dev/sdamv /home /tmppoweroffhaltinit 0fdisk /dev/sda
此外,以下命令前缀也被限制(以防止潜在的危险操作):
rm(文件删除)chmod(权限修改)chown(所有者修改)
如果尝试执行这些命令,API 将返回 403 Forbidden 状态码。
该服务默认阻止潜在危险命令,包括:
- 系统重启/关机命令(
reboot、shutdown、poweroff等) - 文件系统破坏命令(
rm -rf /、dd、mkfs.ext4等) - 潜在破坏性的磁盘操作
- 权限修改命令(
chmod、chown)
服务默认绑定到127.0.0.1以确保安全,不应在没有适当身份验证的情况下公开暴露。
- ⚡ 并行执行: 默认 8 个工作线程,最大支持 64 个并行任务
- 🎯 多种调度模式: 支持一次性任务、cron 表达式和定时间隔
- 🔄 动态配置: 监控配置文件变化,自动重载任务
- 🔐 进程锁: 防止同一配置目录下多个实例同时运行
- 💫 变量替换: 支持
${tasksDir}变量替换 - 📄 日志管理: 自动日志轮转,支持自定义日志文件权限
原生太的cron只支持分钟级,无法满足秒级需求。 因此简单任务调度器实现了秒级及cron的整合。
- 配置目录:
tasks目录 - 任务描述: 每个任务以一个 JSON 文件进行描述
- 文件格式: 必须以
.spec.json结尾
基础格式示例:
{
"name": "关键数据备份任务",
"interval": 60,
"command": "/usr/bin/bash /opt/scripts/backup.sh",
"log": "${tasksDir}/backup.sh.log",
"error": "${tasksDir}/backup.sh.error.log"
}完整格式支持:
{
"name": "完整任务示例",
"enabled": true,
"interval": 60,
"cron": "0 0 * * *",
"once": false,
"command": "/usr/bin/bash ${tasksDir}/scripts/task.sh",
"log": "${tasksDir}/task.log",
"error": "${tasksDir}/task.error.log",
"notify_url": "http://localhost:3000/webhook"
}- ✅ 初始化时加载
tasks目录下的所有.spec.json任务 - ✅ 使用动态 JSON 解析,无需定义额外的 task 结构体
- ✅ 支持递归扫描子目录
- ✅ 监控
tasks目录文件变化 - ✅ JSON 文件修改时自动重新加载
- ✅ 支持热重载,无需重启进程
- ✅ 使用
shell-words处理复杂长命令 - ✅ 支持变量替换 (
${tasksDir}) - ✅ 安全的命令解析和执行
- ✅ 记录任务每个周期的开始时间
- ✅ 记录 CPU、内存使用情况
- ✅ 记录总执行时间
- ✅ 日志输出到指定的 log 文件
- ✅ 自动日志轮转(20MB)
- ✅ 错误日志写入对应的 error 文件
- ✅ 异常处理不影响其他任务执行
- ✅ 详细的错误信息记录
- ✅ 使用最新的 Rust 组件和依赖
- ✅ 所有实现集中在
main.rs中 - ✅ 异步编程模型
优化目标: 将串行任务执行器修改为并行任务执行器
调度算法:
下一个任务开始时间 = 任务间隔时间 - 任务执行时间
边界条件处理:
如果 任务间隔时间 < 任务执行时间:
则 下一个任务开始时间 = 0 (立即执行)
实现特性:
- ✅ 默认支持最多 64 个并行任务
- ✅ 智能调度避免任务堆积
- ✅ 独立线程池管理
- ✅ 任务执行状态监控
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 配置监控器 │────│ 任务调度器 │────│ 执行器线程池 │
│ (File Watcher) │ │ (Task Scheduler) │ │ (Thread Pool) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│ JSON 文件变化 │ 任务分发 │ 并行执行
▼ ▼ ▼
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 动态重载 │ │ 智能调度 │ │ 日志 & 监控 │
│ (Hot Reload) │ │ (Smart Schedule) │ │ (Log & Monitor) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
| 组件 | 版本 | 描述 |
|---|---|---|
| 操作系统 | Linux | 支持所有主流发行版 |
| Rust | 1.70+ | 编译时必需 |
| 内存 | 128MB+ | 推荐配置 |
# 克隆项目
git clone <repository-url>
cd simple-scheduler
# 构建发布版本
cargo build --release
# 或使用构建脚本生成多架构二进制文件
./build.sh| 命令 | 描述 |
|---|---|
cargo build --release |
构建当前架构的优化版本 |
./build.sh |
构建多架构二进制文件 |
cargo test |
运行测试 |
simple-scheduler/
├── 🚀 simple-scheduler # 可执行文件
└── 📂 tasks/ # 任务配置目录
├── 📂 hello/
│ ├── 📜 hello.js
│ └── ⚙️ hello.spec.json # 任务配置文件
└── 📂 sysinfo/
├── 📜 sysinfo.sh
└── ⚙️ sysinfo.spec.json
任务配置文件必须以 .spec.json 结尾,格式如下:
{
"name": "任务名称",
"enabled": true,
"command": "/usr/bin/bash ${tasksDir}/script.sh",
"log": "${tasksDir}/task.log",
"error": "${tasksDir}/task.error.log",
"once": false,
"cron": "0 0 * * *",
"interval": 60,
"notify_url": "http://localhost:3000/webhook"
}| 字段 | 类型 | 必需 | 默认值 | 描述 |
|---|---|---|---|---|
name |
string | ✅ | - | 任务名称(唯一标识) |
enabled |
boolean | ❌ | true |
是否启用任务 |
command |
string | ✅ | - | 要执行的命令 |
log |
string | ✅ | - | 标准输出日志文件路径 |
error |
string | ✅ | - | 错误日志文件路径 |
once |
boolean | ❌ | false |
是否只执行一次 |
cron |
string | ❌ | - | Cron 表达式(支持标准 5 字段格式) |
interval |
number | ❌ | 60 |
执行间隔(秒) |
notify_url |
string | ❌ | - | 任务完成后的通知 URL |
任务调度按以下优先级执行:
once > cron > interval
📖 点击查看详细示例
{
"once": true,
"cron": "0 0 * * *",
"interval": 60
}⚡ 执行一次后退出,忽略 cron 和 interval
{
"once": false,
"cron": "0 0 * * *",
"interval": 60
}⏰ 每天 0 点执行一次,忽略 interval
{
"once": false,
"cron": "",
"interval": 3
}🔄 每 3 秒执行一次
# 使用默认配置目录 ./tasks
./simple-scheduler
# 指定配置目录
./simple-scheduler --tasks-dir /path/to/tasks
# 查看版本
./simple-scheduler --version
# 查看帮助
./simple-scheduler --help
# 使用systemd方案启动(默认)
./simple-scheduler/install.sh
# 使用pm2方案启动
pm2 start /opt/simple-scheduler/simple-scheduler-$(uname -m) --name "simple-scheduler"
pm2 save# 设置工作线程数和最大任务数
export SCHEDULER_WORKER_THREADS=4
export SCHEDULER_MAX_TASKS=16
# 运行调度器
./simple-scheduler --tasks-dir $(pwd)/tasks在 command、log、error 字段中都可以使用 ${tasksDir} 变量:
{
"command": "/usr/bin/node ${tasksDir}/scripts/app.js",
"log": "${tasksDir}/app.log",
"error": "${tasksDir}/app.error.log"
}💡 字段替换规则:
command中的${tasksDir}会替换为任务根目录(--tasks-dir对应目录)的绝对路径。log/error中的${tasksDir}会替换为当前任务*.spec.json所在目录的绝对路径。
- 🔄 自动轮转: 当日志文件超过 20MB 时自动清空
- 🔐 权限管理: 自动设置日志文件权限为 777,支持多用户访问
- 📊 详细记录: 包含任务开始时间、执行时长、输出内容等信息
2025-07-03 15:44:11 - Started at 2025-07-03 15:44:11, Total Time: 0.05s
Task output content here...
每个 tasks 目录对应一个进程锁,防止多个实例同时管理相同的任务集:
# 实例 1 - 成功启动
./simple-scheduler --tasks-dir /opt/tasks
# ✅ 成功启动
# 实例 2 - 启动失败
./simple-scheduler --tasks-dir /opt/tasks
# ❌ 输出: Error: Another simple-scheduler instance is already running for tasks directory '/opt/tasks'💡 锁文件存储在
/tmp/simple-scheduler-*.lock
当 once: true 的任务执行完成后,会向 notify_url 发送 POST 请求:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"name":"任务名称","command":"...","..."}' \
http://localhost:3000/webhook🔐 权限被拒绝错误
- 检查日志文件权限
- 确保进程有写入日志目录的权限
解决方案:
# 检查日志目录权限
ls -la /tmp/
# 修改权限
chmod 777 /tmp/your-log-directory⏸️ 任务不执行
- 检查
enabled字段是否为true - 验证 cron 表达式格式
- 查看错误日志文件
检查清单:
- 配置文件格式正确
- 任务已启用
- 命令路径正确
- 日志文件路径可写
⚡ 多实例冲突
- 确保不同的 tasks 目录
- 检查
/tmp/simple-scheduler-*.lock文件
解决方案:
# 查看现有锁文件
ls -la /tmp/simple-scheduler-*.lock
# 清理无效锁文件(确保进程已退出)
rm /tmp/simple-scheduler-*.lock