Rsync是一款远程同步软件,在同步文件(文件和目录)的时候可以保持原文件的权限、修改时间、包括链接文件也可以同步。

这个东西应用在什么地方呢?不管你是什么业务,如果你有几十台以上的服务器,并且这些服务器需要实时保持拥有的文件一致,那你就会用到这个工具。比如你有几十台web服务器,网页文件会经常修改,如何批量同步这些新的网页文件到所有服务器上呢?


Inotify又是什么呢?它是一种通知机制,随着时间的推移,你的原文件夹可能越来越大,如果每次同步都把整个目录扫描一遍那简直不敢想象,inotify就是内核提供的一种功能,可以实现对目录的监控,如果该目录有任何文件的删除、修改、新建它都会捕捉到这一动作,并可以把这一事件发送给其他应用程序。


主机名IP说明
Linux01.localhost
10.10.6.12
推送文件
Linux02.localhost10.10.6.15
运行rsync守护进程,作为Server端,接收推送。

说明:这个模式你要了解,不是服务器端向客户端推送数据,是客户端其实就是使用rsync工具向运行rsync服务的服务器端推送数据。所以一般运行的Web服务器上都安装此服务,用于接收页面文件的更新。


安装Rsync

实现原理:

rsync采用的是C/S结构,不过S端是服务器端,运行rsync的守护进程,C端通过命令来向S端推送文件。

安装:

rpm -qa rsync    #检查是安装了rsync
rpm -e rsync     #如果安装了,那么就删除,系统自带的版本比较低
wget https://download.samba.org/pub/rsync/rsync-3.1.2.tar.gz --no-check-certificate

wKiom1ggOQPTaOL6AABLKuR6Ss0298.jpg-wh_50

wKioL1ggOQXjVSeVAADXf7CR8vw300.jpg-wh_50

tar -xzf rsync-3.1.2.tar.gz
cd rsync-3.1.2
./configure --prefix=/usr/local/rsync --disable-ipv6

wKiom1ggPhug-F3vAABntIlYpq0354.jpg-wh_50

wKioL1ggPhvALmp9AABje6vAKAg056.jpg-wh_50

如果不指定安装目录,则默认会安装到如下目录中:

wKioL1ggPlmzhIZSAABN5Is9DTc296.jpg-wh_50

make && make install

设置账户和目录:

groupadd -r rsync                         #创建rsync系统组
useradd -g rsync rsync -r -s /sbin/login  #创建rsync系统账号并加入到rsync组

wKiom1ggNoLig1X_AABWZiegTDA944.jpg-wh_50

mkdir backup                #创建用于接收推送文件的目录
chown rsync.rsync ./backup  #设置改目录的属主和属组位rsync

wKiom1ggNxXggiRUAADIk8kcfNc126.jpg-wh_50

编辑配置文件:编译安装后它使用的配置文件默认位置为/etc/rsyncd.conf,但是默认这个文件并不存在,需要你手动建立。

touch /etc/rsyncd.conf    #守护进程使用的配置文件,必须是rsyncd.conf
touch /etc/rsyncd.passwd  #用户名和密码文件,这个名字可以随便
chmod 600 /etc/rsyncd.passwd #修改密码文件权限

wKiom1ggRB2wCspQAABUd-0nTYw624.jpg-wh_50

vim /etc/rsyncd.conf   #编辑配置文件
#[全局配置]
# 服务器IP和监听端口号
address = 10.10.6.15
port = 873
# 服务器传输文件时服务器用哪个属主和属组去保存接收到的文件,也就是权限替换,如果在这里设
# 置成root,则会保留权限。
uid = root
gid = root
# 在文件传输之前,服务器守护进程将chroot到指定的PATH,优点是实时特殊的保护,缺点是需要roo# t权限
use chroot = no
# 最大并发数
max connection = 10
# 超时时长
timeout = 300
# 锁、日志和pid文件位置
lock file = /var/run/rsync.lock
log file = /var/run/rsync.log
pid file = /var/run/rsync.pid
# 消息文件,当客户端连接到服务器是该文件的内容将会显示给客户端,这个文件不是必须的
motd file = /etc/rsyncd.mod
# 存放虚拟用户和密码的文件
secrets file = /etc/rsyncd.passwd
# 允许的网段
hosts allow = *
# 不允许的网段
#hosts deny = 0.0.0.0/32
# 是否允许客户上传文件,true不允许,false允许。
read only = false

#[模块配置],所谓模块配置其实就是要使用的目录,客户端推送文件到服务器端的哪个目录
[backup]
# 需要接收同步的目录
path = /usr/local/backup
# 是否忽略错误
ignore errors
list = false
# 虚拟的用户,也就是客户端用那个用户来连接服务器,可以用空格或者逗号分隔多个用户,该用户
# 必须在上面secrets file = /etc/rsyncd.passwd文件中
auth users = rsync_backup
# 该模块的描述,客户端连接后会显示给客户端
commment = hello
vim /etc/rsyncd.passwd   #编辑密码文件

wKioL1ghZ1Ozt45jAABAukkst6Y239.jpg-wh_50

./rsync --daemon   #启动服务

wKiom1ghZ9DROs0VAABvCzOYqFA300.jpg-wh_50

说明:rsync可以以守护进程的方式运行,也可以由xinetd来激活,如果你的服务器负载比较高建议rsync以独立的守护进程方式来运行。区别在于,如果以独立的守护进程方式运行,它会一直监听端口随时准备接受文件推送,如果由xinetd这个超级进程来管理的话,那没有推送的时候rsync是休眠状态,当有推送的时候,由xinetd这个超级进程激活rsync,对于繁忙的服务器来说这种方式会有额外开销。


连接测试:

在数据源服务器,这里的环境是Linux01,安装rsync。步骤同上。

touch /etc/rsyncd.passwd  #建立密码文件
echo "123456" > /etc/rsyncd.passwd   #这个文件仅存储密码,服务器端和用户名对应的密码
chmod 600 /etc/rsyncd.passwd  #修改权限

wKioL1ghbiagxOzfAABo32VYdFA846.jpg-wh_50

参数说明
-v显示执行过程中的详细信息
-a归档模式,以递归方式传送文件,并保持文件所有属性,相当于rlptgoD
-r对子目录以递归模式处理
-R使用相对路径
-b

创建备份,如果目标有相同文件名是,在覆盖前,先对老文件进行重命名操作。

--backup-dir将文件备份到指定目录,如果不指定目录则在被同步的目录下建立同名备份,不过名字上会多一个~。

-l
保留软连接
-H
保留硬链接
-p
保持文件权限
-o
保持文件属主信息
-g
保持文件属组信息
-t保留文件时间
-D保持设备文件信息
--include-from=从文件中读取需要传输的文件或者目录列表,文件内容为一行一个,如果包含的文件或者目录名称是中文,要把该文件用UTF-8格式保存
--include=指定哪些文件或者目录需要传输
--exclude-from=从文件中读取要排除的文件或者目录,文件内容为一行一个,如果排除的文件或者目录名称是中文,要把该文件用UTF-8格式保存
--eclude=指定排除哪些文件或者目录
--progress
显示传输过程
--password-file=
指定密码文件路径
--port=如果rsyncServer使用非标准端口,那么你在用客户端连接的时候就要指定端口

发送文件

wKioL1ghg0ngPEczAACqxrj_TSE806.jpg-wh_50

查看接收到的文件

wKioL1ghg3TQWohmAAA9mOgKqXI941.jpg-wh_50

有没有注意到权限问题?发送文件的时候hello的属主和属组是chen,但是发送过去以后就变成rsync了。因为我们知道-a参数的含义是保留权限、属主和属组,那为什么传递过去之后就变了呢?

原因就在于配置文件里面的uid和gid设置,看下图:

wKiom1ghjvDhe89SAAA9vizqcfk366.jpg-wh_50

这样的设置结果就是文件同步到服务器端以后,rsync服务器器会根据这个设置来修改文件的属主和属组信息,如果把这里写成root那么rsync就会修改属主和属组了,而会保留信息。

我们修改成root再测试一下

wKiom1ghkpKRyvdHAAB4hkWJfP8889.jpg-wh_50

wKioL1ghkpXyudYGAAA_lVKUP3w814.jpg-wh_50

你会发现权限一样、时间一样、属主和属组也一样。

注意:前提也得是你服务器端有这个同名账户才行,如果没有,这里你查看到的就仅仅是一串数字,也就是权限信息。

下面我更新一下Liux01主机上的hello文件

wKiom1ghmEOgKxqSAABNk2ZmQ_0818.jpg-wh_50

下图是在Linux02上也就是rsync服务器端看到的hello文件内容

wKioL1ghk7uDOnAjAAA54MzQODQ153.jpg-wh_50

在Linux01上执行命令

rsync -avz -b --backup-dir=/aaa hello --progress rsync_backup@10.10.6.15::backup --password-file=/etc/rsyncd.passwd

如果要同步目录的话,就把上面的hello文件换成目录

wKiom1ghmPbD2Jj4AACOAEKfx5I283.jpg-wh_50

可以看到服务器端的hello文件已经更新了,并且在该目录下还建立了一个目录,里面放了是没有更新之前的hello文件。

wKioL1ghmPfwVaqkAABJjC7C82U579.jpg-wh_50

因为我们加了一个-d的参数,这个参数就是对同名文件更新前先备份,备份到--backup-dir=指定目录,如果没有就新建一个。

现在我们说一下如何关闭rsync进程,它如果作为守护进程来运行的话,你得手动KILL掉进程,然后删除PID文件才行。为了方便我们写一个脚本来执行上述操作

#/bin/bash
#

#该脚本的执行前提是你的rsync服务端配置文件要是/etc/rsyncd.conf,如果不是,请替换成实际位
#置

#从配置文件中获取PID文件位置
PIDFILE=`cat /etc/rsyncd.conf | grep 'pid file' | awk -F' ' '{print $4}'`
#echo $PIDFILE
#判断该文件是否存在
if [ -e $PIDFILE ]; then
  cat $PIDFILE | xargs kill -9
  rm -rf $PIDFILE
  echo "rsync服务已经关闭。"
else
  echo "当前系统没有运行rsync服务。"
fi

我们知道通过这个rsync是为了实现批量同步,但是我们发现同步命令一次指定同步一台主机,如何做到批量同步多台主机呢?rsync本身没有这个功能,需要自己写脚本实现:

首先准备一个服务器IP列表的文件,文件格式如下:

wKioL1gitvHgmmUqAAAxaRXrtz0531.jpg-wh_50

其次写一个执行批量同步的脚本,并且能区分是删除还是其他更新操作

#/bin/bash
#

#该参数是服务器IP列表文件
SERVERLIST=$1

#源目录
SRC_DIR=data
#目标目录
DES_DIR=backup
#目标服务器上的用于备份的目录,该目录用于备份更新时出现同名文件的时候
BACKUP_DIR=/backup/aaa
#连接服务器使用的用户名
USER=rsync_backup


#判断文件是否存在
if [ -e $1 ]; then
  #读取逐行读取文件
  for line in $(cat $SERVERLIST);do
    echo $line
    if [ $2 = 'DELETE' ]; then
      result=`rsync -avz -b --backup-dir=$BACKUP_DIR $SRC_DIR --delete $USER@$line::$DES_DIR --password-file=/etc/rsyncd.passwd >> /dev/null`
    else
      result=`rsync -avz -b --backup-dir=$BACKUP_DIR $SRC_DIR $USER@$line::$DES_DIR --password-file=/etc/rsyncd.passwd >> /dev/null`
    fi
    if $result; then
      echo "推送文件到服务器:$line成功。"
    else
      echo "推送文件到服务器:$line失败。"
    fi
  done
else
  echo "服务器IP列表文件不存在。"
fi

wKioL1giv8vBS8rVAABINDGK7WI883.jpg-wh_50

对rsync各种参数的测试,我看到的也是别人转的,所以把链接贴在这里 rsync深入分析

上面都是基于手动运行脚本或命令来进行同步的,那能否自动化执行呢?简单的办法是就是把命令或脚本加入到计划任务重crontab,或者使用inotify,我们推荐使用inotity,因为它不是单一定期的执行推送,关键它可以变化。


安装Inotify:

Inotify它是Linux内核的一个特性,在内核2.6.13开始引入该机制,用于监控文件系统的操作,比如读取、写入和创建等等,当它获取到这些操作以后以后通知应用程序。

首先看看当前系统是否支持

wKiom1gix_WT18lVAACEJifqGwk566.jpg-wh_50

下载工具inotify-tools,这个为是Linux下inotify文件监控工具提供的C开发库函数,同时也包含一系列的命令行工具,使用这些工具可以来做监控文件系统的事件。它提供两种工具:

  • inotifywait:监控文件或目录变化

  • inotifywatch:统计文件系统访问的次数

wget  --no-check-certificate
如果遇到SSL无法连接之类的,请升级wget
tar -zxf inotify-tools-3.14.tar.gz
cd inotifi-tools-3.14
/configure --prefix=/usr/local/inotify
make && make install

inotifywait命令参数:

参数说明
--fromfile从文件读取需要监视或者排除的文件或目录,一个文件一行,排除的文件以@开头
-m针对事件持续监听,如果不加该参数那么获取一次事件后就退出了。
-d运行在后台
-r递归监控目录
-o输出事件到文件中,而不是输出到STDOUT
-s输出错误到系统日志中,而不是输出到STEDRR
-q除了输出事件之外,不显示其他信息
-t超时时间
--format

设置事件输出格式

%w  表示发生事件的目录

%f   表示发生事件的文件

%e   所发生的事件

%Xe  事件以X分隔

%T  使用由-timefmt定义的时间格式

--timefmt

指定时间输出格式,用于-fromat选项中的%T格式

%y-%m-%d %H:%M

-excludei排除文件或目录,不区分大小写
-e -event

如果使用该参数,你就需要手动指定要监控的事件,多个事件用逗号分隔

  • access  读取文件或目录内容

  • modify  修改文件或目录内容

  • move     移动文件或目录

  • create    建立文件或目录 

  • delete    删除文件或目录

  • attrib     修改文件或目录属性

  • close_write    修改真实的文件内容

  • close_nowrite

  • close

  • open  文件或目录被打开

  • move-to  移动文件或目录到哪

  • move-from   从哪移动文件或目录

  • umount   卸载文件系统

inotifywatch命令参数:

参数说明
-fromfile从文件读取需要监视或者排除的文件或目录,一个文件一行,排除的文件以@开头
-z输出表格的行和列
-excludei同上
-e设置指定的监听事件,具体事件同上
-r递归监视
-a
以指定事件的升序排列
-d
以指定事件的降序排列

要实现监控,并且捕捉文件变化,然后根据变化来进行同步,这里主要是思路问题:

  • 首先我们要把对文件或者目录的操作分成2大类,一是删除和移动、二是其他(包括建立、修改),因为这两类不同操作对于要执行的rsync语句所带的参数不同

  • 其次要通过inotifywait来监控指定的目录,并获取那些文件或目录发生了哪些变化

  • 最后根据这些捕捉的变化来执行rsync语句

我们这里先测试一下inotifywait的效果,为了让inotifywait可以直接执行请修改环境变量。

监控脚本

#/bin/bash
#

inotifywait -mrq -e modify,create,move,delete --fromfile '/tmp/filelist' --timefmt '%y-%m-%d %H:%M' --format '%T %w %f %e' --outfile '/tmp/i.log'

要监控的目录列表文件

wKioL1gi6TPiHeoqAAA4wWJa1dg792.jpg-wh_50

测试,这个脚本目前是在前台运行,所以是这种状况,你也可以设置为后台运行,只要在脚本后面加一个 &,然后exit正常退出终端,那么脚本就可以很好的后台继续运行,如果你直接关闭窗口的话,脚本会中断。

wKioL1gi6Wqi7tu6AAAtuBr7V5U590.jpg-wh_50

wKiom1gi6fnxbF-CAADRL08q434541.jpg-wh_50

可以看到我们建立了一个文件,然后查脚本中事件信息输出的日志,你会发现有一个事件发生了。

下面我们结合rsync来完整的写一个监控脚本。

网上有一篇文章写的不错,我这里把链接放过来,我是在他的脚本基础上做了一点修改,我也测试过不按照他那么写是否可行结果失败了,这个就跟rsync的同步机制有关。

原文出处 真正的inotify+rsync实时同步 彻底告别同步慢

#/bin/bash
#运行此脚本的时候服务器列表文件要输入绝对路径,否则后面切换路径后会找到该文件。

#该参数是服务器IP列表文件
SERVER_LIST=$1

#源目录
SRC_DIR=/tmp/data
#目标目录
DES_DIR=backup
#目标服务器上的用于备份的目录,该目录用于备份更新时出现同名文件的时候
BACKUP_DIR=/backup/aaa
#连接服务器使用的用户名
USER=rsync_backup
#密码文件
PASSWD_FILE=/etc/rsyncd.passwd

if [ ! -n "$SERVER_LIST" ]; then
  echo "请传递服务器IP列表文件,要用绝对路径。"
  exit 5
else
  if [ -d $SRC_DIR ] && [ -e $PASSWD_FILE ]; then
    echo "必要文件检查通过......"
  else
    echo "检查源路径或者密码文件失败,脚本无法执行,请检查。"
    exit 5
  fi
fi

#切换到源目录
cd $SRC_DIR         
                  
inotifywait -mrq --timefmt '%y-%m-%d %H:%M' --format  '%T %w %f %e' -e modify,create,delete,attrib,close_write,move ./ | while read file
do
  INFO_EVENT=$(echo $file | awk '{print $5}')      
  INFO_PATH=$(echo $file | awk '{print $5}')   
  echo "-------------------------------$(date)------------------------------------"
  echo $file
  for line in $(cat $SERVER_LIST);do
    #文件建立、修改、移出事件
    if [[ $INFO_EVENT =~ 'CREATE' ]] || [[ $INFO_EVENT =~ 'MODIFY' ]] || [[ $INFO_EVENT =~ 'CLOSE_WRITE' ]] || [[ $INFO_EVENT =~ 'MOVED_TO' ]];then
      rsync -avzcR --password-file=$PASSWD_FILE $(dirname ${INFO_PATH}) $USER@$line::$DES_DIR
    fi
    #删除、移动出事件
    if [[ $INFO_EVENT =~ 'DELETE' ]] || [[ $INFO_EVENT =~ 'MOVED_FROM' ]];then
      rsync -avzR --delete --password-file=$PASSWD_FILE $(dirname ${INFO_PATH}) $USER@$line::$DES_DIR  
    fi
    #修改属性事件
    if [[ $INFO_EVENT =~ 'ATTRIB' ]];then
      if [ ! -d "$INFO_FILE" ];then
        rsync -avzcR --password-file=$PASSWD_FILE $(dirname ${INFO_PATH}) $USER@$line::$DES_DIR            
      fi
    fi
  done
done

服务器列表文件

wKiom1gqgzWjYjH2AAA88z6X-NY149.jpg-wh_50

上面的代码在文字比较中用了

[[ VARIABLES =~ VALUE ]]

用=~这种操作符支持SHELL的正则表达式,在字符串比较的时候右边可以是一个模式而不一定是字符串。也就是变量内容中包括VALUE的内容,那么就匹配模式。不过在这种形式下必须使用双中括号

在Inotify事件中有CREATE事件和CREATE.ISDIR事件,如果你使用=~那么就都会匹配。另外在IF多条件判断的时候,建议都使用独立的双中括号,这样可以避免逻辑错误。


include和exclude的精确匹配:

我在工作中遇到一个问题,服务器(Windows服务器)上有几十个文件夹,需要把其中几个同步到文件服务器上,同时这些文件夹中有log目录,里面存放日志,我们不需要备份日志,只需要把指定文件夹中的除log目录的其他文件备份。

目录结构如下(我只是模拟了目录结构,并不是实际公司环境目录):

wKioL1hJctvwJplPAABomZfl0EU109.jpg-wh_50

要求是把Folder_A、B、E进行备份,同时它们里面的log_folder不备份。

rsync --port=28950 -avz --progress admin@192.168.100.10::Services /backup/Service --password-file=/etc/rsyncd.passwd --include-file=./includeFile --exclude-file=./excludeFile

意思是使用28950端口连接服务器,使用递归并保留权限,同时显示过程,备份100.10服务器中rsyncServer配置文件中[Services]段PATH指定的目录到本地/backup/Services目录中,使用密码文件为rsyncd.passwd,使用了包含文件为includeFile,排除文件为excludeFile


includeFile

Folder_A
Folder_B
Folder_E

excludeFile

log
Folder_C
Folder_D

说明:你想排除哪些文件或者目录就只用--exclude-from就行,但是如果说只想包含哪些文件的话你单独使用--include-from是不行的,必须用--exclude-from进行排除。


如果rsyncServer服务器是Windows的话,就要这样写路径

rsync --port 28950 -avz --progress admin@192.168.100.20::test /cygdrive/d/rsyncscripts/test/backup --password-file=rsyncPwd.txt --include-from=includeFile.txt --exclude-from=excludeFile.txt

/cygdirve/d/rsyncscripts/test/backup 等于 d:/rsyncscripts/test/back


特别注意:

另外如果服务器上有中文目录的话,首先保存文件列表的文件要是UTF-8格式,同时文件的写法也要注意:文件第一行不要写真实目录名或者文件名,你随便写一些字母即可,从第二行开始写真实的文件名称或者目录名。

xxxxxxxxxxxxxx
文件1
文件2
目录1
目录2
File1
Folder1

我不知道为什么,反正我的环境中rsyncServer安装在一台Windows上,然后另外台Windows安装rsync客户端,从客户端上同步,因为包含一些中文名的目录,所以出了很多问题如果你的include文件和exclude文件第一个不加任意字符,那么同步过来的文件或者目录就会少。如果你的目录都是英文的则不需要这样操作。目录中的子目录或者文件如果有中文也无需在第一行添加任意字符。