Inotify+rsync 系统实时热备【毫秒级】
一、部署流程
1. 环境准备(主 / 备两台 CentOS/RHEL 7 服务器)
| 节点 | 角色 | 核心配置 |
|---|---|---|
| 主节点 | 被监控端 | 部署 inotify+rsync 客户端 |
| 备节点 | 同步目标端 | 开启 SSH 22 端口 |
| 同步目录 | 主 / 备一致 | /apps(可自定义) |
| 依赖 | 主节点必装 | rsync、inotify-tools、sshpass、curl、openssl、net-tools |
| 核心网卡 | 主节点 | ens36(内网网卡,rsync同步绑定此网卡) |
| 网卡IP | ens36 | 需配置固定IP(如10.0.0.191) |
2. 主节点安装基础依赖(补充 net-tools)
# 安装epel源(inotify-tools依赖)
[ -f /etc/yum.repos.d/epel.repo ] || yum install epel-release -y
# 安装核心组件(补充net-tools用于网卡检测)
yum install rsync inotify-tools sshpass curl openssl net-tools -y
3. 主节点内核优化(保留原配置,增加文件描述符)
cat >/etc/sysctl.d/inotify.conf<<EOF
fs.inotify.max_queued_events=99999999 # 最大监听事件队列
fs.inotify.max_user_watches=99999999 # 最大监听文件数
fs.inotify.max_user_instances=65535 # 最大监听实例数
fs.file-max=6553500 # 新增:提升文件描述符上限
EOF
# 生效配置
sysctl -p /etc/sysctl.d/inotify.conf
4. 主节点创建目录结构(新增进程 PID 目录)
# 业务目录(需同步的目录)
mkdir -p /apps
# 脚本/配置目录
mkdir -p /shell
# 日志目录(同步/推送日志)
mkdir -p /var/log/rsync_inotify
# PID目录(进程守护用)
mkdir -p /var/run/rsync_inotify
5. 主节点编写优化版配置文件
vim /shell/inotify_config.conf
# 实时热备配置文件
# 文件路径:/shell/inotify_config.conf
# 修改后需执行:systemctl restart inotify-rsync
# 远程备节点信息
# 说明:配置备份目标服务器(备节点)的SSH连接信息
REMOTE_USER="root" # SSH登录用户名,建议使用专用账号而非root
REMOTE_HOST="172.16.1.192" # 备节点IP地址(业务内网IP)
REMOTE_PATH="/apps" # 备节点同步目录,建议与主节点DIRNAME保持一致
REMOTE_PASSWORD="oldboy123.com" # SSH登录密码,请确保密码强度足够
# 注意事项:
# 1. 备节点必须开启SSH服务(默认端口22),确保主节点可以SSH免密或密码登录到备节点
# 2. REMOTE_PATH目录必须在备节点上已存在且有写入权限
# 3. 主备节点必须通过同一内网通信,建议使用业务内网IP(172.16.1.x)
# 本地主节点信息
# 说明:配置主节点的监控目录和网络接口
DIRNAME="/apps" # 需要实时监控的本地目录路径
MONITOR_NIC="ens36" # 业务网卡名称,rsync同步将绑定此网卡
MONITOR_NIC_IP="172.16.1.191" # ens36网卡的实际IP地址(业务内网IP)
# 重要提示:
# 1. 使用 ifconfig ens36 命令确认网卡名称和IP地址
# 2. MONITOR_NIC_IP 必须与实际配置完全一致,否则网络检测会失败
# 3. rsync同步会强制通过此网卡进行(使用BindAddress参数)
# 4. DIRNAME目录必须有读取权限,且存在文件变更事件
# 5. 主备节点ens36网卡必须配置在同一网段(172.16.1.0/24)
# 日志文件路径
# 说明:配置各类日志文件的存储路径,便于问题排查
RSYNC_ERR_PATH="/var/log/rsync_inotify/rsync_err.log" # rsync同步失败的错误日志
SCRIPT_RUN_LOG="/var/log/rsync_inotify/script_run.log" # 脚本运行日志(只记录错误和异常)
PUSH_LOG_PATH="/var/log/rsync_inotify/push.log" # 钉钉推送记录
PID_FILE="/var/run/rsync_inotify/inotify_rsync.pid" # 进程PID文件,systemd用于管理进程
ALERT_COOLDOWN_FILE="/tmp/alert_cooldown" # 告警冷却标记文件,防止重复告警
ALERT_COOLDOWN_TIME="300" # 告警冷却时间(秒),300秒=5分钟
# 日志使用说明:
# - 同步失败时查看 RSYNC_ERR_PATH
# - 脚本异常时查看 SCRIPT_RUN_LOG
# - 告警未推送时检查 ALERT_COOLDOWN_FILE
# - 推送记录查看 PUSH_LOG_PATH
# 钉钉机器人配置
# 说明:配置钉钉群机器人的Webhook和加签密钥,用于发送告警通知
# 注意:只推送异常告警,不推送成功消息
ROBOT="https://oapi.dingtalk.com/robot/send?access_token=457eba50934214c031a6fbc1a9f4561389ae70652fdd5d2f51c8c5605034dd2d"
SECRET="SEC6a40e01f9632fd2885dcc339063dd7041b21de521166379bf33742f2f81af0bd"
PUSH_RETRY_TIMES="2" # 推送失败重试次数,网络不稳定时可增加到3-5次
PUSH_TIMEOUT="5" # 单次推送超时时间(秒),内网可设为3,外网建议5-10
# 钉钉配置获取方法:
# 1. 在钉钉群中添加「自定义机器人」
# 2. 安全设置选择「加签」方式
# 3. 复制Webhook地址到 ROBOT 配置项
# 4. 复制加签密钥到 SECRET 配置项
# 5. 测试推送:curl -X POST "${ROBOT}×tamp=xxx&sign=xxx" -H 'Content-Type: application/json' -d '{"msgtype":"text","text":{"content":"test"}}'
# 告警类型说明:
# - nic_error:网卡未启用
# - nic_ip_error:网卡IP不匹配
# - network_error:无法连接备节点
# - sync_fail:文件同步失败
# - sync_success:文件同步成功(仅当OPEN_PUSH_INFO=on时推送)
# - script_exit:脚本异常退出
6. 主节点部署优化版同步脚本
vim /shell/inotify_rsync.sh
#!/bin/bash
# =====================================================
# 金融系统 Inotify+rsync 实时热备脚本
# 功能:监控目录变更,实时同步到备节点,异常告警
# =====================================================
set -uo pipefail
# ================= 第一步:加载配置 =================
CONFIG_FILE="/shell/inotify_config.conf"
if [ ! -f "${CONFIG_FILE}" ]; then
echo "错误:配置文件不存在 ${CONFIG_FILE}"
exit 1
fi
source "${CONFIG_FILE}" || {
echo "错误:配置文件加载失败"
exit 1
}
LOCAL_SERVER_IP=$(ifconfig ${MONITOR_NIC} | grep 'inet ' | awk '{print $2}' | sed 's/addr://')
REMOTE_PORT="22"
PUSH_LOG_PATH="/var/log/rsync_inotify/push.log"
# ================= 第二步:初始化 =================
mkdir -p "$(dirname ${PID_FILE})" \
"$(dirname ${SCRIPT_RUN_LOG})" \
"$(dirname ${RSYNC_ERR_PATH})" \
"$(dirname ${PUSH_LOG_PATH})"
echo $$ > ${PID_FILE}
# ================= 第三步:定义函数 =================
log_info() {
echo "$(date +%F_%T) [INFO] $1" >> ${SCRIPT_RUN_LOG}
}
log_error() {
local msg="$(date +%F_%T) [ERROR] $1"
echo "${msg}" >> ${SCRIPT_RUN_LOG}
echo "${msg}" >> ${RSYNC_ERR_PATH}
}
ding_sign() {
local timestamp=$(date +%s%3N)
local sign=$(echo -en "${timestamp}\n${SECRET}" | \
openssl dgst -sha256 -hmac "${SECRET}" -binary | \
base64 | tr -d '\n')
echo "timestamp=${timestamp}&sign=${sign}"
}
# 钉钉告警推送
ding_push() {
local alert_title="$1"
local alert_detail="$2"
local alert_type="${3:-}"
local push_success=0
# 防重复告警(抢占式冷却)
if [ -n "${alert_type}" ]; then
local cooldown_file="${ALERT_COOLDOWN_FILE}.${alert_type}"
local lock_file="${cooldown_file}.lock"
# 检查文件锁(防止并发)
if [ -f "${lock_file}" ]; then
local lock_age=$(($(date +%s) - $(cat "${lock_file}" 2>/dev/null || echo 0)))
if [ ${lock_age} -lt 5 ]; then # 5秒内认为正在推送
log_info "告警推送中,跳过: ${alert_type}"
return 0
else
rm -f "${lock_file}" # 锁超时,清除
fi
fi
# 检查冷却时间
if [ -f "${cooldown_file}" ]; then
local last_time=$(cat "${cooldown_file}" 2>/dev/null || echo 0)
local now=$(date +%s)
local age=$((now - last_time))
if [ ${age} -lt ${ALERT_COOLDOWN_TIME} ]; then
log_info "告警被冷却拦截: ${alert_type} (剩余 $((ALERT_COOLDOWN_TIME - age)) 秒)"
return 0
fi
fi
# 抢占锁和冷却文件(在推送前就创建)
date +%s > "${lock_file}"
date +%s > "${cooldown_file}"
fi
# 设置默认告警详情
local alert_detail="${2:-无详细信息}"
# 构建告警消息
local timestamp=$(date +%Y-%m-%d\ %H:%M:%S)
local msg_content="🔔 *${alert_title}*
${alert_detail}
━━━━━━━━━━━━━━
• 服务器: ${LOCAL_SERVER_IP}
• 时间: ${timestamp}"
local push_url="${ROBOT}&$(ding_sign "${SECRET}")"
for ((i=1; i<=${PUSH_RETRY_TIMES}; i++)); do
local result=$(curl -s --max-time ${PUSH_TIMEOUT} -X POST "${push_url}" \
-H "Content-Type: application/json" -d "${msg_content}" 2>/dev/null)
echo "$(date +%F_%T) [PUSH-${i}] ${result}" >> ${PUSH_LOG_PATH}
if echo "${result}" | grep -q '"errcode":0'; then
push_success=1
break
fi
sleep 1
done
[ ${push_success} -eq 0 ] && log_error "钉钉推送失败"
}
# 网络检测
check_network() {
if ! ifconfig ${MONITOR_NIC} >/dev/null 2>&1; then
log_error "网卡异常:${MONITOR_NIC}"
ding_push "网卡异常" "网卡 ${MONITOR_NIC} 未启用" "nic_error"
return 1
fi
local nic_ip=$(ifconfig ${MONITOR_NIC} | grep 'inet ' | awk '{print $2}' | sed 's/addr://')
if [ "${nic_ip}" != "${MONITOR_NIC_IP}" ]; then
log_error "网卡IP异常:${MONITOR_NIC}=${nic_ip}"
ding_push "网卡IP异常" "网卡: ${MONITOR_NIC}\n当前IP: ${nic_ip}\n期望IP: ${MONITOR_NIC_IP}" "nic_ip_error"
return 1
fi
if ! nc -z -w 3 -s ${MONITOR_NIC_IP} ${REMOTE_HOST} ${REMOTE_PORT} >/dev/null 2>&1; then
log_error "网络异常:无法连接 ${REMOTE_HOST}:${REMOTE_PORT}"
ding_push "网络异常" "无法连接备节点 ${REMOTE_HOST}:${REMOTE_PORT}\n使用网卡: ${MONITOR_NIC}(${MONITOR_NIC_IP})" "network_error"
return 1
fi
return 0
}
# ================= 第四步:启动监控 =================
ssh-keyscan -H ${REMOTE_HOST} >> ~/.ssh/known_hosts 2>/dev/null || true
inotifywait -mrq -e 'create,close_write' \
--timefmt "%Y-%m-%d_%H:%M:%S" \
--format "%T %w%f %e" \
--exclude '.swp$|.swx$|4913|~$' \
"${DIRNAME}" | while read -r line
do
LOCAL_SOURCE=$(echo "${line}" | awk '{print $2}')
if ! check_network; then
continue
fi
sshpass -p "${REMOTE_PASSWORD}" rsync -avz \
--timeout=10 \
-e "ssh -o StrictHostKeyChecking=no \
-o ConnectTimeout=5 \
-o BindAddress=${MONITOR_NIC_IP}" \
${DIRNAME}/ ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/ \
2>>${RSYNC_ERR_PATH}
if [ $? -ne 0 ]; then
log_error "同步失败: ${LOCAL_SOURCE}"
ding_push "文件同步失败" "同步文件: ${LOCAL_SOURCE}\n目标节点: ${REMOTE_HOST}:${REMOTE_PATH}" "sync_fail"
fi
done
# ================= 第五步:异常退出 =================
log_error "脚本异常退出!"
ding_push "脚本异常退出" "监控脚本异常退出,请检查日志" "script_exit"
rm -f ${PID_FILE}
exit 1
7.主节点脚本授权与验证
# 赋予执行权限
chmod +x /shell/inotify_rsync.sh
# 创建日志目录
mkdir -p /var/log/rsync_inotify
mkdir -p /var/run/rsync_inotify
8. 创建 systemd 服务(推荐:系统级进程管理)
vim /etc/systemd/system/inotify-rsync.service
[Unit]
Description=Inotify Rsync Real-time Backup Service
After=network.target
Wants=network.target
[Service]
Type=simple
ExecStart=/shell/inotify_rsync.sh
Restart=always
RestartSec=10
StandardOutput=append:/var/log/rsync_inotify/script_run.log
StandardError=append:/var/log/rsync_inotify/script_run.log
# 资源限制(可选)
LimitNOFILE=65535
LimitNPROC=4096
# 进程管理
PIDFile=/var/run/rsync_inotify/inotify_rsync.pid
[Install]
WantedBy=multi-user.target
启用并启动服务:
# 重新加载 systemd 配置
systemctl daemon-reload
# 设置开机自启动并启动服务
systemctl enable --now inotify-rsync
# 查看服务状态
echo "=== 服务状态 ==="
systemctl status inotify-rsync --no-pager
echo "=== 进程状态 ==="
ps -ef | grep inotify_rsync | grep -v grep
echo "=== PID文件 ==="
cat /var/run/rsync_inotify/inotify_rsync.pid 2>/dev/null || echo "PID文件暂未生成"
输出示例(正常状态):
● inotify-rsync.service - Inotify Rsync Real-time Backup Service Loaded: loaded (/etc/systemd/system/inotify-rsync.service; enabled) Active: active (running) since Mon 2026-05-04 14:20:00 CST; 5s ago Main PID: 15680 (inotify_rsync.sh) Tasks: 3 Memory: 8.5M CGroup: /system.slice/inotify-rsync.service ├─15680 /bin/bash /shell/inotify_rsync.sh └─15681 inotifywait -mrq -e create,close_write /apps
9. 钉钉机器人测试
source /shell/inotify_config.conf
# 简单测试推送
ding_sign() {
local timestamp=$(date +%s%3N)
local sign=$(echo -en "${timestamp}\n${SECRET}" | openssl dgst -sha256 -hmac "${SECRET}" -binary | base64 | tr -d '\n')
curl -s -X POST "${ROBOT}×tamp=${timestamp}&sign=${sign}" \
-H "Content-Type: application/json" \
-d "{\"msgtype\":\"text\",\"text\":{\"content\":\"热备系统测试:服务器${LOCAL_SERVER_IP}\\n时间:$(date +%F_%T)\"}}"
}
ding_sign
成功返回:
{"errcode":0,"errmsg":"ok"}
三、快速验证
1. 服务状态检查
# 查看 systemd 服务状态
systemctl status inotify-rsync --no-pager
# 查看进程是否运行
ps -ef | grep inotify_rsync | grep -v grep
# 查看 PID 文件
cat /var/run/rsync_inotify/inotify_rsync.pid
预期结果:
- ✅ 服务状态为
active (running) - ✅ 进程正常运行
- ✅ PID 文件存在且有效
2. 基础同步验证
# 主节点创建测试文件
echo "test $(date)" > /apps/test_sync.log
sleep 5
# 备节点验证
ssh root@172.16.1.192 "cat /apps/test_sync.log"
# 清理
rm -f /apps/test_sync.log
ssh root@172.16.1.192 "rm -f /apps/test_sync.log"
预期结果: 备节点文件内容与主节点一致
3. 告警功能验证
# 模拟网络故障(会触发告警)
ssh root@172.16.1.192 "systemctl stop sshd"
echo "test_alert" > /apps/test_alert.txt
sleep 5
# 查看日志
tail -5 /var/log/rsync_inotify/script_run.log
tail -5 /var/log/rsync_inotify/push.log
# 恢复
ssh root@172.16.1.192 "systemctl start sshd"
rm -f /apps/test_alert.txt
预期结果:
- script_run.log 显示“网络异常”
- push.log 显示钉钉推送成功
- 收到钉钉告警消息
4. 日志查看
# 脚本运行日志(只记录错误)
tail -20 /var/log/rsync_inotify/script_run.log
# rsync错误日志
tail -20 /var/log/rsync_inotify/rsync_err.log
# 钉钉推送记录
tail -20 /var/log/rsync_inotify/push.log
# 实时监控日志
tail -f /var/log/rsync_inotify/script_run.log
四、日常维护
1. 服务管理
# 重启服务(修改配置后需要重启)
systemctl restart inotify-rsync
# 停止/启动服务
systemctl stop inotify-rsync
systemctl start inotify-rsync
# 查看状态
systemctl status inotify-rsync --no-pager
# 开机自启
systemctl enable inotify-rsync
2. 日志查看
# systemd日志(推荐)
journalctl -u inotify-rsync -f
# 脚本运行日志(只记录错误)
tail -f /var/log/rsync_inotify/script_run.log
# rsync错误日志
tail -f /var/log/rsync_inotify/rsync_err.log
# 钉钉推送记录
tail -f /var/log/rsync_inotify/push.log
# 查看最近100行
journalctl -u inotify-rsync -n 100 --no-pager
3. 配置管理
# 备份配置文件
cp /shell/inotify_config.conf /shell/inotify_config.conf.bak_$(date +%Y%m%d)
# 编辑配置
vim /shell/inotify_config.conf
# 重新加载(需重启服务)
systemctl restart inotify-rsync
4. 网络测试
# 测试SSH连接
ssh root@172.16.1.192 "echo OK"
# 测试端口连通性
nc -z -w 3 172.16.1.192 22 && echo "OK" || echo "FAIL"
# 查看网卡状态
ifconfig ens36
正文完