biglittleant

不被嘲笑的梦想,是不值得去实现的

0%

haproxy简介

HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费、快速并且可靠的一种解决方案。HAProxy特别适用于那些负载特大的web站点,这些站点通常又需要会话保持或七层处理。HAProxy运行在当前的硬件上,完全可以支持数以万计的并发连接。并且它的运行模式使得它可以很简单安全的整合进您当前的架构中, 同时可以保护你的web服务器不被暴露到网络上。

haproxy的优势

  • 专门做反向代理负载均衡
  • 有八种以上的负载均衡算法
  • 性能大于等于 nginx
  • 支持动态管理,通过haproxy的sock进行通信,可以进行管理。
  • 有比较丰富的dashboard页面。
  • 有比较强大的七层功能。

haproxy实战

haproxy基础环境准备

实验环境

主机名 ip address 操作系统
linux-node1.example.com 192.168.56.11 centos7
linux-node2.example.com 192.168.56.12 centos7

安装EPEL及基础软件包

1
2
rpm -ivh http://mirrors.aliyun.com/epel/epel-release-latest-7.noarch.rpm
yum install -y gcc glibc gcc-c++ make screen tree lrzsz

使用两个Apache的8080端口作为后端真实服务器

linux-node1.example.com

1
2
3
4
5
6
[root@linux-node1 ~]# yum install -y httpd
[root@linux-node1 ~]# sed -i 's/Listen 80/Listen 8080/g' /etc/httpd/conf/httpd.conf
[root@linux-node1 ~]# systemctl start httpd
[root@linux-node1 ~]# echo "linux-node1.example.com" > /var/www/html/index.html
[root@linux-node1 ~]# curl http://192.168.56.11:8080/
linux-node1.example.com

linux-node2.example.com

1
2
3
4
5
6
[root@linux-node2 ~]# yum install -y httpd
[root@linux-node2 ~]# sed -i 's/Listen 80/Listen 8080/g' /etc/httpd/conf/httpd.conf
[root@linux-node2 ~]# systemctl start httpd
[root@linux-node2 ~]# echo "linux-node2.example.com" > /var/www/html/index.html
[root@linux-node2 ~]# curl http://192.168.56.12:8080/
linux-node2.example.com

让本机服务器监听非本机的IP

1
2
3
vim /etc/sysctl.conf
net.ipv4.ip_nonlocal_bind = 1
sysctl -p

Haproxy源码编译安装

1
2
3
4
5
6
7
8
9
10
[root@linux-node1 ~]# cd /usr/local/src
[root@linux-node1 src]# wget http://www.haproxy.org/download/1.6/src/haproxy-1.6.3.tar.gz
[root@linux-node1 src]# tar zxf haproxy-1.6.3.tar.gz
[root@linux-node1 src]# cd haproxy-1.6.3
[root@linux-node1 src]# make TARGET=linux2628 PREFIX=/usr/local/haproxy-1.6.3
[root@linux-node1 src]# make install
[root@linux-node1 ~]# cp /usr/local/sbin/haproxy /usr/sbin/
[root@linux-node1 ~]# haproxy -v
HA-Proxy version 1.6.3 2015/12/25
Copyright 2000-2015 Willy Tarreau <willy@haproxy.org>

Haproxy启动脚本

1
2
3
[root@linux-node1 ~]# cd /usr/local/src/haproxy-1.6.3
[root@linux-node1 haproxy-1.6.3]# cp examples/haproxy.init /etc/init.d/haproxy
[root@linux-node1 haproxy-1.6.3]# chmod 755 /etc/init.d/haproxy

Haproxy配置文件

1
2
3
4
[root@linux-node1 ~]# useradd -r haproxy
[root@linux-node1 ~]# mkdir /etc/haproxy
[root@linux-node1 ~]# mkdir /var/lib/haproxy
[root@linux-node1 ~]# mkdir /var/run/haproxy

haproxy日志文件配置

haproxy默认使用系统的 local3.* 来存储日志

1
2
3
4
5
vim /etc/rsyslog.conf

local3.* /var/log/haproxy.log

systemctl restart rsyslog.service

haproxy配置文件解读

haproxy的配置文件分为五大块

  • global (全局)
  • defaults (默认)
  • listen (监听的配置)
  • frontend (前端)name vip
  • backend(后端)name server 列表

下面来分别讲解各个配置文件的定义

global (全局)

1
2
3
4
5
6
7
8
9
10
global ##定义这是全局配置
log 127.0.0.1 local3 info
###全局的日志配置,使用log关键字,指定使用127.0.0.1上的syslog服务中的local0日志设备,记录日志等级为info的日志。
chroot /var/lib/haproxy
##使用chroot的模式运行haproxy
user haproxy
group haproxy
##设置运行haproxy的用户和组,可以使用uid gid 关键字代替。
daemon
##以守护进程的方式运行

defaults (默认)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
defaults
##defaults模块标识
log global
##使用global中定义的日志
mode http
##mode 语法: mode{http|tcp|health} , http是七层模式,tcp是四层模式,health是检查检测。返回OK。
option httplog
##启用日志记录HTTP请求,默认haproxy日志记录是不记录HTTP请求的,只记录“时间[jan 5 13:23:46] 日志服务器[127.0.0.1] 实例名以及PID [haproxy[25218]] 信息[proxy http 80 in stoped]” ,日志格式很简单。
option dontlognull
##启用该项,日志中将不会记录空连接,所谓空连接就是在上游的负载均衡器或者监听系统为l探测该服务是否存活可用时,需要定期的连接或者获取某一股东的组件或页面,或者探测扫描端口是否在监听或开放等动作被称为空连接。官方文档中标注,如果该服务上游没有其他的负载均衡器的话,建议不要使用该参数。因为互联网上的恶意扫描或其他动作就不会被记录。

timeout connect 5000
##设置成功连接到一台服务器的最长等待时间。默认单位是毫秒,新颁布的haproxy使用timeout connect 代替。该参数向换兼容。
timeout client 50000
##设置连接客户端发送数据时的成功连接最长等待时间,默认单位是毫秒。
timeout server 50000
##设置服务器端回应客户端数据发送的最长等待时间,默认单位是毫秒

listen (监听页面的配置)

1
2
3
4
5
6
7
8
9
10
11
12
13
listen stats
##定义页面监控的名称
mode http
##http的7层模式
bind 0.0.0.0:8888
##服务器监听端口
stats enable
##开启状态模块
stats uri /haproxy-status
##状态模块的URI
stats auth haproxy:haproxy
##状态模块登录的用户名和密码

frontend (前端)name vip

1
2
3
4
5
6
7
8
frontend http_front
#定义前端的名称
bind *:80
#服务器对外监听的端口,类似于lvs的vip端口
stats uri /haproxy?stats
#状态页面定义
default_backend http_back
#对应的后端服务名称

backend(后端)name server 列表

1
2
3
4
5
6
7
8
9
10
11
12
backend http_back
#后端服务的名称
balance roundrobin
#定义轮询算法,roundrobin 轮询,source 源IPhash,leastconn 最小连接数,这里只列举了常用的三个。

option forwardfor header X-REAL-IP
option httpchk HEAD / HTTP/1.0
##haproxy的健康检查,如果这两行被禁用,就是四次检测。
server web-node1 192.168.56.11:8080 check inter 2000 rise 30 fall 3
server web-node2 192.168.56.12:8080 check inter 2000 rise 30 fall 3
##后端真实ip配置,check 表示开启健康检查,inter 2000ms 每隔多长时间检查一次,rise 30 服务可用的连续次数,连续30次可用,在把你加进来,fall 3 失败三次 就把你踢出去。

完整的haproxy配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
global
log 127.0.0.1 local3 info
chroot /var/lib/haproxy
user haproxy
group haproxy
daemon

defaults
log global
mode http
option httplog
option dontlognull
timeout connect 5000
timeout client 50000
timeout server 50000

frontend http_front
bind *:80
stats uri /haproxy?stats
default_backend http_back

backend http_back
balance roundrobin
option httpchk GET /index.html
option httpchk GET /
server linux-node1 192.168.56.11:8080 check
server linux-node2 192.168.56.12:8080 check

haproxy的ACL控制。

推荐生产上不要使用ACL访问控制。所有这里就没有研究。如果感兴趣的可以自行百度。

haproxy动态管理

方法一:

可以listen模式中开启stats admin if TRUE #开启页面管理 通过图形界面管理服务器上下线。

方法二:

使用socat 管理haproxy服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#在global开启socket
global
stats socket /var/run/haproxy.sock mode 600 level admin
stats timeout 2m
#安装socat管理软件
yum install socat
使用socat管理haproxy
socat /var/run/haproxy.sock stdio
基本用法:
echo "help"| socat stdio /var/run/haproxy.sock #socat常用命令
echo "show info "| socat stdio /var/run/haproxy.sock # 显示dashboad 的信息。
echo "show pools "| socat stdio /var/run/haproxy.sock #显示内部资源池的状态
echo "show stat "| socat stdio /var/run/haproxy.sock # 显示前后端服务器的状态

echo "set maxconn"| socat stdio /var/run/haproxy.sock #修改最大连接数
echo "disable server backend_www_example_com/web-node2 "| socat stdio /var/run/haproxy.sock # 关闭 web-node2 的服务器
echo "disable server backend_www_docker_nginx/docker-web2 "| socat stdio /var/run/haproxy.sock # 关闭 web-node2 的服务器
echo "enable server backend_www_docker_nginx/docker-web2 "| socat stdio /var/run/haproxy.sock # 开启 web-node2 的服务器

Apache介绍

Apache是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机平台上,由于其跨平台和安全性被广泛使用,是最流行的Web服务器端软件之一。它快速、可靠并且可通过简单的API扩充,将Perl/Python等解释器编译到服务器中。

特点:

  • Apache模块分为静态模块与动态模块,静态模块是Apache最基本的模块,是无法随时添加与卸载的模块,静态模块在编译软件时设定的。动态模块是可以随时添加和删除的模块。
  • Apache模块将被编译为动态共享对象(DSO),这些动态共享对象独立于httpd程序,DSO模块可以在编译Apache时添加,也可以在后期随时通过Apache extension Tool(apxs)工具编译添加模块。
  • 使用HTTP -M 命令查看模块加载清单。

应用场合:

  • 使用Apache运行静态html网页、图片、处理静态小文件能力不及nginx。
  • 使用Apache结合php引擎运行PHP、Perl、Python等程序,LAMP被称为经典组合。
  • 使用Apache结合tomcat、resin运行jsp、java等程序,成为中小企业的首选。
  • 使用Apache做代理、负载均衡、rewrite规则过滤等等。

实战

安装依赖包

1
yum install zlib-devel zlib apr-devel  apr-util -y

安装Apache软件

1
2
3
4
5
6
7
8
cd /usr/local/src
wget http://apache.mirror.iweb.ca//httpd/httpd-2.4.25.tar.gz
tar -zxf httpd-2.4.25.tar.gz
cd httpd-2.4.25
./configure --prefix=/data/app/apache-2.4.25 --enable-deflate --enable-expires --enable-headers --enable-modules=most --enable-so --with-mpm=worker --enable-rewrite
make
make install
ln -s /data/app/apache-2.4.25/ /data/app/apache

编译参数解释

1
2
3
4
5
6
7
8
./configure --prefix=/application/apache-2.4.25 \
> --enable-deflate \ #启用压缩模式 对文件压缩
> --enable-expires \ #过期时间
> --enable-headers \ #激活HTTP头
> --enable-modules=most \ 激活大多数模块
> --enable-so \ # 开启模块化功能,支持DSO(动态共享对象)
> --with-mpm=worker \ #两种模式:worker,prefork。
> --enable-rewrite #开启地址重写

启动Apache服务:

1
2
3
4
/data/app/apache/bin/apachectl -t
#httpd: Could not reliably determine the server's fully qualified domain name, using 61.50.248.117 for #ServerName
#Syntax OK
/data/app/apache/bin/apachectl start

常见的启动命令

  • start 启动httpd程序
  • stop 停止httpd程序
  • restart 重启httpd程序
  • graceful 优雅重启,不中断现有的HTTP连接请求。
  • graceful-stop 优雅停止,不中断现有的http连接请求。
  • status 查看httpd当前的状态。
  • configtest 检查httpd主配置文件语法。

查看结果:

1
2
3
4
5
6
[root@nfs-client apache]# lsof -i :80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
httpd 98511 root 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 98513 daemon 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 98514 daemon 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 98515 daemon 4u IPv6 129886 0t0 TCP *:http (LISTEN)
1
2
3
4
5
6
7
[root@nfs-client apache]# ps -ef |grep httpd
root 98511 1 0 19:12 ? 00:00:00 /application/apache2.2.27/bin/httpd -k start
daemon 98512 98511 0 19:12 ? 00:00:00 /application/apache2.2.27/bin/httpd -k start
daemon 98513 98511 0 19:12 ? 00:00:00 /application/apache2.2.27/bin/httpd -k start
daemon 98514 98511 0 19:12 ? 00:00:00 /application/apache2.2.27/bin/httpd -k start
daemon 98515 98511 0 19:12 ? 00:00:00 /application/apache2.2.27/bin/httpd -k start
root 98599 16930 0 19:13 pts/0 00:00:00 grep httpd

在客户端使用浏览器访问该web站点,出现IT works 。表示成功。

bin目录下命令介绍

  • ab : Apache 服务器性能测试工具,同类软件还有jmeterloadrunnerwebbench等。
  • apachectl: 这是Apache的启动脚本。
  • apxs: apxs是一个为Apache服务器编译和安装扩展模块的工具,在进行dso方式编译模块时会用到。
  • htcacheclean: 这是清理磁盘缓存去的命令,需要在编译时指定相关参数才可使用。
  • htpasswd: 建立和更新基本认证文件。
  • httpd: httpd 为Apache的控制命令程序,apachectl 执行时会调用httpd。
  • rotatelogs: Apache 自带的日志轮询明白,工作中习惯用由cronolog 代替。

配置文件解析

Apache主要配置文件主要有指令及容器组成,容器使用<容器名称>开始,以</容器名称>结尾,容器的指令一般仅在容器内有效。

/data/app/apache/conf/extra #这是额外的Apache配置文件目录,这个目录里的文件我们会经常访问修改。

/data/app/apache/conf/httpd.conf #Apache的主配置文件,这个文件我们会经常访问修改,其中的每一行的参数做用都应该弄清楚明白。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
ServerRoot "/data/app/apache-2.4.25"
Listen 80
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so
LoadModule dir_module modules/mod_dir.so
LoadModule alias_module modules/mod_alias.so
<IfModule unixd_module>
User daemon
Group daemon
</IfModule>
ServerAdmin you@example.com
<Directory />
AllowOverride none
Require all denied
</Directory>
DocumentRoot "/data/app/apache-2.4.25/htdocs"
<Directory "/data/app/apache-2.4.25/htdocs">
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
<IfModule dir_module>
DirectoryIndex index.html
</IfModule>
<Files ".ht*">
Require all denied
</Files>
ErrorLog "logs/error_log"
LogLevel warn
<IfModule log_config_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
<IfModule logio_module>
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
</IfModule>
CustomLog "logs/access_log" common
</IfModule>
<IfModule alias_module>
ScriptAlias /cgi-bin/ "/data/app/apache-2.4.25/cgi-bin/"
</IfModule>
<IfModule cgid_module>
</IfModule>
<Directory "/data/app/apache-2.4.25/cgi-bin">
AllowOverride None
Options None
Require all granted
</Directory>
<IfModule headers_module>
RequestHeader unset Proxy early
</IfModule>
<IfModule mime_module>
TypesConfig conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
</IfModule>
<IfModule proxy_html_module>
Include conf/extra/proxy-html.conf
</IfModule>
<IfModule ssl_module>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</IfModule>
  • ServerRoot "/data/app/apache-2.4.25":指定Apache软件的安装主目录

  • Listen 80:指定服务器监听的ip和端口号,格式:Listien [ip地址:]端口 [协议]。一个配置文件中多次使用Listen指令开启多个端口。

  • LoadModule alias_module modules/mod_alias.so:加载动态模块,模块文件一般都位于modules目录中。

  • ifModule容器可以封装仅在条件满足时才会出来的指令,语法格式:<IfModule [!] 模块>指令

1
2
3
4
<IfModule unixd_module>
User daemon
Group daemon
</IfModule>

上面的配置文件解释:仅在Apache加载了unixd_module模块后,User daemon和Group daemon才会被处理。

  • ServerAdmin you@example.com:提供一个管理员邮箱。

  • DocumentRoot "/data/app/apache-2.4.25/htdocs":该指令设置web服务器对客户端开发可见的文档根目录,即客户端访问的根目录。

  • Directory 容器内的指令仅应用于特定的文件系统目录、子目录已经目录下的内容。路径可以使用~匹配正则表达式。

1
2
3
4
5
<Directory "/data/app/apache-2.4.25/htdocs">
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
  • DirectoryIndex index.html:指定当前目录下默认访问的HTML是什么。
  • Files :该容器类似于Directory容器,但Files容器内的指令仅应用于特定的文件。
1
2
3
<Files ".ht*">
Require all denied
</Files>
  • ErrorLog "logs/error_log" : 指定服务器错误日志的位置。

  • LogLevel warn: 指定日志级别。

  • LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined : 用户日志文件格式。使用LogFormat 指令设置日志格式创建别名,通过CustomLog 指令调用该日志格式别名。

  • CustomLog "logs/access_log" common:该指令设置客户端访问日志文件名及日志格式。

  • ScriptAlias /cgi-bin/ "/data/app/apache-2.4.25/cgi-bin/": 设置脚本的别名。

  • AllowOverride None:在 AllowOverride 设置为 None 时, .htaccess 文件将被完全忽略。当此指令设置为 All 时,所有具有 “.htaccess” 作用域的指令都允许出现在 .htaccess 文件中。

通常利用Apache的rewrite模块对 URL 进行重写的时候, rewrite规则会写在 .htaccess 文件里。但要使 apache 能够正常的读取.htaccess 文件的内容,就必须对.htaccess 所在目录进行配置。从安全性考虑,根目录的AllowOverride属性一般都配置成不允许任何Override 。

  • Options None:为特定目录设置选项。None表示不启用额外选项。
    • All:开启除MultiViews之外的所有选项。
    • ExecCGI:允许执行Options指定目录下的所有CGI脚本。
    • FollowSymlinks:允许Options指定目录下的文件链接到目录外的文件或目录。
    • Indexes:如果与URL对应的Options 目录下找不到DirectoryIndex指定的首页文档,则Apache会将当前目录下的所有文件索引出来。
  • RequestHeader unset Proxy early
  • Include conf/extra/proxy-html.conf

Order 指令

控制默认访问状态已经Allow与Deny的次序。
如果使用 Order Deny,Allow ,则先检查拒绝,在检查允许,当拒绝与允许有冲突是,允许优先,默认规则为允许。
如果使用 Order Allow,Deny ,则先检查允许,在检查拒绝,当允许与拒绝有冲突是,拒绝优先,默认规则为拒绝。

实例

1
2
order deny,allow
Deny from all

先检查拒绝规则,在检查允许规则,默认为允许,deny拒绝所有,所有最终结果为拒绝所有。

1
2
order allow,deny
allow from all

先检查允许规则,在检查拒绝规则,默认为拒绝,allow允许所有,所有最终结果为允许所有。

1
2
order allow,deny
allow from 192.168.56.1

先检查allow 规则,允许192.168.56.1,其余为默认,默认为拒绝所有,最终除192.168.56.1外拒绝所有。

1
2
3
order allow,deny
allow from 192.168.56.1
deny from All

先检查允许规则允许192.168.56.1访问,在检查拒绝规则为拒绝所有,而192.168.56.1也包含在all中。当allow 与 deny 有冲突时,以order最后的规则覆盖其他规则,本例将使用deny规则覆盖allow规则,最终效果为拒绝所有人访问。

1
2
3
order deny,allow
deny from All
allow from 192.168.56.1

先检查拒绝规则,在检查允许规则,拒绝规则为拒绝所有,允许规则为允许192.168.56.1,拒绝与允许规则有冲突的部分,以order最后的规则覆盖其他规则,最终实现仅允许192.168.56.1访问,其他任何主机均无法访问Apache服务。

Require指令

Apache 2.2 使用order命令限制访问,Apache 2.4 使用require命令限制访问。

Some of the allowed syntaxes provided by mod_authz_user, mod_authz_host, and mod_authz_groupfile are:

Require valid-user
All valid users can access the resource.
Require ip 10 172.20 192.168.2
Clients in the specified IP address ranges can access the resource.
Other authorization modules that implement require options include mod_authnz_ldap, mod_authz_dbm, mod_authz_dbd, mod_authz_owner and mod_ssl.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Require all denied # 拒绝所有请求

Require all granted # 允许所有请求

Require host example.org # 允许局域网访问指定域名,外网拒绝

Require env env-var [env-var] # 只有在设置了给定的环境变量之一才能通过。

Require method http-method [http-method] #只允许指定的请求头,才允许通过。

Require expr expression #只允许满足正则表达式的通过。

Require user userid [userid] ##只有指定的用户ID能访问

Require group group-name [group-name] ##只有指定的组ID能访问。

Require valid-user ##所有的有效用户都可以访问资源。

Require local # 仅允许本地访问

Require ip 192.168.0.1 # 仅允许IP:192.168.0.1 访问

Require ip 192.168.0.1 192.168.0.2 # 仅允许IP:192.168.0.1 和 192.168.0.2 访问

Require all granted
Require not ip 192.168.0.1 # 仅禁止IP:192.168.0.1 访问

TypesConfig 指定mime type的配置文件。
AddType 添加服务器支持的的mime type 类型。

1
2
3
4
5
<IfModule mime_module>
TypesConfig conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz
</IfModule>

SSLRandomSeed startup builtin
SSLRandomSeed connect builtin

apache2.4开始SSLCertificateChainFile指令以不存在,把服务器证明书和中继证明书的内容保存到一个文件,并在SSLCertificateFile指令里指定该文件。

补充说明

支持CGI的配置,现在基本已经淘汰了。

1
2
3
4
5
6
7
8
9
10
11
<IfModule alias_module>
ScriptAlias /cgi-bin/ "/application/apache2.2.27/cgi-bin/"
</IfModule>
<IfModule cgid_module>
</IfModule>
<Directory "/application/apache2.2.27/cgi-bin">
AllowOverride None
Options None
Order allow,deny
Allow from all
</Directory>

httpd-vhosts.conf 虚拟机主机配置

虚拟主机:部署多个站点,每个站点,希望用不同的域名和站点目录,或者是不同的端口,不同的IP。需要虚拟主机功能。

虚拟主机分类:

  • 基于域名。
  • 基于端口。
  • 基于IP。
1
2
3
4
5
6
7
8
9
10
NameVirtualHost *:80  #虚拟主机 * 表示所有的IP地址。

<VirtualHost *:80>
ServerAdmin webmaster@dummy-host.example.com
DocumentRoot "/application/apache2.2.27/docs/dummy-host.example.com"
ServerName dummy-host.example.com
ServerAlias www.dummy-host.example.com
ErrorLog "logs/dummy-host.example.com-error_log"
CustomLog "logs/dummy-host.example.com-access_log" common
</VirtualHost>

MPM 多路处理模块

查看当前使用的是那种MPM模块

1
2
3
4
5
6
./bin/httpd -l
Compiled in modules:
core.c
mod_so.c
http_core.c
worker.c

如果你看到perfork.c 则表示当前为perfork MPM模式。worker.c 则表示为 worker MPM模式。

如何指定MPM

指定./configure –with-mpm=worker/prefork 选项指定MPM,NAME就是你想使用的MPM的名称。--with-mpm=worker 指定worker模式。

使用event模块时,编译安装的时候加上以下参数: --enable-nonportable-atomics=yes
需要注意的是Event MPM对于老的CPU可能是不支持的。

MPM配置解释

1
2
3
<IfModule !mpm_netware_module>
PidFile "logs/httpd.pid"
</IfModule>

指定pidfile的位置。

prefork模块

1
2
3
4
5
6
7
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 250
MaxConnectionsPerChild 0
</IfModule>
  • StartServers: :设置程序启动时,会创建多少个进程。
  • MinSpareServers::设置最小空闲数,Apache会一直保持这个空闲的数量,不会销毁。* MaxSpareServers::设置最大空闲数,另外当max<min时,Apache会自动将maxMinSpareServers+1。
  • MaxRequestWorkers::一个进程可以同时处理多少个请求。
  • MaxConnectionsPerChild::进程处理多少个请求后将自动销毁,0意味着无限,即永不销毁。负载较高时,为使每个进程处理更多的请求,避免销毁、创建线程的开销,一般建议设定这个值为0或较大的数字。但是要注意即使负载降低后,MaxRequestsPerChild也会造成进程占用的内存无法释放,造成”负载降低,但是系统开销居高不下的局面”。

perfork创建进程的过程:perfork先创建一个进程,并在等待一秒后再创建两个进程,下一秒4个、再下一秒16个、最后32个(指数级)。并以后一直保持每秒创建32个进程的速度,直到满足MinSpareServers为止。

apache2.3.13以前的版本MaxRequestWorkers被称为MaxClients 。

MaxRequestWorkers是这些指令中最为重要的一个,是对Apache性能影响最大的参数。其缺省值150是远远不够的,如果请求总数已达到这个值(可通过ps -ef|grep http|wc -l来确认),那么后面的请求就要排队,直到前面的请求处理完毕。这就是系统资源还剩下很多而HTTP访问却很慢的主要原因。虽然理论上这个值越大,可以处理的请求就越多,但Apache默认的限制不能大于256。幸好Apche也意识到这个问题,提供了一个ServerLimit指令来加大MaxClients。

prefork 线程优化设置

Prefork优化的关键在于MaxClients与MaxRequestsPerChild。

MaxRequestWorkers 优化

最佳的值:

1
2
3
4
apache_max_process_with_good_perfermance < (total_hardware_memory /
apache_memory_per_process ) * 2 ;

apache_max_process = apache_max_process_with_good_perfermance * 1.5

第一步:计算httpd平均占用内存

1
2
ps aux|grep -v grep|awk '/httpd/{sum+=$6;n++};END{print sum/n}'
108468(KB)

显示每个进程占用了大约100M的内存,假设机器内存为32G,可拿出16G用于Apache。代入公式,得出

1
2
3
4
apache_max_process_with_good_perfermance = 16 * 1024 * 1024 /
108468*2=309

apache_max_process = 309 * 1.5=464

所以MaxClients可以设置为464

MaxRequestsPerChild 优化

这个值大了影响资源的释放,小了apache不断的fork新的进程,增加CPU资源的开销。有时其影响不限于apache本身的资源,还有相关外部资源的释放。比如网站大多采用apache pool管理数据库连接,MaxRequestsPerChild过大就会造成数据库连接的资源迟迟不释放,给数据库带来不寻常的压力。
关于这一点,最典型的就是数据库出现tomieout等超时连接错误,重启Apache以后,即使访问量恢复到峰值,数据库仍然表现毫无压力,但是持续较长时间后,又出现同样的超时异常。

我采取一分钟pv/MaxRequestWorkers得到这个值。

worker模块

1
2
3
4
5
6
7
8
<IfModule mpm_worker_module>
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 0
</IfModule>
  • StartServers 3: 控制启动的时候生成的进程数。
  • MinSpareThreads 75: 最小空闲线程数。
  • MaxSpareThreads 250: 最大空闲线程数。
  • ThreadsPerChild 25: 每个子进程包含多少个线程数。
  • MaxRequestWorkers 400: 一个进程可以同时处理多少个请求。
  • MaxConnectionsPerChild 0: 进程处理多少个请求后将自动销毁,0意味着无限,即永不销毁。

MinSpareThreads和MaxSpareThreads的最大缺省值分别是75和250。这两个参数对Apache的性能影响并不大,可以按照实际情况相应调节。

ThreadsPerChild是worker MPM中与性能相关最密切的指令。
ThreadsPerChild的最大缺省值是64,如果负载较大,64也是不够的。这时要显式使用 ThreadLimit指令,它的最大缺省值是20000。

上述两个值位于源码树server/mpm/worker/worker.c中的以下两行:

1
2
#define DEFAULT_THREAD_LIMIT 64
define MAX_THREAD_LIMIT 20000

这两行对应着ThreadsPerChild和ThreadLimit的限制数。最好在configure之前就把64改成所希望的值。注意,不要把这两个值设得太高,超过系统的处理能力,从而因Apache不起动使系统很不稳定。

Worker模式下所能同时处理的请求总数是由子进程总数乘以ThreadsPerChild值决定的,应该大于等于MaxClients。如果负载很大,现有的子进程数不能满足时,控制进程会派生新的子进程。默认最大的子进程总数是16,加大时也需要显式声明ServerLimit(最大值是20000)。这两个值位于源码树server/mpm/worker/worker.c中的以下两行:

1
2
#define DEFAULT_SERVER_LIMIT 16
define MAX_SERVER_LIMIT 20000

需要注意的是,如果显式声明了ServerLimit,那么它乘以ThreadsPerChild的值必须大于等于MaxClients,而且MaxClients必须是ThreadsPerChild的整数倍,否则Apache将会自动调节到一个相应值(可能是个非期望值)。

1
2
3
4
5
6
7
8
9
10
<IfModule worker.c>;
StartServers 3
MaxClients 2000
ServerLimit 25
MinSpareThreads 50
MaxSpareThreads 200
ThreadLimit 200
ThreadsPerChild 100
MaxRequestsPerChild 0
</IfModule>;

通过上面的叙述,可以了解到Apache 2.0中prefork和worker这两个重要MPM的工作原理,并可根据实际情况来配置Apache相关的核心参数,以获得最大的性能和稳定性。

补充知识

httpd并发连接情况查看

查看当前的连接数

1
#ps aux|grep httpd|wc -l

实时检测httpd连接数

1
#watch -n 1 -d "pgrep httpd|wc -l"

计算httpd进程占用内在的平均数

1
#ps aux|grep -v grep|awk '/httpd/{sum+=$6}; END{print sum/n}'

查看Apache的并发请求数及期TCP连接状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a, S[a]}'

返回结果示例:

LAST_ACK 5

SYN_RECV 30

ESTABLISHED 1597

FIN_WAIT1 51

FIN_WAIT2 504

TIME_WAIT 1057

apache 网络状态描述

  • CLOSED:无连接是活动的或正在进行
  • LISTEN:服务器在等待进入呼叫
  • SYN_RECV:一个连接请求已经到达,等待确认
  • SYN_SENT:应用已经开始,打开一个连接
  • ESTABLISHED:正常数据传输状态
  • FIN_WAIT1:应用说它已经完成
  • FIN_WAIT2:另一边已同意释放
  • ITMED_WAIT:等待所有分组死掉
  • CLOSING:两边同时尝试关闭
  • TIME_WAIT:另一边已初始化一个释放
  • LAST_ACK:等待所有分组死掉
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<IfModule mpm_event_module>
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 0
</IfModule>
<IfModule mpm_netware_module>
ThreadStackSize 65536
StartThreads 250
MinSpareThreads 25
MaxSpareThreads 250
MaxThreads 1000
MaxConnectionsPerChild 0
</IfModule>
<IfModule mpm_mpmt_os2_module>
StartServers 2
MinSpareThreads 5
MaxSpareThreads 10
MaxConnectionsPerChild 0
</IfModule>
<IfModule mpm_winnt_module>
ThreadsPerChild 150
MaxConnectionsPerChild 0
</IfModule>
<IfModule !mpm_netware_module>
MaxMemFree 2048
</IfModule>
<IfModule mpm_netware_module>
MaxMemFree 100
</IfModule>

2:curl 查看
[root@nfs-client apache]# curl 192.168.1.106

It works!

[root@nfs-client apache]# bin/apachectl -l |egrep “_so|_rewrite|header|expire|deflate”
mod_deflate.c
mod_expires.c
mod_headers.c
mod_rewrite.c
mod_so.c

定时任务-日志轮询:
cd /application/apache/logs
mv www-access_log www-access_$(date +%F).log
/application/apache/bin/apachectl graceful

Apache 优化:
1:不对外展示目录结构
<Directory “/application/apache2.2.27/htdocs”> #自己的站点目录
Options -Indexes FollowSymLinks #indexes 不对外展示目录结构

<Directory “/application/apache2.2.27/htdocs”> #自己的站点目录
Options FollowSymLinks #indexes 不对外展示目录结构

扩展阅读

Apache并行处理模块介绍

Apache 2.X 支持插入式并行处理模块,称为多路处理模块(MPM)。在编译apache时必须选择也只能选择一个MPM,对类UNIX系统,有几个不同的MPM可供选择,它们会影响到apache的速度和可伸缩性。

prefork模块

  Prefork MPM : 这个多路处理模块(MPM)实现了一个非线程型的、预派生的web服务器,它的工作方式类似于Apache 1.3。它适合于没有线程安全库,需要避免线程兼容性问题的系统。它是要求将每个请求相互独立的情况下最好的MPM,这样若一个请求出现问题就不会影响到其他请求。

  这个MPM具有很强的自我调节能力,只需要很少的配置指令调整。最重要的是将MaxClients设置为一个足够大的数值以处理潜在的请求高峰,同时又不能太大,以致需要使用的内存超出物理内存的大小。

worker 模块

  Worker MPM : 此多路处理模块(MPM)使网络服务器支持混合的多线程多进程。由于使用线程来处理请求,所以可以处理海量请求,而系统资源的开销小于基于进程的MPM。但是,它也使用了多进程,每个进程又有多个线程,以获得基于进程的MPM的稳定性。

  每个进程可以拥有的线程数量是固定的。服务器会根据负载情况增加或减少进程数量。一个单独的控制进程(父进程)负责子进程的建立。每个子进程可以建立ThreadsPerChild数量的服务线程和一个监听线程,该监听线程监听接入请求并将其传递给服务线程处理和应答。

  不管是Worker模式或是Prefork 模式,Apache总是试图保持一些备用的(spare)或者是空闲的子进程(空闲的服务线程池)用于迎接即将到来的请求。这样客户端就不需要在得到服务前等候子进程的产生。

Event 模块

  Event MPM:以上两种稳定的MPM方式在非常繁忙的服务器应用下都有些不足。尽管HTTP的Keepalive方式能减少TCP连接数量和网络负载,但是 Keepalive需要和服务进程或者线程绑定,这就导致一个繁忙的服务器会耗光所有的线程。 Event MPM是解决这个问题的一种新模型,它把服务进程从连接中分离出来。在服务器处理速度很快,同时具有非常高的点击率时,可用的线程数量就是关键的资源限 制,此时Event MPM方式是最有效的。一个以Worker MPM方式工作的繁忙服务器能够承受每秒好几万次的访问量(例如在大型新闻服务站点的高峰时),而Event MPM可以用来处理更高负载。值得注意的是,Event MPM不能在安全HTTP(HTTPS)访问下工作。

对于Event 模式,apache给出了以下警告:

This MPM is experimental, so it may or may not work as expected .

这种MPM目前处于试验状态,他可能不能按照预期的那样工作。

mime type 类型

MIME (Multipurpose Internet Mail Extensions) 是描述消息内容类型的因特网标准。
MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据。
MIME参考

参考资料

Apache Prefork、Worker和Event三种MPM分析
一次Apache性能优化
apache2.4废除SSLCertificateChainFile指令

报错汇总

报错一:编译报错

1
2
3
Centos 6.5 ./configure 报错:
checking for zlib location... not found (一般都是devel没有)
checking whether to enable mod_deflate... configure: error: mod_deflate has been requested but can not be built due to prerequisite failures

解决办法:

1
[root@nfs-client httpd-2.2.27]# yum install zlib-devel zlib -y

报错二:编译报错

1
2
3
编译报错
checking for APR... no
configure: error: APR not found. Please read the documentation.

解决办法:

1
yum install apr-devel  apr-util -y

什么是FastCGI

快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。
FastCGI致力于减少网页服务器与CGI程序之间互动的开销,从而使服务器可以同时处理更多的网页请求。

###FastCGI 的优点
FastCGI 的优点是把动态语言和HTTP Server分离开来,所以Nginx与PHP/PHP-FPM经常被部署在不同的服务器上,以分担前端Nginx服务器的压力,使Nginx专一处理静态请求和转发动态请求,而PHP/PHP-FPM服务器专一解析PHP动态请求。

nginx+FastCGI运行原理

Nginx不支持对外部程序的直接调用或者解析,所有的外部程序(包括PHP)必须通过FastCGI接口来调用。FastCGI接口在Linux下是socket(这个socket可以是文件socket,也可以是ip socket)。为了调用CGI程序,还需要一个FastCGI的wrapper(wrapper可以理解为用于启动另一个程序的程序),这个wrapper绑定在某个固定socket上,如端口或者文件socket。当Nginx将CGI请求发送给这个socket的时候,通过FastCGI接口,wrapper接收到请求,然后派生出一个新的线程,这个线程调用解释器或者外部程序处理脚本并读取返回数据;接着,wrapper再将返回的数据通过FastCGI接口,沿着固定的socket传递给Nginx;最后,Nginx将返回的数据发送给客户端。这就是Nginx+FastCGI的整个运作过程。

spawn-fcgi和PHP-FPM对比

  • ligttpd的spwan-fcgi在高并发访问的时候,会出现内存泄漏甚至自动重启FastCGI的问题。
  • PHP-FPM作为PHP的一个补丁,在安装的时候需要和PHP源码一起编译,也就是说PHP-FPM被编译到PHP内核中,因此在处理性能方面更加优秀。同时PHP-FPM在处理高并发方面也比spawn-fcgi引擎好很多,因此,推荐使用Nginx+PHP/PHP-FPM这个组合对PHP进行解析。

php安装

  • 尽量使用PHP和PHP-FPM版本一致,如果版本之间相差太大,可能会出现兼容问题。
  • 早期的时候fpm是PHP的一个补丁包,PHP在 5.3.3 之后已经将php-fpm写入php源码核心了。所以已经不需要另外下载了。

安装相关依赖及软件

centos 5 安装包组:Xsoftware Development
centos 6

1
yum install zlib libxml libjpeg freetype libpng gd  curl libiconv  zlib-devel libxml2-devel libjpeg-devel freetype-devel libpng-devel gd-devel curl-devel libxslt*  -y

iconv字符编码转换命令

1
2
3
4
5
6
7
tar zxf libiconv-1.14.tar.gz
cd libiconv-1.14
./configure --prefix=/usr/local/libiconv
make
make install
sleep 2
cd ../

mcrypt 是php里面重要的加密支持扩展库,这里注意的是mcrypt软件依赖libmcrypt和mhash两个库。

libmcrypt依赖库安装

默认路径:/usr/local/lib/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
wget http://downloads.sourceforge.net/mcrypt/libmcrypt-2.5.8.tar.gz
tar zxf libmcrypt-2.5.8.tar.gz
cd libmcrypt-2.5.8
./configure
make
make install
sleep 2
/sbin/ldconfig
cd libltdl/
./configure --enable-ltdl-install
make
make install
sleep 2
cd ../../

Mhash是基于离散数学原理的不可逆向的php加密方式扩展库,其在默认情况下不开启。mhash的可以用于创建校验数值,消息摘要,消息认证码,以及无需原文的关键信息保存(如密码)等。

默认路径:/usr/local/lib/

1
2
3
4
5
6
7
8
wget http://downloads.sourceforge.net/mhash/mhash-0.9.9.9.tar.gz
tar zxf mhash-0.9.9.9.tar.gz
cd mhash-0.9.9.9/
./configure
make
make install
sleep 2
cd ../

默认路径:/usr/local/bin/

1
2
3
4
5
6
7
8
9
wget http://downloads.sourceforge.net/mcrypt/mcrypt-2.6.8.tar.gz
tar zxf mcrypt-2.6.8.tar.gz
cd mcrypt-2.6.8/
/sbin/ldconfig
./configure LD_LIBRARY_PATH=/usr/local/lib
make
make install
sleep 2
cd ../

配置相关连接

1
2
3
4
5
6
7
8
9
10
11
12
rm -f /usr/lib64/libmcrypt.*
rm -f /usr/lib64/libmhash*
ln -s /usr/local/lib64/libmcrypt.la /usr/lib64/libmcrypt.la
ln -s /usr/local/lib64/libmcrypt.so /usr/lib64/libmcrypt.so
ln -s /usr/local/lib64/libmcrypt.so.4 /usr/lib64/libmcrypt.so.4
ln -s /usr/local/lib64/libmcrypt.so.4.4.8 /usr/lib64/libmcrypt.so.4.4.8
ln -s /usr/local/lib64/libmhash.a /usr/lib64/libmhash.a
ln -s /usr/local/lib64/libmhash.la /usr/lib64/libmhash.la
ln -s /usr/local/lib64/libmhash.so /usr/lib64/libmhash.so
ln -s /usr/local/lib64/libmhash.so.2 /usr/lib64/libmhash.so.2
ln -s /usr/local/lib64/libmhash.so.2.0.1 /usr/lib64/libmhash.so.2.0.1
ln -s /usr/local/bin/libmcrypt-config /usr/bin/libmcrypt-config
1
2
ln -s /application/mysql/lib/libmysqlclient.so.18  /usr/lib64/
mkdir -p ext/phar/phar.phar

PHP安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
./configure \
--prefix=/application/php5.3.27 \
--with-mysql=/application/mysql \
--with-iconv-dir=/usr/local/libiconv \
--with-freetype-dir \
--with-jpeg-dir \
--with-png-dir \
--with-zlib \
--with-libxml-dir=/usr \
--enable-xml \
--disable-rpath \
--enable-safe-mode \
--enable-bcmath \
--enable-shmop \
--enable-sysvsem \
--enable-inline-optimization \
--with-curl \
--with-curlwrappers \
--enable-mbregex \
--enable-fpm \
--enable-mbstring \
--with-mcrypt \
--with-gd \
--enable-gd-native-ttf \
--with-openssl \
--with-mhash \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-zip \
--enable-soap \
--enable-short-tags \
--enable-zend-multibyte \
--enable-static \
--with-xsl \
--with-fpm-user=nginx \
--with-fpm-group=nginx \
--enable-ftp

结果:

1
2
3
4
5
6
7
8
9
10
+--------------------------------------------------------------------+
| License: |
| This software is subject to the PHP License, available in this |
| distribution in the file LICENSE. By continuing this installation |
| process, you are bound by the terms of this license agreement. |
| If you do not agree with the terms of this license, you must abort |
| the installation process at this point. |
+--------------------------------------------------------------------+

Thank you for using PHP.

编译和安装

1
make && make install

配置文件补充
--enable-fastcgi :启用对PHP的FastCGI支持。
--enable-fpm:激活对 FastCGI模式的fpm支持。

Linux 可以不装MySQL,如果连软件包都没有,这样情况可以使用:
1
2
3
4
5
—with-mysql=mysqlnd
其他需要MySQL相关包场景的PHP编译参数:
--enable-mysqlnd \
--with-pdo-mysql=mysqlnd \
--with-mysqli=mysqlnd \

配置PHP:

解压包中包含两个配置文件:

  • php.ini-development :测试环境使用配置文件,相关debug功能都开启了。
  • php.ini-production :生产环境使用配置文件,相关debug功能都关闭了。
1
# cp php.ini-production /application/php/lib/php.ini

PHP配置完毕。

配置PHP及FCGI进程管理

1
2
3
4
ln -s /application/php5.3.27/ /application/php
cp php.ini-production /application/php/lib/php.ini
cd /application/php/etc/
mv php-fpm.conf.5.3.27 php-fpm.conf

php-fpm.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
{pid = /app/logs/php-fpm.pid
error_log = /app/logs/php-fpm.log
log_level= error
rlimit_files=32768
listen.owner=nginx
listen.group=nginx
pm.max_children = 1024
pm.start_servers = 16
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.process_idle_timeout=15s;
pm.max_requests=2048
slowlog=/app/logs/$pool.log.slow}

mkdir -p /app/logs

启动:

1
2
3
4
/application/php/sbin/php-fpm -t
/application/php/sbin/php-fpm
netstat -lntup|grep php-fpm
ps -ef|grep php-fpm

添加开机自启动:

1
2
3
4
5
vim /etc/rc.local
/etc/init.d/nysqld start
/application/php/sbin/php-fpm
/application/nginx/sbin/nginx

PHP-FPM 配置文件详解

[global]配置

  • pid string: PID文件的位置。默认为空。
  • error_log string: 错误日志的位置。默认:安装路径 #INSTALL_PREFIX#/log/php-fpm.log。
  • log_level string: 错误级别。可用级别为:alert(必须立即处理),error(错误情况),warning(警告情况),notice(一般重要信息),debug(调试信息)。默认:notice。
  • emergency_restart_threshold int : 如果子进程在 emergency_restart_interval 设定的时间内收到该参数设定次数的 SIGSEGV 或者 SIGBUS退出信息号,则FPM会重新启动。0 表示“关闭该功能”。默认值:0(关闭)。
  • emergency_restart_interval mixed : emergency_restart_interval 用于设定平滑重启的间隔时间。这么做有助于解决加速器中共享内存的使用问题。可用单位:s(秒),m(分),h(小时)或者 d(天)。默认单位:s(秒)。默认值:0(关闭)。
  • process_control_timeout mixed: 设置子进程接受主进程复用信号的超时时间。可用单位:s(秒),m(分),h(小时)或者 d(天)。默认单位:s(秒)。默认值:0(关闭)。
  • daemonize boolean: 设置 FPM 在后台运行。设置“no”将 FPM 保持在前台运行用于调试。默认值:yes。

Pool Definitions 进程池设置

在FPM中,可以使用不同的设置来运行多个进程池。 这些设置可以针对每个进程池单独设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[www]
user = nginx
group = nginx
listen = 127.0.0.1:9000
listen.owner = nginx
listen.group = nginx
pm = dynamic
pm.max_children = 1024
pm.start_servers = 16
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 2048
slowlog = /app/logs/$pool.log.slow
request_slowlog_timeout = 10
php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f 49000448@qq.com

重要的配置参数

  • listen string: 设置接受 FastCGI 请求的地址。
  • user string: FPM 进程运行的Unix用户。必须设置。
  • group string: FPM 进程运行的 Unix 用户组。如果没有设置,则默认用户的组被使用。
  • pm string: 设置进程管理器如何管理子进程。可用值:static,ondemand,dynamic。必须设置。
  • request_terminate_timeout mixed: 设置单个请求的超时中止时间。该选项可能会对 php.ini 设置中的 ‘max_execution_time’ 因为某些特殊原因没有中止运行的脚本有用。设置为 ‘0’ 表示 ‘Off’。可用单位:s(秒),m(分),h(小时)或者 d(天)。默认单位:s(秒)。默认值:0(关闭)。
  • rlimit_files int: 设置文件打开描述符的 rlimit 限制。默认值:系统定义值。
  • pm.max_requests int: 设置每个子进程重生之前服务的请求数。对于可能存在内存泄漏的第三方模块来说是非常有用的。如果设置为 ‘0’ 则一直接受请求,等同于 PHP_FCGI_MAX_REQUESTS 环境变量。默认值:0。
  • listen.allowed_clients string: 设置允许连接到 FastCGI 的服务器 IPV4 地址。等同于 PHP FastCGI (5.2.2+) 中的 FCGI_WEB_SERVER_ADDRS 环境变量。仅对 TCP 监听起作用。每个地址是用逗号分隔,如果没有设置或者为空,则允许任何服务器请求连接。默认值:any。

详细配置参数

  • listen string: 设置接受 FastCGI 请求的地址。可用格式为:’ip:port’,’port’,’/path/to/unix/socket’。每个进程池都需要设置。

  • listen.backlog int: 设置 listen(2) 的半连接队列长度。“-1”表示无限制。默认值:-1。

  • listen.allowed_clients string: 设置允许连接到 FastCGI 的服务器 IPV4 地址。等同于 PHP FastCGI (5.2.2+) 中的 FCGI_WEB_SERVER_ADDRS 环境变量。仅对 TCP 监听起作用。每个地址是用逗号分隔,如果没有设置或者为空,则允许任何服务器请求连接。默认值:any。

  • listen.owner string: 如果使用,表示设置 Unix 套接字的权限。在Linux中,读写权限必须设置,以便用于 WEB 服务器连接。在很多 BSD 派生的系统中可以忽略权限允许自由连接。默认值:运行所使用的用户和组,权限为 0666。

  • listen.group string: 参见 listen.owner。

  • listen.mode string: 参见 listen.owner。

  • user string: FPM 进程运行的Unix用户。必须设置。

  • group string: FPM 进程运行的 Unix 用户组。如果没有设置,则默认用户的组被使用。

  • pm string: 设置进程管理器如何管理子进程。可用值:static,ondemand,dynamic。必须设置。

    • static: 子进程的数量是固定的(pm.max_children)。
    • ondemand: 进程在有需求时才产生(当请求时,与 dynamic 相反,pm.start_servers 在服务启动时即启动。
    • dynamic: 子进程的数量在下面配置的基础上动态设置:pm.max_children,pm.start_servers,pm.min_spare_servers,pm.max_spare_servers。
  • pm.max_children int: pm 设置为 static 时表示创建的子进程的数量,pm 设置为 dynamic 时表示最大可创建的子进程的数量。必须设置。该选项设置可以同时提供服务的请求数限制。类似 Apache 的 mpm_prefork 中 MaxClients 的设置和 普通PHP FastCGI中的 PHP_FCGI_CHILDREN 环境变量。

  • pm.start_servers in: 设置启动时创建的子进程数目。仅在 pm 设置为 dynamic 时使用。默认值:min_spare_servers + (max_spare_servers - min_spare_servers) / 2。

  • pm.min_spare_servers int: 设置空闲服务进程的最低数目。仅在 pm 设置为 dynamic 时使用。必须设置。

  • pm.max_spare_servers int: 设置空闲服务进程的最大数目。仅在 pm 设置为 dynamic 时使用。必须设置。

  • pm.max_requests int: 设置每个子进程重生之前服务的请求数。对于可能存在内存泄漏的第三方模块来说是非常有用的。如果设置为 ‘0’ 则一直接受请求,等同于 PHP_FCGI_MAX_REQUESTS 环境变量。默认值:0。

  • pm.status_path string: FPM 状态页面的网址。如果没有设置,则无法访问状态页面,默认值:无。

  • ping.path string: FPM 监控页面的 ping 网址。如果没有设置,则无法访问 ping 页面。该页面用于外部检测 FPM 是否存活并且可以响应请求。请注意必须以斜线开头(/)。

  • ping.response string: 用于定义 ping 请求的返回响应。返回为 HTTP 200 的 text/plain 格式文本。默认值:pong。

  • request_terminate_timeout mixed: 设置单个请求的超时中止时间。该选项可能会对 php.ini 设置中的 ‘max_execution_time’ 因为某些特殊原因没有中止运行的脚本有用。设置为 ‘0’ 表示 ‘Off’。可用单位:s(秒),m(分),h(小时)或者 d(天)。默认单位:s(秒)。默认值:0(关闭)。

  • request_slowlog_timeout mixed: 当一个请求该设置的超时时间后,就会将对应的 PHP 调用堆栈信息完整写入到慢日志中。设置为 ‘0’ 表示 ‘Off’。可用单位:s(秒),m(分),h(小时)或者 d(天)。默认单位:s(秒)。默认值:0(关闭)。

  • slowlog string: 慢请求的记录日志。默认值:#INSTALL_PREFIX#/log/php-fpm.log.slow。

  • rlimit_files int: 设置文件打开描述符的 rlimit 限制。默认值:系统定义值。

  • rlimit_core int: 设置核心 rlimit 最大限制值。可用值:’unlimited’,0 或者正整数。默认值:系统定义值。

  • chroot string: 启动时的 Chroot 目录。所定义的目录需要是绝对路径。如果没有设置,则 chroot 不被使用。

  • chdir string: 设置启动目录,启动时会自动 Chdir 到该目录。所定义的目录需要是绝对路径。默认值:当前目录,或者根目录(chroot时)。

  • catch_workers_output boolean: 重定向运行过程中的 stdout 和 stderr 到主要的错误日志文件中。如果没有设置,stdout 和 stderr 将会根据 FastCGI 的规则被重定向到 /dev/null。默认值:无。

管理FastCGI 进程

启动FastCGI进程

方法一:

1
/data/app/php/bin/php-cgi --fpm

方法二:

1
/data/app/php/sbin/php-fpm start

推荐采用第二种方法启动FastCGI。
php-fpm logrotate 重新启用log文件。

使用信号重启php-fpm

  • INT, TERM 立刻终止
  • QUIT 平滑终止
  • USR1 重新打开日志文件
  • USR2 平滑重载所有worker进程并重新载入配置和二进制模块

示例:

php-fpm 关闭:

1
kill -INT `cat /data/app/php/var/run/php-fpm.pid`

php-fpm 重启:

1
kill -USR2 `cat /data/app/php/var/run/php-fpm.pid`

php与web中间件整合

php与nginx整合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 80;
server_name blog.biglittleant.cn;
root www/blog;
location / {
root www/blog;
index index.html index.htm;
}
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
}

fastcgi的优化主要集中在nginx的fastcgi.conf配置文件中。
相关内容参考死磕nginx系列–配置文档解读

测试PHP程序是否正常

1
2
3
4
 cat test.php
<?php
phpinfo();
?>

测试MYSQL是否正常

1
2
3
4
5
6
7
8
9
10
<?php
//$link_id=mysql_connect('主机名','用户','密码');
$link_id=mysql_connect('localhost','root','oldboy123') or mysql_error();

if($link_id){
echo "mysql successful by oldboy !\n";
}else{
echo "mysql_error()";
}
?>

报错汇总:

报错一 编译报错

1
configure: error: Cannot find OpenSSL's <evp.h>

解决办法

1
yum yum install openssl openssl-devel

报错二 make 报错

1
2
3
Generating phar.php
/root/tools/php-5.3.27/sapi/cli/php: error while loading shared libraries: libmysqlclient.so.18: cannot open shared object file: No such file or directory
make: *** [ext/phar/phar.php] Error 127

解决方法:

1
ln -s /application/php5.3.27 /application/php

报错三 make 报错

1
2
3
4
5
6
Generating phar.phar
chmod: cannot access `ext/phar/phar.phar': No such file or directory
make: [ext/phar/phar.phar] Error 1 (ignored)

Build complete.
Don't forget to run 'make test’.

解决办法:

1
[root@test2 php-5.3.27]# mkdir  -p ext/phar/phar.phar

报错四 编译报错:

1
configure: error: Cannot find OpenSSL's <evp.h>

解决方法:

1
[root@nfs-client php-5.3.27]# yum install openssl-devel -y

报错五编译报错:

1
2
checking for XSL support... yes
configure: error: xslt-config not found. Please reinstall the libxslt >= 1.1.0 distribution

解决方法:

1
[root@nfs-client php-5.3.27]# yum install libxslt-devel -y

报错六 configure 报错:

1
2
3
4
checking if we should use cURL for url streams... no
checking for cURL in default path... not found
configure: error: Please reinstall the libcurl distribution -
easy.h should be in <curl-dir>/include/curl/

解决办法

1
2
ln -s /application/mysql/lib/libmysqlclient.so.18  /usr/lib64/
mkdir -p ext/phar/phar.phar

参考文档

官方FPM配置文件解读

搞不清FastCgi与PHP-fpm之间是个什么样的关系

搞不清FastCgi与PHP-fpm之间是个什么样的关系

刚开始对这个问题我也挺纠结的,看了《HTTP权威指南》后,感觉清晰了不少。

首先,CGI是干嘛的?CGI是为了保证web server传递过来的数据是标准格式的,方便CGI程序的编写者。

1
2
3
web server(比如说nginx)只是内容的分发者。比如,如果请求/index.html,那么web server会去文件系统中找到这个文件,发送给浏览器,这里分发的是静态数据。好了,如果现在请求的是/index.php,根据配置文件,nginx知道这个不是静态文件,需要去找PHP解析器来处理,那么他会把这个请求简单处理后交给PHP解析器。Nginx会传哪些数据给PHP解析器呢?url要有吧,查询字符串也得有吧,POST数据也要有,HTTP header不能少吧,好的,CGI就是规定要传哪些数据、以什么样的格式传递给后方处理这个请求的协议。仔细想想,你在PHP代码中使用的用户从哪里来的。

当web server收到/index.php这个请求后,会启动对应的CGI程序,这里就是PHP的解析器。接下来PHP解析器会解析php.ini文件,初始化执行环境,然后处理请求,再以规定CGI规定的格式返回处理后的结果,退出进程。web server再把结果返回给浏览器。

好了,CGI是个协议,跟进程什么的没关系。那fastcgi又是什么呢?Fastcgi是用来提高CGI程序性能的。

1
提高性能,那么CGI程序的性能问题在哪呢?"PHP解析器会解析php.ini文件,初始化执行环境",就是这里了。标准的CGI对每个请求都会执行这些步骤(不闲累啊!启动进程很累的说!),所以处理每个时间的时间会比较长。这明显不合理嘛!那么Fastcgi是怎么做的呢?首先,Fastcgi会先启一个master,解析配置文件,初始化执行环境,然后再启动多个worker。当请求过来时,master会传递给一个worker,然后立即可以接受下一个请求。这样就避免了重复的劳动,效率自然是高。而且当worker不够用时,master可以根据配置预先启动几个worker等着;当然空闲worker太多时,也会停掉一些,这样就提高了性能,也节约了资源。这就是fastcgi的对进程的管理。

那PHP-FPM又是什么呢?是一个实现了Fastcgi的程序,被PHP官方收了。

1
大家都知道,PHP的解释器是php-cgi。php-cgi只是个CGI程序,他自己本身只能解析请求,返回结果,不会进程管理(皇上,臣妾真的做不到啊!)所以就出现了一些能够调度php-cgi进程的程序,比如说由lighthttpd分离出来的spawn-fcgi。好了PHP-FPM也是这么个东东,在长时间的发展后,逐渐得到了大家的认可(要知道,前几年大家可是抱怨PHP-FPM稳定性太差的),也越来越流行。

好了,最后来回来你的问题。 网上有的说,fastcgi是一个协议,php-fpm实现了这个协议

1
对。

有的说,php-fpm是fastcgi进程的管理器,用来管理fastcgi进程的

1
2
3
对。php-fpm的管理对象是php-cgi。但不能说php-fpm是fastcgi进程的管理器,因为前面说了fastcgi是个协议,似乎没有这么个进程存在,就算存在php-fpm也管理不了他(至少目前是)。 有的说,php-fpm是php内核的一个补丁

以前是对的。因为最开始的时候php-fpm没有包含在PHP内核里面,要使用这个功能,需要找到与源码版本相同的php-fpm对内核打补丁,然后再编译。后来PHP内核集成了PHP-FPM之后就方便多了,使用--enalbe-fpm这个编译参数即可。

有的说,修改了php.ini配置文件后,没办法平滑重启,所以就诞生了php-fpm

1
是的,修改php.ini之后,php-cgi进程的确是没办法平滑重启的。php-fpm对此的处理机制是新的worker用新的配置,已经存在的worker处理完手上的活就可以歇着了,通过这种机制来平滑过度。

还有的说PHP-CGI是PHP自带的FastCGI管理器,那这样的话干吗又弄出个php-fpm

1
不对。php-cgi只是解释PHP脚本的程序而已。

假设nginx服务器的IP地址:192.168.56.11
当我们访问:http://192.168.56.11/test/test.html

如果proxy_pass 后端包含反斜线,后端服务器得到的URL为:192.168.56.12:8002/test.html
如果proxy_pass 后端不包含反斜线时,后端服务器得到的URL为:
192.168.56.12:8002/test/test.html

实例

proxy_pass 包含反斜线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
worker_processes  1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream test {
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
}
server {
listen 80;
location /test {
proxy_pass http://test/;
}

后端的access访问日志

- - [07/Nov/2016:10:33:38 +0000] "GETHTTP/1.0" 404 571 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36" "-"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
```

> URI的`/test/`被切割了。

## proxy_pass 后端不包含反斜杠

```shell
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
upstream test {
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
}
server {
listen 80;
location /test {
proxy_pass http://test;
}

后端的access访问日志

- - [07/Nov/2016:10:37:49 +0000] "GETHTTP/1.0" 304 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36" "-"
1

URL 是完整得URI。

简介:upsync模块是用来解决配置文件修改后,reload nginx进程造成性能下降的问题。

处理流程
它的功能是拉取 consul 的后端 server 的列表,并更新 Nginx 的路由信息。此模块不依赖于任何第三方模块。consul 作为 Nginx 的 db,利用 consul 的 KV 服务,每个 Nginx work 进程独立的去拉取各个 upstream 的配置,并更新各自的路由。

实战

给nginx打补丁包

这步可以不做,如果不做,编译的时候删除这个模块

1
2
3
git clone https://github.com/xiaokai-wang/nginx_upstream_check_module
## 打补丁包
patch -p0 < /usr/local/src/nginx_upstream_check_module-master/check_1.9.2+.patch

下载nginx-upsync-module源码

1
2
git clone https://github.com/weibocom/nginx-upsync-module.git

1
2
3
4
5
6
7
8
下载nginx源码
wget 'http://nginx.org/download/nginx-1.10.1.tar.gz'
tar -xzvf nginx-1.10.1.tar.gz
cd nginx-1.10.1/
开始编译
./configure --prefix=/data/app/nginx-1.10.1 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --add-module=/usr/local/src/nginx-upsync-module-master/ --add-module=/usr/local/src/nginx_upstream_check_module-master/
make
make install

启动consul

1
2
3
wget https://releases.hashicorp.com/consul/0.6.4/consul_0.6.4_linux_amd64.zip
unzip consul_0.6.4_linux_amd64.zip
./consul agent -advertise=x.x.x.x -client=0.0.0.0 -dev

创建nginx配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
mkdir -p /usr/local/nginx/conf/servers
/usr/local/nginx/conf/nginx.conf
events {
worker_connections 4096; ## Default: 1024
}

http {
upstream test {
# fake server otherwise ngx_http_upstream will report error when startup
server 127.0.0.1:11111;

# all backend server will pull from consul when startup and will delete fake server
upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off;
upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf;
}

upstream bar {
server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3;
}

server {
listen 8080;

location = /proxy_test {
proxy_pass http://test;
}

location = /bar {
proxy_pass http://bar;
}

location = /upstream_show {
upstream_show;
}

}
}

测试

1
2
3
4
5
for i in `seq 3`;do mkdir html$i/test -p && echo $i >html$i/test/test.html; done;

docker run -d -p 8001:80 -v /root/html1/:/usr/share/nginx/html nginx
docker run -d -p 8002:80 -v /root/html2/:/usr/share/nginx/html nginx
docker run -d -p 8003:80 -v /root/html3/:/usr/share/nginx/html nginx

添加服务

1
2
3
4
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://127.0.0.1:8500/v1/kv/upstreams/test/192.168.56.12:8001
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://127.0.0.1:8500/v1/kv/upstreams/test/192.168.56.12:8002
curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://127.0.0.1:8500/v1/kv/upstreams/test/192.168.56.12:8003

查看conf/servers/servers_test.conf 文件中是否有内容

1
2
3
4
cat conf/servers/servers_test.conf
server 192.168.56.12:8003 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8001 weight=1 max_fails=2 fail_timeout=10s;

或者浏览器打开http://192.168.56.11:8080/upstream_show?test
显示内容如下:

1
2
3
4
Upstream name: test; Backend server count: 3
server 192.168.56.12:8003 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8002 weight=1 max_fails=2 fail_timeout=10s;
server 192.168.56.12:8001 weight=1 max_fails=2 fail_timeout=10s;

总结
此模块只修改upstream 中的缓存信息,不能修改或添加其他配置

测试中遇到的问题
在添加服务时出现如下错误,导致服务添加不能实时进行,大约需要3分钟左右时间。

consul日志:

1
2
3
4
5
6
7
8
2016/03/22 05:34:42 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (149.023µs) from=127.0.0.1:38853
2016/03/22 05:34:43 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (146.759µs) from=127.0.0.1:38854
2016/03/22 05:34:45 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (149.853µs) from=127.0.0.1:38855
2016/03/22 05:34:46 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (111.46µs) from=127.0.0.1:38856
2016/03/22 05:34:48 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (142.696µs) from=127.0.0.1:38857
2016/03/22 05:34:48 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (112.089µs) from=127.0.0.1:38858
2016/03/22 05:34:49 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (114.29µs) from=127.0.0.1:38859
2016/03/22 05:34:50 [DEBUG] http: Request GET /v1/kv/upstreams/test?recurse&index=169 (148.245µs) from=127.0.0.1:38860

nginx日志

1
2
3
4
5
6
7
8
9
10
11
2016/03/22 05:35:09 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:09 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:10 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:10 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:11 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:11 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:13 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:13 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:13 [error] 18879#0: recv() failed (104: Connection reset by peer)
2016/03/22 05:35:13 [error] 18879#0: upsync_recv: recv error with upsync_server: 127.0.0.1:8500
2016/03/22 05:35:14 [error] 18879#0: recv() failed (104: Connection reset by peer)

问题现象
当添加一个服务时,出现此问题,新增服务不能及时添加到负载中,不影响运行正常的服务。 此时再往consul继续添加一个服务时,可能会导致此错误终止,并能成功添加当前两条服务记录。

帮助文档

官方github地址
nginx_upstream_check_module

rewrite这个模块允许使用正则表达式重写URI(需PCRE库),并且可以根据相关变量重定向和选择不同的配置。
如果这个指令在server字段中指定,那么将在被请求的location确定之前执行,如果在指令执行后所选择的location中有其他的重写规则,那么它们也被执行。如果在location中执行这个指令产生了新的URI,那么location又一次确定了新的URI。
这样的循环可以最多执行10次,超过以后nginx将返回500错误。

1
2
3
格式:	rewrite regex replacement [flag];
默认参数: —
作用域 : server, location, if
  • regex: 支持正则表达式,字符串等。
  • replacement:匹配规则后的重定向。
  • [flag]的参数包括:
    • last :表示完成rewrite,浏览器地址栏URL地址不变
    • break;本条规则匹配完成后,终止匹配,不再匹配后面的规则,浏览器地址栏URL地址不变
    • redirect:返回302临时重定向,浏览器地址会显示跳转后的URL地址,如果替换字段用http://开头则被使用
    • permanent:返回301永久重定向,浏览器地址栏会显示跳转后的URL地址

last 和 break的区别 break匹配完成不继续匹配,last匹配完成后,会使用新的URL继续向下匹配。

1
2
3
4
5
6
7
8
9
10
11
12
server {
location / {
rewrite /last/ /1.html last;
rewrite /break/ /1.html break;
}

location = /1.html {
return 400;
}
}
访问/last/时重写到/1.html,然后使用新的uri再匹配,正好匹配到locatoin = /q.html然后返回了400
访问/break时重写到/1.html,由于返回了break,则直接停止了

实例一:将 /work/* 重写为* 、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream work {
server 192.168.56.12:8080;
server 192.168.56.11:8080;
}
server {
listen 80;
server_name blog.biglittleant.cn;

location /work/ {
rewrite "^/work/(.+)" /$1 break;
proxy_pass http://work;
}
location / {
root html;
index index.html index.htm;
}

实例二:补充官方的例子

1

if模块

1
2
3
格式:	if (condition) { ... }
默认值: —
作用域: server, location

if判断如果结果为true,那么在大括号中指定的模块指令将被执行,并且请求被分配在if指令内部的配置。 if指令内的配置继承自先前的配置级别。条件可以是以下任何一个:变量名;如果变量的值为空字符串或“0”,则为false;在版本1.0.1之前,以“0”开头的任何字符串被视为false值。

  • 使用“=”和“!=”运算符将变量与字符串进行比较;
  • 使用“〜”(区分大小写匹配)和“〜*”(区分大小写匹配)运算符匹配正则表达式。
  • 正则表达式中的括号包含起来以后在$ 1 .. $ 9变量中重用的捕获。负运算符“!〜”和“!〜*”也可用。
  • 如果正则表达式包含“}”或“;”字符,则整个表达式应用单引号或双引号括起来。
  • 使用“-f”和“!-f”运算符检查文件存在;
  • 使用“-d”和“!-d”运算符检查目录存在;
  • 使用“-e”和“!-e”运算符检查文件,目录或符号链接是否存在;
  • 使用“-x”和“!-x”运算符检查可执行文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if ($http_user_agent ~ MSIE) {
rewrite ^(.*)$ /msie/$1 break;
}

if ($http_cookie ~* "id=([^;]+)(?:;|$)") {
set $id $1;
}

if ($request_method = POST) {
return 405;
}

if ($slow) {
limit_rate 10k;
}

if ($invalid_referer) {
return 403;
}

return模块

1
2
3
4
5
6
格式:	return code [text];
return code URL;
return URL;
默认值: —
作用域: server, location, if

使用nginx做负载均衡的两大模块:

  • upstream 定义负载节点池。
  • location 模块 进行URL匹配。
  • proxy模块 发送请求给upstream定义的节点池。

相关功能模块讲解

upstream模块解读

nginx 的负载均衡功能依赖于 ngx_http_upstream_module模块,所支持的代理方式有 proxy_pass(一般用于反向代理),fastcgi_pass(一般用于和动态程序交互),memcached_pass,proxy_next_upstream,fastcgi_next_pass,memcached_next_pass 。

upstream 模块应该放于http{}标签内。

模块写法:

1
2
3
4
5
6
7
upstream backend {
ip_hash;
server backend1.example.com weight=5;
server backend2.example.com:8080;
server backup1.example.com:8080 backup;
server backup2.example.com:8080 backup;
}

实例一:

1
2
3
4
5
6
7
8
9
10
11
upstream dynamic {
zone upstream_dynamic 64k;

server backend1.example.com weight=5;
server backend2.example.com:8080 fail_timeout=5s slow_start=30s;
server 192.0.2.1 max_fails=3;
server backend3.example.com resolve;

server backup1.example.com:8080 backup;
server backup2.example.com:8080 backup;
}

语法解释:
nginx默认支持四种调度算法

  • 轮询(rr),每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器故障,故障系统自动清除,使用户访问不受影响。
  • 轮询权值(weight),weight值越大,分配到的访问几率越高,主要用于后端每个服务器性能不均的情况。
  • ip_hash,每个请求按访问IP的hash结果分配,这样来自同一个IP的固定访问一个后端服务器,主要解决动态网站session共享的问题。
  • url_hash,按照访问的URL的hash结果来分配请求,是每个URL定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率,nginx本身不支持,如果想使用需要安装nginx的hash软件包。
  • fair,这个算法可以依据页面大小和加载时间长短智能的进行负载均衡,也就是根据后端服务器的响应时间来分配请求,相应时间短的优先分配,默认不支持,如果想使用需要安装upstream_fail模块。
  • least_conn 最少链接数,那个机器连接数少就分发。

server模块的写法 server IP 调度状态

server指令指定后端服务器IP地址和端口,同时还可以设定每个后端服务器在负载均衡调度中的状态。

  • down 表示当前的server暂时不参与负载均衡。
  • backup 预留的备份服务器,当其他所有的非backup服务器出现故障或者忙的时候,才会请求backup机器,因为这台集群的压力最小。
  • max_fails 允许请求失败的次数,默认是1,当超过最大次数时,返回proxy_next_upstream模块定义的错误。0表示禁止失败尝试,企业场景:2-3.京东1次,蓝汛10次,根据业务需求去配置。
  • fail_timeout,在经历了max_fails次失败后,暂停服务的时间。京东是3s,蓝汛是3s,根据业务需求配置。常规业务2-3秒合理。

例:如果max_fails是5,他就检测5次,如果五次都是502.那么,他就会根据fail_timeout 的值,等待10秒,再去检测。

server 如果接域名,需要内网有DNS服务器,或者在负载均衡器的hosts文件做域名解析。server后面还可以直接接IP或IP加端口。

location 模块解读

location作用:基于一个指令设置URI。
基本语法:

1
2
3
4
Syntax:	location [ = | ~ | ~* | ^~ ] uri { ... }
location @name { ... }
Default: —
Context: server, location
  • = 精确匹配,如果找到匹配=号的内容,立即停止搜索,并立即处理请求(优先级最高)
  • ~ 区分大小写
  • ~* 不区分大小写
  • ^~ 只匹配字符串,不匹配正则表达式
  • @ 指定一个命名的location,一般用于内部重定义请求,location @name {…}

官方的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
[ configuration E ]
}

测试用的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
location / {
return 401;
}
location = / {
return 402;
}
location /documents/ {
return 403;
}
location ^~ /images/ {
return 404;
}
location ~* \.(gif|jpg|jpeg)$ {
return 500;
}

测试结果(重点看):

1
2
3
4
5
6
7
8
9
10
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/
402
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/index.html
401
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/documents/document.html
403
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/images/1.gif
404
[root@lb01 conf]# curl -I -s -o /dev/null -w "%{http_code}\n" http://10.0.0.7/dddd/1.gif
500

结果总结:
匹配的优先顺序,=>^~(匹配固定字符串,忽略正则)> 完全相等>~*>>/

proxy_pass 模块解读

proxy_pass 指令属于ngx_http_proxy_module 模块,此模块可以将请求转发到另一台服务器。

写法:

1
proxy_pass http://localhost:8000/uri/;

实例一:

1
2
3
4
5
6
7
8
9
10
11
12
13
upstream blog_real_servers {
server 10.0.0.9:80 weight=5;
server 10.0.0.10:80 weight=10;
server 10.0.0.19:82 weight=15;
}
server {
listen 80;
server_name blog.etiantian.org;
location / {
proxy_pass http://blog_real_servers;
proxy_set_header host $host;
}
}
  • proxy_set_header:当后端Web服务器上也配置有多个虚拟主机时,需要用该Header来区分反向代理哪个主机名,proxy_set_header host $host;
  • proxy_set_header X-Forwarded-For :如果后端Web服务器上的程序需要获取用户IP,从该Header头获取。proxy_set_header X-Forwarded-For $remote_addr;

配置后端服务器接收前端真实IP

配置如下:

1
2
3
4

log_format commonlog '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

rs_apache节点的httpd.conf配置

1
2
3
4
LogFormat "\"%{X-Forwarded-For}i\" %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{U
ser-Agent}i\"" combined修改日志记录
apache
LogFormat "\"%{X-Forwarded-For}i\" %l %u %t \"%r\" %>s %b" common

proxy_pass相关的优化参数

  • client_max_body_size 10m; 允许客户端请求的最大的单个文件字节数

  • client_body_buffer_size 128k; 缓冲区代理缓冲用户端请求的最大字节数 可以理解为先保存到本地再传给用户

  • proxy_connect_timeout 600; 跟后端服务器连接的超时时间_发起握手等候响应超时时间

  • proxy_read_timeout 600; 连接成功后_等候后端服务器响应时间_其实已经进入后端的排队之中等候处理

  • proxy_send_timeout 600; 后端服务器回传数据时间,就是在规定时间之内后端服务器必须传完所有的数据

  • proxy_buffer_size 8k; 代理请求缓存区,这个缓存区间会保存用户的头信息以供Nginx进行规则处理,一般只要设置能保存下头信息即可

  • proxy_buffers 4 32k; 同上 告诉Nginx保存单个页面使用的空间大小,假设网页大小平均在32k以下的话。

  • proxy_busy_buffers_size 64k; 如果系统很忙的时候可以申请更大的proxy_buffers 官方推荐(proxy_buffers*2)

  • proxy_max_temp_file_size 1024m; 当 proxy_buffers 放不下后端服务器的响应内容时,会将一部分保存到硬盘的临时文件中,这个值用来设置最大临时文件大小,默认1024M,它与 proxy_cache 没有关系。大于这个值,将从upstream服务器传回。设置为0禁用。

  • proxy_temp_file_write_size 64k; proxy缓存临时文件的大小 proxy_temp_path(可以在编译的时候)指定写到哪那个目录。

一个完整的nginx实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@lb01 conf]# cat nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#blog lb by oldboy at 201303
upstream blog_real_servers {
server 10.0.0.9:80 weight=1 max_fails=1 fail_timeout=10s;
server 10.0.0.10:80 weight=1 max_fails=2 fail_timeout=20s;

}
server {
listen 80;
server_name blog.etiantian.org;
location / {
proxy_pass http://blog_real_servers;
include proxy.conf;
}
}
}
1
2
3
4
5
6
7
8
9
[root@lb01 conf]# cat proxy.conf
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k; proxy_temp_file_write_size 64k;

扩展补充

只允许使用GET,HEAD,POST方法去请求

1
2
3
4
## Only allow these request methods ##
if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}

实战

根据URI及location实现动静分离。

最终实现:

  1. /static/的URL都去访问10.0.0.9。
  2. /dynamic/的URL都去访问10.0.0.10。
  3. 图片这些静态文件去访问10.0.0.9。
  4. /upload/的URL都去访问10.0.0.10。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

[root@lb01 conf]# cat nginx.conf
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
#blog lb by oldboy at 201303

upstream static_pools {
server 10.0.0.9:80;
}
upstream dynamic_pools {
server 10.0.0.10:80;
}
upstream upload_pools {
server 10.0.0.9:80;
}

server {
listen 80;
server_name blog.biglittleant.cn;

location / {
proxy_pass http://static_pools;
include proxy.conf;
}

location /static/ {
proxy_pass http://static_pools;
include proxy.conf;
}

location ~* \.(gif|jpg|jpeg)$ {
proxy_pass http://static_pools;
include proxy.conf;
}

location /dynamic/ {
proxy_pass http://dynamic_pools;
include proxy.conf;
}
location /upload/ {
proxy_pass http://upload_pools;
include proxy.conf;
}
}
}

实现苹果手机和安卓手机访问不同的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
server_name blog.etiantian.org;
location / {
if ($http_user_agent ~* "android")
{
proxy_pass http://android_pools;
}
if ($http_user_agent ~* "iphone")
{
proxy_pass http://iphone_pools;
}
proxy_pass http://pc_pools;
include extra/proxy.conf;
}
access_log off;
}

参考文档

nginx-proxy_pass官网

死磕nginx系列–配置文档解读

nginx配置文件结构图

nginx配置文件结构

nginx配置文件主要分为四个部分:

  • main(全局设置)

  • server(主机设置)

  • upstream(负载均衡服务器设置)

  • location(URL匹配特点位置的设置)

    server部分的指令主要用于指定主机和端口,upstream指令主要用于负载均衡和设置一系列的后端服务器,location部分用于匹配网页位置位置。
    关系如下:
    server继承main,location继承server,upstream即不会继承其他设置也不会被继承。

main全局配置

nginx在运行时与具体业务功能(比如http服务或者email服务代理)无关的一些参数,比如工作进程数,运行的身份等。

1
2
3
4
5
6
user  www www;
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
error_log /data/logs/nginx_error.log crit;
pid /usr/local/webserver/nginx/nginx.pid;
worker_rlimit_nofile 65535;
  • user www www; # 指定nginx进程使用什么用户启动
  • worker_processes 4; #指定启动多少进程来处理请求,一般情况下设置成CPU的核数,如果开启了ssl和gzip更应该设置成与逻辑CPU数量一样甚至为2倍,可以减少I/O操作。使用grep ^processor /proc/cpuinfo | wc -l查看CPU核数。
  • worker_cpu_affinity 0001 0010 0100 1000; #在高并发情况下,通过设置将CPU和具体的进程绑定来降低由于多核CPU切换造成的寄存器等现场重建带来的性能损耗。如worker_cpu_affinity 0001 0010 0100 1000; (四核)。
  • error_log /data/logs/nginx_error.log crit; error_log是个主模块指令,用来定义全局错误日志文件。日志输出级别有debug、info、notice、warn、error、crit可供选择,其中,debug输出日志最为最详细,而crit输出日志最少。
  • pid /usr/local/webserver/nginx/nginx.pid; # 指定进程pid文件的位置。
  • worker_rlimit_nofile 65535; ##用于指定一个nginx进程可以打开的最多文件描述符数目,这里是65535,需要使用命令“ulimit -n 65535”来设置。

events模块

1
2
3
4
events{
use epoll;
worker_connections 65536;
}
  • use epoll;use是个事件模块指令,用来指定Nginx的工作模式。Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。其中select和poll都是标准的工作模式,kqueue和epoll是高效的工作模式,不同的是epoll用在Linux平台上,而kqueue用在BSD系统中。对于Linux系统,epoll工作模式是首选。在操作系统不支持这些高效模型时才使用select。

  • worker_connections 65536; 每一个worker进程能并发处理(发起)的最大连接数(包含与客户端或后端被代理服务器间等所有连接数)。nginx作为反向代理服务器,计算公式 最大连接数 = worker_processes * worker_connections/4,所以这里客户端最大连接数是1024,这个可以增到到8192都没关系,看情况而定,但不能超过后面的worker_rlimit_nofile。当nginx作为http服务器时,计算公式里面是除以2。进程的最大连接数受Linux系统进程的最大打开文件数限制,在执行操作系统命令ulimit -n 65536worker_connections的设置才能生效。

http服务器

1
2
3
4
5
6
http
{
include mime.types;
default_type application/octet-stream;
#charset gb2312;
}
  • include是个主模块指令,实现对配置文件所包含的文件的设定,可以减少主配置文件的复杂度。类似于Apache中的include方法。
  • default_type属于HTTP核心模块指令,这里设定默认类型为二进制流,也就是当文件类型未定义时使用这种方式,例如在没有配置PHP环境时,Nginx是不予解析的,此时,用浏览器访问PHP文件就会出现下载窗口。
  • charset gb2312; 指定客户端编码格式。

客户端head缓存的设置

1
2
3
4
5
6
7
8
9
10
11
12
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 128k;
client_max_body_size 8m;
client_max_body_size 10m;
client_body_buffer_size 128k;
sendfile on ;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65 :
client_body_timeout 60s;
send_timeout 60s;
  • server_names_hash_bucket_size 128; 服务器名字的hash表大小
  • client_header_buffer_size 32k; 用来指定来自客户端请求头的header buffer 大小。对于大多数请求,1K的缓存已经足够了,如果自定义了消息头或有更大的cookie,可以增大缓存区大小。
  • large_client_header_buffers 4 128k; 用来指定客户端请求中较大的消息头的缓存最大数量和大小,4为个数,128k为大小,最大缓存为4个128KB。
  • client_max_body_size 8m; 客户端请求的最大的单个文件字节数。
  • client_max_body_size 10m; 允许客户端请求的最大单文件字节数。如果有上传较大文件,请设置它的限制值。
  • client_body_buffer_size 128k; 缓冲区代理缓冲用户端请求的最大字节数
  • sendfile on ; 开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,减少用户空间到内核空间的上下文切换。对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。开启 tcp_nopush on;tcp_nodelay on; 防止网络阻塞。
  • keepalive_timeout 65 : 长连接超时时间,单位是秒,这个参数很敏感,涉及浏览器的种类、后端服务器的超时设置、操作系统的设置,可以另外起一片文章了。长连接请求大量小文件的时候,可以减少重建连接的开销,但假如有大文件上传,65s内没上传完成会导致失败。如果设置时间过长,用户又多,长时间保持连接会占用大量资源。
  • client_body_timeout 60s; 用于设置客户端请求主体读取超时时间,默认是60s。如果超过这个时间,客户端还没有发送任何数据,nginx将返回Request time out(408)错误。
  • send_timeout : 用于指定响应客户端的超时时间。这个超时仅限于两个连接活动之间的时间,如果超过这个时间,客户端没有任何活动,Nginx将会关闭连接。

FastCGI相关参数是为了改善网站的性能:减少资源占用,提高访问速度。下面参数看字面意思都能理解。

1
2
3
4
5
6
7
8
9
10
11
12
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;
fastcgi_cache TEST;
fastcgi_cache_path /usr/local/nginx/fastcgi_cache levels=1:2 keys_zone=TEST:10m inactive=5m;
fastcgi_cache_valid 200 302 1h;
fastcgi_cache_valid 301 1d;
fastcgi_cache_valid any 1m;
  • fastcgi_connect_timeout 300; 指定连接到后端FastCGI的超时时间。
  • fastcgi_send_timeout 300; 指定向FastCGI传送请求的超时时间,这个值是已经完成两次握手后向FastCGI传送请求的超时时间。
  • fastcgi_read_timeout 300; 指定接收FastCGI应答的超时时间,这个值是已经完成两次握手后接收FastCGI应答的超时时间。
  • fastcgi_buffer_size 64k; 用于指定读取FastCGI应答第一部分需要多大的缓冲区,这个值表示将使用1个64KB的缓冲区读取应答的第一部分(应答头),可以设置为fastcgi_buffers选项指定的缓冲区大小。
  • fastcgi_buffers 4 64k; 指定本地需要用多少和多大的缓冲区来缓冲FastCGI的应答请求。如果一个PHP脚本所产生的页面大小为256KB,那么会为其分配4个64KB的缓冲区来缓存;如果页面大小大于256KB,那么大于256KB的部分会缓存到fastcgi_temp指定的路径中,但是这并不是好方法,因为内存中的数据处理速度要快于硬盘。一般这个值应该为站点中PHP脚本所产生的页面大小的中间值,如果站点大部分脚本所产生的页面大小为256KB,那么可以把这个值设置为“16 16k”、“4 64k”等。
  • fastcgi_busy_buffers_size 128k; 默认值是fastcgi_buffers的两倍。
  • fastcgi_temp_file_write_size 128k; 表示在写入缓存文件时使用多大的数据块,默认值是fastcgi_buffers的两倍。
  • fastcgi_cache TEST; 表示开启FastCGI缓存并为其指定一个名称。开启缓存非常有用,可以有效降低CPU的负载,并且防止502错误的发生。但是开启缓存也会引起很多问题,要视具体情况而定。
  • fastcgi_cache_path /usr/local/nginx/fastcgi_cache levels=1:2 keys_zone=TEST:10m inactive=5m; FastCGI缓存指定一个文件路径、目录结构等级、关键字区域存储时间和非活动删除时间。
  • fastcgi_cache_valid 200 302 1h; 用来指定应答代码的缓存时间。实例中的值表示将200和302应答缓存一个小时,将301应答缓存1天,其他应答均缓存1分钟。

gzip模块设置

1
2
3
4
5
6
7
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_types text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;
  • gzip on; 开启gzip压缩输出
  • gzip_min_length 1k; 最小压缩文件大小,页面字节数从header头的Content-Length中获取。默认值为0,不管多大页面都压缩,建议设置成大于1K的字节数,小于1K可能会越压越大。
  • gzip_buffers 4 16k; 压缩缓冲区,表示申请四个16K的内存作为压缩结果流缓存,默认是申请与原始数据大小相同的内存空间来存储gzip压缩结果。
  • gzip_http_version 1.1; 用于设置识别HTTP协议版本,默认是1.1,目前主流浏览器都已成指出。(默认1.1,前端如果是squid2.5请使用1.0)
  • gzip_comp_level 6; 压缩等级,1压缩比最小,处理速度最快,9压缩比最大,传输速度快,但是消耗CPU资源。
  • gzip_types text/plain application/x-javascript text/css application/xml; 压缩类型,默认就已经包含text/html,所以下面就不用再写了,写上去也不会有问题,但是会有一个warn。
  • gzip_vary on; 和http头有关系,会在响应头加个 Vary: Accept-Encoding ,可以让前端的缓存服务器缓存经过gzip压缩的页面,例如,用Squid缓存经过Nginx压缩的数据。
  • gzip_proxied any; Nginx作为反向代理的时候启用,决定开启或者关闭后端服务器返回的结果是否压缩,匹配的前提是后端服务器必须要返回包含”Via”的 header头。
  • limit_zone crawler $binary_remote_addr 10m; 开启限制IP连接数的时候需要使用

nginx 配置虚拟主机

配置虚拟主机流程:

  1. 复制一段完整的server标签段,到结尾。注意:要放在http的结束大括号前,也就是server标签段放入http标签。
  2. 更改server_name 及对应网页的root根目录。
  3. 检查配置文件语法,平滑重启服务。
  4. 创建server_name 对应网页的根目录,并且建立测试文件,如果没有index首页会出现403错误。
  5. 对客户端server_name 的主机做host 解析或DNS配置。并检查(ping)。
  6. 浏览器访问,或者在Linux客户端做host解析,用wget或curl 访问。

http服务上支持若干虚拟主机。每个虚拟主机一个对应的server配置项,配置项里面包含该虚拟主机相关的配置。在提供mail服务的代理时,也可以建立若干server。每个server通过监听地址或端口来区分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server
{
listen 80 default;
server_name _;
index index.html index.htm index.php;
root /data/htdocs/www;
#server_name_in_redirect off;
location ~ .*\.(php|php5)?$
{
#fastcgi_pass unix:/tmp/php-cgi.sock;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 1h;
}
}
  • listen 80; 监听端口,默认80,小于1024的要以root启动。可以为listen *:80、listen 127.0.0.1:80等形式。
  • server_name blog.biglittleant.cn; 服务器名,如localhost、www.example.com,可以通过正则匹配。
  • root /var/www/html 定义服务器的默认网站根目录位置。如果locationURL匹配的是子目录或文件,root没什么作用,一般放在server指令里面或/下。
  • index index.jsp index.html index.htm 定义路径下默认访问的文件名,一般跟着root放。

location

关于location匹配规则的写法,参考死磕nginx系列–使用nginx做负载均衡

proxy_pass http:/backend

请求转向backend定义的服务器列表,即反向代理,对应upstream负载均衡器。也可以proxy_pass http://ip:port。

1
2
3
4
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

这四个暂且这样设,如果深究的话,请参考死磕nginx系列–使用nginx做负载均衡

其它

访问控制 allow/deny

Nginx 的访问控制模块默认就会安装,而且写法也非常简单,可以分别有多个allow,deny,允许或禁止某个ip或ip段访问,依次满足任何一个规则就停止往下匹配。如:

1
2
3
4
5
6
7
8
9
location /nginx-status {
stub_status on;
access_log off;
# auth_basic "NginxStatus";
# auth_basic_user_file /usr/local/nginx-1.6/htpasswd;
allow 192.168.10.100;
allow 172.29.73.0/24;
deny all;
}

我们也常用 httpd-devel 工具的 htpasswd 来为访问的路径设置登录密码:

1
2
3
4
5
6
# htpasswd -c htpasswd admin
New passwd:
Re-type new password:
Adding password for user admin
# htpasswd htpasswd admin //修改admin密码
# htpasswd htpasswd sean //多添加一个认证用户

这样就生成了默认使用CRYPT加密的密码文件。打开上面nginx-status的两行注释,重启nginx生效。

列出目录 autoindex

Nginx默认是不允许列出整个目录的。如需此功能,打开nginx.conf文件,在location,server 或 http段中加入如下参数:

1
2
3
4
5
6
location /images {
root /var/www/nginx-default/images;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
}
  • autoindex on; 运行列出目录内容。另外两个参数最好也加上去。
  • autoindex_exact_size off; 默认为on,显示出文件的确切大小,单位是bytes。改为off后,显示出文件的大概大小,单位是kB或者MB或者GB。
  • autoindex_localtime on; 默认为off,显示的文件时间为GMT时间。改为on后,显示的文件时间为文件的服务器时间。

完整得通用配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
user  www www;
worker_processes 2;
error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
pid logs/nginx.pid;
events {
use epoll;
worker_connections 2048;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
# tcp_nopush on;
keepalive_timeout 65;
# gzip压缩功能设置
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_types text/html text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml;
gzip_vary on;

# http_proxy 设置
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 75;
proxy_send_timeout 75;
proxy_read_timeout 75;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_temp_path /usr/local/nginx/proxy_temp 1 2;
# 设定负载均衡后台服务器列表
upstream backend {
#ip_hash;
server 192.168.10.100:8080 max_fails=2 fail_timeout=30s ;
server 192.168.10.101:8080 max_fails=2 fail_timeout=30s ;
}
# 很重要的虚拟主机配置
server {
listen 80;
server_name itoatest.example.com;
root /apps/oaapp;
charset utf-8;
access_log logs/host.access.log main;
#对 / 所有做负载均衡+反向代理
location / {
root /apps/oaapp;
index index.jsp index.html index.htm;
proxy_pass http://backend;
proxy_redirect off;
# 后端的Web服务器可以通过X-Forwarded-For获取用户真实IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;

}
#静态文件,nginx自己处理,不去backend请求后端的服务
location ~* /download/ {
root /data/app/nginx/downloads;

}
location ~ .*\.(gif|jpg|jpeg|bmp|png|ico|txt|js|css)$
{
root /data/app/nginx/images;
expires 7d;
}
location /nginx_status {
stub_status on;
access_log off;
allow 192.168.10.0/24;
deny all;
}
location ~ ^/(WEB-INF)/ {
deny all;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
## 其它虚拟主机,server 指令开始
}

参考

优化Nginx中FastCGI参数的实例

nginx 功能介绍

简介

Nginx因为它的稳定性、丰富的模块库、灵活的配置和低系统资源的消耗而闻名.业界一致认为它是Apache2.2+mod_proxy_balancer的轻量级代替者,不仅是因为响应静态页面的速度非常快,而且它的模块数量达到Apache的近2/3。对proxy和rewrite模块的支持很彻底,还支持mod_fcgi、ssl、vhosts ,适合用来做mongrel clusters的前端HTTP响应。
nginx和Apache一样使用模块化设计,nginx模块包括内置模块和第三方模块,其中内置模块中包含主模块和事件模块。

nginx处理请求逻辑图
Nginx模块的HTTP请求和响应过程

nginx可以提供的服务

  1. WWW web 服务.
  2. 负载均衡 (反向代理)
  3. web cache(web 缓存)

nginx 的优点

  1. 高并发。静态小文件
  2. 占用资源少。2万并发、10个线程,内存消耗几百M。
  3. 功能种类比较多。web,cache,proxy。每一个功能都不是特别强。
  4. 支持epoll模型,使得nginx可以支持高并发。
  5. nginx 配合动态服务和Apache有区别。(FASTCGI 接口)
  6. 利用nginx可以对IP限速,可以限制连接数。
  7. 配置简单,更灵活。

nginx应用场合

  1. 静态服务器。(图片,视频服务)另一个lighttpd。并发几万,html,js,css,flv,jpg,gif等。
  2. 动态服务,nginx——fastcgi 的方式运行PHP,jsp。(PHP并发在500-1500,MySQL 并发在300-1500)。
  3. 反向代理,负载均衡。日pv2000W以下,都可以直接用nginx做代理。
  4. 缓存服务。类似 SQUID,VARNISH。

主流web服务产品对比说明

Apache-特性

  1. 2.2版本本身稳定强大,据官方说:其2.4版本性能更强。
  2. prefork模式取消了进程创建开销,性能很高。
  3. 处理动态业务数据时,因关联到后端的引擎和数据库,瓶颈不在与Apache本身。
  4. 高并发时消耗系统资源相对多一些。
  5. 基于传统的select模型。
  6. 扩展库,DSO方法,

nginx-特性

  1. 基于异步IO模型,(epoll,kqueue),性能强,能够支持上万并发。
  2. 对小文件支持很好,性能很高(限静态小文件1M)。
  3. 代码优美,扩展库必须编译进主程序。
  4. 消耗代码资源比较低。
  5. lighttpd(百度贴吧,豆瓣)
  6. 基于异步IO模式,性能和nginx相近。
  7. 扩展库是SO模式,比nginx要灵活。
  8. 通过差距(mod_secdownload)可实现文件URL地址加密。

web服务产品性能对比测试

静态数据性能对比
  1. 处理静态文件Apache性能比nginx和lighttpd要差。
  2. nginx在处理小文件优势明显。
  3. 处理静态小文件(小于1M),nginx和lighttpd比Apache更有优势,lighttpd最强。
    动态数据性能对比
  4. 处理动态内容三者相差不大,主要取决于PHP和数据库的压力。
  5. 当处理动态数据时,三者差距不大,从测试结果看,Apache更有优势一点。这是因为处理动态数据能力取决于PHP和后端数据的提供服务能力。也就是说瓶颈不在web服务器上。
  6. 一般PHP引擎支持的并发参考值300-1000,JAVA引擎并发300-1000,数据库的并发300-1000.
为什么nginx的总体性能比Apache高。
  1. nginx使用最新的epoll和kqueue网络IO模型,而Apache使用床头的select模式。
  2. 目前Linux下能够承受高并发访问的squid、Memcached 都采用的是epoll网络IO模型。

如何选择WEB服务器:

静态业务:高并发、采用nginx,lighttpd,根据自己的掌握程度或公司的要求。
动态业务:采用nginx和Apache均可。
既有静态业务又有动态业务:nginx或Apache,不要多选要单选。
动态业务可以由前端代理(haproxy),根据页面元素的类型,向后转发相应的服务器进行处理。
思想:我们工作都不要追求一步到位,满足需求的前提下,先用,然后逐步完善。
提示:nginx做web(Apache,lighttpd)、反向代理(haproxy,lvs,nat)及缓存服务器(squid)也是不错的。
最终建议:对外的业务nginx,对内的业务Apache(yum httpd mysql-server php)。

nginx实战过程

安装依赖包

nginx安装依赖GCC、openssl-devel、pcre-devel和zlib-devel软件库
Pcre全称(Perl Compatible Regular Expressions),中文perl兼容正则表达式,pcre官方站点

1
2
yum install  pcre pcre-devel -y
yum install openssl openssl-devel -y

开始编译

使用./configure --help 查看各个模块的使用情况,使用--without-http_ssi_module的方式关闭不需要的模块。可以使用--with-http_perl_modules方式安装需要的模块。
查看编译参数中已经开启的选项

编译命令

1
2
3
4
5
./configure --prefix=/application/nginx-1.6.2 --user=nginx --group=nginx  --with-http_ssl_module  --with-http_stub_status_module

useradd nginx -M -s /sbin/nologin
make && make install
ln -s /application/nginx1.6.2/ /application/nginx

测试nginx配置文件是否正常

1
2
3
/application/nginx/sbin/nginx -t
#nginx: the configuration file /application/nginx-1.6.2/conf/nginx.conf syntax is ok
#nginx: configuration file /application/nginx-1.6.2/conf/nginx.conf test is successful

启动nginx服务器

1
2
3
4
/application/nginx/sbin/nginx -t  ##检查配置文件
/application/nginx/sbin/nginx ##确定nginx服务
netstat -lntup |grep nginx ## 检查进程是否正常
curl 192.168.56.12 ## 确认结果

nginx其他命令

1
2
3
4
5
6
7
nginx -s signal
signal:
stop — fast shutdown
quit — graceful shutdown
reload — reloading the configuration file
reopen — reopening the log files
用来打开日志文件,这样nginx会把新日志信息写入这个新的文件中

/data/app/nginx/sbin/nginx -V 查看已经编译的参数。

使用kill命令操作nginx。格式:kill -信号 PID

信号名称

  • TERM,INT 快速关闭
  • QUIT 优雅的关闭,保持吸纳有的客户端连接
  • HUP 重启应用新的配置文件
  • USR1 重新打开日志文件
  • USR2 升级程序
  • WINCH 优雅的关闭工作进程

例子

1
2
kill -QUIT  `cat /data/app/nginx/nginx.pid`
kill -HUP `cat /data/app/nginx/nginx.pid`

nginx配置文件

配置基础配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
user  www www;
worker_processes 8;
error_log /data/logs/nginx_error.log crit;
pid /usr/local/webserver/nginx/nginx.pid;
#Specifies the value for maximum file descriptors that can be opened by this process.
worker_rlimit_nofile 65535;
events
{
use epoll;
worker_connections 65535;
}
http
{
include mime.types;
default_type application/octet-stream;
#charset gb2312;
server_names_hash_bucket_size 128;
client_header_buffer_size 32k;
large_client_header_buffers 4 32k;
client_max_body_size 8m;
sendfile on;
tcp_nopush on;
keepalive_timeout 60;
tcp_nodelay on;
include gzip.conf;
include blog.biglittle.cn.conf;

gzip.conf文件内容

1
2
3
4
5
6
7
8
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
#limit_zone crawler $binary_remote_addr 10m;

blog.biglittle.cn.conf文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server
{
listen 80 default;
server_name blog.biglittleant.cn;
index index.html index.htm index.php;
root /data/nginx/biglittleant/blog;
#server_name_in_redirect off;
location ~ .*\.(php|php5)?$
{
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi.conf;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 1h;
}
}

fastcgi.conf文件内容

1
2
3
4
5
6
7
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 128k;

nx access日志配置

access_log日志配置

access_log用来定义日志级别,日志位置。语法如下:
日志级别: debug > info > notice > warn > error > crit > alert > emerg

1
2
3
4
语法格式:	access_log path [format [buffer=size] [gzip[=level]] [flush=time] [if=condition]];
access_log off;
默认值 : access_log logs/access.log combined;
作用域 : http, server, location, if in location, limit_except

实例一:

1
access_log /spool/logs/nginx-access.log compression buffer=32k;

log_format 定义日志格式

1
2
3
语法格式:	log_format name [escape=default|json] string ...;
默认值 : log_format combined "...";
作用域 : http

实例一:

1
2
3
4
5
log_format compression '$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" "$gzip_ratio"';

access_log /spool/logs/nginx-access.log compression buffer=32k;

常见的日志变量

  • $remote_addr, $http_x_forwarded_for 记录客户端IP地址
  • $remote_user 记录客户端用户名称
  • $request 记录请求的URL和HTTP协议(GET,POST,DEL,等)
  • $status 记录请求状态
  • $body_bytes_sent 发送给客户端的字节数,不包括响应头的大小; 该变量与Apache模块mod_log_config里的“%B”参数兼容。
  • $bytes_sent 发送给客户端的总字节数。
  • $connection 连接的序列号。
  • $connection_requests 当前通过一个连接获得的请求数量。
  • $msec 日志写入时间。单位为秒,精度是毫秒。
  • $pipe 如果请求是通过HTTP流水线(pipelined)发送,pipe值为“p”,否则为“.”。
  • $http_referer 记录从哪个页面链接访问过来的
  • $http_user_agent 记录客户端浏览器相关信息
  • $request_length 请求的长度(包括请求行,请求头和请求正文)。
  • $request_time 请求处理时间,单位为秒,精度毫秒; 从读入客户端的第一个字节开始,直到把最后一个字符发送给客户端后进行日志写入为止。
  • $time_iso8601 ISO8601标准格式下的本地时间。
  • $time_local 通用日志格式下的本地时间。

open_log_file_cache

使用open_log_file_cache来设置日志文件缓存(默认是off)。

  • max:设置缓存中的最大文件描述符数量,如果缓存被占满,采用LRU算法将描述符关闭。
  • inactive:设置存活时间,默认是10s
  • min_uses:设置在inactive时间段内,日志文件最少使用多少次后,该日志文件描述符记入缓存中,默认是1次
  • valid:设置检查频率,默认60s
  • off:禁用缓存
1
2
3
4
语法格式:	open_log_file_cache max=N [inactive=time] [min_uses=N] [valid=time];
open_log_file_cache off;
默认值: open_log_file_cache off;
作用域: http, server, location

实例一

1
open_log_file_cache max=1000 inactive=20s valid=1m min_uses=2;

nginx日志调试技巧

设置 Nginx 仅仅记录来自于你的 IP 的错误

当你设置日志级别成 debug,如果你在调试一个在线的高流量网站的话,你的错误日志可能会记录每个请求的很多消息,这样会变得毫无意义。

events{...}中配置如下内容,可以使 Nginx 记录仅仅来自于你的 IP 的错误日志。

1
2
3
events {
debug_connection 1.2.3.4;
}
调试 nginx rewrite 规则

调试rewrite规则时,如果规则写错只会看见一个404页面,可以在配置文件中开启nginx rewrite日志,进行调试。

1
2
3
4
server {
error_log /var/logs/nginx/example.com.error.log;
rewrite_log on;
}

rewrite_log on; 开启后,它将发送所有的 rewrite 相关的日志信息到 error_log 文件中,使用 [notice] 级别。随后就可以在error_log 查看rewrite信息了。

使用location记录指定URL的日志
1
2
3
4
5
6
server {
error_log /var/logs/nginx/example.com.error.log;
location /static/ {
error_log /var/logs/nginx/static-error.log debug;
}
}

配置以上配置后,/static/ 相关的日志会被单独记录在static-error.log文件中。

nx监控

常用的监控参数

开启状态页

1
2
3
4
5
6
7
#设定查看Nginx状态的地址
location /NginxStatus {
stub_status on;
access_log off;
# auth_basic "NginxStatus";
# auth_basic_user_file conf/htpasswd;
}
  • stub_status on; 表示开启stubStatus的工作状态统计功能。
  • access_log off; 关闭access_log 日志记录功能。
  • auth_basic “NginxStatus”; auth_basic 是nginx的一种认证机制。
  • auth_basic_user_file conf/htpasswd; 用来指定密码文件的位置。

    配置登录密码

1
2
3
yum install -y httpd-tools
/usr/local/apache/bin/htpasswd -c /data/app/nginx/conf/htpasswd biglittleant
New password:

完成后会在/data/app/nginx/conf/目录下生成htpasswd文件。

访问URL

1
2
3
4
5
# curl http://127.0.0.1/NginxStatus
Active connections: 11921
server accepts handled requests
11989 11989 11991
Reading: 0 Writing: 7 Waiting: 42
  • active connections – 活跃的连接数量
  • server accepts handled requests — 总共处理了11989个连接 , 成功创建11989次握手, 总共处理了11991个请求
  • Reading — 读取客户端的连接数.
  • Writing — 响应数据到客户端的数量
  • Waiting — 开启 keep-alive 的情况下,这个值等于 active – (reading+writing), 意思就是 Nginx 已经处理完正在等候下一次请求指令的驻留连接.

    编写zabbix监控脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
nginx_status_fun(){
NGINX_PORT=$1
NGINX_COMMAND=$2
nginx_active(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Active' | awk '{print $NF}'
}
nginx_reading(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Reading' | awk '{print $2}'
}
nginx_writing(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Writing' | awk '{print $4}'
}
nginx_waiting(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| grep 'Waiting' | awk '{print $6}'
}
nginx_accepts(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| awk NR==3 | awk '{print $1}'
}
nginx_handled(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| awk NR==3 | awk '{print $2}'
}
nginx_requests(){
/usr/bin/curl "http://127.0.0.1:"$NGINX_PORT"/NginxStatus/" 2>/dev/null| awk NR==3 | awk '{print $3}'
}
case $NGINX_COMMAND in
active)
nginx_active;
;;
reading)
nginx_reading;
;;
writing)
nginx_writing;
;;
waiting)
nginx_waiting;
;;
accepts)
nginx_accepts;
;;
handled)
nginx_handled;
;;
requests)
nginx_requests;
esac
}

nx优化

nginx内核优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
net.ipv4.tcp_fin_timeout = 2
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_keepalive_time = 600
net.ipv4.ip_local_port_range = 4000 65000
net.ipv4.tcp_max_syn_backlog = 16384
net.ipv4.tcp_max_tw_buckets = 36000
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.core.somaxconn = 16384
net.core.netdev_max_backlog = 16384
net.ipv4.tcp_max_orphans = 16384
#以下参数是对iptables防火墙的优化,防火墙不开会提示,可以忽略不理。
net.ipv4.ip_conntrack_max = 25000000
net.ipv4.netfilter.ip_conntrack_max=25000000
net.ipv4.netfilter.ip_conntrack_tcp_timeout_established=180
net.ipv4.netfilter.ip_conntrack_tcp_timeout_time_wait=120
net.ipv4.netfilter.ip_conntrack_tcp_timeout_close_wait=60
net.ipv4.netfilter.ip_conntrack_tcp_timeout_fin_wait=120

报错汇总

问题1:编译报错

1
2
3
4
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using —without-http_rewrite_module(伪静态)
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option
1
yum install  pcre pcre-devel -y

问题2: 提示找不到libpcre.so.1

解决:

  1. find / -name libpcre.so*
  2. 将找到的路径 追加到 /etc/ld.so.conf
  3. ldconfig 生效。
  4. ln -s /ser/local/lib/libpcre.so.l /lib64
  5. 或编译时指定源码的安装路径:--with-pcre=/data/tools/pcre-8.33
  6. 最终解决方案 yum install pcre-devel -y 不会出现上述报错。

问题3:启动nginx报错

1
2
3
4
5
6
7
[root@centos6 tools]# /application/nginx/sbin/nginx
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
nginx: [emerg] still could not bind()

解决办法:(因为开启了Apache服务)

1
2
3
4
5
6
7
[root@nfs-client application]# lsof -i :80
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
httpd 35833 www 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 35834 www 4u IPv6 129886 0t0 TCP *:http (LISTEN)
httpd 98511 root 4u IPv6 129886 0t0 TCP *:http (LISTEN)
[root@nfs-client application]# /application/apache/bin/apachectl stop
[root@nfs-client application]# /application/nginx/sbin/nginx ##重新启动nginx。

扩展阅读:

nginx全局变量

  • $args :这个变量等于请求行中的参数,同$query_string
  • $content_length : 请求头中的Content-length字段。
  • $content_type : 请求头中的Content-Type字段。
  • $document_root : 当前请求在root指令中指定的值。
  • $host : 请求主机头字段,否则为服务器名称。
  • $http_user_agent : 客户端agent信息
  • $http_cookie : 客户端cookie信息
  • $limit_rate : 这个变量可以限制连接速率。
  • $request_method : 客户端请求的动作,通常为GET或POST。
  • $remote_addr : 客户端的IP地址。
  • $remote_port : 客户端的端口。
  • $remote_user : 已经经过Auth Basic Module验证的用户名。
  • $request_filename : 当前请求的文件路径,由root或alias指令与URI请求生成。
  • $scheme : HTTP方法(如http,https)。
  • $server_protocol : 请求使用的协议,通常是HTTP/1.0或HTTP/1.1。
  • $server_addr : 服务器地址,在完成一次系统调用后可以确定这个值。
  • $server_name : 服务器名称。
  • $server_port : 请求到达服务器的端口号。
  • $request_uri : 包含请求参数的原始URI,不包含主机名,如:”/foo/bar.php?arg=baz”。
  • $uri : 不带请求参数的当前URI,$uri不包含主机名,如”/foo/bar.html”。
  • $document_uri : 与$uri相同。

例子:

1
2
3
4
5
6
7
8
9
访问链接是:http://localhost:88/test1/test2/test.php
网站路径是:/var/www/html

$host:localhost
$server_port:88
$request_uri:http://localhost:88/test1/test2/test.php
$document_uri:/test1/test2/test.php
$document_root:/var/www/html
$request_filename:/var/www/html/test1/test2/test.php

web服务器事件处理模型

select

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

epoll

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

nginx -s reload 过程:

nginx主进程读取配置文件,如果发现配置文件变更,会创建一个新的主进程,然后同时旧的进程,及旧的子进程关闭,旧进程会拒绝新的连接,服务到自己的连接结束,然后关闭。

Apache select模型和 nginx epoll 模型对比讲解

Nginx的高并发得益于其采用了epoll模型,与传统的服务器程序架构不同,epoll是linux内核2.6以后才出现的。下面通过比较Apache和Nginx工作原理来比较。

传统Apache都是多进程或者多线程来工作,假设是多进程工作(prefork),apache会先生成几个进程,类似进程池的工作原理,只不过这里的进程池会随着请求数目的增加而增加。对于每一个连接,apache都是在一个进程内处理完毕。具体是 recv(),以及根据 URI 去进行磁盘I/O来寻找文件,还有 send()都是阻塞的。其实说白了都是 apche 对于套接字的I/O,读或者写,但是读或者写都是阻塞的,阻塞意味着进程就得挂起进入sleep状态,那么一旦连接数很多,Apache必然要生成更多的进程来响应请求,一旦进程多了,CPU对于进程的切换就频繁了,很耗资源和时间,所以就导致apache性能下降了,说白了就是处理不过来这么多进程了。其实仔细想想,如果对于进程每个请求都没有阻塞,那么效率肯定会提高很多。

Nginx采用epoll模型,异步非阻塞。对于Nginx来说,把一个完整的连接请求处理都划分成了事件,一个一个的事件。比如accept(), recv(),磁盘I/O,send()等,每部分都有相应的模块去处理,一个完整的请求可能是由几百个模块去处理。真正核心的就是事件收集和分发模块,这就是管理所有模块的核心。只有核心模块的调度才能让对应的模块占用CPU资源,从而处理请求。拿一个HTTP请求来说,首先在事件收集分发模块注册感兴趣的监听事件,注册好之后不阻塞直接返回,接下来就不需要再管了,等待有连接来了内核会通知你(epoll的轮询会告诉进程),cpu就可以处理其他事情去了。一旦有请求来,那么对整个请求分配相应的上下文(其实已经预先分配好),这时候再注册新的感兴趣的事件(read函数),同样客户端数据来了内核会自动通知进程可以去读数据了,读了数据之后就是解析,解析完后去磁盘找资源(I/O),一旦I/O完成会通知进程,进程开始给客户端发回数据send(),这时候也不是阻塞的,调用后就等内核发回通知发送的结果就行。整个下来把一个请求分成了很多个阶段,每个阶段都到很多模块去注册,然后处理,都是异步非阻塞。异步这里指的就是做一个事情,不需要等返回结果,做好了会自动通知你。

select/epoll的特点

select的特点:select 选择句柄的时候,是遍历所有句柄,也就是说句柄有事件响应时,select需要遍历所有句柄才能获取到哪些句柄有事件通知,因此效率是非常低。但是如果连接很少的情况下, select和epoll的LT触发模式相比, 性能上差别不大。
这里要多说一句,select支持的句柄数是有限制的, 同时只支持1024个,这个是句柄集合限制的,如果超过这个限制,很可能导致溢出,而且非常不容易发现问题, 当然可以通过修改linux的socket内核调整这个参数。
epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的。
对于epoll而言还有ET和LT的区别,LT表示水平触发,ET表示边缘触发,两者在性能以及代码实现上差别也是非常大的。

可以举一个简单的例子来说明Apache的工作流程,我们平时去餐厅吃饭。餐厅的工作模式是一个服务员全程服务客户,流程是这样,服务员在门口等候客人(listen),客人到了就接待安排的餐桌上(accept),等着客户点菜(request uri),去厨房叫师傅下单做菜(磁盘I/O),等待厨房做好(read),然后给客人上菜(send),整个下来服务员(进程)很多地方是阻塞的。这样客人一多(HTTP请求一多),餐厅只能通过叫更多的服务员来服务(fork进程),但是由于餐厅资源是有限的(CPU),一旦服务员太多管理成本很高(CPU上下文切换),这样就进入一个瓶颈。

再来看看Nginx得怎么处理?餐厅门口挂个门铃(注册epoll模型的listen),一旦有客人(HTTP请求)到达,派一个服务员去接待(accept),之后服务员就去忙其他事情了(比如再去接待客人),等这位客人点好餐就叫服务员(数据到了read()),服务员过来拿走菜单到厨房(磁盘I/O),服务员又做其他事情去了,等厨房做好了菜也喊服务员(磁盘I/O结束),服务员再给客人上菜(send()),厨房做好一个菜就给客人上一个,中间服务员可以去干其他事情。整个过程被切分成很多个阶段,每个阶段都有相应的服务模块。我们想想,这样一旦客人多了,餐厅也能招待更多的人。

不管是Nginx还是Squid这种反向代理,其网络模式都是事件驱动。事件驱动其实是很老的技术,早期的select、poll都是如此。后来基于内核通知的更高级事件机制出现,如libevent里的epoll,使事件驱动性能得以提高。事件驱动的本质还是IO事件,应用程序在多个IO句柄间快速切换,实现所谓的异步IO。事件驱动服务器,最适合做的就是这种IO密集型工作,如反向代理,它在客户端与WEB服务器之间起一个数据中转作用,纯粹是IO操作,自身并不涉及到复杂计算。反向代理用事件驱动来做,显然更好,一个工作进程就可以run了,没有进程、线程管理的开销,CPU、内存消耗都小。

所以Nginx、Squid都是这样做的。当然,Nginx也可以是多进程 + 事件驱动的模式,几个进程跑libevent,不需要Apache那样动辄数百的进程数。Nginx处理静态文件效果也很好,那是因为静态文件本身也是磁盘IO操作,处理过程一样。至于说多少万的并发连接,这个毫无意义。随手写个网络程序都能处理几万的并发,但如果大部分客户端阻塞在那里,就没什么价值。

再看看Apache或者Resin这类应用服务器,之所以称他们为应用服务器,是因为他们真的要跑具体的业务应用,如科学计算、图形图像、数据库读写等。它们很可能是CPU密集型的服务,事件驱动并不合适。例如一个计算耗时2秒,那么这2秒就是完全阻塞的,什么event都没用。想想MySQL如果改成事件驱动会怎么样,一个大型的join或sort就会阻塞住所有客户端。这个时候多进程或线程就体现出优势,每个进程各干各的事,互不阻塞和干扰。当然,现代CPU越来越快,单个计算阻塞的时间可能很小,但只要有阻塞,事件编程就毫无优势。所以进程、线程这类技术,并不会消失,而是与事件机制相辅相成,长期存在。

总言之,事件驱动适合于IO密集型服务,多进程或线程适合于CPU密集型服务,它们各有各的优势,并不存在谁取代谁的倾向。

相关参考

nginx-日志高级技巧
nginx-官方文档
nginx优化
查看网站排名

kafka-python-client-example

安装kafka-python

pip安装

1
2
pip install kafka-python

源码安装

1
2
3
4
5
6
7
8
9
10
### pip
git clone https://github.com/dpkp/kafka-python
pip install ./kafka-python
### Setuptools
git clone https://github.com/dpkp/kafka-python
easy_install ./kafka-python
### setup
git clone https://github.com/dpkp/kafka-python
cd kafka-python
python setup.py install

如果想启用压缩功能需要额外安装以下两个模块

1
2
pip install lz4tools
pip install xxhash

使用方法

kafka生产端

第一步:连接到服务器端

1
2
3
4
5
6
from kafka import KafkaProducer
from kafka.errors import KafkaError

## 连接到服务器端
producer = KafkaProducer(bootstrap_servers=['192.168.56.12:9092'])

第二步:发送一个简单的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
## 默认发送普通的消息
datenow = datetime.datetime.now().strftime('%Y-%m-%d:%H-%M-%s')
my_bytes = bytes(source=datenow,encoding='utf-8')
future = producer.send('topic1', my_bytes) ##消息必须是二进制格式

### OR 延时发送,并获取相关参数
try:
record_metadata = future.get(timeout=10)
except KafkaError:
# Decide what to do if produce request failed...
#log.exception()
pass

# Successful result returns assigned partition and offset
print (record_metadata.topic) ##打印写到那个topic上了。
print (record_metadata.partition) ## 打印消息所在的分区。
print (record_metadata.offset) ## 打印消息的位置

第三步:发送json格式的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# produce keyed messages to enable hashed partitioning
producer.send('my-topic', key=b'foo', value=b'bar')

# encode objects via msgpack
producer = KafkaProducer(value_serializer=msgpack.dumps) ##msgpack为自定义json格式。
producer.send('msgpack-topic', {'key': 'value'})

# produce json messages
producer = KafkaProducer(value_serializer=lambda m: json.dumps(m).encode('utf-8'),
bootstrap_servers=['192.168.56.12:9092'])
producer.send('json-topic1', {'key': 'value'})

# produce asynchronously
for _ in range(100):
producer.send('my-topic', b'msg')

# block until all async messages are sent
producer.flush() ##锁住进程,直到所有消息发送完毕,在执行下一步。

# configure multiple retries
producer = KafkaProducer(retries=5)

kafka消费端

kafka 实时消费程序

只消费新写入的消息,不消费旧消息。

1
2
3
4
5
6
7
8
9
10
11
12
from kafka import KafkaConsumer

# To consume latest messages and auto-commit offsets
consumer = KafkaConsumer('my-topic',
group_id='my-group', ## 定义一个组,group中记录office_set的位置。
bootstrap_servers=['localhost:9092'])
for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

kafka消息早期的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    consumer = KafkaConsumer('topic1',
auto_offset_reset='earliest',
enable_auto_commit=False,
bootstrap_servers=['192.168.56.12:9092'])

for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

### 结果
topic1:0:0: key=None value=b'11-16-19:11-2016-00'
topic1:0:1: key=None value=b'11-16-19:11-2016-02'
topic1:0:2: key=None value=b'11-16-19:11-2016-03'
topic1:0:3: key=None value=b'11-16-19:11-2016-03'
topic1:0:4: key=None value=b'2016-11-19:11-05-1479524731'


自定义分析结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    consumer = KafkaConsumer('json-topic1',
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
auto_offset_reset='earliest', ## or latest。
enable_auto_commit=False, ## 如果设置为False,不会自动提交office_set的位置。
bootstrap_servers=['192.168.56.12:9092'])

for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

### 结果
json-topic1:0:0: key=None value={'key': 'value'}

其他参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 如果1s没有数据,就退出。
KafkaConsumer(consumer_timeout_ms=1000)

# 使用正则去匹配topic。
consumer = KafkaConsumer()
consumer.subscribe(pattern='^awesome.*')

# 开启多个客户端去消费消息。
# Use multiple consumers in parallel w/ 0.9 kafka brokers
# typically you would run each on a different server / process / CPU
consumer1 = KafkaConsumer('my-topic',
group_id='my-group',
bootstrap_servers='my.server.com')
consumer2 = KafkaConsumer('my-topic',
group_id='my-group',
bootstrap_servers='my.server.com')

Example

  • 将文件a.txt的内容写入到kafka中。
  • 消费者定义个my-group的组去消费kafka中的数据。

第一步编写一个生产者,生产消息。

1
2
3
4
5
6
7
from kafka import KafkaProducer

producer = KafkaProducer(bootstrap_servers=['192.168.56.12:9092'])
with open('a.txt','rb') as file:
for n in file:
future = producer.send('topic1', n)
producer.flush()

第一步编写一个消费者,消费消息

1
2
3
4
5
6
7
8
9
10
11
12
13
from kafka import KafkaConsumer

consumer = KafkaConsumer('topic1',
group_id='my-group',
bootstrap_servers=['192.168.56.12:9092'])

for message in consumer:
# message value and key are raw bytes -- decode if necessary!
# e.g., for unicode: `message.value.decode('utf-8')`
print ("%s:%d:%d: key=%s value=%s" % (message.topic, message.partition,
message.offset, message.key,
message.value))

帮助文档

kafka-python官方参考