如何用Bash实现主从环境下基于文件的简易通知队列?
基于Bash的主从环境后台脚本完成通知系统方案
场景与核心需求
场景说明
- 主从架构:Environment A(主环境)负责管理Environment B(从环境),支持远程配置、执行脚本等操作
- 通信限制:Environment B无法主动发起与A的网络通信
- 痛点:A在B的后台执行脚本(如
./myScript.sh &)后,无法高效感知脚本完成状态(轮询ps -aux的方式通用性差)
核心需求
- 后台脚本执行完成后,需在B的"邮箱"中留下完成消息
- A需定期通过SSH检查B的"邮箱",消费所有新消息
- 消息的写入与消费需避免竞争条件/并发冲突
- 实现必须基于纯Bash
现有方案分析
方案A:单文件邮箱+文件锁
- 实现逻辑:
myScript.sh执行完成后,向指定文件追加消息:echo "script_done: $(basename "$0") $(date)" >> /var/run/script_mailbox.log- A通过SSH连接B,使用
flock加排他锁后读取并清空文件:ssh user@envB 'flock -x /var/run/script_mailbox.log -c "cat /var/run/script_mailbox.log > /tmp/mailbox_content && > /var/run/script_mailbox.log" && cat /tmp/mailbox_content'
- 优缺点:
- ✅ 单文件管理逻辑简单,适合消息量较小的场景
- ❌ 锁操作依赖严格的逻辑控制,一旦出错易导致消息丢失或重复消费
- ❌ 单文件内容积累过多后,读取、清空的效率会下降
方案B:目录式邮箱(独立消息文件)
- 实现逻辑:
myScript.sh执行完成后,在指定目录创建唯一命名的消息文件:echo "script_done: $(basename "$0") PID:$$ $(date)" > /var/run/script_mailbox/$(basename "$0")_$$_$(date +%s).msg- A通过SSH连接B,遍历目录读取文件后删除:
ssh user@envB 'for msg_file in /var/run/script_mailbox/*.msg; do if [ -f "$msg_file" ]; then cat "$msg_file" rm -f "$msg_file" fi done'
- 优缺点:
- ✅ 实现简单,无需复杂锁逻辑,单个消息文件的读写天然规避部分并发问题
- ✅ 消息文件独立,单条消息处理失败不会影响其他消息
- ❌ A遍历过程中若B有新消息写入,可能出现遗漏
- ❌ 大量消息文件会导致目录遍历效率下降
推荐优化方案:原子目录移动+目录式邮箱
基于方案B做针对性优化,通过文件系统原子操作解决并发遍历的遗漏问题,同时保证消息消费的安全性:
实现步骤
1. Environment B的消息写入逻辑
预先创建固定目录结构,脚本完成后将消息写入临时接收目录:
# 初始化目录(可在系统启动时执行) mkdir -p /var/run/script_mailbox/incoming # myScript.sh 执行完成后的消息写入 MSG_CONTENT="script_done: $(basename "$0") PID:$$ ExitCode:$? FinishTime:$(date +%Y-%m-%d_%H:%M:%S)" # 用PID+纳秒时间戳保证文件名绝对唯一 MSG_FILE="/var/run/script_mailbox/incoming/msg_$$_$(date +%N).msg" echo "$MSG_CONTENT" > "$MSG_FILE"
2. Environment A的消费逻辑
通过原子移动目录的方式,先将待消费消息转移到临时处理目录,再统一处理,彻底避免遍历过程中的新消息遗漏:
ssh user@envB ' # 创建本次消费专用的临时处理目录 PROCESS_DIR="/var/run/script_mailbox/processing_$(date +%s_%N)" mkdir -p "$PROCESS_DIR" # 原子移动incoming目录下的所有消息到临时处理目录 for file in /var/run/script_mailbox/incoming/*.msg; do if [ -f "$file" ]; then mv "$file" "$PROCESS_DIR/" fi done # 处理临时目录中的消息并清理 if [ -n "$(ls -A "$PROCESS_DIR")" ]; then cat "$PROCESS_DIR"/*.msg rm -rf "$PROCESS_DIR" else rm -rf "$PROCESS_DIR" fi '
方案优势
- ✅ 原子移动操作彻底解决了A遍历过程中B写入新消息的遗漏问题
- ✅ 消息文件独立,单条消息处理失败不影响其他消息
- ✅ 无需复杂文件锁,依赖文件系统原生原子操作保证并发安全
- ✅ 用PID+纳秒时间戳命名消息文件,完全避免文件名冲突
补充细节建议
- 权限配置:确保
/var/run/script_mailbox目录的权限允许脚本写入、A的SSH用户读取删除 - 消息扩展:可在消息中加入脚本退出码、标准输出/错误摘要,方便A做后续业务处理
- 异常清理:定期清理
processing目录中残留的未处理文件(如通过cron任务)
内容的提问来源于stack exchange,提问作者ismarlowe
相关产品推荐
相关产品推荐

