rsync+inotify实现实时同步acgred.cn

一、无差异同步数据

1.首先,在实践实时同步之前我们先来了解一下rsync无差异同步
无差异推送数据加–delete

1)备份 –delete风险
本地有,远端就有。本地没有远端有也会删除,服务器端的目录数据可能会丢失

无差异拉取数据 www.gaimor.cn

2)代码发布、下载 –delete风险
远端有,本地就有。远端没有的本地有也会删除,本地的目录数据可能丢失

未分类

ps:上图会将远程端的/hzftp/test里的文件完全克隆到客户端的/data1目录中


多模块共享配置,只需新增模块目录地址即可,其他 www.acgred.cn


inotify实施(切记在客户端上安装)

前提:需要rsync daemon已经搭建完成的情况下,可以在服务端上推拉数据
inotifu安装:
检查服务端的rsync服务是正常启用的,并且可以在客户端往服务端推送文件
检查内核版本:uname -r

[root@localhost inotify]# ls 
max_queued_events  max_user_instances  max_user_watches
[root@localhost inotify]# ll /proc/sys/fs/inotify
总用量 0
-rw-r--r--. 1 root root 0 6月  27 22:58 max_queued_events   
-rw-r--r--. 1 root root 0 6月  27 22:58 max_user_instances  
-rw-r--r--. 1 root root 0 6月  27 22:58 max_user_watches

然后从互联网上下载inotify源码安装包
下载好后,解压:tar xf inotify-tools-3.14.tar.gz

cd inotify-tools-3.14
./configure --prefix=/usr/local/inotify-tools-3.14
make && make install
ln -s /usr/local/inotify-tools-3.14 /usr/local/inotify
[root@localhost inotify]# ll /usr/local/inotify
总用量 4
drwxr-xr-x. 2 root root   43 6月  27 23:10 bin  #inotify执行命令
drwxr-xr-x. 3 root root   25 6月  27 23:10 include  #inotify所需头文件
drwxr-xr-x. 2 root root 4096 6月  27 23:10 lib  #动态链接库文件
drwxr-xr-x. 4 root root   26 6月  27 23:10 share   #帮助文档

使用inotify来监控目录文件,当被监控的目录发生变化会产生输出:
监听创建:create
监听删除:delete
监听写入:close_write
ps:当create和close_write同时使用时,创建文件会被监控两遍
======create,delete,close_write同时使用用逗号隔开=======
/usr/local/inotify-tools-3.14/bin/inotifywait -mrq --timefmt '%d%m%y %H:%M' --format '%T %w%f' -e create /backup

未分类

开发inotify数据同步脚本
创建脚本存放路径:mkdir -p /server/scripts
进入脚本存放目录:cd /server/scripts

CentOS安装和配置Rsync进行文件同步

Liunx系统实现文件同步不需要搭建FTP这类的工具,只需要按照Rsync配置下文件就可以。

本文以Centos7.0为例。

1.首先关闭SELINUX(不关闭无法同步,权限太高了)

vi /etc/selinux/config #编辑防火墙配置文件
#SELINUX=enforcing #注释掉
#SELINUXTYPE=targeted #注释掉
SELINUX=disabled #增加
:wq! #保存,退出
setenforce 0  #立即生效

2.服务端和客户端同时安装Rsync

yum install rsync xinetd #安装

3.客户端和服务端同时新增配置文件(centos7 默认没有了,得单独手工建,否则无法启动)

vim /etc/xinetd.d/rsync
service rsync
{
        disable              = no
        flags                  = IPv6
        socket_type      = stream
        wait                   = no
        user                   = root
        server                = /usr/bin/rsync
        server_args       = --daemon
        log_on_failure  += USERID
}

4.修改服务端配置给客户端调用

vim /etc/rsyncd.conf
log file = /var/log/rsyncd.log #日志文件位置,启动rsync后自动产生这个文件,无需提前创建

pidfile = /var/run/rsyncd.pid  #pid文件的存放位置

lock file = /var/run/rsync.lock  #支持max connections参数的锁文件

secrets file = /etc/rsync.pass  #用户认证配置文件,里面保存用户名称和密码,后面会创建这个文件

motd file = /etc/rsyncd.Motd  #rsync启动时欢迎信息页面文件位置(文件内容自定义)

[test] #自定义名称

path = /data/ #rsync服务端数据目录路径

comment =rsync data comment #对那个文件夹进行描述

uid = root #设置rsync运行权限为root 推荐使用 nobody

gid = root #设置rsync运行权限为root 推荐使用 nobody

port=873  #默认端口

use chroot = no #默认为true,修改为no,增加对目录文件软连接的备份

read only = no  #设置rsync服务端文件为读写权限

list = no #不显示rsync服务端资源列表

max connections = 200 #最大连接数

timeout = 600  #设置超时时间

auth users = test #执行数据同步的用户名,可以设置多个,用英文状态下逗号隔开

hosts allow = 192.168.21.129  #允许进行数据同步的客户端IP地址,可以设置多个,用英文状态下逗号隔开

hosts deny = 192.168.21.254 #禁止数据同步的客户端IP地址,可以设置多个,用英文状态下逗号隔开

5.新增同步用户的配置文件保存密码

vim /etc/rsync.pass
test:123456

6.对配置文件进行授权

chmod 600 /etc/rsyncd.conf  #设置文件所有者读取、写入权限
chmod 600 /etc/rsync.pass  #设置文件所有者读取、写入权限

7.重启Rsync 是用软件生效

systemctl restart xinetd

8.客户端开始同步

首先telnet端口:
telnet 172.16.120.18 83 

服务端同步文件到客户端:
rsync -avz  [email protected]::ftp  /data


客户端同步文件到服务端

rsync -av /data/  [email protected]:ftp 

其中/data/ 若后面不加”/” 那/data 就是表示本身同步过去,切记!

Rsync 数据同步工具应用指南

Rsync 简介

Rsync 是一款开源的,快速的,多功能的,可实现全量及增量(差异化备份)的本地或远程数据同步备份的优秀工具。

Rsync软件适用于Unix、Linux、Windows等多种操作系统。

1)可使本地和远程两台主机之间的数据快速复制同步镜像,远程备份的功能,这个功能类似ssh带scp命令,但又优于scp命令的功能,scp每次都是全量拷贝,而rsync可以增量拷贝。

2)rsync还可以在本地主机的不同分区或目录之间全量及增量的复制数据,

3)利用rsync还可以实现删除文件和目录的功能。相当于rm

4)rsync相当于scp,cp.rm但是还优于他们每一个命令。

在同步备份数据时,默认情况下rsync通过独特的“quick check” 算法,它仅同步大小或者最后修改时间发生变化的文件或目录,当然也可以是根据权限,属主等属性的变化同步,但需要指定相应的参数,甚至可以实现只同步一个文件里有变化的内容部分,所以可以实现快速的同步备份数据。

  • CentOS 5 rsync2.x 比对方法,把所有的文件比对一遍,然后进行同步。

  • CentOS 6 rsync3.x 比对方法,一边比对差异,一边对差异的部分进行同步。

Rsync 特性

1)支持拷贝特殊文件如链接文件,设备等。

2)可以有排除指定文件或目录同步的功能,相当于打包命令tar的排除功能。

3)可以做到保持源文件或目录的权限,时间,软硬链接,属主,组等属性均不改变 -p.

4)可以实现增量同步,即只同步发生变化的数据,因此数据传输的效率很高,tar -N.

5)可以使用rcp,rsh,ssh,等方式来配合传输文件(rsync本身不对数据加密)

6)可以通过soket(进程方式)传输文件和数据(服务端和客户端)*****

7)支持匿名的或认证的(无需系统用户)的进程模式传输,可实现方便安全的进程数据备份及镜像。

实时同步(解决存储服务器等单点问题)

利用rsync结合inotify的功能做实时的数据同步,根据存储服务器上目录的变化,把变化的数据通过inotify或sersync结合rsync命令,同步到备份服务器,还可以通过drbd方案以及双写的方案实现双机数据同步。

Rsync的工作方式

大致使用三种主要的传输数据的方式。

1)单个主机本地之间的数据传输(此时类似于cp命令的功能)

2)借助rcp,ssh等通道来传输数据(此时类似于scp命令的功能)

3)以守护进程(socket)的方式传输数据(这个是rsync自身的重要功能)

服务端与客户端安装 Rsync

修改主机名

hostname backup
vi /etc/sysconfig/network

安装 rsync 与 依赖

yum -y install rsync xinetd

vi /etc/xinetd.d/rsync

将yes 修改为no IPV6修改为IPV4

rsync 命令同步参数详解

local(本地)模式的命令参数

-v --verbose 详细模式输出,传输时的进度等信息。

-z --compress 传输时进行压缩以提高传输效率,--compress-level=NUM可按级别压缩

重要的命令

-a --archive 归档模式,表示以递归方式传输文件,并保持所有文件属性,等价于-rtopgDl

-r 对子目录以递归模式,即目录下的所有目录都同样传输,注意是小写的r.

-o 保持文件属性信息

-p 保持文件权限

-g 保持文件属组信息

-P 显示同步的过程及传输时的进度等信息

-D 保持设备文件信息

-l 保持软连接


-avzP 提示:这里的 相当于 -vzrtopgDlP(还多了Dl功能)生产环境常用 

-avz 定时任务就不用输出过程了可以-az

需了解的命令

-e 使用的信道协议,指定替代rsh的shell程序,例如:ssh

--exclude=PATTERN 指定排除不需要传输的文件模式(和tar参数一样)

--exclude=file(文件名所在的目录文件)(和tar参数一样)

--delete 让目标目录SRC和源目录数据DST一致。

注意:/tmp/ rsync如果tmp/ 加上斜线的话就表示只选中斜线后的文件,不包含tmp。

如果不加上斜线 tmp 那么就是包含目录本身和目录之下的文件。

做数据同步容易将带宽占满,导致网站无法访问

rsync scp ftp 都有限速功能

解决:man rsync里面有一个限速的参数。

我之前刚做运维的时候,在备份数据时,没考虑业务低谷时间点,将带宽占满了,导致网站无法正常访问。发现问题之后,我立即停止数据备份,然后man 了一下,才发现rsync有限速参数。

尽量不要在业务高并发的时候做备份,要在业务低谷时间段进行,限速备份。

当然,scp,ftp都有限速的功能。

借助ssh通道在不同主机之间传输数据

man rsyncd.conf

使用-e参数,利用ssh隧道进行文件传输
[root@backup ~]# rsync -avz /etc/hosts -e 'ssh -p 22' [email protected]:/mnt

可以使用ssh key 免密钥登录,然后可以做定时任务。

优化ssh (让连接服务器进行rsync更快)

[root@backup ~]# vim /etc/ssh/sshd_config 

GSSAPIAuthentication no   (把这个的注释去掉,也就是打开)
#GSSAPIAuthentication yes(把这个注释掉,也就是关闭)
UseDNS no (改成no)

以守护进程(socket)的方式传输数据

rsync 命令使用用法

rsync -参数 用户名@同步服务器的IP::rsyncd.conf中那个方括号里的内容(配置文件中的模块名) 本地存放路径。

rsync -avzP [email protected]::nemo /backup

服务器端的配置过程

安装rysnc

yum -y install rsync xinetd

vi /etc/xinetd.d/rsync

将yes 修改为no IPV6修改为IPV4

1、查看rsync安装包

[root@backup ~]# rpm -qa rsync

2、添加rsync服务的用户,管理本地目录

[root@backup ~]# useradd rsync -s /sbin/nologin -M
[root@backup ~]# tail -1 /etc/passwd
rsync:x:501:501::/home/rsync:/sbin/nologin

3、生成rsyncd.conf配置文件

查看 rsyncd.conf

[root@backup backup]# cat /etc/rsyncd.conf 
config_______________start
#15:01 2007-6-5
#rsyncd.conf start##
uid = rsync
gid = rsync
use chroot = no
max connections = 200
timeout = 300
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd.log
[backup]
path = /backup
ignore errors
read only = false
list = false
hosts allow = 172.16.1.0/24
hosts deny = 0.0.0.0/32
auth users = rsync_backup
secrets file = /etc/rsync.password
#rsync_config____________end

配置rsyncd.conf

[root@backup ~]# vim /etc/rsyncd.conf 
#rsync_config_______________start
#15:01 2018-6-5
#rsyncd.conf start##
uid = rsync #用户远端的命令使用rsync访问共享目录
gid = rsync #用户组
use chroot = no #安全相关
max connections = 200 #最大连接数
timeout = 300 #超时时间(单位/秒)
pid file = /var/run/rsyncd.pid  #(进程号对应的进程号文件)
lock file = /var/run/rsync.lock #锁文件,防止文件不一致
log file = /var/log/rsyncd.log  #日志文件
[backup]  #模块名称
path = /backup  #服务器端提供访问的目录
ignore errors #忽略错误
read only = false #可写
list = false  #不让列表(相当于ls)
hosts allow = 172.16.1.0/24 #允许的网段
hosts deny = 0.0.0.0/32  #拒绝的网段
auth users = rsync_backup #连接的虚拟用户,非系统用户
secrets file = /etc/rsync.password #虚拟用户的账号密码文件
#rsync_config____________end

4、rsyncd.conf的auth users配置账户,远程连接

并根据secrets file参数生成密码文件

法一
echo "rsync_backup:cloudbility" >/etc/rsync.password
cat /etc/rsync.password

法二
[root@backup ~]# vim  /etc/rsync.password
rsync_backup:cloudbility

[root@backup ~]# cat /etc/rsync.password
rsync_backup:cloudbility

5、为密码文件配置权限

chmod 600 /etc/rsync.password
ls -l /etc/rsync.password

6、创建共享的目录并授权rsync服务管理

法一:
[root@backup ~]# mkdir /backup

[root@backup ~]# ls -ld /backup/
drwxr-xr-x 2 root root 4096 1月  16 20:08 /backup/
(如果不修改权限的话,远程访问过来是用rsync但是这个目录的属组和属主都收root 就无法把远方的数据推送过来,没有写权限。)

[root@backup ~]# chown rsync.rsync /backup/
(更改属组和属主)

[root@backup ~]# ls -ld /backup/
drwxr-xr-x 2 rsync rsync 4096 1月  16 20:08 /backup/

法二:
mkdir /backup -p
chown -R rsync.rsync /backup
如果没有/backup目录,就会chdir failed.(失败的)

7、启动rsync服务并检查

[root@backup ~]# rsync --daemon  
开启rsync服务

[root@backup ~]# ps -ef|grep rsync|grep -v grep
root       5116   5100  0 19:14 pts/0    00:00:00 vim /etc/rsyncd.conf
root       5193      1  0 20:05 ?        00:00:00 rsync --daemon
(为什么是root在运行呢?因为远程访问的用户才会使用rsync来使用,上面配置文件有写)

查看运行端口
[root@backup ~]# lsof -i :873
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
rsync   5193 root    3u  IPv4  19935      0t0  TCP *:rsync (LISTEN)
rsync   5193 root    5u  IPv6  19936      0t0  TCP *:rsync (LISTEN)

[root@backup ~]# netstat -lntup|grep 873
tcp        0      0 0.0.0.0:873                 0.0.0.0:*                   LISTEN      5193/rsync          
tcp        0      0 :::873                      :::*                        LISTEN      5193/rsync

8、加入开机自启动

[root@backup ~]# echo "/usr/bin/rsync --damon" >>/etc/rc.local

[root@backup ~]# tail -1 /etc/rc.local
/usr/bin/rsync --damon

排错过程

  1. 输出结果

  2. 日志 tail /var/log/rsyncd.log

  3. 熟练部署过程(原理)

rsync客户端操作方法

1、生成连接服务器的密码文件

法一:
echo "cloudbility" >/etc/rsync.password

法二:
[root@nfs01 ~]# vim /etc/rsync.password

[root@nfs01 ~]# cat /etc/rsync.password
cloudbility

2、为密码文件配置权限

[root@nfs01 ~]# chmod 600 /etc/rsync.password

[root@nfs01 ~]# ll /etc/rsync.password
-rw------- 1 root root 7 1月  16 20:58 /etc/rsync.password

3、同步文件

创建样本

[root@nfs01 ~]# mkdir /backup -p

[root@nfs01 ~]# cd /backup/


[root@nfs01 backup]# touch stu{01..100}

[root@nfs01 backup]# ls
stu001  stu010  stu02   stu029  stu038  stu047  stu056  stu065  stu074  stu083  stu092
stu002  stu011  stu020  stu03   stu039  stu048  stu057  stu066  stu075  stu084  stu093

推送到backup server:

法一:需要密码
[root@nfs01 backup]# rsync -avz /backup/ [email protected]::backup/(模块名)

法二:无需密码
[root@nfs01 backup]# rsync -avz /backup/ [email protected]::backup/(模块名) --password-file=/etc/rsync.password

法三:无需密码
[root@nfs01 backup]# rsync -avz /backup/ rsync://[email protected]/backup/(模块名) --password-file=/etc/rsync.password

从backup server拉取/backup下的文件到本地/backup下。

法一:需要密码
[root@nfs01 backup]# rsync -avz [email protected]::backup(模块名) /backup/ 

法二:免密
[root@nfs01 backup]# rsync -avz [email protected]::backup(模块名) /backup/ --password-file=/etc/rsync.password  

法三:免密
[root@nfs01 backup]# rsync -avz rsync://[email protected]/backup/(模块名) /backup/ --password-file=/etc/rsync.password

提示上述的backup为模块名,不是路径

增加模块

[root@backup backup]# vim /etc/rsyncd.conf
#15:01 2018-6-5
#rsyncd.conf start##
uid = rsync
gid = rsync
use chroot = no
max connections = 200
timeout = 300
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd.log
ignore errors
read only = false
list = false
hosts allow = 172.16.1.0/24
hosts deny = 0.0.0.0/32
auth users = rsync_backup
secrets file = /etc/rsync.password
[backup]
path = /backup
[cloudbility]
path = /cloudbility
##rsync_config____________end

排除同步:

排除推送

排除单个文件:

--exclude=a  (排除a)

[root@nfs01 backup]# rsync -avz --exclude=a /backup/ [email protected]::backup/ --password-file=/etc/rsync.password
sending incremental file list
./bcdefg

排除多个文件:

1)
排除a 和b 
[root@nfs01 backup]# rsync -avz --exclude={a,b} /backup/ [email protected]::backup/ --password-file=/etc/rsync.password
sending incremental file list
./cdefg

2)
排除连续的a-f
[root@nfs01 backup]# rsync -avz --exclude={a..f} /backup/ [email protected]::backup/ --password-file=/etc/rsync.password
sending incremental file list
./g

sent 75 bytes  received 30 bytes  210.00 bytes/sec
total size is 0  speedup is 0.00

1)拉取和推送都可以排除。

2)也可以服务端排除,配置文件里参数。

3)exclude=a b c d

完全同步:无差异同步–delete

[root@nfs01 backup]# rsync -avz --delete /backup/ [email protected]::backup/ --password-file=/etc/rsync.password
sending incremental file list

abcdef

rsync三种工作模式

1)local(本地模式):(cd,rm)

2)通道模式:
 rsync -avzP -e 'ssh -p 22' /etc [email protected]:/tmp/
 一般配合ssh key免密钥传输,结合定时任务。

 3)daemon模式
内网不需要加密,加密性能有损失。

如果要外网的话使用vpn(PPTP。openVPN,ipsec)

rsync服务模式故障常见问题解答

1)小 BUG

[root@backup backup]# vim /etc/rsyncd.conf
#hosts deny = 0.0.0.0/32
把 hosts deny(拒绝的ip段)注释掉,因为当

hosts allow = 172.16.1.0/24
#hosts deny = 0.0.0.0/32
这两个在一起的时候,发现10段的ip 也能把数据推送到backup server。所以必须注释掉
hosts deny。

提示:更改配置文件之后要重启服务,因为每次Linux都是把配置文件放到内存。
先杀死进程,然后检查
[root@backup backup]# pkill rsync
[root@backup backup]# lsof -i :873

重启再检查看看
[root@backup backup]# rsync --daemon
[root@backup backup]# lsof -i :873
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
rsync   7718 root    4u  IPv4  33811      0t0  TCP *:rsync (LISTEN)
rsync   7718 root    5u  IPv6  33812      0t0  TCP *:rsync (LISTEN)

2)服务端没有这个目录

提示报错信息

[root@nfs01 backup]# rsync -avz /backup/ [email protected]::backup/ --password-file=/etc/rsync.password 
@ERROR: chdir failed


解决方法:
服务端创建目录
[root@backup backup]# mkdir /backup
[root@backup backup]# ll -d /backup/
drwxr-xr-x 2 root root 4096 Jan 17 15:52 /backup/

[root@backup backup]# chown -R rsync.rsync /backup/ 
(/etc/rsyncd.conf配置文件里的uid和gid)

3)权限不够

提示报错信息

[root@nfs01 backup]# rsync -avz /backup/ [email protected]::backup/ --password-file=/etc/rsync.password 
sending incremental file list
./
rsync: failed to set times on "." (in backup): Operation not permitted (1)
stu1
stu10
stu2
stu3
stu4
stu5
stu6
stu7
stu8
stu9
rsync: mkstemp ".stu1.oraZ3Y" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu10.n1jKm7" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu2.dLFwFf" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu3.LKKjYn" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu4.nSI7gw" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu5.p4CWzE" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu6.HE5OSM" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu7.jGRIbV" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu8.p4cDu3" (in backup) failed: Permission denied (13)
rsync: mkstemp ".stu9.EZbyNb" (in backup) failed: Permission denied (13)

sent 467 bytes  received 201 bytes  1336.00 bytes/sec
total size is 0  speedup is 0.00
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1039) [sender=3.0.6]

解决方法
[root@backup backup]# ll -d /backup/
drwxr-xr-x 2 root root 4096 Jan 17 15:52 /backup/

[root@backup backup]# chown -R rsync.rsync /backup/ 
(/etc/rsyncd.conf配置文件里的uid和gid)

4)没有创建uid

提示错误信息

[root@nfs01 backup]# rsync -avz /backup/ [email protected]::backup/ --password-file=/etc/rsync.password 
@ERROR: invalid uid rsync


解决方法:
[root@backup backup]# useradd rsync -s /sbin/nologin -M

5)客户端/etc/rsync.password 配置文件里的密码错误(注意空格)

[root@nfs01 backup]# rsync -avz /backup/ [email protected]::backup/ --password-file=/etc/rsync.password 
@ERROR: auth failed on module backup

解决:
查看服务端的日志配置文件的报错信息。
[root@backup backup]# cat /var/log/rsyncd.log  
2017/01/17 16:04:36 [7813] auth failed on module backup from unknown (172.16.1.31): password mismatch

提示我们密码错误:
查看服务器端的配置文件和密码,然后,再看客户端的。
[root@backup backup]# vim /etc/rsync.password 
rsync_backup:cloudbility

6)连接被拒绝

提示信息

[root@nfs01 backup]# rsync -avz /backup/ [email protected]::cloudbility/
rsync: failed to connect to 172.16.1.41: Connection refused (111)

解决,
1)服务端防火墙是否关闭iptables

2)873端口是否开放。
重启rsync服务。
[root@backup cloudbility]# rsync --daemon
[root@backup cloudbility]# lsof -i :873

rsync守护进程(daemon)服务传输数据排错思路:

rsync服务端排错思路

1)查看rsync服务配置文件路径是否正确,正确的默认路径为:/etc/rsyncd.conf

2)查看配置文件里的host allow.host deny允许的网段是否允许客户端访问的IP网段。

3)查看配置文件中path参数里的路径是否存在,权限是否正确(正常应为位置文件中的UID参数对应的属主和组)

4)查看rsync服务是否启动,查看命令为:ps -ef|grep rsync. 端口是否存在 netstat -lnt|grep 873.

5)查看iptables防火墙和SELinux是否开启允许rsync服务通过,也可以考虑关闭。

6)查看服务端rsync配置的密码文件权限是否是600;密码文件格式是否正确。 正确格式为 用户名:密码 。文件路径和配置文件里的secrect files参数对应。

7)如果是推送数据,要查看下,配置rsyncd.conf文件中用户是否对模块下的目录有可读写权限

rsync客户端排除思路

1)查看客户端rsync配置的密码文件是否为600权限,密码文件格式是否正确。
注意,仅西药有密码,并且和服务端的密码一致。

2)用telnet连接rsync服务器IP地址873端口,查看服务是否启动
(可测试服务端防火墙是都阻挡)。 telnet 10.0.0.41 873

3)客户端执行命令时rsync -avzP [email protected]::cloudbility/test/test --password-file=/etc/rsync.password
此命令的细节要记清楚,尤其是10.0.0.41::cloudbility/test/处的双冒号及随其后的cloudbility
的模块名称;

rsync优缺点:

rsync优点:

1)增量备份,支持socket(daemon守护进程),集中备份(支持推拉,都是以客户端为参照物);

2)远程SEHLL通道模式还可以加密(SSH)传输,socket(daemon守护进程)需要加密传输,
可以利用VPN服务或ipsec服务;

rsync缺点:

1)大量小文件同步的时候,比对时间较长,有的时候,rsync进程可能会停止。

2)同步大文件,10G这样的大文件有时也会出现问题,中断。未完整同步之前,是隐藏文件.
可以通过续传等参数实现传输,

一次性远程拷贝可以用scp;

(完)

rsync的小坑——请绕过

今天磁盘满了,打算将占磁盘大的文件给移走,再采用软连接的。同步的时候出现了一点小问题。

第一天:

先将要同步的文件给同步了一遍。

执行命令

rsync -auv /data/mysql/game /data1/mysql
rsync -auv /data/mysql/integral /data1/mysql
rsync -auv /data/mysql/interact /data1/mysql
rsync -auv /data/mysql/match /data1/mysql
rsync -auv /data/mysql/sns_admin /data1/mysql
rsync -auv /data/mysql/stock /data1/mysq

第二天:

又同步了一遍

rsync -auv /data/mysql/game /data1/mysql/game
rsync -auv /data/mysql/integral /data1/mysql/integral
rsync -auv /data/mysql/interact /data1/mysql/interact
rsync -auv /data/mysql/match /data1/mysql/match
rsync -auv /data/mysql/sns_admin /data1/mysql/sns_admin 
rsync -auv /data/mysql/stock /data1/mysql/stock

快要同步完的时候,检查文件大小,发现文件大了一倍。

原来第二步命令将/data/mysql/game文件 放在/data1/mysql/game文件下了。

这也不算是rsync的坑吧

应该这样这行

rsync -auv /data/mysql/game/ /data1/mysql/game/
rsync -auv /data/mysql/integral/ /data1/mysql/integral/
rsync -auv /data/mysql/interact/ /data1/mysql/interact/
rsync -auv /data/mysql/match/ /data1/mysql/match/
rsync -auv /data/mysql/sns_admin/ /data1/mysql/sns_admin/ 
rsync -auv /data/mysql/stock/ /data1/mysql/stock/

或者执行和昨天一样的命令

            rsync -auv /data/mysql/game         /data1/mysql
            rsync -auv /data/mysql/integral    /data1/mysql
            rsync -auv /data/mysql/interact     /data1/mysql
            rsync -auv /data/mysql/match        /data1/mysql
            rsync -auv /data/mysql/sns_admin    /data1/mysql
            rsync -auv /data/mysql/stock        /data1/mysq 

就可以了。

小结:

1、使用rsync同步,只要之前同步的内容和现在同步的内容有一点改变,就会重新全量同步,因此,同步的时候尽量找长时间没有变化的大文件
2、rsync不会追加文件

python的BaseHTTPServer模块接收post请求

#!/usr/bin/python
#encoding=utf-8
'''
基于BaseHTTPServer的http server实现,包括get,post方法,get参数接收,post参数接收。
'''
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
import io,shutil  
import urllib
import os, sys

class MyRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        mpath,margs=urllib.splitquery(self.path) # ?分割
        self.do_action(mpath, margs)

    def do_POST(self):
        mpath,margs=urllib.splitquery(self.path)
        datas = self.rfile.read(int(self.headers['content-length']))
        self.do_action(mpath, datas)

    def do_action(self, path, args):
            self.outputtxt(path + args )

    def outputtxt(self, content):
        #指定返回编码
        enc = "UTF-8"
        content = content.encode(enc)          
        f = io.BytesIO()
        f.write(content)
        f.seek(0)  
        self.send_response(200)  
        self.send_header("Content-type", "text/html; charset=%s" % enc)  
        self.send_header("Content-Length", str(len(content)))  
        self.end_headers()  
        shutil.copyfileobj(f,self.wfile)

配置一个nginx+php-fpm的web服务器

一、基本信息

  • 系统(L):CentOS 6.9 #下载地址:http://mirrors.sohu.com
  • Web服务器(N):NGINX 1.14.0 #下载地址:http://nginx.org/en/download.html
  • 数据库服务器(M):MySQL 5.6.40 #下载地址:https://dev.mysql.com/downloads/mysql
  • PHP-FPM服务器(P):php-5.6.8.tar.gz #下载地址:http://mirrors.sohu.com/php/
  • OPENSSL:openssl-1.0.2o.tar.gz #下载地址:https://www.openssl.org/source/

指定服务安装的通用位置

mkdir /usr/local/services
SERVICE_PATH=/usr/local/services

创建服务运行的账户

useradd -r -M -s /sbin/nologin www

安装所需依赖包

yum -y install pcre pcre-devel 
gperftools gcc zlib-devel 
libxml2 libxml2-devel 
bzip2 bzip2-devel 
curl curl-devel 
libjpeg-devel libjpeg 
libpng-devel libpng 
freetype freetype-devel 
libmcrypt libmcrypt-devel 
openssl-devel

二、软件安装配置

1、NGINX+OPENSSL安装

下载解压NGINX+OPENSSL

NGINX_URL="http://nginx.org/download/nginx-1.14.0.tar.gz"
OPENSSL_URL="https://www.openssl.org/source/openssl-1.1.0h.tar.gz"

wget -P ${SERVICE_PATH} ${NGINX_URL} && tar -zxvf ${SERVICE_PATH}/nginx*.tar.gz -C ${SERVICE_PATH}
wget -P ${SERVICE_PATH} ${OPENSSL_URL} && tar -zxvf ${SERVICE_PATH}/openssl*.gz -C ${SERVICE_PATH}

编译安装NGINX

cd ${SERVICE_PATH}/nginx-*;./configure 
--prefix=${SERVICE_PATH}/nginx 
--user=www --group=www 
--with-http_stub_status_module 
--with-http_ssl_module 
--with-http_flv_module 
--with-pcre 
--with-http_gzip_static_module 
--with-openssl=${SERVICE_PATH}/openssl* 
--with-http_realip_module 
--with-google_perftools_module 
--without-select_module 
--without-mail_pop3_module 
--without-mail_imap_module 
--without-mail_smtp_module 
--without-poll_module 
--without-http_autoindex_module 
--without-http_geo_module 
--without-http_uwsgi_module 
--without-http_scgi_module 
--without-http_memcached_module 
--with-cc-opt='-O2' && cd ${SERVICE_PATH}/nginx-*;make && make install

NGINX+OPENSSL安装完成后的清理与其他配置

ln -sv ${SERVICE_PATH}/nginx /usr/local/
rm -rf ${SERVICE_PATH}/nginx/conf/*.default
cd ${SERVICE_PATH} ; rm -rf nginx*.tar.gz openssl*.tar.gz

写入主配置文件nginx.conf(配置已优化)

cat << EOF >/usr/local/nginx/conf/nginx.conf
user www;
worker_processes WORKERNUMBER;
worker_cpu_affinity auto;
worker_rlimit_nofile 655350;

error_log /var/log/nginx_error.log;
pid /tmp/nginx.pid;

google_perftools_profiles /tmp/tcmalloc;

events {
use epoll;
worker_connections 655350;
multi_accept on;
}

http {
charset utf-8;
include mime.types;
default_type text/html;

log_format main '"$remote_addr" - [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" '
'"$sent_http_server_name $upstream_response_time" '
'$request_time '
'$args';


sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 120;
client_body_buffer_size 512k;
client_header_buffer_size 64k;
large_client_header_buffers 4 32k;
client_max_body_size 300M;
client_header_timeout 15s;
client_body_timeout 50s;
open_file_cache max=102400 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 1;
server_names_hash_max_size 2048;
server_names_hash_bucket_size 256;
server_tokens off;
gzip on;
gzip_proxied any;
gzip_min_length 1024;
gzip_buffers 4 8k;
gzip_comp_level 9;
gzip_disable "MSIE [1-6].";
gzip_types application/json test/html text/plain text/css application/font-woff application/pdf application/octet-stream application/x-javascript application/javascript application/xml text/javascript;
fastcgi_cache_path /dev/shm/ levels=1:2 keys_zone=fastcgicache:512m inactive=10m max_size=3g;
fastcgi_cache_lock on;
fastcgi_ignore_headers Cache-Control Expires Set-Cookie;
fastcgi_send_timeout 300;
fastcgi_connect_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 256k;
fastcgi_buffers 4 128k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;


include vhost/*.conf;
}
EOF

NGINX worker进程数配置,指定为逻辑CPU数量的2倍

THREAD=`expr $(grep process /proc/cpuinfo |wc -l) * 2`
sed -i s"/WORKERNUMBER/$THREAD/" ${SERVICE_PATH}/nginx/conf/nginx.conf

2、PHP-FPM安装

下载并解压PHP-FPM软件

FPM_URL="http://mirrors.sohu.com/php/php-5.6.8.tar.gz"
wget -P ${SERVICE_PATH} ${FPM_URL} && tar -zxvf ${SERVICE_PATH}/php*.tar.gz -C ${SERVICE_PATH}

编译安装PHP-FPM

cd ${SERVICE_PATH}/php-*;./configure 
--prefix=${SERVICE_PATH}/php 
--with-gd 
--with-mcrypt 
--with-mysql=mysqlnd 
--with-mysqli=mysqlnd 
--with-pdo-mysql=mysqlnd 
--enable-maintainer-zts 
--enable-ftp 
--enable-zip 
--with-bz2 
-with-iconv-dir 
--with-freetype-dir 
--with-jpeg-dir 
--with-png-dir 
--with-config-file-path=${SERVICE_PATH}/php 
--enable-mbstring 
--enable-fpm 
--with-fpm-user=www 
--with-fpm-group=www 
--disable-debug 
--enable-opcache 
--enable-soap 
--with-zlib 
--with-libxml-dir=/usr 
--enable-xml 
--disable-rpath 
--enable-bcmath 
--enable-shmop 
--enable-sysvsem 
--enable-inline-optimization 
--with-curl 
--enable-mbregex 
--enable-gd-native-ttf 
--with-openssl 
--with-mhash 
--enable-pcntl 
--enable-sockets 
--with-xmlrpc 
--with-pear 
--with-gettext 
--disable-fileinfo && cd ${SERVICE_PATH}/php-*;make && make install

若FPM程序有插件需求,如mongo或redis连接插件,则可通过pecl安装php相关插件

${SERVICE_PATH}/php/bin/pecl install mongo || exit

${SERVICE_PATH}/php/bin/pecl install redis || exit

安装完成后的配置清理

ln -sv ${SERVICE_PATH}/php /usr/local/

php.ini配置文件写入(配置已优化)

cat << EOF >${SERVICE_PATH}/php/php.ini 
[PHP]
engine = On
short_open_tag = Off
asp_tags = Off
precision = 14
output_buffering = 4096
zlib.output_compression = Off
implicit_flush = Off
unserialize_callback_func =
serialize_precision = 17
disable_functions = shell_exec,phpinfo,exec
disable_classes =
zend.enable_gc = On
expose_php = Off
max_execution_time = 60
max_input_time = 60
memory_limit = 128M
error_reporting = E_WARING & ERROR
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 2048
ignore_repeated_errors = Off
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = Off
error_log = /var/log/php_errors.log
variables_order = "GPCS"
request_order = "GP"
register_argc_argv = Off
auto_globals_jit = On
post_max_size = 8M
auto_prepend_file =
auto_append_file =
default_mimetype = "text/html"
default_charset = "UTF-8"
doc_root =
user_dir =
enable_dl = Off
cgi.fix_pathinfo=0
file_uploads = On
upload_max_filesize = 2M
max_file_uploads = 20
allow_url_fopen = Off
allow_url_include = Off
default_socket_timeout = 60
[CLI Server]
cli_server.color = On
[Date]
[filter]
[iconv]
[intl]
[sqlite]
[sqlite3]
[Pcre]
[Pdo]
[Pdo_mysql]
pdo_mysql.cache_size = 2000
pdo_mysql.default_socket=
[Phar]
[mail function]
SMTP = localhost
smtp_port = 25
mail.add_x_header = On
[SQL]
sql.safe_mode = Off
[ODBC]
odbc.allow_persistent = On
odbc.check_persistent = On
odbc.max_persistent = -1
odbc.max_links = -1
odbc.defaultlrl = 4096
odbc.defaultbinmode = 1
[Interbase]
ibase.allow_persistent = 1
ibase.max_persistent = -1
ibase.max_links = -1
ibase.timestampformat = "%Y-%m-%d %H:%M:%S"
ibase.dateformat = "%Y-%m-%d"
ibase.timeformat = "%H:%M:%S"
[MySQL]
mysql.allow_local_infile = On
mysql.allow_persistent = On
mysql.cache_size = 2000
mysql.max_persistent = -1
mysql.max_links = -1
mysql.default_port =
mysql.default_socket =
mysql.default_host =
mysql.default_user =
mysql.default_password =
mysql.connect_timeout = 60
mysql.trace_mode = Off
[MySQLi]
mysqli.max_persistent = -1
mysqli.allow_persistent = On
mysqli.max_links = -1
mysqli.cache_size = 2000
mysqli.default_port = 3306
mysqli.default_socket =
mysqli.default_host =
mysqli.default_user =
mysqli.default_pw =
mysqli.reconnect = Off
[mysqlnd]
mysqlnd.collect_statistics = On
mysqlnd.collect_memory_statistics = Off
[OCI8]
[PostgreSQL]
pgsql.allow_persistent = On
pgsql.auto_reset_persistent = Off
pgsql.max_persistent = -1
pgsql.max_links = -1
pgsql.ignore_notice = 0
pgsql.log_notice = 0
[Sybase-CT]
sybct.allow_persistent = On
sybct.max_persistent = -1
sybct.max_links = -1
sybct.min_server_severity = 10
sybct.min_client_severity = 10
[bcmath]
bcmath.scale = 0
[browscap]
[Session]
session.save_handler = files
session.save_path = "/tmp"
session.use_strict_mode = 0
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.referer_check =
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5
url_rewriter.tags = "a=href,area=href,frame=src,input=src,form=fakeentry"
[MSSQL]
mssql.allow_persistent = On
mssql.max_persistent = -1
mssql.max_links = -1
mssql.min_error_severity = 10
mssql.min_message_severity = 10
mssql.compatibility_mode = Off
mssql.secure_connection = Off
[Assertion]
[COM]
[mbstring]
[gd]
gd.jpeg_ignore_warning = 0
[exif]
[Tidy]
tidy.clean_output = Off
[soap]
soap.wsdl_cache_enabled=1
soap.wsdl_cache_dir="/tmp"
soap.wsdl_cache_ttl=86400
soap.wsdl_cache_limit = 5
[sysvshm]
[ldap]
ldap.max_links = -1
[mcrypt]
[dba]
[opcache]
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.validate_timestamps=1
opcache.revalidate_freq=30
opcache.fast_shutdown=1
opcache.enable_file_override=1
[curl]
[openssl]
extension_dir='${SERVICE_PATH}/php/lib/php/extensions/'
;extension=mongo.so
;extension=redis.so
EOF

php-fpm.conf配置文件写入(配置已优化)

cat << EOF >${SERVICE_PATH}/php/etc/php-fpm.conf
[global]
error_log = /var/log/php-fpm-error.log
log_level = warning
process_control_timeout = 10
rlimit_files = 655350
events.mechanism = epoll
[www]
user = www
group = www
listen = /dev/shm/php-fpm.sock
listen.backlog = 2048
listen.owner = www
listen.group = www
listen.mode = 0660
pm = dynamic
pm.max_children = 200
pm.start_servers = 105
pm.min_spare_servers = 10
pm.max_spare_servers = 200
pm.process_idle_timeout = 10s;
pm.max_requests = 1000
pm.status_path = /fpmstatus
ping.path = /ping
ping.response = pong
slowlog = /var/log/php-slow-$pool.log
request_slowlog_timeout = 10
request_terminate_timeout = 0
rlimit_files = 655350
security.limit_extensions = .php
EOF

三、基于以上配置PHP网站

mkdir /usr/local/nginx/conf/vhost
cat << EOF > /usr/local/nginx/conf/vhost/erbiao.ex.com.conf
server
{
listen 80 backlog=1024;
server_name erbiao.ex.com;
index index.php index.html ;
root /www/web/;
access_log off;
add_header Server-Name WEBerbiaoEX;

location ~ .php {
fastcgi_pass unix:/dev/shm/php-fpm.sock;
fastcgi_index index.php;
include fastcgi.conf;
set $real_script_name $fastcgi_script_name;
if ($fastcgi_script_name ~ "^(.+?.php)(/.+)$") {
set $real_script_name $1;
set $path_info $2;
}
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
fastcgi_param SCRIPT_NAME $real_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;


location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}

location ~ .*.(js|css)?$
{
expires 12h;
}

}

}
EOF

若在同一服务器运行nginx和php-fpm,并发量不超过1000,选择unix socket,如此可避免一些检查操作(路由等),因此更快,更轻。若是高并发业务,则选择使用更可靠的tcp socket,以负载均衡、内核优化等运维手段维持效率

四、启动服务

启动nginx和php-fpm

/usr/local/nginx/sbin/nginx
/usr/local/php-fpm/sbin/php-fpm

命令其他选项

nginx
├── -s选项,向主进程发送信号
|   ├── reload参数,重新加载配置文件
|   ├── stop参数,快速停止nginx
|   ├── reopen参数,重新打开日志文件
|   ├── quit参数,Nginx在退出前完成已经接受的连接请求
├── -t选项,检查配置文件是否正确
├── -c选项,用于指定特定的配置文件并启动nginx
├── -V选项(大写),显示nginx编译选项与版本信息

php-fpm
├── -t选项,检查配置文件是否正确
├── -m选项,显示所有已安装模块
├── -i选项,显示PHP详细信息
├── -v选项,显示版本信息

nginx+php-fpm搭建wordpress

一开始搭建的hexo博客,hexo博客有个缺点,他是用nodejs的服务器,不太稳定,服务器经常挂。所以最后还是决定用nginx+php-fpm搭建一个wordpress站点,这样网站就比较稳定。废话不多说,直接进入主题。

我是用的centos的服务器,下面的一些个命令也是centos的命令,不过其他的也相差不大,主要的是步骤正确就好。

1. 准备 LNMP 环境

安装nginx

使用yum安装nginx

yum install nginx -y

安装完之后修改配置文件 /etc/nginx/nginx.conf

ps:要是配置文件不在这个位置的,可以利用find命令和whereis命令进行查询,参考我另外两篇博文

配置文件参考下面进行更改:

这一步,只需要两处,去除对 IPv6 地址的监听,修改要监听的域名,即:

#listen [::]:80 default_server;
server_name www.***.com;

修改完了之后,就可以启动nginx,查看自己网站首页了,应该看到的是一个nginx测试页面。

启动nginx

nginx

将 Nginx 设置为开机自动启动:

chkconfig nginx on

安装php+php-fpm+php-mysql

这一步有两个选择,安装php7.0版本或是老的版本

安装老的版本比较简单:

yum install php-fpm php-mysql -y

要是想安装php7.0版本,要先添加源,再安装:

如果是centos6,那么执行以下命令

CentOS/RHEL 6.x:

rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-6.noarch.rpm
rpm -Uvh https://mirror.webtatic.com/yum/el6/latest.rpm

如果是centos7.x,那么执行以下命令

CentOS/RHEL 7.x:

rpm -Uvh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm

安装php和插件:

可以像上面一样,需要哪个装哪个,也可以全部安装

全部安装:

yum install php70w php70w*

安装需要的:

yum install php70w-common php70w-fpm php70w-opcache php70w-gd php70w-mysqlnd php70w-mbstring php70w-pecl-redis php70w-pecl-memcached php70w-devel

具体可以参考:帐号登录

完了之后启动php-fpm:

service php-fpm start

安装mysql

yum install mysql-server -y

行不通可参考:centos7 mysql数据库安装和配置

2. 安装wordpress

yum install wordpress -y

安装完成后,就可以在 /usr/share/wordpress 看到 WordPress 的源代码了。

由于上面的安装方法默认安装的是英文版,所以我们还要下载中文语言包,如果不需要中文安装可跳过此步骤

wget https://cn.wordpress.org/wordpress-4.8.1-zh_CN.tar.gz
tar -zxvf wordpress*
mv -f wordpress/wp-content/languages /usr/share/wordpress/wp-content/
rm -rf wordpress*

3. 配置

登录mysql,这里初次没有密码,你自己要设置一个密码

mysql -uroot

创建数据库

CREATE DATABASE wordpress;

完成退出

exit;

配置wordpress

进入wordpress目录下,根目录下有一个配置文件wp-config.php,把数据库名,用户,密码配置好就可以,数据库就是刚才建立 的数据库

define('DB_NAME', 'wordpress');
define('DB_USER', 'root');
define('DB_PASSWORD', 'MyPas$word4Word_Press');

修改这三行即可

再次配置/etc/nginx/nginx.conf文件:

完全按照下面配置,修改域名为你的域名即可

server {
listen 80 default_server;
#listen [::]:80 default_server;
server_name www.***.com;
root /usr/share/wordpress;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;

location / {
index index.php index.html index.htm;
try_files $uri $uri/ /index.php index.php;
}

location ~ .php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}

到这一步基本就完成了

nginx重新加载配置

nginx -s reload

访问页面进入wordpress仪表盘

关于nginx和php-fpm的修改用户和用户组的问题

修改nginx和php-fpm的用户和用户组时除了修改

nginx.conf 下的

user *****

和/etc/php-fpm.d/www.conf下的

user = ****

group = ****

修改上面之后,重启nginx和php-fpm,ps -ef|grep … 会发现应用的用户和用户组变了,但是这还能算万事大吉

还要修改一下一些目录下的权限

(以 修改的用户和用户组为nobody为例)

  1. 如 nginx的日志目录 /var/log/nginxchown -R nobody:nobody /var/log/nginx 否则不能不能读写access.logerrorlog

  2. 还有 上传文件用到的一个路径 /var/lib/nginx/tmp/chown -R nobody:nobody /var/lib/nginx 否则上传文件出错,因为保存不了上传的临时文件

3.还有的就是php-fpm的配置文件中

php_value[session.save_handler] = files

php_value[session.save_path] = /var/lib/php/session

设置的session保存的路径,如果你修改了php-fpm的用户和用户组,重启之后,你会发现什么登陆状态,验证码都保存不了,没错session没保存,因为没有修改上面保存session路径的权限

所以应该 让你修改的用户拥有/var/lib/php/session的读写权限,chown 或者chmod 反正让php-fpm的应用用户有权限访问就可以了

扩展:

“ nobody就是一个普通账户,因为默认登录shell是 ‘/sbin/nologin’,所以这个用户是无法直接登录系统的,也就是黑客很难通过漏洞连接到你的服务器来做破坏。此外这个用户的权限也给配置的很低。因此有比较高的安全性。一切都只给最低权限。这就是nobody存在的意义。”

——-摘自 https://blog.csdn.net/gjkun0202/article/details/71156205

查看linux用户列表

cat /etc/passwd|grep -v nologin|grep -v halt|grep -v shutdown|awk -F":" '{ print $1"|"$3"|"$4 }'|more

查看nobody用户所在的组,以及组内成员 groups nobody

查看所有用户组 cat /etc/group

——-摘自 https://www.cnblogs.com/jackyyou/p/5498083.html

openresty实现图片(文件)服务器

介绍

前序

该功能是利用openresty的lua脚本实现的图片(文件)保存功能,文件上传使用java代码开发的

数据定义

上传数据和文件信息不分前后,但系统只会保存最后一对信息

  • 数据格式:
{"fileDir":"文件保存的目录","fileName":"文件名"}
  • 返回结果
{"status":"是否成功","result":"返回结果","msg":"异常原因"}
enum status:["success","failed"]
  • 保存文件夹
    所保存到那个文件夹下,在nginx的perfix变量中定义

代码实现

Nginx配置

如下:

server {
    listen       80;
    server_name  localhost;
# 配置保存的文件夹
    set $prefix "/data";

    location /uploadimage {
# 配置是否每次lua更改都生效,适合调试时使用
#       lua_code_cache off;
# 配置lua脚本
        content_by_lua_file /openresty-web/luascript/luascript;
    }
# 用来配合理解传入到nginx的报文结构
    location /uploadtest{
#       lua_code_cache off;
        content_by_lua_file /openresty-web/luascript/luauploadtest;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

lua脚本

luascript:

package.path = '/openresty-web/lualib/resty/?.lua;'
local upload = require "upload"
local cjson = require("cjson")

Result={status="success",result="",msg=""}
Result.__index=Result
function Result.conSuccess(ret)
    ret["status"]="success"
    ret["result"]="upload success"
    return ret
end

function Result.conFailed(ret,err)
    ret["status"]="failed"
    ret["msg"]=err
    ret["result"]="upload failed"
    return ret
end

function Result:new()
    local ret={}
    setmetatable({},Result)
    return ret
end

-- lua-resty-upload
local chunk_size = 4096
local form = upload:new(chunk_size)
if not form then
    ngx.say(cjson.encode(Result.conFailed(Result:new(),"plase upload right info")))
    return 
end
local file
local filelen=0
form:set_timeout(0) -- 1 sec
local filename
local prefix=ngx.var.prefix

-- 匹配文件名,当前案例用于判断是否是文件模块
function get_filename(res)
    local filename = ngx.re.match(res,'(.+)filename="(.+)"(.*)')
    if filename then 
        return filename[2]
    end
end


-- 用来开启输入流,当文件夹不存在时自动创建
function openstream(fileinfo,opt)
    local file,err=io.open(prefix..fileinfo["fileDir"],"r")
    if not file then
        local start=string.find(err,"No such file or directory")
        if start then
            local exeret=os.execute("mkdir -p "..prefix..fileinfo["fileDir"])
            if exeret ~= 0 then
                return nil,"Make directory failed"
            end
        else
            return nil,err
        end
    end
    file,err=io.open(prefix..fileinfo["fileDir"]..fileinfo["fileName"],opt)
    return file,err
end

local osfilepath
local tmpfiletbl
local hasFile=false
local loopfile=false
local fileinfostr
local fileinfo
local result=Result:new()
-- 循环读取文件和文件信息
while true do
    local typ, res, err = form:read()
    if not typ then
        break
    end
    if typ == "header" then
        if res[1] ~= "Content-Type" then
            filename = get_filename(res[2])
            if filename then
                loopfile=true
                hasFile=true
                -- 判断是否有文件信息
                -- 如果没有记录内存
                if fileinfo then
                    file,err=openstream(fileinfo,"w")
                    if not file then
                        break
                    end
                else
                    tmpfiletbl={}
                end
            else
                loopfile = false
                fileinfostr = ""
            end
        end
    end
    if loopfile then
        if typ == "body" then
            if file then
                filelen= filelen + tonumber(string.len(res))    
                file:write(res)
            else
                table.insert(tmpfiletbl,res)
            end
        elseif typ == "part_end" then
            if file then
                file:close()
                file = nil
            end
        end
    else
        if typ == "body" then
            fileinfostr=fileinfostr .. res
        elseif typ == "part_end" then
            fileinfo = cjson.decode(fileinfostr)
        end
    end
    if typ == "eof" then
        break
    end
end

if not hasFile then
    err="plase upload file"
elseif not fileinfo or not fileinfo["fileDir"] or not fileinfo["fileName"] then
    err="plase offer file info"
end

if err then
    ngx.log(ngx.ERR,err)
    Result.conFailed(result,err)
    ngx.say(cjson.encode(result))
    return 
end

-- 因为有文件信息在文件之后传送的
-- 所以需要将输入到内存中的文件信息打印到磁盘
if tmpfiletbl and table.getn(tmpfiletbl) > 0 then
    file,err=openstream(fileinfo,"w")
    if not file then
        ngx.log(ngx.ERR,err)
        Result.conFailed(result,err)
        ngx.say(cjson.encode(result))
        return 
    else
        for index,value in ipairs(tmpfiletbl)
        do
            filelen= filelen + tonumber(string.len(value)) 
            file:write(value)
        end
        file:close()
        file=nil
    end
end


Result.conSuccess(result)
ngx.say(cjson.encode(result))

luauploadtest:

local upload = require "resty.upload"
local cjson = require "cjson"

local chunk_size = 5 -- should be set to 4096 or 8192
                     -- for real-world settings

local form, err = upload:new(chunk_size)
if not form then
    ngx.log(ngx.ERR, "failed to new upload: ", err)
    ngx.exit(500)
end

form:set_timeout(1000) -- 1 sec

while true do
    local typ, res, err = form:read()
    if not typ then
        ngx.say("failed to read: ", err)
        return
    end

    ngx.say("read: ", cjson.encode({typ, res}))

    if typ == "eof" then
        break
    end
end

local typ, res, err = form:read()
ngx.say("read: ", cjson.encode({typ, res}))

luauploadtest代码是官方提供代码

Java

ImageServer:

package cn.com.cgbchina.image;

import cn.com.cgbchina.image.exception.ImageDeleteException;
import cn.com.cgbchina.image.exception.ImageUploadException;
import org.springframework.web.multipart.MultipartFile;

/**
 * Created by 11140721050130 on 16-3-22.
 */
public interface ImageServer {
    /**
     * 刪除文件
     *
     * @param fileName 文件名
     * @return 是否刪除成功
     */
    boolean delete(String fileName) throws ImageDeleteException;

    /**
     *
     * @param originalName 原始文件名
     * @param file 文件
     * @return 文件上传后的相对路径
     */
    String upload(String originalName, MultipartFile file) throws ImageUploadException;
}

LuaResult:

package cn.com.cgbchina.image.nginx;

import lombok.Getter;
import lombok.Setter;

/**
 * Comment: 用来保存返回结果,
 * 原本想放入到LuaImageServiceImpl的内部类中,
 * 但是Jackson不支持,没法反序列化
 * Created by ldaokun2006 on 2017/10/24.
 */
@Setter
@Getter
public class LuaResult{
    private LuaResultStatus status;
    private String result;
    private String msg;
    private String httpUrl;
    public LuaResult(){}

    public void setStatus(String result){
        status=LuaResultStatus.valueOf(result.toUpperCase());
    }
    public enum LuaResultStatus{
        SUCCESS,FAILED;
    }
}

ImageServerImpl:

package cn.com.cgbchina.image.nginx;

import cn.com.cgbchina.common.utils.DateHelper;
import cn.com.cgbchina.image.ImageServer;
import cn.com.cgbchina.image.exception.ImageDeleteException;
import cn.com.cgbchina.image.exception.ImageUploadException;
import com.github.kevinsawicki.http.HttpRequest;
import com.google.common.base.Splitter;
import com.spirit.util.JsonMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Comment: 实现文件上传功能
 * Created by ldaokun2006 on 2017/10/16.
 */
@Service
@Slf4j
public class LuaImageServiceImpl implements ImageServer{
    // 存放nginx服务器url的,某些架构会有多个放置图片的地方
    private List<String> httpUrls;
    private ExecutorService fixedThreadPool ;
    private Integer timeout;
    private int threadSize=50;

    public LuaImageServiceImpl(String httpUrls){
        this(httpUrls,30000);
    }

    /**
     *
     * @param httpUrls 存放nginx服务器url
     * @param timeout http超时时间
     */
    public LuaImageServiceImpl(String httpUrls,int timeout){
        this.httpUrls=Splitter.on(";").splitToList(httpUrls);
        // 没啥看得,就是想让线程池的名字易懂些
        this.fixedThreadPool= new ThreadPoolExecutor(threadSize, threadSize,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),new ThreadFactory(){
                    private final AtomicInteger poolNumber = new AtomicInteger(1);
                    private final ThreadGroup group;
                    private final AtomicInteger threadNumber = new AtomicInteger(1);
                    private final String namePrefix;

                    {
                        SecurityManager s = System.getSecurityManager();
                        group = (s != null) ? s.getThreadGroup() :
                                Thread.currentThread().getThreadGroup();
                        namePrefix = "LuaUploadPool-" +
                                poolNumber.getAndIncrement() +
                                "-thread-";
                    }

                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(group, r,
                                namePrefix + threadNumber.getAndIncrement(),
                                0);
                        if (t.isDaemon())
                            t.setDaemon(false);
                        if (t.getPriority() != Thread.NORM_PRIORITY)
                            t.setPriority(Thread.NORM_PRIORITY);
                        return t;
                    }
                });
        this.timeout=timeout;
    }

    /**
     * Comment: 没必要开发删除功能
     * @param fileName 文件名
     * @return
     * @throws ImageDeleteException
     */
    @Override
    public boolean delete(String fileName) throws ImageDeleteException {
        return true;
    }

    /**
     * Commont: 用来给SpringMVC用
     * @param originalName 原始文件名
     * @param file 文件
     * @return
     * @throws ImageUploadException
     */
    @Override
    public String upload(String originalName, MultipartFile file) throws ImageUploadException {
        try {
            return this.upload(originalName,file.getInputStream());
        } catch (IOException e) {
            log.error("upload fail : " + e.getMessage(), e);
            throw new ImageUploadException("upload fail : "+e.getMessage(),e);
        }
    }

    /**
     * Commont: 上传图片核心代码
     * @param originalName 原始文件名
     * @param inputStream 要上传文件的文件流
     * @return
     * @throws ImageUploadException
     */
    private String upload(String originalName,InputStream inputStream) throws ImageUploadException {
        ByteArrayOutputStream byteOutStream = null;
        try {
            //准备数据
            byte[] tmpData=new byte[1024];
            byte[] inputData;
            byteOutStream = new ByteArrayOutputStream();
            int len=0;
            while((len=inputStream.read(tmpData,0,tmpData.length))!=-1){
                byteOutStream.write(tmpData,0,len);
            }
            inputData=byteOutStream.toByteArray();
            LuaSend sendInfo = new LuaSend(generateFileDir(),generateFileName(originalName));
            List<Future<LuaResult>> resultList=new ArrayList<>(httpUrls.size());

            //发送图片
            for(String httpUrl:httpUrls) {
                SendImg sendImg = new SendImg(httpUrl,sendInfo, inputData,this.timeout);
                resultList.add(fixedThreadPool.submit(sendImg));
            }
            for(Future<LuaResult> future:resultList) {
                // 线程池异常在这里抛出
                LuaResult resultLuaResult = future.get();
                if (LuaResult.LuaResultStatus.SUCCESS != resultLuaResult.getStatus()) {
                    throw new ImageUploadException("lua result url:"+resultLuaResult.getHttpUrl()+" msg : " + resultLuaResult.getMsg());
                }
            }

            return sendInfo.toString();
        }catch (Exception e){
            log.error("upload fail : "+e.getMessage(),e);
            throw new ImageUploadException("upload fail : "+e.getMessage(),e);
        }finally {
            try {
                if(byteOutStream!=null) {
                    byteOutStream.close();
                }
                if(inputStream!=null) {
                    inputStream.close();
                }
            } catch (IOException e) {
                throw new ImageUploadException("upload fail : "+e.getMessage(),e);
            }
        }
    }
    String separator=File.separator;
    String dateFormat=separator+"yyyy"+separator+"MM"+separator+"dd"+ separator;

    /**
     * Comment:根据时间做路径,防止某一个文件夹东西太多
     * @return 返回要保存的路径
     */
    private String generateFileDir(){
        return DateHelper.date2string(new Date(),dateFormat);
    }

    /**
     * Comment: 用UUID防止文件名重复
     * @param originalName 源文件名字
     * @return 要保存的文件名
     */
    private String generateFileName(String originalName){
        return UUID.randomUUID().toString();
    }

    /**
     * Comment: 用来发送图片的
     */
    @AllArgsConstructor
    class SendImg implements  Callable<LuaResult>{

        private String httpUrl;
        private LuaSend sendInfo;
        private byte[] inputStream;
        private Integer timeout;


        @Override
        public LuaResult call() throws Exception {
            try {
                String resultStr = HttpRequest
                        .post(httpUrl, false)
                        .part("fileInfo", JsonMapper.JSON_NON_EMPTY_MAPPER.toJson(sendInfo))
                        // 这个地方有个坑,part上传图片必须要用这个方式,
                        // 不能用没有Content-Type和fileName的
                        .part("file", sendInfo.getFileName(), "multipart/form-data; boundary=00content0boundary00", new ByteArrayInputStream(inputStream))
                        .connectTimeout(timeout).body();
                log.info("result:"+resultStr);
                LuaResult result = JsonMapper.JSON_NON_DEFAULT_MAPPER.fromJson(resultStr, LuaResult.class);
                result.setHttpUrl(httpUrl);
                return result;
            }catch(Exception e){
                throw new ImageUploadException("upload failed url:"+httpUrl+" info:"+sendInfo.toString(),e);
            }
        }
    }

    /**
     * Comment:文件数据
     */
    @Setter
    @Getter
    @AllArgsConstructor
    class LuaSend {
        // 文件目录
        private String fileDir;
        // 文件名
        private String fileName;
        @Override
        public String toString(){
            return fileDir+fileName;
        }
    }


    /**
     * Comment:测试用
     * @param args
     * @throws ImageUploadException
     * @throws FileNotFoundException
     */
    public static void main(String[] args) throws ImageUploadException, FileNotFoundException {
        LuaImageServiceImpl service=new LuaImageServiceImpl("http://192.168.99.102/uploadimage");
        try {
            System.out.println(service.upload("qqqqq", new FileInputStream("D:\shsh.txt")));
        }finally {
            service.fixedThreadPool.shutdown();
        }
    }
}

总结

可能出现的问题

  1. 上传两个图片或图片信息时系统只保留最后一个信息
  2. 图片和图片信息可以随意放置,但是这两个必须成对发送,建议先发送图片信息后发送图片,这样图片不用在lua处保存到内存中
  3. 上传大图片时会出现文件太大的提示,需要在nginx配置文件中添加client_max_body_size 100M;
  4. Http Header的Content-Type必须使用multipart/form-data;
    boundary=00content0boundary00,boundary必须存在不然不好用
  5. 传送图片HttpRequest.part上传图片必须写明Content-type和fileName,不然不好用但是Content-type不用非的用例子上的方式
  6. 图片信息必须拷贝成byte型,因为多线程使用时需要各自发送

开发中遇到的问题

  1. 传送图片HttpRequest.part上传图片必须写明Content-type,不然不好用
  2. Jackson和fastjson对于需要反序列化的类,必须有无参构造函数,并且不能是内部类
  3. lua的string.find如果没有找到,返回结果为nil
  4. CSDN的编辑器,无需功能不好用

涉及到知识

  1. HttpRequest.part用来上传Content-type:multipart/form-data;
  2. lua的使用:http://www.runoob.com/lua/lua-tutorial.html
  3. openresty的api:http://openresty.org/cn/components.html

Nginx下如何设置WordPress为多站点?

WordPress的多站点功能允许安装一个WordPress程序的情况下,实现多个站点(也就是一套程序,可以绑定多个域名或子域名)。

每个站点拥有独立的主题、插件、文章以及页面。

这样可以极大的减少了维护和更新多个WordPress安装程序的麻烦,

并且,每个站点之间又能够相互独立,互不影响。

WordPress multisite有两个方式:子目录和子域名,这里我们主要介绍子域名方式。

也就是说,在主域名的基础上,我们会创建一个子域名,例如:http://shop.awaimai.com。

同时,我们可以映射这个子域名到一个一级域名如:http://shop.com,

对于访问者来说,访问的就是独立的一级域名。

1、准备

WordPress介绍其多站点功能页面:站点网络管理页面

接着,我们准备几个域名,如下:

  • 站点一:www.awaimai.com(主域名),这是安装WordPress时用的域名
  • 站点二:blog.awaimai.com,二级域名
  • 站点三:news.com,映射的二级域名 news.awaimai.com
  • 站点四:shop.com,映射的二级域名 shop.awaimai.com

注意:WordPress安装后请勿擅自在后台修改域名,即使是把有www改成无www,或者反过来,都有可能引起 redirected you too many times. 错误,详情请看[参考资料2]

然后,登录域名服务商的解析页面,把以上域名的A记录全部设置为WordPress安装的服务器IP。

也可以在本地电脑测试,可以直接修改hosts文件,加入下面1行:

127.0.0.1 www.awaimai.com blog.awaimai.com news.com shop.com

2、Nginx配置

在Nginx配置目录下创建新建一个配置文件,如下:

$ sudo vi /etc/nginx/conf.d/awaimai.conf

内容为:

server {
    listen 80;
    server_name www.awaimai.com blog.awaimai.com news.com shop.com;

    root /usr/share/nginx/wordpress;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$args ;
    }

    location ~ /favicon.ico {
        access_log off;
        log_not_found off;
    }

    location ~ .php$ {
        try_files $uri /index.php;
        include fastcgi_params;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    access_log /var/log/nginx/$host-access.log;
    error_log /var/log/nginx/wpms-error.log;
}

这里我们使用$host变量可以让Nginx为每个域名生成独立的访问日志,

如:news.com-access.log 和 shop.com-access.log。

但是error日志不能用$host变量,所以所有的错误会记录在一个文件里面。

再重启Nginx服务器:

$ nginx -s reload

3、安装WordPress

按照WordPress正常安装步骤安装WordPress。

4、启用多站点功能

用文本编辑器打开 wp-config.php 文件,在注释:/* 好了!请不要再继续编辑。请保存本文件。使用愉快! */之前加上如下一行:

/* Multisite settings */
define( 'WP_ALLOW_MULTISITE', true );

接下来我们还会编辑这个文件几次。

保存后登录WordPress后台,点击:工具 > 网络设置,选择 子域名,网络标题和网络管理员邮箱任意输入。

然后端机安装。

未分类

稍等片刻后,界面出现两个代码块,提示分别加入wp-config.php和.htaccesss文件。

这里我们用的是Nginx,所以不需要管 .htaccess 部分。

打开wp-config.php文件,还是在注释:/* 好了!请不要再继续编辑。请保存本文件。使用愉快! */之前,加上如下几行:

define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', true);
define('DOMAIN_CURRENT_SITE', 'www.awaimai.com');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);
Log out of the WordPress admin panel, and log in again.

登出WordPress后台,再登入。

打开面板左上角 我的站点 > 网络管理 > 站点。

未分类

点击 添加新的 按钮,打开添加新站点表单,依次添加 blog、news、shop三个子域名。

未分类

添加完成后,选 所有站点 ,编辑 news.awaimai.com 和 shop.awaimai.com 两个子域名,站点标题分别为新闻和商城,

把 站点地址(URL)分别改成:news.com和 shop.com。

这一步完成后,我们就可以访问blog.awaimai.com了,它已经是一个独立的站点了,拥有独立的资源了。

但是要能访问news.com和shop.com,还需继续往下看。

5、设置域名映射

打开面板左上角 我的站点 > 网络管理 > 插件。

未分类

在这里安装 WordPress MU Domain Mapping 插件,直接搜索或者下载安装都可以,然后启用。

接着复制插件目录(目录wp-content/plugins/wordpress-mu-domain-mapping)下的 sunrise.php 文件到 wp-content 目录。

打开wp-config.php文件,还是在注释:/* 好了!请不要再继续编辑。请保存本文件。使用愉快! */之前,加上如下一行:

define('SUNRISE', 'on');

保存,然后返回浏览器,在后台中打开面板左上角 我的站点 > 网络管理 > 设置。

再选择Domain Mapping,修改 Domain Options 为如下图:

未分类

然后保存。

这里的配置的功能是:重定向所有二级域名(如 news.awaimai.com )到各自的外部域名(如 news.com),包括管理页面(/wp-admin)。

接下来,我们要映射一级域名到各个站点ID。

默认在后台不显示站点ID,所以我们用一个最简单的方法让后台直接显示站点ID。

这个方法就是用WordPress的 Must-use plugin。

在 wp-content 目录下创建一个 mu-plugins 目录,再在新建的目录下创建一个名为 wpms_blogid.php 的文件,

这个PHP文件的内容为:

<?php
add_filter( 'wpmu_blogs_columns', 'do_get_id' );
add_action( 'manage_sites_custom_column', 'do_add_columns', 10, 2 );
add_action( 'manage_blogs_custom_column', 'do_add_columns', 10, 2 );

function do_add_columns( $column_name, $blog_id ) {
    if ( 'blog_id' === $column_name )
        echo $blog_id;
    return $column_name;
}

function do_get_id( $columns ) {
    $columns['blog_id'] = 'ID';
    return $columns;
}

保存后再访问后台的 站点 > 所有站点,在站点列表中就会多一列ID,下一步就会用到。

后台控制面板切换到 设置 > Domains,加入两个域名:

  • Site ID:3(以自己实际为主)
  • Domian:news.com
  • Primary:√

以及:

  • Site ID:4(以自己实际为主)
  • Domian:shop.com
  • Primary:√

如果域名是有www的,一样的操作方式。

6、结果

以上步骤完成之后,基本就OK了。

主站点域名还是不变,还是www.awaimai.com。

用 news.com 就可以访问新闻站点,

用 shop.com 就可以访问商城站点,

博客还是可以用二级域名 blog.awaimai.com 访问。

同时,这几个站点的后台也有独立的地址:

http://www.awaimai.com/wp-admin/
http://blog.awaimai.com/wp-admin/
http://news.com/wp-admin/
http://shop.com/wp-admin/

以后再安装主题和插件不能在每个站点中安装了,

都统一在网络管理(面板左上角 我的站点 > 网络管理 )中进行配置。