分布式web环境文件上传的实时同步解决方案

两个web服务器,当用户访问server01时上传了文件,下次访问到server02时发现文件不存在了,因为上传的文件在server01上

解决方案无非就是使用第三方存储、文件同步。

使用第三方存储可以使用nfs、ftp同一保存在第三方文件服务器。而且PHP实现保存文件到第三方服务器也非常简单,比如说保存在ftp服务器,直接copy($file, 'ftp://test:test123@ftpserver/')就可以实现了,几乎连代码都不需要改,只需修改存储路径。但是在我的需求中遇到了一个问题,有时需要读取文件,貌似ftp协议的文件fopen之后无法fseek

 

文件同步最简单的方法就是使用inotify+rsync了,网上教程一大堆,但是都太啰嗦

有一点需要注意:网上大多数教程是只要监控到有改变的时候,直接同步整个目录,如果目录中文件很多,是非常消耗性能的,正确的做法是只同步当前变动的文件

还有一点需要注意:当创建文件的时候,会触发create事件,之后马上又会触发modify事件,所以,根本没有必要监控那么多事件

 

#!/bin/sh
inotifywait -rmq --format '%Xe %w%f' /var/www/web/public/upload/ /var/www/web/private/upload -e delete,create --exclude "/\." | while read line
do
echo $line
EVENT=$(echo $line | awk '{print $1}')
FILE=$(echo $line | awk '{print $2}')

if [ "$EVENT" = 'CREATE' -o "$EVENT" = 'MODIFY' -o "$EVENT" = 'CLOSE_WRITE' -o "$EVENT" = 'MOVED_TO' ]
then
rsync -ar $(dirname ${FILE})/ root@server02:$(dirname ${FILE})/
fi
if [ "$EVENT" = 'DELETE' -o "$EVENT" = 'MOVED_FROM' ]
then
rsync -ar --delete $(dirname ${FILE})/ root@server02:$(dirname ${FILE})/
fi
done

这种方式遇到了一些问题,经常出现一些文件没有同步过来,有时候同步文件的时候报文件不存在,因为是用root运行的,同步的文件目录权限是root,导致之后的文件上传都没有写权限

就算加上了保持文件属性和权限信息的参数rsync -artpogv,还是经常发生目录权限是root的问题

 

试试用rsync服务同步,不用ssh同步

 

首先配置rsync服务器

vi /etc/rsyncd.conf

uid = www-data
gid = www-data
log file = /var/log/rsyncd.log

[www]
path = /var/www                     #要同步的目录
auth users = user                   #rsync用户
read only = no                     #这个一定要设置,否则无法写入
secrets file = /etc/rsyncd.secrets        #用户密码配置文件
comment = web upload file              #备注,随意填写

 

客户端配置,配置rsync账号和密码

vi /etc/rsyncd.secrets

这个文件里面存储rsync的账号和密码,格式:user:password,每行一个

 

同步脚本:

#!/bin/sh
cd /var/www
inotifywait -rmq --format '%Xe %w%f' upload -e delete,create,modify,MOVED_TO,MOVED_FROM,CLOSE_WRITE --exclude "/\." | while read line
do
    echo $line
    EVENT=$(echo $line | awk '{print $1}')
    FILE=$(echo $line | awk '{print $2}')

    if [ "$EVENT" = 'CREATE' -o "$EVENT" = 'MODIFY' -o "$EVENT" = 'CLOSE_WRITE' -o "$EVENT" = 'MOVED_TO' -o "$EVENT" = "CLOSE_WRITEXCLOSE" -o "$EVENT" = "CREATEXISDIR" ]
    then
        rsync -aRtzpogv --password-file=rsyncd.password $FILE rsync://user@server01/www/
    fi
    if [ "$EVENT" = 'DELETE' -o "$EVENT" = 'MOVED_FROM' -o "$EVENT" = 'DELETEXISDIR' ]
    then
        rsync -aRrtpogv --delete --password-file=rsyncd.password $(dirname ${FILE})/ rsync://user@server01/www/
    fi
done

说明:

inotifywait那一行是监听的upload 目录,这里可以监控多个目录,用空格分隔

这里rsync同步的时候用了参数R,表示使用相对路径,所以后面的目标目录只需要根路径就可以了

我对inotify的事件触发顺序不太清楚,所以尽量多监听一些事件,比如说在编辑文件的时候是否会同时触发modify、close_write、close_writexclose事件?创建文件的时候是否会同时触发create、close_write、close_writexclose事件?

 

手动测试了一下,上面的方案完美解决了问题。不知道部署之后会不会出现问题

 

 

2019.07.18追加:

当有文件写入耗时比较长的时候会出现文件内容错乱的问题,导致的原因是写入数据的时候会触发文件同步,同步到另一台服务器的时候又会触发文件同步,如此循环,导致新写入的数据被文件同步给冲了

解决办法是不监听create和modify事件,只需要监听close_write,只有在文件写入完毕之后才触发文件同步

 

参考:

http://www.ttlsa.com/web/let-infotify-rsync-fast/

https://www.cnblogs.com/Dev0ps/p/8948457.html