使用Inotifywait监控事件并Rsync同步变更

1. 背景

服务无法做成高可用,只能采用2个节点一主、一备(冷备)模式。为了保证主节点文件实时同步到备份节点,使用inotifywait监控主节点目录的事件,并触发rsync同步变更到备份节点。

2. 环境

  1. 服务器 VM 8C/32G/200G 2台

    • 192.168.16.41 主节点
    • 192.168.16.51 备份节点
  2. 操作系统 Ubuntu24.04

3. 安装依赖

bash 复制代码
# CentOS
#yum install inotify-tools rsync cron  
# Ubuntu
apt install inotify-tools rsync cron   

4. 创建目录

bash 复制代码
mkdir -p /public/script/logs

5. 修改系统参数

5.1 系统参数

bash 复制代码
vim /etc/sysctl.conf 
bash 复制代码
# 增加 inotify 的相关配置,解决tail: inotify 资源耗尽的错误
# 默认128
fs.inotify.max_user_instances = 12800
# 默认8192
fs.inotify.max_user_watches = 819200

5.2 加载生效

bash 复制代码
sysctl -p

6. 备份节点

6.1 配置rsync

  1. 配置rsync
bash 复制代码
vim /etc/rsyncd.conf 
bash 复制代码
# /etc/rsyncd: configuration file for rsync daemon mode
# See rsyncd.conf man page for more options.
# configuration example:

uid = 0
gid = 0
use chroot = no

max connections = 100
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd/rsyncd.log 
# exclude = lost+found/
transfer logging = yes
# timeout = 900
ignore nonreadable = yes
dont compress   = *.gz *.tgz *.zip *.z *.Z *.rpm *.deb *.bz2
#include = *.gz *.tgz *.zip *.z *.Z *.bz2

[public]
        path = /public/
        # 允许写入
        read only = false
        list = yes
        #exclude = logs/ log/ *.hprof *.log log*
        auth users = rsyncuser
        secrets file = /etc/rsync.password
        # 只允许192.168.16.41连接
        hosts allow = 192.168.16.41
       
  1. 配置密码
bash 复制代码
vim /etc/rsync.password 
  • 账号和密码为示例,请自定义并与主节点上的密码保持一致
bash 复制代码
rsyncuser:kxx0000
  1. 文件赋权
bash 复制代码
ll /etc/rsync*
bash 复制代码
# 注意密码文件必须是600
-rw-------   1 root root   18 Aug 23  2024 rsync.password
-rw-r--r--   1 root root 1204 Apr  9 15:03 rsyncd.conf
  1. 重启rsync
bash 复制代码
systemctl start rsync
systemctl enable rsync

7. 主节点

7.1 编写脚本

  1. 脚本说明

    • 脚本目录:/public/script
    • 脚本rsync密码文件(权限600):/public/script/rsync_conf/rsync.password
    • rsync密码需要与备份节点设置相同:注意只需要密码(不需要账号),例如:
      kxx0000
    • 监控目录:/public/application
    • 远程目录:application (远程rsync服务的根目录是/public,所以合并/public/application,保持与监控目录一致)
    • 监控事件忽略某些文件和目录(inotifywait语法,正则匹配):EXCLUDE_PATTERN
    • rsync同步忽略某些文件和目录(rsync语法):RSYNC_EXCLUDES
    • rsync使用排它锁,保证同一时间只有一个rsync进程执行:/usr/bin/flock
    • inotifywait监控事件包括: create, modify, delete, move, attrib
  2. 脚本内容(详见注释)

bash 复制代码
#!/bin/bash

#set -x

echo """
#########################################################################################################################

  Real-time sync /public/application to remote rsync server
  1. Monitor /public/application recursively using inotifywait
  2. Trigger rsync when any file/directory changes
  3. Preserve original filenames (ignore some files)
  4. Log events and delete old logs

#########################################################################################################################
"""

# Define vars
SCRIPT_HOME=/public/script
PWD_FILE=${SCRIPT_HOME}/rsync_conf/rsync.password

LOG_FILE_NAME=`basename "$0" .sh`
CURRENT_DATE=$(date +"%Y-%m-%d")
LOG_FILE=$SCRIPT_HOME/logs/${LOG_FILE_NAME}_${CURRENT_DATE}.log
LOG_RETAIN_DAY=14

# The directory to monitor
WATCH_DIR=/public/application

# Remote rsync settings
LOCAL_HOST_IP="192.168.16.41"      # 本机IP,用于远程路径组织
REMOTE_HOST_IP="192.168.16.51"
REMOTE_MODULE="public"         # rsync 模块名
REMOTE_BASE_PATH="application"

# Sync lock file to prevent concurrent rsync runs
LOCK_FILE="/tmp/rsync_public_application.lock"


# 定义inotifywait 排除的正则表达式
# ^onlyoffice/rabbitmq/ 匹配路径中包含该目录片段(注意前导 ^ 表示从监控根开始匹配)
# .*\.(swp|tmp|log|out) 匹配任意文件名,只要它以 .swp、.tmp、.log 或 .out 结尾的文件
# .*~ 匹配以 ~ 结尾的文件
EXCLUDE_PATTERN='(^onlyoffice/rabbitmq/|.*[.](swp|tmp|log|out)$|.*~$)'

# 定义rsync 排除项(相对于源目录 /public/application/)
#RSYNC_EXCLUDES="--exclude=onlyoffice/rabbitmq"
# 如果有多个排除项,可以继续添加,例如:
RSYNC_EXCLUDES="--exclude=onlyoffice/rabbitmq --exclude=*.swp --exclude=*.tmp --exclude=*.log --exclude=*.out"


###############################   start   #########################################
DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
echo -e "### ${DATE_TIME}: $(dirname $0)/$(basename $0) starts ###" >> $LOG_FILE

# Create logs directory if not exists
if [ ! -d "$SCRIPT_HOME/logs" ]; then
   mkdir -p "$SCRIPT_HOME/logs"
   DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
   echo -e "### ${DATE_TIME}: Directory $SCRIPT_HOME/logs has been created ###" >> $LOG_FILE
fi

# Check if watch directory exists
if [ ! -d "$WATCH_DIR" ]; then
   DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
   echo -e "### ${DATE_TIME}: $WATCH_DIR does not exist, exit ###" >> $LOG_FILE
   exit 1
fi

# Function to perform rsync sync
do_rsync() {
    # Use flock to avoid multiple rsync processes
    (
        /usr/bin/flock -x -w 60 200 || exit 1
        # -x:请求排他锁(exclusive lock)
        # -w 60:超时等待 60 秒,退出子shell,返回1(非0)
        # 200:自定义文件描述符的编号,与200>"$LOCK_FILE" 中值保持一致,表示打开同一个文件描述符。
        DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
        echo -e "### ${DATE_TIME}: Syncing $WATCH_DIR to remote ..." >> $LOG_FILE
        
        /usr/bin/rsync -avz --delete --password-file=${PWD_FILE} \
            $RSYNC_EXCLUDES \
            "$WATCH_DIR/" \
            "rsync://rsyncuser@${REMOTE_HOST_IP}:873/${REMOTE_MODULE}/${REMOTE_BASE_PATH}/"
        
        if [ $? -eq 0 ]; then
            DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
            echo -e "### ${DATE_TIME}: Sync completed successfully ###" >> $LOG_FILE
        else
            DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
            echo -e "### ${DATE_TIME}: Sync failed with error code $? ###" >> $LOG_FILE
        fi
    ) 200>"$LOCK_FILE"
}

# Delayed sync: collect events, wait for a quiet period, then sync
sync_triggered=0
delayed_sync() {
    if [ $sync_triggered -eq 0 ]; then
        sync_triggered=1
        # Wait 5 seconds to see if more events come
        sleep 5
        do_rsync
        sync_triggered=0
    fi
}

# Monitor events recursively
DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
echo -e "### ${DATE_TIME}: Start monitoring $WATCH_DIR for changes ###" >> $LOG_FILE

# Use inotifywait to monitor events: create, modify, delete, move, attrib
/usr/bin/inotifywait -m -r --exclude "$EXCLUDE_PATTERN" -e create -e modify -e delete -e move -e attrib "$WATCH_DIR" | while read path action file; do
    # 忽略匹配模式的文件
    #case "$file" in
    #    $IGNORE_PATTERNS) continue ;;
    #esac

    DATE_TIME=$(date +"%Y-%m-%d %H:%M:%S")
    echo -e "### ${DATE_TIME}: Event: $action on $path$file ###" >> $LOG_FILE
    
    # Trigger rsync after a short delay (to combine bursts of events)
    delayed_sync &
    
    # Clean old logs periodically (once per event, but it's cheap)
    find "$SCRIPT_HOME/logs" -maxdepth 1 -mtime +$LOG_RETAIN_DAY -name "${LOG_FILE_NAME}*.log" -exec rm -rf {} \;
done

7.2 配置服务

  1. 编写服务脚本
bash 复制代码
vim rsync_backup.service
bash 复制代码
[Unit]
Description=rsync_backup.sh script
After=network.target
 
[Service]
Type=simple
PIDFile=/run/rsync_backup.pid
ExecStart=/public/script/rsync_backup.sh
ExecStop=/bin/kill -9 $MAINPID
#Restart=on-failure
Restart=always
PrivateTmp=true
#User=username
#Group=groupname
 
[Install]
WantedBy=multi-user.target
  1. 配置systemd
bash 复制代码
ln -s /public/script/rsync_backup.service /etc/systemd/system/
bash 复制代码
systemctl daemon-reload
systemctl enable rsync_backup.service 
bash 复制代码
systemctl start rsync_backup.service 
systemctl status rsync_backup.service 
bash 复制代码
● rsync_backup.service - rsync_backup.sh script
     Loaded: loaded (/etc/systemd/system/rsync_backup.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-04-09 19:51:31 CST; 23min ago
   Main PID: 2342963 (rsync_backup.sh)
      Tasks: 3 (limit: 38332)
     Memory: 1.1M (peak: 6.3M)
        CPU: 5.238s
     CGroup: /system.slice/rsync_backup.service
             ├─2342963 /bin/bash /public/script/rsync_backup.sh
             ├─2342971 /usr/bin/inotifywait -m -r --exclude "(^onlyoffice/rabbitmq/|.*[.](swp|tmp|log|out)\$|.*~\$)" -e create -e modify -e delete -e move -e attrib /public/application
             └─2342972 /bin/bash /public/script/rsync_backup.sh

......

7.3 配置重启

bash 复制代码
 crontab -e
bash 复制代码
@reboot /usr/sbin/ntpdate 192.168.5.254 > /dev/null 2>&1
0 */2 * * * /usr/sbin/ntpdate 192.168.5.254 > /dev/null 2>&1
30 */12 * * * /usr/sbin/ntpdate ntp1.aliyun.com > /dev/null 2>&1

# 每日凌晨0点,重启同步服务(rsync_backup)
0 0 * * *  systemctl restart rsync_backup.service

8 验证测试

8.1 测试忽略文件

  1. 主节点创建test.log
bash 复制代码
root@192-168-016-041:/public/application# echo "test" >> test.log
root@192-168-016-041:/public/application# ls
images  nextcloud  onlyoffice  test.log
  1. 备份节点上未同步test.log
bash 复制代码
root@192-168-016-051:/public/application# ls
images  nextcloud  onlyoffice
root@192-168-016-051:/public/application# 

8.2 测试同步文件

  1. 主节点创建ok.txt
bash 复制代码
root@192-168-016-041:/public/application# echo "ok" >> ok.txt
root@192-168-016-041:/public/application# ls
images  nextcloud  ok.txt  onlyoffice  test.log
  1. 备份节点上未同步ok.txt
bash 复制代码
root@192-168-016-051:/public/application# ls
images  nextcloud  ok.txt  onlyoffice
相关推荐
DeeplyMind2 小时前
Linux 内核补丁提交(Upstream)完整指南
linux·upstream
三道渊2 小时前
Linux进程通信与信号处理全解析
linux·服务器·网络
AI_Claude_code2 小时前
ZLibrary访问困境方案六:自建RSS/Calibre内容同步服务器的完整指南
运维·服务器·网络·爬虫·python·tcp/ip·http
Java后端的Ai之路2 小时前
sudo 命令详解:Linux 权限管理的“万能钥匙“
linux·运维·服务器·sudo
AI_零食2 小时前
开源鸿蒙跨平台Flutter开发:生日纪念日提醒应用
运维·flutter·开源·harmonyos·鸿蒙
努力努力再努力wz2 小时前
【C++高阶系列】告别内查找局限:基于磁盘 I/O 视角的 B 树深度剖析与 C++ 泛型实现!(附B树实现源码)
java·linux·开发语言·数据结构·c++·b树·算法
艾莉丝努力练剑2 小时前
【QT】Qt常用控件与布局管理深度解析:从原理到实践的架构思考
linux·运维·服务器·开发语言·网络·qt·架构
以太浮标2 小时前
华为eNSP模拟器综合实验之- WLAN瘦AP配置实战案例详解
运维·网络·网络协议·华为·智能路由器·信息与通信
个性小王2 小时前
华为-AC+FIT AP组网(web方式)
运维·网络·华为