一、Nginx简介

Nginx是俄罗斯软件工程师Igor Sysoev开发的免费开源web服务器软件。Nginx本身是一款静态(html、js、css、jpg等)的www软件,不能解析动态的PHP,JSP,DO,如果要解析,还需要装对应的引擎。

具备IMAP/POP3和SMTP服务器功能,Nginx最大的特点是对高并发的支持和高效的负载均衡,在高并发的需求场景下,是Apache服务器不错的替代品。目前,包括新浪、腾讯等知名网站都已使用Nginx作为Web应用服务器。

1)Nginx的特性

  • 模块化设计、较好的扩展性;
  • 高可靠性:一个master启动一或多个worker,每个worker响应多个请求;
  • 低内存消耗:10000个keepalive连接在Nginx中仅消耗2.5MB内存(官方数据);
  • 支持热部署:不停机更新配置文件、更新日志文件、更新服务器程序版本;

2)Nginx的基本功能

  • 静态web资源服务器,能够缓存打开的文件描述符;
  • 支持http/imap/pop3/smtp的反向代理;支持缓存、负载均衡;
  • 支持fastcgi(fpm);
  • 模块化,非DSO机制,支持过滤器zip压缩,SSI以及图像大小调整;
  • 支持SSL;

3)Nginx的扩展功能

  • 基于名称和IP的虚拟主机;
  • 支持keepalive的保持机制;
  • 支持平滑升级;
  • 定制访问日志,支持使用日志缓存区提高日志存储性能;
  • 支持url rewrite;
  • 支持路径别名(root或alias指定);
  • 支持基于IP以及用户的访问控制;
  • 支持传输速率限制,并发限制;

4)Nginx的基本架构

  • 一个master进程,生成一个或者多个worker进程,每个worker响应多个请求;
  • 事件驱动:epoll、kqueue、poll、select、rt signals;
  • 支持sendfile,sendfile64;
  • 支持AIO;
  • 支持mmap;

5)Nginx模块类型

  • Nginx core module: nginx的核心模块;
  • Standard HTTP modules:nginx的标准模块;
  • Optional HTTP modules:nginx的可选模块;
  • Mail modules :nginx的邮件模块;
  • 3rd party modules:nginx的第三方模块;

6)Nginx进程详解

主进程主要完成如下工作:

  • 读取并验正配置信息;
  • 创建、绑定及关闭套接字;
  • 启动、终止及维护worker进程的个数;
  • 无须中止服务而重新配置工作特性;
  • 控制非中断式程序升级,启用新的二进制程序并在需要时回滚至老版本;
  • 重新打开日志文件,实现日志滚动;
  • 编译嵌入式perl脚本;
  • worker进程主要完成的任务包括:
  • 接收、传入并处理来自客户端的连接;
  • 提供反向代理及过滤功能;

cache loader进程主要完成的任务包括:

  • 检查缓存存储中的缓存对象;
  • 使用缓存元数据建立内存数据库;

cache manager进程的主要任务:

  • 缓存的失效及过期检验;

7)优越的特性

  • 作为Web服务器;相比较与Apache,Nginx使用更少的资源,支持更多的并发连接,体现更高的效率,这点使Nginx尤为受到虚拟主机提供商的欢迎,能够支持高达50000个并发的连接数的响应;
  • 作为负载均衡服务器器:Nginx既可以在内部直接支持Rails和PHP,也可以支持作为HTTP代理服务器对外惊醒服务,Nginx用C语言编写,不论是系统资源开销还是CPU使用效率都比Perlbal要好的多;
  • 作为邮件代理服务器,Nginx同时也是一个非常优秀的邮件代理服务器(最早开发这个产品的目的之一也是作为邮件代理服务器),Last.fm描述了成功并且美妙的使用经验;
  • Nginx安装非常简单,配置文件非常简介(还能够支持perl语法),Bugs非常少的服务器:Nginx启动特别容易,并且几乎可以做到7*24不间断运行,即使运行数月也不需要重新启动。还能够在不间断服务的情况下进行软件版本平滑升级;

8)Nginx的应用场合

  • 提供静态服务(图片,视频服务),html,js,css,flv,jpg等。类似Lighttpd并发:几万并发;
  • 动态服务,Nginx+Fastcgi的方式运行php,jsp。动态并发:500-1500;
  • 提供反向代理服务,或者称之为负载均衡,日PV2000W以下,都可以直接使用Nginx做代理。F5、A10、haproxy、NetScaler;
  • 缓存服务。类似squid,varnish,sts;

9)Nginx支持虚拟主机

一个server标签就是虚拟主机:

  • 基于域名的虚拟主机。通过域名来区分虚拟主机;

应用:外部网站

  • 基于端口的虚拟主机。通过端口来区分虚拟主机;

应用:公司内部网站,网站的后台……

  • 基于IP的虚拟主机。几乎不用,不支持ifconfig别名,配置文件可以;

二、Nginx安装配置

1、基础软件准备

在安装操作系统的安装软件配置部分,建议选择“Server with GUI”,并选择“Development Tools”和“Compatibility Libraries”两项附加软件。确保gcc、libgcc、gcc-c++等编译器已经正确安装。

在安装Nginx之前,需要安装一些Nginx的依赖程序,Nginx的主要依赖程序有zlib、pcre、openssl三个,其中,zlib用于支持gzip模块,pcre用于支持rewrite模块,openssl用于支持ssl功能。

1)安装PCRE

PCRE(Perl Compatible Regular Expressions),中文perl 兼容的正则表达式)是一个Perl库,包括 perl 兼容的正则表达式库。

官网:https://www.perl.org/

原因:HTTP rewrite module requires the PCRE library.

rpm -aq pcre pcre-develyum install -y pcre-devel

2)安装openssl

原因:SSL module require the openssl library

yum install -y openssl-devel

推荐通过yum安装zlib、pcre、openssl软件包,安装方式如下:

yum -y install zlib pcre pcre-devel openssl openssl-devel

2、安装Nginx

useradd -s /sbin/nologin -M nginx         #创建用户,也可以在安装后创建mkdir /server/tools -pcd /server/toolswget http://nginx.org/download/nginx-1.6.3.tar.gztar zxvf nginx-1.6.3.tar.gzcd nginx-1.6.3./configure --help                #查看帮助***********************************************……--without-http_rewrite_module      disable ngx_http_rewrite_module--without-http_proxy_module        disable ngx_http_proxy_module……***********************************************

以上模块默认都是被加进去的,如果不想安装类似模块客家以上参数,本次安装参数如下:

./configure --prefix=/application/nginx-1.6.3 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_modulemake && make installcd ..ln -s /application/nginx-1.6.3 /application/nginx/application/nginx/sbin/nginx                     #启动进程,不报错则表示启动成功ps -ef|grep nginx|egrep -v grep                   #查看启动进程netstat -lntup|grep nginx                         #查看端口,直接过滤80有可能查处多余项/application/nginx/sbin/nginx -V                  #查看编译参数

如果/application/nginx/sbin/nginx 出现如下错误:

/usr/local/nginx/sbin/nginx: error while loading shared libraries: libpcre.so.1: cannot open shared object file: No such file or directory

解决办法:

1. ln -s /usr/local/lib/libpcre.so.1 /lib642. echo "/usr/local/lib/">>/etc/ld.so.confldconfig

3、Nginx编译参数详解

Nginx有很多编译参数,这里仅列出常用的一些参数,configure过程如下:

[root@localhost nginx-1.14.0]# ./configure \--user=www \--group=www \--prefix=/usr/local/nginx \--sbin-path=/usr/local/nginx/sbin/nginx \--conf-path=/usr/local/nginx/conf/nginx.conf \--error-log-path=/usr/local/nginx/logs/error.log \--http-log-path=/usr/local/nginx/logs/access.log \--pid-path=/var/run/nginx.pid \--lock-path=/var/lock/subsys/nginx \--with-http_stub_status_module \--with-http_ssl_module \--with-http_gzip_static_module \--with-pcre[root@localhost nginx-1.14.0]# make[root@localhost nginx-1.14.0]# make install

其中,每个编译参数的含义如下所示:

–user:指定启动程序所属用户

–group:指定启动程序所属组

–prefix:指定Nginx程序的安装路径

–sbin-path:设置Nginx二进制文件的路径名

–conf-path:指定Nginx配置文件路径

–error-log-path:指定Nginx错误日志文件路径

–http-log-path:指定Nginx访问日志文件路径

–pid-path:设置Nginx的pid文件nginx.pid的路径

–lock-path:设置Nginx的lock文件nginx.lock文件路径

–with-openssl:指定OpenSSL源码包的路径,如果编译的时候没有指定“–with-openssl”选项,那么默认会使用系统自带的openssl库

–with-pcre:设置Nginx启用正则表达式

–with-http_stub_status_module:安装用来监控Nginx状态的模块

–with-http_ssl_module:表示启用Nginx的SSL模块,此模块依赖“–with-openssl”这个选项,通常一起使用。

–with-http_gzip_static_module:表示启用Nginx的gzip压缩

4、Nginx缓存安装

cd /server/scriptswget http://labs.frickle.com/files/ngx_cache_purge-2.3.tar.gztar zxvf ngx_cache_purge-2.3.tar.gzwget http://nginx.org/download/nginx-1.6.3.tar.gztar zxvf nginx-1.6.3.tar.gzcd nginx-1.6.3./configure --prefix=/data/nginx-1.6.3 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --add-module=../ngx_cache_purge-2.3make && make installln -s /data/nginx-1.6.3/ /data/nginx

5、Nginx的启动、关闭与重启

Nginx对进程的控制能力非常强大,可以通过信号指令控制进程。常用的信号有:

QUIT,表处理完当前请求后,关闭进程。

HUP,表示重新加载配置,也就是关闭原有的进程,并开启新的工作进程。此操作不会中断用户的访问请求,因此可以通过此信号平滑的重启Nginx。

USR1,用于Nginx的日志切换,也就是重新打开一个日志文件,例如每天要生成一个新的日志文件时,可以使用这个信号来控制。

USR2,用于平滑升级可执行程序。 WINCH ,从容关闭工作进程。 Nginx的启动非常简单,只需输入:

[root@centos ~]# /usr/local/nginx/sbin/nginx 

如果要关闭Nginx进程,可以使用如下命令:

[root@centos ~]# kill -XXX pid 其中,XXX就是信号名,pid是Nginx的进程号 

要不间断服务地重新启动Nginx,可以使用如下命令:

[root@centos ~]# kill -HUP `cat /usr/local/nginx/logs/nginx.pid`

三、Nginx技术深入理解

1、Nginx配置文件

Nginx安装完毕后,会产生相应的安装目录,根据前面的安装路径,Nginx的配置文件路径为/usr/local/nginx/conf,其中nginx.conf为Nginx的主配置文件。

这里重点介绍下nginx.conf这个配置文件。

Nginx配置文件默认有五个部分组成:分别是main、events、http、server和location,其中,main部分设置的指令将影响其他所有设置;events部分用来配置影响nginx服务器或与用户的网络连接;http部分可以嵌套多个server,主要用来配置代理,缓存,自定义日志格式等绝大多数功能和第三方模块的配置,server部分用于配置虚拟主机的相关参数;location部分用于配置请求的处理规则,以及各种页面的处理情况(比如,根目录“/”,“/p_w_picpaths”,等等)。这五者之间的关系是:main与events平级,一个http中可以有多个server,server继承main,location继承server。

他们之间的关系式:

完整的Nginx配置文件信息:

cat /application/nginx/conf/nginx.conf#user  nobody;                      #运行用户worker_processes  1;                #启动进程,通常设置成和cpu的数量相等#全局错误日志定义类型,[ debug | info | notice | warn | error | crit ]#error_log  logs/error.log;               #error_log  logs/error.log  notice;      #error_log  logs/error.log  info;        #pid        logs/nginx.pid;                #PID进程文件events {#参考事件模型,use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll模型是Linux 2.6以上版本内核中的高性能网络I/O模型,如果跑在FreeBSD上面,就用kqueue模型。use epoll;     worker_connections  1024;            ;#单个后台worker process进程的最大并发链接数}#设定http服务器,利用它的反向代理功能提供负载均衡支持http {include       mime.types;        #设定mime类型,类型由mime.type文件定义    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;      #开启高效文件传输模式,sendfile指令指定nginx是否调用sendfile函数来输出文件,对于普通应用设为 on,如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络I/O处理速度,降低系统的负载。注意:如果图片显示不正常把这个改成off。    #tcp_nopush     on;      #防止网络阻塞    keepalive_timeout  65;      #长连接超时时间,单位是秒    #gzip  on;            #开启gzip压缩输出#虚拟主机的配置    server {        listen       80;                   #监听端口        server_name  localhost;            #域名可以有多个,用空格隔开        #charset koi8-r;                   #设置字符集        #access_log  logs/host.access.log  main;     #设定本虚拟主机的访问日志        location / {            root   html;          #定义服务器的默认网站根目录位置,相对于安装目录            index  index.html index.htm;        #定义首页索引文件的名称        }        #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 {        # 如果访问50x.html,则定位到下面目录去找             root   html;              #如果没有这个目录,则定位到默认目录“/”        }        # proxy the PHP scripts to Apache listening on 127.0.0.1:80        #        #location ~ \.php$ {                  #对 "~ \.php$" 启用反向代理        #    proxy_pass   http://127.0.0.1;        #}        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000        #        #location ~ \.php$ {            #所有php后缀的,都通过fastcgi发送到9000端口上        #    root           html;        #    fastcgi_pass   127.0.0.1:9000;        #    fastcgi_index  index.php;        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;        #    include        fastcgi_params;        #}        # deny access to .htaccess files, if Apache's document root        # concurs with nginx's one        #        #location ~ /\.ht {        #    deny  all;        #}    }    # another virtual host using mix of IP-, name-, and port-based configuration    #    #server {    #    listen       8000;    #    listen       somename:8080;    #    server_name  somename  alias  another.alias;    #    location / {    #        root   html;    #        index  index.html index.htm;    #    }    #}    # HTTPS server    #    #server {    #    listen       443 ssl;    #    server_name  localhost;    #    ssl_certificate      cert.pem;    #    ssl_certificate_key  cert.key;    #    ssl_session_cache    shared:SSL:1m;    #    ssl_session_timeout  5m;    #    ssl_ciphers  HIGH:!aNULL:!MD5;    #    ssl_prefer_server_ciphers  on;    #    location / {    #        root   html;    #        index  index.html index.htm;    #    }    #}}

1)Nginx的全局配置项

user:指定Nginx Worker进程运行用户以及用户组,默认由nobody账号运行,这里指定用www用1户和组运行。

worker_processes:设置nginx工作的进程数,一般来说,设置成CPU核的数量即可,这样可以充分利用CPU资源,可通过如下命令查看CPU核数: [root@centos nginx]#grep ^processor /proc/cpuinfo | wc –l 在nginx1.10版本后,worker_processes指令新增了一个配置值auto,它表示nginx会自动检测CPU核数并打开相同数量的worker进程。

worker_cpu_affinity:此指令可将Nginx工作进程与指定CPU核绑定,降低由于多核CPU切换造成的性能损耗。

worker_cpu_affinity使用方法是通过1、0来表示的,CPU有多少个核?就有几位数,1代表内核开启,0代表内核关闭,例如:有一个4核的服务器,那么nginx配置中worker_processes、worker_cpu_affinity的写法如下:

worker_processes 4; worker_cpu_affinity 0001 0010 0100 1000;  

上面的配置表示4核CPU,开启4个进程,每个进程都与CPU的每个核进行绑定。其中,0001表示开启第一个cpu内核,0010表示开启第二个cpu内核,其它含义依次类推。如果是8核CPU,绑定第一个CPU核,可以写成00000001,绑定第二个CPU核,可以写成00000010,依次类推。

worker_cpu_affinity指令一般与worker_processes配合使用,以充分发挥nginx的性能优势。

error_log:用来定义全局错误日志文件。日志输出级别有debug、info、notice、warn、error、crit可供选择,其中,debug输出日志最为最详细,而crit输出日志最少。

pid:用来指定进程id的存储文件位置。

worker_rlimit_nofile:用于指定一个Nginx进程可以打开的最多文件描述符数目,这里是65535,需要使用命令“ulimit -n 65535”来设置。

events:设定Nginx的工作模式及连接数上限。其中参数“use”用来指定Nginx的工作模式,Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。其中select和poll都是标准的工作模式,kqueue和epoll是高效的工作模式,对于Linux系统,epoll工作模式是首选。而参数“worker_connections”用于定义Nginx每个进程的最大连接数,默认是1024。在一个纯Nginx(无反向代理应用)应用中,最大客户端连接数由worker_processes和worker_connections决定,即为:max_client=worker_processes*worker_connections。

进程的最大连接数受Linux系统进程的最大打开文件数限制,在执行操作系统命令“ulimit -n 65536”后worker_connections的设置才能生效。

2)HTTP服务器配置

include:是个主模块指令,实现对配置文件所包含的文件的设定,可以减少主配置文件的复杂度。类似于Apache中的include方法。

default_type:属于HTTP核心模块指令,这里设定默认类型为二进制流,也就是当文件类型未定义时使用这种方式,例如在没有配置PHP环境时,Nginx是不予解析的,此时,用浏览器访问PHP文件就会出现下载窗口。

log_format:用于指定Nginx日志的输出格式。main为此日志输出格式的名称,可以在下面的access_log指令中引用。

Sendfile on:用于开启高效文件传输模式。将tcp_nopush和tcp_nodelay两个指令设置为on用于防止网络阻塞。

keepalive_timeout:设置客户端连接保持活动的超时时间。在超过这个时间之后,服务器会关闭该连接。

server_names_hash_bucket_size 128:为了提高快速寻找到相应server name的能力,Nginx使用散列表来存储server name,而server_names_hash_bucket_size就是设置每个散列桶占用的内存大小。

client_max_body_size 30m:用来设置允许客户端请求的最大的单个文件字节数。 client_header_buffer_size 32k:用于指定来自客户端请求头的header buffer大小。对于大多数请求,1K的缓冲区大小已经足够,如果自定义了消息头或有更大的Cookie,可以增加缓冲区大小。这里设置为32K。

large_client_header_buffers 4 128k:用来指定客户端请求中较大的消息头的缓存最大数量和大小, “4”为个数,“128K”为大小,最大缓存量为4个128K。

3)HttpGzip模块配置

常用的HttpGzip配置项含义如下:

gzip on:用于设置开启或者关闭gzip模块,“gzip on”表示开启GZIP压缩,实时压缩输出数据流。

gzip_min_length 1k:设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取。默认值是0,不管页面多大都进行压缩。建议设置成大于1k的字节数,小于1k可能会越压越大。

gzip_buffers 4 16k:表示申请4个单位为16K的内存作为压缩结果流缓存,默认是申请与原始数据大小相同的内存空间来存储gzip压缩结果。

gzip_http_version 1.1:用于设置识别HTTP协议版本,默认是1.1,目前大部分浏览器已经支持GZIP解压,使用默认即可。

gzip_comp_level 2:用来指定GZIP压缩比,1表示压缩比最小,处理速度最快;9表示压缩比最大,传输速度快,但处理最慢,也比较消耗CPU资源。

gzip_types text/plain application/x-javascript text/css application/xml:用来指定压缩的类型,无论是否指定,“text/html”类型总是会被压缩的。

4)server虚拟主机配置

server:定义虚拟主机开始的关键字。

listen:用于指定虚拟主机的服务端口。

server_name:用来指定IP地址或域名,多个域名之间用空格分开。 index:用于设定访问的默认首页地址。

root:用于指定虚拟主机的网页根目录,这个目录可以是相对路径,也可以是绝对路径。

access_log:用来指定此虚拟主机的访问日志存放路径,最后的main用于指定访问日志的输出格式。

error_page:可以定制各种错误信息的返回页面。在默认情况下,Nginx会在主目录的html目录中查找指定的返回页面,特别需要注意的是,这些错误信息的返回页面的大小一定要超过512K,否者会被IE浏览器替换为IE默认的错误页面。

2、检查Nginx配置文件的正确性

Nginx提供的配置文件调试功能非常有用,可以快速定位配置文件存在的问题。执行如下命令检测配置文件的正确性:

[root@centos ~]# /usr/local/nginx/sbin/nginx -t 

或者

[root@centos ~]# /usr/local/nginx/sbin/nginx -t -c /usr/local/nginx/conf/nginx.conf 

其中,“-t”参数用于检查配置文件是否正确,但并不执行。“-c”参数用于指定配置文件路径,如果不指定配置文件路径,Nginx默认会在安装时指定的安装目录下查找conf/nginx.conf文件。

如果检测结果显示如下信息,说明配置文件正确:

the configuration file/usr/local/nginx/conf/nginx.conf syntax is ok configuration file/usr/local/nginx/conf/nginx.conf test is successful

命令行执行以下命令可以显示安装Nginx的版本信息:

[root@centos ~]# /usr/local/nginx/sbin/nginx -v nginx version: nginx/1.13.9 

执行以下命令显示安装的Nginx版本和相关编译信息:

[root@centos ~]# /usr/local /nginx/sbin/nginx -V 

不但显示Nginx的版本信息,同时显示nginx在编译时指定的相关模块信息。

3、Nginx模块处理流程

1)Nginx模块委派概述

Nginx的模块有三种角色:

* handlers 处理http请求并构造输出
* filters 处理handler产生的输出
* load-balancers 当有多于一个的后端服务器时,选择一台将http请求发送过去

许多可能你认为是web server的工作,实际上都是由模块来完成的:任何时候,Nginx提供文件或者转发请求到另一个server,都是通过handler来实现的;而当 需要Nginx用gzip压缩输出或者在服务端加一些东东的话,filter就派上用场了;Nginx的core模块主要管理网络层和应用层协议,并启动 针对特定请求的一系列后续模块。这种分散式的体系结构使得由你自己来实现强大的内部单元成为了可能。

注意:不像Apache的模块那样,Nginx的模块都_不是动态链接的。(换句话说,Nginx的模块都是静态编译的) 模块是如何被调用的呢” /> * NGX_CONF_1MORE: 指令至少读入1个参数
* NGX_CONF_2MORE: 指令至少读入2个参数

这里还有很多其他的选项:core/ngx_conf_file.h. 结构体成员 set 是一个函数指针,它指向的函数用来进行模块配置;这个设定函数一般用来将配置文件中的参数传递给程序,并保存在配置结构体中。设定函数有三个入参:

1. 指向结构体 ngx_conf_t 的指针, 这个结构体里包含需要传递给指令的参数
2. 指向结构体 ngx_command_t 的指针
3. 指向模块自定义配置结构体的指针

设定函数会在遇到指令时执行,Nginx提供了多个函数用来保存特定类型的数据,这些函数包含有:

* ngx_conf_set_flag_slot: 将 “on” or “off” 转换成 1 or 0
* ngx_conf_set_str_slot: 将字符串保存为 ngx_str_t
* ngx_conf_set_num_slot: 解析一个数字并保存为int
* ngx_conf_set_size_slot: 解析一个数据大小(如:”8k”, “1m”) 并保存为size_t

当然还有其他的,在core/ngx_conf_file.h中很容易查到。如果你觉得现有这些内置的函数还不能满足你,当然也可以传入自己的函数引用。

这些内置函数是如何知道把数据存放在哪里的呢?这就是接下来两个结构体成员 conf 和 offset要做的事了. conf 告诉Nginx把数据存在模块的哪个配置中,是main配置、server 配置, 还是 location 配置 ?(通过 NGX_HTTP_MAIN_CONF_OFFSET, NGX_HTTP_SRV_CONF_OFFSET, 或者 NGX_HTTP_LOC_CONF_OFFSET). offset 确定到底是保存在结构体的哪个位置。

最后, post指向模块在读配置的时候需要的一些零碎变量。一般它是NULL。

ngx_command_t数组以ngx_null_command 为终结符(就好像字符串以’\0’为终结符一样)。

3. 模块上下文

静态的ngx_http_module_t结构体,包含一大坨函数引用,用来创建和合并三段配置 (main,server,location),命名方式一般是:ngx_http__module_ctx. 这些函数引用依次是:

* preconfiguration 在读入配置前调用
* postconfiguration 在读入配置后调用
* create_main_conf 在创建main配置时调用(比如,用来分配空间和设置默认值)
* init_main_conf 在初始化main配置时调用(比如,把原来的默认值用nginx.conf读到的值来覆盖)
* init_main_conf 在创建server配置时调用
* merge_srv_conf 合并server和main配置时调用
* create_loc_conf 创建location配置时调用
* merge_loc_conf 合并location和server配置时调用

函数的入参各不相同,取决于他们具体要做的事情。这里http/ngx_http_config.h是结构体的具体定义:

typedef struct {ngx_int_t(*preconfiguration)(ngx_conf_t *cf);ngx_int_t(*postconfiguration)(ngx_conf_t *cf);void*(*create_main_conf)(ngx_conf_t *cf);char*(*init_main_conf)(ngx_conf_t *cf, void *conf);void*(*create_srv_conf)(ngx_conf_t *cf);char*(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);void*(*create_loc_conf)(ngx_conf_t *cf);char*(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);} ngx_http_module_t;

可以把你不需要的函数设置为NULL,Nginx会忽略掉他们。

绝大多数的 handler只使用最后两个: 一个用来为特定location配置来分配内存,(叫做 ngx_http__create_loc_conf), 另一个用来设定默认值以及合并继承过来的配置值(叫做 ngx_http__merge_loc_conf)。合并函数同时还会检查配置的有效性,如果有错误,则server的启动将被挂起。

下面是一个使用模块上下文结构体的例子:

static ngx_http_module_tngx_http_circle_gif_module_ctx = {NULL,/* preconfiguration */NULL,/* postconfiguration */NULL,/* create main configuration */NULL,/* init main configuration */NULL,/* create server configuration */NULL,/* merge server configuration */ngx_http_circle_gif_create_loc_conf,/* create location configuration */ngx_http_circle_gif_merge_loc_conf /* merge location configuration */};

现在开始讲得更深一点。这些配置回调函数看其来很像,所有模块都一样,而且Nginx的API都会用到这个部分,所以值得好好看看。

①create_loc_conf

下面这段摘自我自己写的模块circle_gif(源代码),create_loc_conf的骨架大概就是这个样子. 它的入参是(ngx_conf_t),返回值是更新了的模块配置结构体(ngx_http_circle_gif_loc_conf_t).

static void *ngx_http_circle_gif_create_loc_conf(ngx_conf_t *cf){ngx_http_circle_gif_loc_conf_t*conf;conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_circle_gif_loc_conf_t));if (conf == NULL) {return NGX_CONF_ERROR;}conf->min_radius = NGX_CONF_UNSET_UINT;conf->max_radius = NGX_CONF_UNSET_UINT;return conf;}

首先需要指出的是Nginx的内存分配;只要使用了 ngx_palloc(malloc的一个包装函数)或者 ngx_pcalloc (calloc的包装函数),就不用担心内存的释放了。(TODO: to see why?) UNSET可能的常量有NGX_CONF_UNSET_UINT, NGX_CONF_UNSET_PTR, NGX_CONF_UNSET_SIZE, NGX_CONF_UNSET_MSEC, 以及无所不包的NGX_CONF_UNSET,UNSET让合并函数知道哪些变量是需要覆盖的。

②merge_loc_conf

下面的例子是我的模块circle_gif中的合并函数:

static char *ngx_http_circle_gif_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child){ngx_http_circle_gif_loc_conf_t *prev = parent;ngx_http_circle_gif_loc_conf_t *conf = child;ngx_conf_merge_uint_value(conf->min_radius, prev->min_radius, 10);ngx_conf_merge_uint_value(conf->max_radius, prev->max_radius, 20);if (conf->min_radius max_radius min_radius) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"max_radius must be equal or more than min_radius");return NGX_CONF_ERROR;}return NGX_CONF_OK;}

这里的需要注意的是Nginx提供了一些好用的合并函数用来合并不同类型的数据(ngx_conf_merge__value),这类函数的入参是:

1. 当前location 的变量值
2. 如果第一个参数没有被设置而采用的值
3. 如果第一第二个参数都没有被设置而采用的值

结果会被保存在第一个参数中。能用的合并函数包括 ngx_conf_merge_size_value, ngx_conf_merge_msec_value 等等. 可参见 core/ngx_conf_file.h.

问: 第一个参数是传值的,那如何能做到将结果保存到第一个参数中?

答: 这些函数都是由预处理命令定义的(在真正编译之前,它们会被扩展成一些if语句)

同时还需要注意的是错误的产生。函数会往log文件写一些东西,同时返回NGX_CONF_ERROR。这个返回值会将server的启动挂起。(因为被 标示为NGX_LOG_EMERG级别,所以错误同时还会输出到标准输出。作为参考,core/ngx_log.h列出了所有的日志级别。)

4. 模块定义

接下来我们间接地介绍更深一层:结构体ngx_module_t。该结构体变量命名方式为ngx_http__module。它包含模块的内容和指令执行方式,同时也还包含一些回调函数(退出线程,退出进程,等等)。模块定义在有的时候会被用作 查找的关键字,来查找与特定模块相关联的数据。

模块定义通常像是这样:

ngx_module_tngx_http__module = {NGX_MODULE_V1,&ngx_http__module_ctx, /* module context */ngx_http__commands,/* module directives */NGX_HTTP_MODULE,/* module type */NULL,/* init master */NULL,/* init module */NULL,/* init process */NULL,/* init thread */NULL,/* exit thread */NULL,/* exit process */NULL,/* exit master */NGX_MODULE_V1_PADDING};

仅仅替换掉合适的就可以了。模块可以添加一些回调函数来处理线程/进程的创建和销毁,但是绝大多数模块都用NULL忽略这些东东。(关于这些回调函数的入参,可以参考 core/ngx_conf_file.h.)。

5. 模块装载

模块的装载方式取决于模块的类型:handler、filter还是load-balancer。所以具体的装载细节将留在其各自的章节中再做介绍。

4)剖析Handler(非代理)

接下来我们把模块的细节放到显微镜下面来看,它们到底怎么运行的。

Handler一般做4件事:获取location配置;生成合适的响应;发送响应头;发送响应体。Handler有一个参数,即请求结构体。请求结构体包含很多关于客户请求的有用信息,比如说请求方法,URI,请求头等等,我们一个个地来看。

1. 获取location配置

这部分很简单,只需要调用 ngx_http_get_module_loc_conf,传入当前请求的结构体和模块定义即可。下面是我的circle gif handler的相关部分:

static ngx_int_tngx_http_circle_gif_handler(ngx_http_request_t *r){ngx_http_circle_gif_loc_conf_t*circle_gif_config;circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);...

现在我们就可以访问之前在合并函数中设置的所有变量了。

2. 生成响应

这才是模块真正干活的地方,很有趣哦。

这里要用到请求结构体,主要是这些结构体成员:

typedef struct {.../* the memory pool, used in the ngx_palloc functions */ngx_pool_t*pool;ngx_str_turi;ngx_str_targs;ngx_http_headers_in_theaders_in;...} ngx_http_request_t;

uri 是请求的路径, e.g. “/query.cgi”. args 请求串参数中问号后面的参数 (e.g. “name=john”). headers_in 包含有很多有用的东西,比如说cookie啊,浏览器信息啊什么的,但是许多模块可能用不到这些东东。如果你感兴趣的话,可以参看 http/ngx_http_request.h 。

对于生成输出,这些信息应该是够了。完整的ngx_http_request_t结构体定义在http/ngx_http_request.h。

3.发送响应头

响应头存放在结构体headers_out中,它的引用存放在请求结构体中。 Handler设置相应的响应头的值,然后调用ngx_http_send_header(r)。headers_out中比较有用的是:

typedef stuct {...ngx_uint_tstatus;size_tcontent_type_len;ngx_str_tcontent_type;ngx_table_elt_t*content_encoding;off_tcontent_length_n;time_tdate_time;time_tlast_modified_time;..} ngx_http_headers_out_t;

(剩下的可以在 http/ngx_http_request.h找到。) 举例来说,如果一个模块要设置Content-Type 为 “p_w_picpath/gif”, Content-Length 为 100, 并返回 HTTP 200 OK 的响应, 代码应当是这样的:

r->headers_out.status = NGX_HTTP_OK;r->headers_out.content_length_n = 100;r->headers_out.content_type.len = sizeof("p_w_picpath/gif") - 1;r->headers_out.content_type.data = (u_char *) "p_w_picpath/gif";ngx_http_send_header(r);

上面的设定方式针对大多数参数都是有效的。但一些头部的变量设定要比上面的例子要麻烦;比如,content_encoding 还含有类型(ngx_table_elt_t*), 所以必须先为此分配空间。可以用一个叫做ngx_list_push的函数来做,它传入一个ngx_list_t(与数组类似),返回一个list中的新 成员(类型是ngx_table_elt_t)。下面的代码设置了Content-Encoding为”deflate”并发送了响应头:

r->headers_out.content_encoding = ngx_list_push(&r->headers_out.headers);if (r->headers_out.content_encoding == NULL) {return NGX_ERROR;}r->headers_out.content_encoding->hash = 1;r->headers_out.content_encoding->key.len = sizeof("Content-Encoding") - 1;r->headers_out.content_encoding->key.data = (u_char *) "Content-Encoding";r->headers_out.content_encoding->value.len = sizeof("deflate") - 1;r->headers_out.content_encoding->value.data = (u_char *) "deflate";ngx_http_send_header(r);

当头部有多个值时,这个机制常常被用到。它(理论上讲)使得过滤模块添加、删除某个值而保留其他值的时候更加容易,在操纵字符串的时候,不需要把字符串重新排序。

4. 发送响应体

现在模块已经生成了一个响应,并存放在了内存中。接下来它需要将这个响应分配给一个特定的缓冲区,然后把这个缓冲区加入到链表,然后调用链表中“发送响应体”的函数。

链表在这里起什么作用呢?Nginx 中,handler模块(其实filter模块也是)生成响应到buffer中是同时完成的;链表中的每个元素都有指向下一个元素的指针,如果是NULL 则说明链表到头了。简单起见,我们假设只有一个buffer。

首先,模块需要先声明buffer和链表:

ngx_buf_t*b;ngx_chain_tout;

接着,需要给buffer分配空间,并将我们的响应数据指向它:

b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));if (b == NULL) {ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,"Failed to allocate response buffer.");return NGX_HTTP_INTERNAL_SERVER_ERROR;}b->pos = some_bytes; /* first position in memory of the data */b->last = some_bytes + some_bytes_length; /* last position */b->memory = 1; /* content is in read-only memory *//* (i.e., filters should copy it rather than rewrite in place) */b->last_buf = 1; /* there will be no more buffers in the request */

现在就可以把数据挂在链表上了:

out.buf = b;out.next = NULL;

最后,我们发送这个响应体,返回值是链表在一次调用后的状态:(and return the status code of the output filter chain all in one go)

return ngx_http_output_filter(r, &out);

Buffer链是Nginx IO模型中的关键部分,你得比较熟悉它的工作方式。

问: 为什么buffer还需要有个`last_buf`变量啊,我们不是可以通过判断next是否是NULL来知道哪个是链表的最末端了吗?

答: 链表可能是不完整的,比如说,当有多个buffer的时候,并不是所有的buffer都属于当前的请求和响应。所以有些buffer可能是buffer链表的表尾,但是不是请求的结束。这给我们引入了接下来的内容……

5)剖析Upstream(又称 Proxy) Handler

我已经帮你了解了如何让你的handler来产生响应。有些时候你可以用一小段C代码就可以得到响应,但是通常情况下你需要同另外一台server打交道 (比如你正在写一个用来实现某种网络协议的模块)。你当然可以自己实现一套网络编程的东东,但是如果你只收到部分的响应,需要等待余下的响应数据,你会怎 么办?你不会想阻塞整个事件处理循环吧?这样会毁掉Nginx的良好性能!

幸运的是,Nginx允许你在它处理后端服务器(叫做”upstreams”) 的机制上加入你的回调函数,因此你的模块将可以和其他的server通信,同时还不会妨碍其他的请求。这一节将介绍模块如何和一个upstream(如 Memcached, FastCGI,或者另一个 HTTP server)通信。

1.Upstream 回调函数概要

与其他模块的回调处理函数不一样,upstream模块的处理函数几乎不做“实事”。它压根不调用ngx_http_output_filter。它仅仅 是告诉回调函数什么时候可以向upstream server写数据了,以及什么时候能从upstream server读数据了。实际上它有6个可用的钩子: create_request 生成发送到upstream server的请求缓冲(或者一条缓冲链) reinit_request 在与后端服务器连接被重置的情况下(在create_request 被第二次调用之前)被调用 process_header 处理upstream 响应的第一个bit,通常是保存一个指向upstream “payload”的指针 abort_request 在客户端放弃请求时被调用 finalize_request 在Nginx完成从upstream读取数据后调用 input_filter 这是一个消息体的filter,用来处理响应消息体(例如把尾部删除) 这些钩子是怎么勾上去的呢?下面是一个例子,简单版本的代理模块处理函数:

static ngx_int_tngx_http_proxy_handler(ngx_http_request_t *r){ngx_int_trc;ngx_http_upstream_t*u;ngx_http_proxy_loc_conf_t*plcf;plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);/* set up our upstream struct */u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));if (u == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}u->peer.log = r->connection->log;u->peer.log_error = NGX_ERROR_ERR;u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;u->conf = &plcf->upstream;/* attach the callback functions */u->create_request = ngx_http_proxy_create_request;u->reinit_request = ngx_http_proxy_reinit_request;u->process_header = ngx_http_proxy_process_status_line;u->abort_request = ngx_http_proxy_abort_request;u->finalize_request = ngx_http_proxy_finalize_request;r->upstream = u;rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {return rc;}return NGX_DONE;}

看上去都是些例行事务,不过重要的是那些回调函数。同时还要注意的是ngx_http_read_client_request_body,它又设置了一个回调函数,在Nginx完成从客户端读数据后会被调用。

这些个回调函数都要做些什么工作呢?通常情况下,reinit_request, abort_request, 和 finalize_request用来设置或重置一些内部状态,但这些都是几行代码的事情。真正做苦力的是create_request 和 process_header。

2. create_request 回调函数

简单起见,假设我有一个upstream server,它读入一个字符打印出两个字符。那么函数应该如何来写呢? create_request需要申请一个buffer来存放“一个字符”的请求,为buffer申请一个链表,并且把链表挂到upstream结构体 上。看起来就像这样:

static ngx_int_tngx_http_character_server_create_request(ngx_http_request_t *r){/* make a buffer and chain */ngx_buf_t *b;ngx_chain_t *cl;b = ngx_create_temp_buf(r->pool, sizeof("a") - 1);if (b == NULL)return NGX_ERROR;cl = ngx_alloc_chain_link(r->pool);if (cl == NULL)return NGX_ERROR;/* hook the buffer to the chain */cl->buf = b;/* chain to the upstream */r->upstream->request_bufs = cl;/* now write to the buffer */b->pos = "a";b->last = b->pos + sizeof("a") - 1;return NGX_OK;}

不是很难,对吧?当然实际应用中你很可能还会用到请求里面的URI。r->uri作为一个 ngx_str_t类型也是有效的,GET的参数在r->args中,最后别忘了你还能访问请求头和 cookie信息。

3. process_header 回调函数

现在轮到process_header了,就像create_request把链表指针挂到请求结构体上去一样,process_header把响应指针 移到客户端可以接收到的部分。同时它还会从upstream 读入头信息,并且相应的设置发往客户端的响应头。

这里有个小例子,读进两个字符的响应。我们假设第一个字符代表“状态”字符。如果它是问号,我们将返回一个404错误并丢弃剩下的那个字符。如果它是空 格,我们将以 200 OK的响应把另一个字符返回给客户端。好吧,这不是什么多有用的协议,不过可以作为一个不错的例子。那么我们如何来实现这个process_header 函数呢?

static ngx_int_tngx_http_character_server_process_header(ngx_http_request_t *r){ngx_http_upstream_t*u;u = r->upstream;/* read the first character */switch(u->buffer.pos[0]) {case '?':r->header_only; /* suppress this buffer from the client */u->headers_in.status_n = 404;break;case ' ':u->buffer.pos++; /* move the buffer to point to the next character */u->headers_in.status_n = 200;break;}return NGX_OK;}

就是这样。操作头部,改变指针,搞定!注意headers_in实际上就是我们之前提到过的头部结构体( http/ngx_http_request.h),但是它位于来自upstream的头中。一个真正的代理模块会在头信息的处理上做很多文章,不光是错 误处理,做什么完全取决于你的想法。

但是……如果一个buffer没有能够装下全部的从upstream来的头信息,该怎么办呢?

4.状态保持

好了,还记得我说过abort_request, reinit_request和finalize_request 可以用来重置内部状态吗?这是因为许多upstream模块都有其内部状态。模块需要定义一个 自定义上下文结构 ,来标记目前为止从upstream读到了什么。这跟之前说的“模块上下文”不是一个概念。“模块上下文”是预定义类型,而自定义上下文结构可以包含任何 你需要的数据和字段(这可是你自己定义的结构体)。这个结构体在create_request函数中被实例化,大概像这样:

ngx_http_character_server_ctx_t*p;/* my custom context struct */p = ngx_pcalloc(r->pool, sizeof(ngx_http_character_server_ctx_t));if (p == NULL) {return NGX_HTTP_INTERNAL_SERVER_ERROR;}ngx_http_set_ctx(r, p, ngx_http_character_server_module);

最后一行实际上将自定义上下文结构体注册到了特定的请求和模块名上,以便在稍后取用。当你需要这个结构体时(可能所有的回调函数中都需要它),只需要:

ngx_http_proxy_ctx_t*p;p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);

指针 p 可以得到当前的状态. 设置、重置、增加、减少、往里填数据……你可以随心所欲的操作它。当upstream服务器返回一块一块的响应时,读取这些响应的过程中使用持久状态机是个很nx的办法,它不用阻塞主事件循环。很好很强大!

6)剖析Handler装载

Handler的装载通过往模块启用了的指令的回调函数中添加代码来完成。比如,我的circle gif 中ngx_command_t是这样的:

{ ngx_string("circle_gif"),NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,ngx_http_circle_gif,0,0,NULL }

回调函数是里面的第三个元素,在这个例子中就是那个ngx_http_circle_gif。回调函数的参数是由指令结构体(ngx_conf_t, 包含用户配置的参数),相应的ngx_command_t结构体以及一个指向模块自定义配置结构体的指针组成的。我的circle gif模块中,这些函数是这样子的:

static char *ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ngx_http_core_loc_conf_t*clcf;clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);clcf->handler = ngx_http_circle_gif_handler;return NGX_CONF_OK;}

这里可以分为两步:首先,得到当前location的“core”结构体,再分配给它一个 handler。很简单,不是吗? 我已经把我知道的关于hanler模块的东西全招了,现在可以来说说输出过滤链上的filter模块了。

7)剖析 Filters

Filter操作handler生成的响应。头部filter操作HTTP头,body filter操作响应的内容。

1.Header Filter

头部Filter由三个步骤组成:

1. 决定何时操作响应
2. 操作响应
3. 调用下一个filter

举个例子,比如有一个简化版本的“不改变”头部filter:如果客户请求头中的If- Modified-Since和响应头中的Last-Modified相符,它把响应状态设置成304。注意这个头部filter只读入一个参 数:ngx_http_request_t结构体,而我们可以通过它操作到客户请求头和一会将被发送的响应消息头。

staticngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r){time_tif_modified_since;if_modified_since = ngx_http_parse_time(r->headers_in.if_modified_since->value.data,r->headers_in.if_modified_since->value.len);/* step 1: decide whether to operate */if (if_modified_since != NGX_ERROR &&if_modified_since == r->headers_out.last_modified_time) {/* step 2: operate on the header */r->headers_out.status = NGX_HTTP_NOT_MODIFIED;r->headers_out.content_type.len = 0;ngx_http_clear_content_length(r);ngx_http_clear_accept_ranges(r);}/* step 3: call the next filter */return ngx_http_next_header_filter(r);}

结构headers_out和我们在hander那一节中看到的是一样的(参考http/ngx_http_request.h),也可以随意处置。

2.Body Filter

因为body filter一次只能操作一个buffer(链表),这使得编写body filter需要一定的技巧。模块需要知道什么时候可以覆盖输入buffer,用新申请的buffer_替换已有的,或者在现有的某个buffer前或后 插入一个新buffer。有时候模块会收到许多buffer使得它不得不操作一个_不完整的链表,这使得事情变得更加复杂了。而更加不幸的是,Nginx 没有为我们提供上层的API来操作buffer链表,所以body filter是比较难懂(当然也比较难写)。但是,有些操作你还是可以看出来的。

一个body filter原型大概是这个样子(例子代码从Nginx源代码的“chunked” filter中取得):

static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in);

第一个参数是我们的老朋友——请求结构体,第二个参数则是指向当前部分链表(可能包含0,1,或更多的buffer)头的指针。

再来举个例子好了。假设我们想要做的是在每个请求之后插入文本””。首先,我们需要判断给我们的buffer链表中是否已经包含响应的最终buffer。就像之前我说的,这里没有简便好用的API,所以我 们只能自己来写个循环:

ngx_chain_t *chain_link;int chain_contains_last_buffer = 0;for ( chain_link = in; chain_link != NULL; chain_link = chain_link->next ) {if (chain_link->buf->last_buf)chain_contains_last_buffer = 1;}

如果我们没有最后的缓冲区,就返回:

if (!chain_contains_last_buffer)return ngx_http_next_body_filter(r, in);

很好,现在最后一个缓冲区已经存在链表中了。接下来我们分配一个新缓冲区:

ngx_buf_t*b;b = ngx_calloc_buf(r->pool);if (b == NULL) {return NGX_ERROR;}

把数据放进去:

b->pos = (u_char *) "";b->last = b->pos + sizeof("") - 1;

把这个缓冲区挂在新的链表上:

ngx_chain_tadded_link;added_link.buf = b;added_link.next = NULL;

最后,把这个新链表挂在先前链表的末尾:

chain_link->next = added_link;

并根据变化重置变量”last_buf”的值:

chain_link->buf->last_buf = 0;added_link->buf->last_buf = 1;

再将修改过的链表传递给下一个输出过滤函数:

return ngx_http_next_body_filter(r, in);

现有的函数做了比我们更多的工作,比如mod_perl($response->body =~ s/$//),但是缓冲区链确实是一个强大的构想,它可以让程序员渐进地处理数据,这使得客户端可以尽可能早地得到响应。但是依我来看,缓冲区链表实 在需要一个更为干净的接口,这样程序员也可以避免操作不一致状态的链表。但是目前为止,所有的操作风险都得自己控制。

3.Filter的装载

Filter在在回调函数post-configuration中被装载。header filter和body filter都是在这里被装载的。

我们以chunked filter模块为例来具体看看:

static ngx_http_module_tngx_http_chunked_filter_module_ctx = {NULL,/* preconfiguration */ngx_http_chunked_filter_init,/* postconfiguration */...};

ngx_http_chunked_filter_init中的具体实现如下:

static ngx_int_tngx_http_chunked_filter_init(ngx_conf_t *cf){ngx_http_next_header_filter = ngx_http_top_header_filter;ngx_http_top_header_filter = ngx_http_chunked_header_filter;ngx_http_next_body_filter = ngx_http_top_body_filter;ngx_http_top_body_filter = ngx_http_chunked_body_filter;return NGX_OK;}

发生了什么呢?好吧,如果你还记得,过滤模块组成了一条”接力链表“。当handler生成一个响应后,调用2个函 数:ngx_http_output_filter它调用全局函数ngx_http_top_body_filter;以及 ngx_http_send_header 它调用全局函数ngx_top_header_filter。

ngx_http_top_body_filter 和 ngx_http_top_header_filter是body和header各自的头部filter链的”链表头“。链表上的每一个”连接“都保存着 链表中下一个连接的函数引用(分别是 ngx_http_next_body_filter 和 ngx_http_next_header_filter)。当一个filter完成工作之后,它只需要调用下一个filter,直到一个特殊的被定义 成”写“的filter被调用,这个”写“filter的作用是包装最终的HTTP响应。你在这个filter_init函数中看到的就是,模块把自己添 加到filter链表中;它先把旧的”头部“filter当做是自己的”下一个“,然后再声明”它自己“是”头部“filter。(因此,最后一个被添加 的filter会第一个被执行。)

边注:这到底是怎么工作的?

每个filter要么返回一个错误码,要么用`return ngx_http_next_body_filter();`来作为返回语句

因此,如果filter顺利链执行到了链尾(那个特别定义的的”写“filter),将返回一个”OK”响应,但如果执行过程中遇到了错误,链将被砍断,同时Nginx将给出一个错误的信息。这是一个单向的,错误快速返回的,只使用函数引用实现的链表。帅啊!

8)Load-balancers

Load-balancer用来决定哪一个后端将会收到请求;具体的实现是round-robin方式或者把请求进行hash。本节将介绍load- balancer模块的装载及其调用。我们将用upstream_hash_module(full source)作例子。upstream_hash将对nginx.conf里配置的变量进行 hash,来选择后端服务器。

一个load-balancer分为六个部分:

1. 启用配置指令 (e.g, hash;) 将会调用注册函数
2. 注册函数将定义一些合法的server 参数 (e.g., weight=) 并注册一个 upstream初始化函数
3. upstream初始化函数将在配置经过验证后被调用,并且:
* 解析 server 名称为特定的IP地址
* 为每个sokcet连接分配空间
* 设置对端初始化函数的回调入口
4. 对端初始化函数将在每次请求时被调用一次,它主要负责设置一些负载均衡函数将会使用的数据结构。
5. 负载均衡函数决定把请求分发到哪里;每个请求将至少调用一次这个函数(如果后端服务器失败了,那就是多次了),有意思的事情就是在这里做的。
6. 最后,对端释放函数 可以在与对应的后端服务器结束通信之后更新统计信息 (成功或失败)

好像很多嘛,我来逐一讲讲。

1. 启用指令

指令声明,既确定了他们在哪里生效又确定了一旦流程遇到指令将要调用什么函数。load-balancer的指令需要置 NGX_HTTP_UPS_CONF标志位,一遍让Nginx知道这个指令只会在upstream块中有效。同时它需要提供一个指向注册函数的指针。下面 列出的是upstream_hash模块的指令声明:

{ ngx_string("hash"),NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,ngx_http_upstream_hash,0,0,NULL },

都是些很眼熟的东西。

2. 注册函数

上面的回调函数ngx_http_upstream_hash就是所谓的注册函数。之所以这样叫(我起得名字)是因为它注册了把upstream初始化函 数和周边的upstream配置注册到了一块。另外,注册函数还定义了特定upstream块中的server指令的一些选项(如weight=, fail_timeout=),下面是upstream_hash模块的注册函数:

ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){ngx_http_upstream_srv_conf_t*uscf;ngx_http_script_compile_tsc;ngx_str_t*value;ngx_array_t*vars_lengths, *vars_values;value = cf->args->elts;/* the following is necessary to evaluate the argument to "hash" as a $variable */ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));vars_lengths = NULL;vars_values = NULL;sc.cf = cf;sc.source = &value[1];sc.lengths = &vars_lengths;sc.values = &vars_values;sc.complete_lengths = 1;sc.complete_values = 1;if (ngx_http_script_compile(&sc) != NGX_OK) {return NGX_CONF_ERROR;}/* end of $variable stuff */uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);/* the upstream initialization function */uscf->peer.init_upstream = ngx_http_upstream_init_hash;uscf->flags = NGX_HTTP_UPSTREAM_CREATE;/* OK, more $variable stuff */uscf->values = vars_values->elts;uscf->lengths = vars_lengths->elts;/* set a default value for "hash_method" */if (uscf->hash_function == NULL) {uscf->hash_function = ngx_hash_key;}return NGX_CONF_OK;}

除了依葫芦画瓢的用来计算$variable的代码,剩下的都很简单,就是分配一个回调函数,设置一些标志位。哪些标志位是有效的呢?

* NGX_HTTP_UPSTREAM_CREATE: 让upstream块中有 server 指令。我实在想不出那种情形会用不到它。
* NGX_HTTP_UPSTREAM_WEIGHT: 让server指令获取选项 weight=
* NGX_HTTP_UPSTREAM_MAX_FAILS: 允许选项max_fails=
* NGX_HTTP_UPSTREAM_FAIL_TIMEOUT: 允许选项fail_timeout=
* NGX_HTTP_UPSTREAM_DOWN: 允许选项 down
* NGX_HTTP_UPSTREAM_BACKUP: 允许选项backup

每一个模块都可以访问这些配置值。

一切都取决于模块自己的决定 。也就是说,max_fails不会被自动强制执行;所有的失败逻辑都是由模块作者决定的。过会我们再说这个。目前,我们还没有完成对回调函数的追踪呢。 接下来,我们来看upstream初始化函数 (上面的函数中的回调函数init_upstream )。

3. upstream 初始化函数

upstream 初始化函数的目的是,解析主机名,为socket分配空间,分配(另一个)回调函数。下面是upstream_hash:

ngx_int_tngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us){ngx_uint_ti, j, n;ngx_http_upstream_server_t*server;ngx_http_upstream_hash_peers_t*peers;/* set the callback */us->peer.init = ngx_http_upstream_init_upstream_hash_peer;if (!us->servers) {return NGX_ERROR;}server = us->servers->elts;/* figure out how many IP addresses are in this upstream block. *//* remember a domain name can resolve to multiple IP addresses. */for (n = 0, i = 0; i servers->nelts; i++) {n += server[i].naddrs;}/* allocate space for sockets, etc */peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_hash_peers_t)+ sizeof(ngx_peer_addr_t) * (n - 1));if (peers == NULL) {return NGX_ERROR;}peers->number = n;/* one port/IP address per peer */for (n = 0, i = 0; i servers->nelts; i++) {for (j = 0; j peer[n].sockaddr = server[i].addrs[j].sockaddr;peers->peer[n].socklen = server[i].addrs[j].socklen;peers->peer[n].name = server[i].addrs[j].name;}}/* save a pointer to our peers for later */us->peer.data = peers;return NGX_OK;}

这个函数包含的东西ms比我们期望的多些。大部分的工作ms都该被抽象出来,但事实却不是,我们只能忍受这一点。倒是有一种简化的策略:调用另一个模块的 upstream初始化函数,把这些脏活累活(对端的分配等等)都让它干了,然后再覆盖其us->peer.init这个回调函数。例子可以参见 http/modules/ngx_http_upstream_ip_hash_module.c。

在我们这个观点中的关键点是设置对端初始化函数的指向,在我们这个例子里是ngx_http_upstream_init_upstream_hash_peer。

4. 对端初始化函数

对端初始化函数每个请求调用一次。它会构造一个数据结构,模块会用这个数据结构来选择合适的后端服务器;这个数据结构保存着和后端交互的重试次数,通过它 可以很容易的跟踪链接失败次数或者是计算好的哈希值。这个结构体习惯性地被命名为ngx_http_upstream__peer_data_t。

另外,对端初始化函数还会构建两个回调函数:

* get: load-balancing 函数
* free: 对端释放函数 (通常只是在连接完成后更新一些统计信息)

似乎还不止这些,它同时还初始化了一个叫做tries的变量。只要tries是正数,Nginx将继续重试当前的load-banlancer。当tries变为0时,Nginx将放弃重试。一切都取决于get 和 free 如何设置合适的tries。

下面是upstream_hash中对端初始化函数的例子:

static ngx_int_tngx_http_upstream_init_hash_peer(ngx_http_request_t *r,ngx_http_upstream_srv_conf_t *us){ngx_http_upstream_hash_peer_data_t*uhpd;ngx_str_t val;/* evaluate the argument to "hash" */if (ngx_http_script_run(r, &val, us->lengths, 0, us->values) == NULL) {return NGX_ERROR;}/* data persistent through the request */uhpd = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t)+ sizeof(uintptr_t)* ((ngx_http_upstream_hash_peers_t *)us->peer.data)->number/ (8 * sizeof(uintptr_t)));if (uhpd == NULL) {return NGX_ERROR;}/* save our struct for later */r->upstream->peer.data = uhpd;uhpd->peers = us->peer.data;/* set the callbacks and initialize "tries" to "hash_again" + 1*/r->upstream->peer.free = ngx_http_upstream_free_hash_peer;r->upstream->peer.get = ngx_http_upstream_get_hash_peer;r->upstream->peer.tries = us->retries + 1;/* do the hash and save the result */uhpd->hash = us->hash_function(val.data, val.len);return NGX_OK;}

看上去不错,我们现在可以来选择一台upstream服务器了。

5. 负载均衡函数

主要部分现在才开始。货真价实的哦。模块就是在这里选择upstream服务器的。负载均衡函数的原型看上去是这样的:

static ngx_int_tngx_http_upstream_get__peer(ngx_peer_connection_t *pc, void *data);

data是我们存放所关注的客户端连接中有用信息的结构体。pc则是要存放我们将要去连接的server的相关信息。负载均衡函数做的事情就是填写 pc->sockaddr, pc->socklen, 和 pc->name。如果你懂一点网络编程的话,这些东西应该都比较熟悉了;但实际上他们跟我们手头上的任务来比并不算很重要。我们不关心他们代表什 么;我们只想知道从×××到合适的值来填写他们。

这个函数必须找到一个可用server的列表,挑一个分配给pc。我们来看看upstream_hash是怎么做的吧: 之前upstream_hash模块已经通过调用ngx_http_upstream_init_hash,把server列表存放在了 ngx_http_upstream_hash_peer_data_t 这一结构中。这个结构就是现在的data:

ngx_http_upstream_hash_peer_data_t *uhpd = data;

对端列表现在在uhpd->peers->peer中了。我们通过对哈希值与 server总数取模来从这个数组中取得最终的对端服务器:

ngx_peer_addr_t *peer = &uhpd->peers->peer[uhpd->hash % uhpd->peers->number];

终于大功告成了:

pc->sockaddr = peers->sockaddr;pc->socklen= peers->socklen;pc->name= &peers->name;return NGX_OK;

就是这样!如果load-balancer模块返回 NGX_OK,则意味着”来吧,上这个 server吧!“。如果返回的是NGX_BUSY,说明所有的后端服务器目前都不可用,此时Nginx应该重试。

但是……我们怎么记录哪些个服务器不可用了?我们如果不想重试了怎么办?

6.对端释放函数

对端释放函数在upstream连接就绪之后开始运行,它的目的是跟踪失败。函数原型如下:

voidngx_http_upstream_free__peer(ngx_peer_connection_t *pc, void *data,ngx_uint_t state);

头两个参数和我们在load-balancer函数中看到的一样。第三个参数是一个state变量,它表明了当前连接是成功还是失败。它可能是 NGX_PEER_FAILED (连接失败) 和 NGX_PEER_NEXT (连接失败或者连接成功但程序返回了错误)按位或的结果。如果它是0则代表连接成功。

这些失败如何处理则由模块的开发者自己定。如果根本不再用,那结果则应存放到data中,这是一个指向每个请求自定义的结构体。

但是对端释放函数的关键作用是可以设置pc->tries为 0来阻止Nginx在load-balancer模块中重试。最简单的对端释放函数应该是这样的:

pc->tries = 0;

这样就保证了如果发往后端服务器的请求遇到了错误,客户端将得到一个502 Bad Proxy的错误。

这儿还有一个更为复杂的例子,是从upstream_hash模块中拿来的。如果后端连接失败,它会在位向量 (叫做 tried,一个 uintptr_t类型的数组)中标示失败,然后继续选择一个新的后端服务器直至成功。

#define ngx_bitvector_index(index) index / (8 * sizeof(uintptr_t))#define ngx_bitvector_bit(index) (uintptr_t) 1 <tries){/* the backend that failed */current = uhpd->hash % uhpd->peers->number;/* mark it in the bit-vector */uhpd->tried[ngx_bitvector_index(current)] |= ngx_bitvector_bit(current);do { /* rehash until we're out of retries or we find one that hasn't been tried */uhpd->hash = ngx_hash_key((u_char *)&uhpd->hash, sizeof(ngx_uint_t));current = uhpd->hash % uhpd->peers->number;} while ((uhpd->tried[ngx_bitvector_index(current)] & ngx_bitvector_bit(current)) && --pc->tries);}}

因为load-balancer函数只会看新的uhpd->hash的值,所以这样是行之有效的。

许多应用程序不提供重试功能,或者在更高层的逻辑中进行了控制。但其实你也看到了,只需这么几行代码这个功能就可以实现了。

7.编写并编译一个新的Nginx模块

至此,你应该可以来找一个现成的Nginx模块来看看,尝试着理解其工作原理。可以看看src/http/modules/,这里一些现成可用的模块。从 里面找一个跟你想要的大概相似的模块深入地看看。看上去很熟悉?没错,应该很熟悉。对照着代码和这篇文章,慢慢理解吧。

注:介于这节已不再是晦涩的概念介绍,就不做翻译了。

But Emiller didn’t write a Balls-In Guide to Reading Nginx Modules. Hell no. This is a Balls-Out Guide. We’re not reading. We’re writing. Creating. Sharing with the world. First thing, you’re going to need a place to work on your module. Make a folder for your module anywhere on your hard drive, but separate from the Nginx source (and make sure you have the latest copy from nginx.net). Your new folder should contain two files to start with:

* “config”
* “ngx_httpmodule.c”

The “config” file will be included by ./configure, and its contents will depend on the type of module.

“config” for filter modules:

ngx_addon_name=ngx_http__module
HTTP_AUX_FILTER_MODULES=”$HTTP_AUX_FILTER_MODULES ngx_http__module”
NGX_ADDON_SRCS=”$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http__module.c”

“config” for other modules:

ngx_addon_name=ngx_http__module
HTTP_MODULES=”$HTTP_MODULES ngx_http__module”
NGX_ADDON_SRCS=”$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http__module.c”

Now for your C file. I recommend copying an existing module that does something similar to what you want, but rename it “ngx_httpmodule.c”. Let this be your model as you change the behavior to suit your needs, and refer to this guide as you understand and refashion the different pieces. When you’re ready to compile, just go into the Nginx directory and type

./configure –add-module=path/to/your/new/module/directory

and then make and make install like you normally would. If all goes well, your module will be compiled right in. Nice, huh? No need to muck with the Nginx source, and adding your module to new versions of Nginx is a snap, just use that same ./configure command. By the way, if your module needs any dynamically linked libraries, you can add this to your “config” file:

CORE_LIBS=”$CORE_LIBS -lfoo”

Where foo is the library you need. If you make a cool or useful module, be sure to send a note to the Nginx mailing list and share your work.

本节只涵盖了Nginx模块开发的基础。想开发更为精巧的模块,一定要去 Emiller’s Advanced Topics In Nginx Module Development看看。

4、Nginx日志配置

1)Nginx error_log配置

nginx的error_log类型如下(从左到右:debug最详细 crit最少):

[ debug | info | notice | warn | error | crit]

注意:当访问量较大时,不要使用debug,消耗磁盘IO

例如:

error_log logs/nginx_error.log crit;

解释:日志文件存储在nginx安装目录下的 logs/nginx_error.log ,错误类型为 crit ,也就是记录最少错误信息;

默认值:

error_log logs/error.log error;

配置段:

main, http, server, location

详情参考:Core functionality

2)Nginx 访问日志配置

语法:

access_log path [format [buffer=size [flush=time]]];access_log path format gzip[=level] [buffer=size] [flush=time];access_log syslog:server=address[,parameter=value] [format];access_log off;

例如:

access_log logs/access.log combined gzip buffer=32k flush=5

默认值:

access_log logs/access.log combined;

日志配置:

http, server, location, if in location, limit_except

访问日志解释:

gzip压缩等级;

buffer设置内存缓存区大小;

flush保存在缓存区中的最长时间;

不记录日志:access_log off;

使用默认combined格式记录日志:access_log logs/access.log 或 access_log logs/access.log combined;

日志格式允许包含的变量注释如下:

  • $remote_addr, $http_x_forwarded_for 记录客户端IP地址
  • $remote_user 记录客户端用户名称
  • $request 记录请求的URL和HTTP协议
  • $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 通用日志格式下的本地时间。

详情参考:Module ngx_http_log_module

3)nginx使用json格式日志

log_format json '{"@timestamp":"$time_iso8601",''"remote_ip":"$remote_addr",''"status":$status,''"bytes":$body_bytes_sent,''"referer":"$http_referer",' '"agent":"$http_user_agent",''"request_time":$request_time,''"request":"$uri"}';

配置使用Json格式记录日志:

access_log logs/access.log;access_log logs/access.json.log json;

4)nginx自动切割访问日志

自动切割访问日志脚本:

#!/bin/bashDataformat=`date +%F -d -1day`Basedir="/application/nginx"Nginxlogdir="$Basedir/logs"Logname="access"[ -d $Nginxlogdir ] && cd $Nginxlogdir||exit 1[ -f ${Logname}.log ] || exit 1/bin/mv ${Logname}.log ${Dataformat}_${Logname}.log$Basedir/sbin/nginx -s reload

添加定时任务实现每天定时切割:

00 00 * * * /bin/sh /server/scripts/cut_log.sh &>/dev/null

也可以在脚本中添加多个mv命令对每个虚拟主机进行切割。

四、Nginx反向代理配置

1、反向代理与正向代理

反向代理(Reverse Proxy)是指通过代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并且将从内部网络服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。当一个代理服务器能够代理外部网络上的访问请求来访问内部网络时,这种代理服务的方式称为反向代理服务。

客户端无法直接访问外部的web,需要在客户端所在的网络内架设一台代理服务器,客户端通过代理服务器访问外部的web(需要在客户端的浏览器中设置代理服务器),这就是正向代理。

正向代理适用于:

① 局域网的代理服务器

② 访问某个受限网络的代理服务器,如教育网访问某些国外网站需要找代理。

2、Nginx中location应用实例

location主要用于对URL进行匹配。 location支持正则表达式匹配,也支持条件判断匹配。

以下这段设置是通过location指令来对网页URL进行分析处理,所有扩展名以.gif、.jpg、.jpeg、.png、.bmp、.swf结尾的静态文件都交给nginx处理。

location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {         root  /data/wwwroot/www.ixdba.net;        } 

以下这段设置是将upload和html下的所有文件都交给nginx来处理,需要注意的是,upload和html目录是在/data/wwwroot/www.ixdba.net目录下的一个子目录。

location ~ ^/(upload|html)/ {         root  /data/wwwroot/www.ixdba.net;        }

在下面这段设置中,location是对此虚拟主机下动态网页的过滤处理,也就是将所有以.jsp为后缀的文件都交给本机的8080端口处理。

location ~ .*.jsp$ {     index index.jsp;        proxy_pass http://localhost:8080; }

location支持各种匹配规则,在多个匹配规则下,Nginx对location的处理是有优先级的,优先级高的规则会优先进行处理,而优先级低的规则可能会最后处理或者不进行处理,下面列出location多个匹配规则下,每个规则的处理优先级顺序。

location  = / {  [ config A ]}location ^~ /images/ {  [ config B ]}location ~* \.(gif|jpg|png|swf)$ {  [ config C ]} location  /abc/def {  [ config D ]}location  /abc {  [ config E ]}location  / {  [ config F ]}

3、最简单的反向代理实例

实现反向代理功能的是一个叫做proxy_pass的模块,最简单的一个反向代理应用如下所示,这里仅列出整个配置中的server部分:

server {    listen    80;    server_name www.a.com;    location / {    proxy_pass http://172.16.213.18;    }}

这个反向代理实现的功能是:当访问www.a.com的时候,所有访问请求都会转发到后端172.16.213.18这个服务器的80端口上。

一个典型的反向代理服务器配置如下所示,这里仅列出整个配置中的server部分:

server {    listen    80;    server_name www.b.com;    location / {    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;    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;    proxy_pass http://172.16.213.77:5601;    }}

这个反向代理实现的功能是:当访问www.b.com的时候,所有访问请求都会转发到后端172.16.213.77这个服务器的5601端口上。与上面那个反向代理实例相比,此反向代理配置增加了一些反向代理属性,这些属性一般用于生产环境下对代理性能要求很高的环境中。

4、Nginx反向代理uri的用法

Nginx的这种反向代理用法,主要有如下两种情况,这里仅列出整个配置中的server部分。

第一种情况请看如下配置:

server {          server_name www.abc.com;          location /uri/ {          proxy_pass http://192.168.99.100:8000;          }}

nginx的proxy_pass对于此种情况的处理方式是:将location中的uri传递给后端服务器,也就是当客户端访问http://www.abc.com/uri/iivey.html 时,会被反向代理到http://192.168.99.100:8000/uri/iivey.html 进行访问。

第二种uri代理方式配置如下:

server {          server_name www.abc.com;          location /uri/ {          proxy_pass http://192.168.99.100:8000/new_uri/;          }        }

nginx的proxy_pass对于此种情况的处理方式是:替换成proxy_pass指令中URL中含有的uri,也就是当客户端访问http://www.abc.com/uri/iivey.html 时,会被反向代理到http://192.168.99.100:8000/new_uri/iivey.html 进行访问。

其实还有一种uri代理方式,配置如下:

server {          server_name www.abc.com;          location /uri/ {          proxy_pass http://192.168.99.100:8000/;          }        }

nginx的proxy_pass对于此种情况的处理方式是:替换成proxy_pass指令中URL中含有的uri,也就是当客户端访问http://www.abc.com/uri/iivey.html 时,会被反向代理到http://192.168.99.100:8000/iivey.html 进行访问。

这种反向代理方式其实是上面第二种uri代理方式的扩展,这里要重点注意下“proxy_pass http://192.168.99.100:8000/;” 这个url结尾有个“/”和没有”/”的区别。

五、Nginx虚拟主机配置

虚拟主机功能是Nginx经常用到的一个特性,每个虚拟主机就是一个独立的站点,对应一个域名,如果需要多个域名指向到一个IP上时,通过虚拟主机功能可以轻松实现。

下面在Nginx中创建三个虚拟主机,需要说明的是,这里仅仅列出了虚拟主机配置部分。

http {server {listen          80;server_name     www.domain1.com;access_log      logs/domain1.access.log main;location / {index index.html;root  /data/www/domain1.com;}  }server {listen          80;server_name     www.domain2.com;access_log      logs/domain2.access.log main;location / {index index.html;root  /data/www/domain2.com;}  }  include    /usr/local/nginx/conf/vhosts/www.domain3.conf;}

1、搭建基于域名的多虚拟机主机

egrep -v "#|^$" nginx.conf.default >nginx.conf          #过滤#及空行worker_processes  1;error_log logs/error.log error;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '                      '$status $body_bytes_sent "$http_referer" '                      '"$http_user_agent" "$http_x_forwarded_for"';    #nginx vhosts config    include extra/www.conf;    include extra/bbs.conf;    include extra/blog.conf;}

创建配置文件目录及配置文件:

cd /application/nginx/conf/mkdir extratouch extra/{www,bbs,blog}.conf[root@nginx conf]# vi extra/www.confserver {    listen       80;    server_name  www.etiantian.org;    location / {        root   /var/html/www;        index  index.html index.htm;    }    error_page   500 502 503 504  /50x.html;    location = /50x.html {        root   html;    }    access_log logs/www_access.log main;}

创建站点目录:

mkdir /var/html/{www,bbs,blog} -ptouch /var/html/{www,bbs,blog}/index.htmlfor name in www bbs blog; do echo "$name " >/var/html/$name/index.html;done

重启web服务:

/application/nginx/sbin/nginx -t/application/nginx/sbin/nginx -s reloadecho " 192.168.80.104 www.etiantian.org bbs.etiantian.org blog.etiantian.org etiantian.org">>/etc/hosts

如果配置基于域名的多虚拟机主机,使用IP地址访问默认访问第一个server。

2、基于端口的多虚拟主机

在基于域名的多虚拟机主机的基础上,修改两个server使用同一个域名,不同的端口,重启,使用不同的端口访问。

3、基于IP的虚拟主机

man ip              #查找帮助********************************************……ip addr { add | del } IFADDR dev STRING……ip addr add 192.168.80.99/24 dev eth0          #在一个网卡上增加另一个IP地址

仅仅修改一个server中的 listen 192.168.80.99:80;
另一个server中为listen 192.168.80.100:80;即可

4、利用include功能优化Nginx的配置文件

将配置文件中的server模块置于另外一个文件extra/vhost.conf中,然后在nginx.conf文件中加入include extra/vhost.conf;重启即可。也可以添加多个.conf文件,使用include分别包括,也可以直接加入include extra/*.conf;以包含所有.conf文件。

例如:

include extra/www.conf;

5、虚拟主机别名配置

在server_name后直接加别名,可以有多个,使用空格隔开,并且在hosts文件中对别名解析。可以利用此功能监控集群节点下的主机。

6、Nginx状态信息功能实战

1. Nginx status

Nginx软件功能模块中有一个ngx_http_stub_status_module模块,这个模块的功能是记录Nginx的基本访问状态信息,让读者了解Nginx的工作状态,例如:网站的负载情况。要想使用状态模块,在编译时必须增加http_stub_status_module模块。

可通过如下方式查看安装时编译的参数:

./nginx/sbin/nginx -Vnginx version: nginx/1.6.3built by gcc 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC) TLS SNI support enabledconfigure arguments: --prefix=/application/nginx-1.6.3 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module

2. 配置Nginx状态信息功能

配置文件中添加如下server:

    server {        listen       80;        server_name  status.etiantian.com;        location / {            stub_status on;            access_log off;        }}

重启服务并在hosts文件中添加主机记录,在浏览器中访问:status.etiantian.com,显示如下:

Active connections: 1            #正在处理的活动链接数server accepts handled requests 2 2 13 Reading: 0 Writing: 1 Waiting: 0 

说明:

Active connections        //当前 Nginx 正处理的活动连接数。server accepts handledrequests  //总共处理了2个连接 , 成功创建 2 次握手,总共处理了13个请求。Reading   //nginx读取到客户端的 Header 信息数。Writing   //nginx返回给客户端的 Header 信息数。Waiting   //开启 keep-alive 的情况下,这个值等于 active – (reading + writing),意思就是 Nginx 已经处理完正在等候下一次请求指令的驻留连接

六、Nginx rewrite 重定向配置

1、Nginx内置变量

以http://188.19.236.18:8000/abc” />

6、SSL证书分类和应用环境

目前市面上的SSL证书都是通过第三方SSL证书机构颁发的,常见可靠的第三方 SSL证书颁发机构有DigiCert、GeoTrust、GlobalSign、Comodo等。

根据不同使用环境,SSL证书可分为如下几种:

1. 增强型SSL证书(EV SSL,即为Extended Validation SSL),适合银行金融类电子商务网站(网上购物)使用,证书里显示单位名称,显示绿色地址栏;有EV超安SSL和EV超安SSL Pro两个型号。

2. 机构验证型SSL证书(OV SSL, 即为Organization Validation SSL),适合电子商务、电子政务网站、企事业单位管理系统、电子邮件系统使用,证书里显示单位名称; 有OV超真 SSL和OV 超真SSL Pro两个型号。

3. 个人验证型SSL证书(IV SSL,即为IdentityValidation SSL),适用于个人专业网站使用,显示个人姓名;

4. 域名验证型SSL证书(DV SSL 即为Domain Validation SSL),不显示任何信息,是最基础级的SSL 证书。通常是验证域名下某个指定文件的内容,或者验证与域名相关的某条 TXT 记录;

7、配置HTTPS需要的证书文件

Nginx配置HTTPS并不复杂,主要有两个步骤:

1. 签发第三方可信任的SSL证书

2. Nginx下配置 HTTPS

要获取第三方CA机构的证书文件,需要证书申请者提供三个证书文件:

1. 私钥文件(以.key结尾)

2. 证书签署请求文件(以.crs结尾)

3. 证书文件(以.crt结尾)

其中:私钥文件由证书申请者生成,它是证书申请者的私钥文件,证书签署请求文件(crs文件),此文件里面包含申请者的DN(Distinguished Name,标识名)和公钥信息,此文件由证书申请者生成。将crs文件提交给第三方证书颁发机构,然后第三方CA机构会返回给申请者一个CRT文件。

8、使用OpenSSL生成私钥文件和CSR文件

在申请SSL证书之前,证书申请者需要先生成一个私钥文件和一个CSR文件,

可通过openssl命令来生成这两个文件,操作如下:

[root@nginx ~]# cd /application/nginx/conf/[root@nginx conf]# openssl genrsa -des3 -out server.key 1024    #创建服务器私钥,命令会让你输入一个口令Generating RSA private key, 1024 bit long modulus......++++++...................++++++e is 65537 (0x10001)Enter pass phrase for server.key:Verifying - Enter pass phrase for server.key:[root@nginx conf]# openssl req -new -key server.key -out server.csr   #创建签名请求的证书(CSR)Enter pass phrase for server.key:You are about to be asked to enter information that will be incorporatedinto your certificate request.What you are about to enter is what is called a Distinguished Name or a DN.There are quite a few fields but you can leave some blankFor some fields there will be a default value,If you enter '.', the field will be left blank.-----Country Name (2 letter code) [XX]:chinastring is too long, it needs to be less than  2 bytes longCountry Name (2 letter code) [XX]:CNState or Province Name (full name) []:beijing         Locality Name (eg, city) [Default City]:beijingOrganization Name (eg, company) [Default Company Ltd]:ekwingOrganizational Unit Name (eg, section) []:sreCommon Name (eg, your name or your server's hostname) []:liwenbinEmail Address []:1935845114@qq.comPlease enter the following 'extra' attributesto be sent with your certificate requestA challenge password []:123456An optional company name []:[root@nginx conf]# cp server.key server.key.org[root@nginx conf]# openssl rsa -in server.key.org -out server.key#在加载SSL支持的Nginx并使用上述私钥时除去必须的口令:提示:在生成证书请求csr文件时,如果输入了密码,nginx每次启动时都会提示输入这个密码,可以使用私钥来生成解密后的key来代替,效果是一样的,达到免密码重启的效果:Enter pass phrase for server.key.org:writing RSA key[root@nginx conf]# openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt            #最后标记证书使用上述私钥和CSRSignature oksubject=/C=CN/ST=beijing/L=beijing/O=ekwing/OU=sre/CN=liwenbin/emailAddress=193545114@qq.comGetting Private key

C字段:即Country,表示单位所在国家,为两位数的国家缩写,如CN表示中国;

ST字段: State/Province,单位所在州或省;

L字段: Locality,单位所在城市/或县区;

O字段: Organization,此网站的单位名称;

OU字段: Organization Unit,下属部门名称;也常常用于显示其他证书相关信息,如证书类型,证书产品名称或身份验证类型或验证内容等;

CN字段:Common Name,网站的域名;

快速创建过程:

cd /application/nginx/conf/openssl genrsa -des3 -out server.key 1024openssl req -new -key server.key -out server.csrcp server.key server.key.orgopenssl rsa -in server.key.org -out server.keyopenssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

9、Nginx下配置SSL证书

默认情况下ssl模块并未被安装,如果要使用该模块则需要在编译时指定with-http_ssl_module参数,安装模块依赖于OpenSSL库和一些引用文件,通常这些文件并不在同一个软件包中。通常这个文件名类似libssl-dev。

server  {    listen       443;    server_name  www.etiantian.org;;    index  index.php index.html;    root  /application/nginx/conf;    sslon;    ssl_certificateserver.crt;    ssl_certificate_keyserver.key;    ssl_prefer_server_cipherson;    ssl_protocolsTLSv1 TLSv1.1 TLSv1.2;    ssl_ciphersHIGH:!aNULL:!MD5;    add_header X-Frame-Options DENY;    add_header X-Content-Type-Options nosniff;    add_header X-Xss-Protection 1; }

参数说明:

ssl on:表示启用SSL功能。

ssl_certificate:用来指定CRT文件的路径,可以是相对路径,也可以是绝对路径

ssl_certificate_key:用来指定秘钥文件的路径,可以是相对路径,也可以是绝对路径。

ssl_prefer_server_ciphers on:设置协商加密算法时,优先使用服务端的加密套件,而不是客户端浏览器的加密套件。

ssl_protocols:此指令用于启动特定的加密协议,这里设置为“TLSv1 TLSv1.1 TLSv1.2”,TLSv1.1与TLSv1.2要确保openssl版本大于等于openssl1.0.1 ,SSLv3也可以使用,但是有不少被攻击的漏洞,所以现在很少使用了。

ssl_ciphers:选择加密套件和加密算法,不同的浏览器所支持的套件和顺序可能会有不同。这里指定的是OpenSSL库能够识别的写法,你可以通过 openssl -v cipher ‘RC4:HIGH:!aNULL:!MD5’(后面是你所指定的套件加密算法) 来看所支持算法。

add_header X-Frame-Options DENY:这是个增强安全性的选项,表示减少点击劫持。

add_header X-Content-Type-Options nosniff:同样是增强安全性的选项,表示禁止服务器自动解析资源类型。

add_header X-Xss-Protection 1:同样是增强安全性的选项,表示防止XSS攻击。

重启nginx。

通过https://www.etiantian.org/访问。

80端口重定向到443:

    server {        listen 80;        server_name www.etiantian.org;        rewrite ^(.*) https://$server_name$1 permanent;    }

10、部分页面ssl

一个站点并不是所有信息都是非常机密的,如网上商城,一般的商品浏览可以不通过https,而用户登录以及支付的时候就强制经过https传输,这样用户访问速度和安全性都得到兼顾。

但是请注意不要理解错了,是对页面加密而不能针对某个请求加密,一个页面或地址栏的URL一般会发起许多请求的,包括css/png/js等静态文件和动态的java或php请求,所以要加密的内容包含页面内的其它资源文件,否则就会出现http与https内容混合的问题。在http页面混有https内容时,页面排版不会发生乱排现象;在https页面中包含以http方式引入的图片、js等资源时,浏览器为了安全起见会阻止加载。

下面是只对example.com/account/login登录页面进行加密的例子:

root /apps/www;index index.html index.htm;server {    listen      80;    server_name example.com;    location ^~ /account/login {        rewrite ^ https://$server_name:443$request_uri? permanent;    }    location / {        proxy_pass  http://localhost:8080;        ### Set headers ####        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_redirect     off;     }}server {    listen 443 ssl;    server_name example.com;    ssl on;    ssl_certificate ../SSL/ittest.pem;    ssl_certificate_key ../SSL/ittest.key;    ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;    ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;    ssl_prefer_server_ciphers on;    location ^~ /account/login {        proxy_pass  http://localhost:8080;        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_redirect     off;         ### Most PHP, Python, Rails, Java App can use this header -> https ###        proxy_set_header X-Forwarded-Proto  $scheme;    }    location / {        rewrite  ^  http://$server_name$request_uri? permanent;    }}

11、实现双向ssl认证

上面的两种配置都是去认证被访问的站点域名是否真实可信,并对传输过程加密,但服务器端并没有认证客户端是否可信。(实际上除非特别重要的场景,也没必要去认证访问者,除非像银行U盾这样的情况)

要实现双向认证HTTPS,nginx服务器上必须导入CA证书(根证书/中间级证书),因为现在是由服务器端通过CA去验证客户端的信息。还有必须在申请服务器证书的同时,用同样的方法生成客户证书。取得客户证书后,还要将它转换成浏览器识别的格式(大部分浏览器都认识PKCS12格式):

12、验证HTTPS功能是否正常

验证HTTPS功能是否正常有两种方法:

1. 直接通过浏览器访问https服务

2. 在线网站验证:
亚数信息-SSL/TLS安全评估报告
SSL Server Test (Powered by Qualys SSL Labs)

八、Web 防盗链

1、什么是资源盗链

简单的说,就是某些不法的网站,通过在其自身网站程序里未经许可非法调用其他网站的资源,然后在自己的网站上显示这些调用的资源,达到了填充自身网站显示的效果,但是浪费了调用资源网站的网络流量,造成其他网站的带宽及服务压力吃紧,甚至宕机。

2、网站资源被盗链带来的问题

当网站图片及相关资源被盗链后,最直接的影响就是网络带宽占用加大了,带宽费用多了,网络流量也可能忽高忽低,nagios/zabbix等报警服务频繁报警。

最严里的情况就是网站的资源被非法使用,导致网站带宽成本加大以及服务器压力加大,多的导致数万元的损失以及网站的正常用户访问受到影响.

3、网站资源被盗链严重问题企业真实案例

某月某日,接到运维的朋友紧急求助,其公司的CDN源站,源站的流量没有变动,CDN那边的流量无故超了好几个G, 不知道怎么处理?老男孩补充,曾遇到过一张图片不到一天,跑了20多T的网络流量。

该故障的影响:由于是购买的CDN网站加速服务,虽然流量多了几个G,但是业务未受影响,但是,这么大的异常流量,持续下去可直接导致公司无故损失数万元.解决这个问题体现运维的价值。

那么这样的问题如何能够及时发现呢?

第一、对IDC及CDN带宽做监控报警。

第二、作为高级运维或者运维经理,每天上班的一个重要任务,就是经常查看网站流量图,关注流量变化,关注异常流量。

第三、对访问日志做分析,对于异常流量能迅速定位,并且和公司市场推广等有比较好的默契沟通交流,以便调度带宽和服务器资源.确保网站正常的访问体验得到保证。

4、网站被盗链的解决方案

1. 根据http referer实现防盗链

在HTTP协议中,有一个表头字段叫referer,使用URL格式来表示从哪里来的链接到当前网页的资源。通过referer可以检测目标访问的来源网页,如果是资源文件,可以跟踪到显示它的网页地址,一旦检测出来源不是本站进行阻止或返回指定的页面。

Apache,nginx,lighttpd三者都支持根据http referer实现防盗链

2. 根据cookie处理

3. 通过加密变换访问路径实现防盗链

lighttpd有类似的插件mod-secdownload

5、Apache web服务实现防盗链

Apache 防盗链的第一种实现方法,可以用 Rewrite 实现。首先要确认 Apache 的 rewrite module可用:能够控制 Apache httpd.conf 文件的,打开 httpd.conf,确保有这么一行配置:

LoadModule rewrite_module modules/mod_rewrite.so

然后在相应虚拟主机配置的地方,加入下列代码:

ServerName www.etiantian.org#防盗链配置参数RewriteEngine OnRewriteCond %{HTTP_REFERER} !^http://www.etiantian.org/.*$ [NC]RewriteCond %{HTTP_REFERER} !^http://www.etiantian.org$ [NC]RewriteCond %{HTTP_REFERER} !^http://blog.etiantian.org/.*$ [NC]RewriteCond %{HTTP_REFERER} !^http://blog.etiantian.org$ [NC]RewriteRule .*\.(gif|jpg|swf)$ http://www.etiantian.org/img/nolink.gif [R,NC]

说明:

1. www.etiantian.org、blog.etiantian.org表示网站的信任站点;

2. .(gif|jpg|swf)表示要保护的防止被盗连的文件的扩展名;

3. nolink.gif为上述扩展名的资源被盗链后的重定向页面/图片。用以输出警示信息,这张图片应该尽可能的小;

在其他的网站中,如果调用www.etiantian.org提供的图片URL,这样就会转向http://www.etiantian.org/img/nolink.gif,这个转向图片可能是版权信息的提示。

6、利用Cookie和rewrite实现Apache防盗链下载

常见的的网站防盗链都是通过Referer来判断用户来路的,不过这样的方法对于下载工具来说形同虚设,因为现在的下载工具早就能伪造Referer了。

Cookie配合Rewr1te模块很简单的可实现防盗链下载,首先在浏览页面的时候,会向客户端发送一个特别的Cookie,例如“Site=etiantian.org”,盗链而来的将没有这个Cookie。

在主配置文件httpd.conf或者在虚拟主机httpd-vhosts.conf中配置如下:

RewriteEngine OnRewriteCond %{HTTP_COOKIE} !^.*(?:Site=etiantian.org).*$#对于Cookie里面没有特殊记录的请求进行重定向到错误页面RewriteRule ^.*$/error.html          #将非法访问重定向到错误页面

说明:这样如果一个盗链而来的请求将会因为役有特殊Cookie而被重定向到错误页面.就算实际地址暴露也不怕。至于这个cookie的内容是什么以及有效时间完全可以由管理员自己来设定,也就是说下载工具也没法伪造,从而防止了服务器资源被盗链的危险。

7、Nginx web服务实现防盗链实战

在默认情况下,只需要进行简单的配置,即可实现防盗链处理。请看下面的实例:

location ~* \.(gif|jpg|png|swf|flv|bmp|wmv|asf|mp3|zip|rar)$ { valid_referers none blocked *.etiantian.org etiantian.org;  if ($invalid_referer) {   rewrite ^/ http://www.etiantian.org/img/nolink.gif;  #或者下面一行   #return 403;  }}

提示:要根据自己公司实际业务,是否有外链的合作,进行设置域名允许。

8、NginxHttpAccessKeyModule实现防盗链介绍

如果不怕麻烦,有条件实现的话,推荐使用NginxHttpAccessKeyModule这个东西。他的运行方式是:如我的download目录下有一个file.zip的文件.对应的URI是http://www.ccvita.com/download/file.zip使用ngx_http_accesskey_module模块后http//www.ccvita.com/download/file.zip?key=09093abeac094.只有给定的key值正确了,才能够下载download目录下的file.zip。而且key值是根据用户的IP有关的,这样就可以避免被盗链了。据说NginxHttpAccessKeyModule现在连迅雷都可以防了,可以尝试一下。

9、在产品设计上解决盗链方案

将计就计,为网站上传的图片增加水印。

图片添加版权水印。很多网站一般直接转载图片是为了快捷,但是对于有水印的图片,很多站长是不愿意进行转载的

rewrite规则:工作中301跳转,URI跳转运维做(产品,运营,市场),复杂的rewrite开发做的多。

九、Nginx负载均衡配置

1、负载均衡调度算法

Nginx的负载均衡模块目前支持4种调度算法,下面分别进行介绍,其中后两项属于第三方的调度方法。

1. 轮询(默认),每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某台服务器死机,故障系统被自动剔除,使用户访问不受影响。

2. Weight,指定轮询权值,Weight值越大,分配到的访问机率越高,主要用于后端每个服务器性能不均的情况下。

3. ip_hash,每个请求按访问IP的hash结果分配,这样来自同一个IP的访客固定访问一个后端服务器,有效解决了动态网页存在的session共享问题。

4. fair,比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身是不支持fair的,如果需要使用这种调度算法,必须下载Nginx的upstream_fair模1块。

5. url_hash,按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身是不支持url_hash的,如果需要使用这种调度算法,必须安装Nginx 的hash软件包。

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

常用的状态有:

1. down,表示当前的server暂时不参与负载均衡。

2. backup,预留的备份机器。当其他所有的非backup机器出现故障或者忙的时候,才会请求backup机器,因此这台机器的压力最轻。

3. max_fails,允许请求失败的次数,默认为1。当超过最大次数时,返回proxy_next_upstream 模块定义的错误。

4. fail_timeout,在经历了max_fails次失败后,暂停服务的时间。max_fails可以和fail_timeout一起使用。

注意 当负载调度算法为ip_hash时,后端服务器在负载均衡调度中的状态不能是weight和backup。

下面通过Nginx的反向代理功能配置一个Nginx负载均衡服务器。假定后端有三个服务节点,通过80端口提供Web服务,三个web服务器IP分别是192.168.12.181、192.168.12.182、192.168.12.183,要通过Nginx的调度实现三个节点的负载均衡,配置文件如下,这里仅列出配置文件中http和server部分。

http{ upstream myserver {  server  192.168.12.181:80  weight=3 max_fails=3 fail_timeout=20s;  server  192.168.12.182:80  weight=1 max_fails=3 fail_timeout=20s;  server  192.168.12.183:80  weight=4 max_fails=3 fail_timeout=20s; } server {  listen    80;  server_name www.domain.com 192.168.12.189;  index index.htm index.html;  root /data/web/wwwroot; location / { proxy_pass http://myserver; proxy_next_upstream http_500 http_502 http_503 error timeout invalid_header; include  /usr/local/nginx/conf/proxy.conf; } }}

2、Nginx +Tomcat整合的必要性

Tomcat在高并发环境下处理动态请求时性能很低,而在处理静态页面更加脆弱。虽然Tmcat的最新版本支持epoll,但是通过Nginx来处理静态页面要比通过Tomcat处理在性能方面好很多。
Nginx可以通过两种方式来实现与Tomcat的耦合。

将静态页面请求交给Nginx,动态请求交给后端Tomcat处理。

将所有请求都交给后端的Tomcat服务器处理,同时利用Nginx自身的负载均衡功能,进行多台Tomcat服务器的负载均衡。

omcat在高并发环境下处理动态请求时性能很低,而在处理静态页面更加脆弱。虽然Tmcat的最新版本支持epoll,但是通过Nginx来处理静态页面要比通过Tomcat处理在性能方面好很多。
Nginx可以通过两种方式来实现与Tomcat的耦合。

将静态页面请求交给Nginx,动态请求交给后端Tomcat处理。

将所有请求都交给后端的Tomcat服务器处理,同时利用Nginx自身的负载均衡功能,进行多台Tomcat服务器的负载均衡。

1)Nginx +Tomcat动静分离配置实例

server { listen 80; server_name www.ixdba.net; root /web/www/html;location /img/ { alias /web/www/html/img/;}location ~ (\.jsp)|(\.do)$ {  proxy_pass http://192.168.12.130:8080;  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;    client_max_body_size 10m;    client_body_buffer_size 128k;    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;}}

alias和root的区别:

location /i {       #www.a.com/i/123.gif alias /var/www/html/images/;  #/var/www/html/images/123.gif}

在这个location段配置中,如果url请求“/i/logo.gif”,那么Nginx将会在服务器上查找“/var/www/html/images/logo.gif文件,也就是说请求的url中location后面的部分会被追加到alias指定的目录后面,而location后面的”/i“路径将被自动丢弃

对比:

location /i {      # www.a.com/i/123.gif root /var/www/html/images/;  #/var/www/html/images/i/123.gif}

在这个location段配置中,如果url请求”/i/logo.gif”,那么Nginx将会在服务器上查找“/var/www/html/images/i/logo.gif文件。

从这两个例子可以看出,alias指令和root指令的区别:alias指定的目录是当前目录,而root指定的是根目录,一般情况下,建议在“location /”中通过root指令来配置根目录,而在其它目录匹配的位置使用alias指令。

2)Nginx +Tomcat多tomcat负载均衡配置实例

这里假定有三台Tomcat服务器,分别开放不同的端口,地址分别是:
主机192.168.12.131开放8000端口、主机192.168.12.132开放8080端口、主机192.168.12.133开放8090端口。

Nginx配置文件中相关配置代码如下,这里仅列出了部分配置代码:

upstream mytomcats { server 192.168.12.131:8000; server 192.168.12.132:8080; server 192.168.12.133:8090;  ip_hash;}server { listen 80; server_name www.ixdba.net;location ~* \.(jpg|gif|png|swf|flv|wma|wmv|asf|mp3|mmf|zip|rar)$ { root /web/www/html/;}location / {   proxy_pass http://mytomcats;   include  /usr/local/nginx/conf/proxy.conf;}}

十、企业级Web Nginx服务优化

1、隐藏nginx header内版本号信息

一些特定的系统及服务漏洞一般都和特定的软件及版本号有关,我们应尽量隐藏服务器的敏感信息(软件名称及版本等信息),这样黑客无法猜到有漏洞的服务是否是对应服务的版本,从而确保web服务器最大的安全。

利用curl查看隐藏前header内的web版本号信息:

[root@nginx ~]# curl -I 192.168.80.104HTTP/1.1 200 OKServer: nginx/1.6.3Date: Tue, 24 May 2016 11:46:12 GMTContent-Type: text/htmlContent-Length: 5Last-Modified: Fri, 29 Apr 2016 13:10:16 GMTConnection: keep-aliveETag: "57235d38-5"Accept-Ranges: bytes

浏览器访问web服务报错信息:

以上两个访问不但暴漏了nginx软件名称,而且暴漏了nginx特定的版本号,这样就会给服务的安全带来一定的风险,应禁止掉。

修改配置文件参数实现隐藏版本号

在nginx配置文件nginx.conf的http标签中加入”server_tokens off;”

官方文档:Module ngx_http_core_module

Syntax:server_tokens on | off | string;Default:server_tokens on;Context:http, server, locationEnables or disables emitting nginx version in error messages and in the “Server” response header field.

2、更改默认用户及用户组

nginx服务启动,使用的默认用户是nobody:

[root@nginx www]# grep "#user" /application/nginx/conf/nginx.conf.default #user  nobody;

为了防止黑客猜到这个用户,我们需要更改下特殊的用户名,提供nginx服务用。

更改默认用户的方法有两种:

第一种为:

user nginx nginx #配置文件中修改 

设置Nginx worker进程运行的用户以及用户组,如果注释或不设置,默认即是nobody用户和组,不推荐使用nobody用户名称,最好采用一个普通用户,如nginx。注意Nginx的主进程还是以root身份运行的,后文也会有不用root进程起服务的配置。

建立nginx用户的操作过程如下:

useradd -s /sbin/nologin -M nginx

第二种为:

useradd -s /sbin/nologin -M nginx #先添加一个用户./configure --prefix=/application/nginx-1.6.3 --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module #编译nginx时指定用户ps -ef|grep nginx|grep -v grep #检查nginx进程的对应用户

3、配置nginx worker进程个数

在高并发场景,我们需要事先启动更多的nginx进程以保证快速响应并处理用户的请求。具体的配置参数如下:

worker_processes 1;

指定了nginx要开启的进程数。建议指定和CPU的数量相等或乘2的进程数。

worker_processes参数开始的设置可以等于CPU的个数或核数(worker_cpu_affinity参数中的配置可以指定第一个到最后一个进程分别使用的哪个cpu),进程数多一些,起始提供服务时就不会临时启动新进程提供服务,减少了系统开销,提升了服务速度。特殊场合也可以考虑提高至CPU*2的进程数,具体情况要根据实际的业务来选择,因为这个参数,除了CPU核数的影响外,和硬盘存储的数据以及负载也有关。

查看linux服务器的核数的方法:

例如:CPU个(核)数为4,就配置worker_processes 4;

[root@nginx conf]# grep "physical id" /proc/cpuinfophysical id : 0

这里我们修改参数值为4,然后重新加载nginx服务,操作过程及结果:

[root@nginx conf]# grep worker_processes nginx.confworker_processes 4;[root@nginx conf]# /application/nginx/sbin/nginx -tnginx: the configuration file /application/nginx-1.6.3/conf/nginx.conf syntax is oknginx: configuration file /application/nginx-1.6.3/conf/nginx.conf test is successful[root@nginx conf]# /application/nginx/sbin/nginx -s reload[root@nginx conf]# ps -ef|grep nginx|grep -v greproot 2144 1 0 19:53 " />Core functionality

Syntax: worker_cpu_affinity cpumask ...;

worker_cpu_affinity auto [cpumask];

Default: —

Context: main

Binds worker processes to the sets of CPUs. Each CPU set is represented by a bitmask of allowed CPUs. There should be a separate set defined for each of the worker processes. By default, worker processes are not bound to any specific CPUs.

For example,

worker_processes 4;

worker_cpu_affinity 0001 0010 0100 1000;

binds each worker process to a separate CPU, while

worker_processes 2;

worker_cpu_affinity 0101 1010;

binds the first worker process to CPU0/CPU2, and the second worker process to CPU1/CPU3. The second example is suitable for hyper-threading.

测试:

webbench -c 20000 -t 180 http://192.168.80.104

测试过程这里不再叙述,也可以用ab。

通过观察,我们发现配置后不同CPU使用率相对平均,和测试前变化不大。因此就认为比较平均,一方面是软件自身再逐渐的优化使用多核CPU,另一方面测试的数有待调整。

另外(taskset - retrieve or set a process's CPU affinity)命令本身也有分配CPU的功能:

taskset -c 1,2,3 /etc/init.d/mysql start

5、事件处理模型优化

nginx的连接处理机制在于不同的操作系统采用不同的10模型,在linux使用epoll的IO多路复用模型,在freebsd使用kqueue的IO多路复用模型,在solaris使用/dev/Poll方式的IO多路复用模型,在windows使用的是icop等等。

根据系统类型不同选择不同 use [kqueue|rtsig|epoll|/dev/poll|select|poll];该参数结合系统使用,不同系统使用参数不同,我们使用的是Centos6.5,因此我们调整为epoll模型。

events {worker_connections 1024;use epoll;}

官方文档:Connection processing methods

Syntax: use method;

Default: —

Context: events

Specifies the connection processing method to use. There is normally no need to specify it explicitly, because nginx will by default use the most efficient method.

The following connection processing methods are supported:

select— standard method. The supporting module is built automatically on platforms that lack more efficient methods. The --with-select_module and --without-select_module configuration parameters can be used to forcibly enable or disable the build of this module.

poll— standard method. The supporting module is built automatically on platforms that lack more efficient methods. The --with-poll_module and --without-poll_module configuration parameters can be used to forcibly enable or disable the build of this module.

kqueue— efficient method used on FreeBSD 4.1+, OpenBSD 2.9+, NetBSD 2.0, and Mac OS X.

epoll— efficient method used on Linux 2.6+.

Some older distributions like SuSE 8.2 provide patches that add epoll support to 2.4 kernels.

/dev/poll— efficient method used on Solaris 7 11/99+, HP/UX 11.22+ (eventport), IRIX 6.5.15+, and Tru64 UNIX 5.1A+.

eventport— event ports, efficient method used on Solaris 10.

6、调整单个进程允许的客户端最大连接数

这个值根据具体服务器性能和程序的内存使用量来指定(一个进程启动使用的内存根据程序确定)

events {worker_connections 1024;use epoll;}

worker_connections也是个事件模块指令,用于定义Nginx每个进程的最大连接数,默认是1024.最大客户端连接数由worker_processes和worker_connections决定,即Max_client=worker_connections*worker_processes。进程的最大连接数受Linux系统进程的最大打开文件数限制,在执行操作系统命令“ulimit -HSn 65535”或配置相应文件后worker_connections的设置才能生效。

官方文档:Core functionality

Syntax: worker_connections number;

Default:

worker_connections 512;

Context: events

Sets the maximum number of simultaneous connections that can be opened by a worker process.

It should be kept in mind that this number includes all connections (e.g. connections with proxied servers, among others), not only connections with clients. Another consideration is that the actual number of simultaneous connections cannot exceed the current limit on the maximum number of open files, which can be changed by worker_rlimit_nofile.

7、配置每个进程最大文件打开数

worker_rlimit_nofile 32768;

每个进程打开的最大文件数,可设里为系统优化后的ulimit -HSn的结果,在第一章系统安装时,调整文件描述符和这个处理的一个问题。

官方文档:Core functionality

Syntax: worker_rlimit_nofile number;

Default: —

Context: main

Changes the limit on the maximum number of open files (RLIMIT_NOFILE) for worker processes. Used to increase the limit without restarting the main process.

8、优化服务器名字的hash表大小

确切名字和通配符名字存储在哈希表中。哈希表和监听端口关联,每个端口都最多关联到三张表:确切名字的哈希表,以星号起始的通配符名字的哈希表和以星号结束的通酝符名字的哈希表。哈希表的尺寸在配置阶段进行了优化,可以以最小的CPU缓存命中失败来找到名字。nginx首先搜素确切名字的哈希表,如果没有找到,搜索以星号起始的通配符名字的哈希表,如果还是没有找到,继续搜索以星号结束的通配符名字的哈希表。因为名字是按照域名的节来搜索的,所以搜索通配符名字的哈希表比搜索确切名字的哈希表慢。注意.nginx.org存储在通配符名字的哈希表中,而不在确切名字的哈希表中。正则表达式是一个一个串行的测试,所以是最慢的,而且不可扩展。

鉴于以上原因,请尽可能使用确切的名字。举个例子,如果使用nginx.org和www.nginx.org来访问服务器是最频繁的,那么将它们明确的定义出来就更为有效:

​server {listen 80;server_name www.etiantian.org etiantian.org *.etiantian.org ;....}​

下面这种方法相比更简单,但是效率也更低:

server {listen 80;server_name .etiantian.org ;....}

如果定义了大量名字,或者定义了非常长的名字,那就需要在http配置块中调整server_names_hash_max_size and server_names_hash_bucket_size的值。server_names_hash_bucket_size的默认值可能是32、64或其他值,取决于缓存行的长度。如果这个值是32,那么定义“too.long.server.name.example.org”作为虚拟主机名就会失败,显示下面错误信息:

could not build the server_names_hash,you should increase either server_names_hash_max_size: 512or server_names_hash_bucket_size: 3

可以在http标签中添加如下一行:

server_names_hash_bucket_size 64;

官方文档:Module ngx_http_core_module

Syntax: server_names_hash_bucket_size size;

Default:

server_names_hash_bucket_size 32|64|128;

Context: http

Sets the bucket size for the server names hash tables. The default value depends on the size of the processor’s cache line. The details of setting up hash tables are provided in a separate document.

Syntax: server_names_hash_max_size size;

Default:

server_names_hash_max_size 512;

Context: http

Sets the maximum size of the server names hash tables. The details of setting up hash tables are provided in a separate document.

9、开启高效文件传输模式

sendfile on;tcp_nopush on;

sendfile参数用于开启文件高效传输模式。同时将tcp_nopush和tcp_nodelay两个指令设为on用于防止网络阻塞

官方文档:Module ngx_http_core_module

Syntax: sendfile on | off;

Default:

sendfile off;

Context: http, server, location, if in location

Enables or disables the use of sendfile().

Starting from nginx 0.8.12 and FreeBSD 5.2.1, aio can be used to pre-load data for sendfile():

10、设置连接超时时间

keepalive_timeout 65;

#设置客户端连接保持会话的超时时间。超过这个时间,服务器会关闭该连接

tcp_nodelay on;

打开tcp_nodelay,在包含了keepalive参数才有效

client_header_timeout 15;

#设置客户端请求头读取超时时间.如超过这个时间,客户端还没有发送任何数据,Nginx将返回“Request timeout(408)"错误,默认值是60。

client_body_timeout 15;

#设置客户端请求主体读取超时时间。如超过这个时间,客户端还没有发送任何数据,Nginx将返回“Request timeout(408)错误,默认值是60。

send_timeout 15;

#指定响应客户端的超时时间。这个超时仅限于两个连接活动之间的时间,如果超过这个时间,客户端没有任何活动,Nginx将会关闭连接。

官网文档:Module ngx_http_core_module

Syntax: client_header_timeout time;

Default:

client_header_timeout 60s;

Context: http, server

11、上传文件大小限制(动态应用)

主配置文件里加入如下参数,具体大小根据你自己的业务做调整.

client_max_body_size 10m;

官方文档:Module ngx_http_core_module

Syntax: client_max_body_size size;

Default: client_max_body_size 1m;

Context: http, server, location

Sets the maximum allowed size of the client request body, specified in the “Content-Length” request header field. If the size in a request exceeds the configured value, the 413 (Request Entity Too Large) error is returned to the client. Please be aware that browsers cannot correctly display this error. Setting size to 0 disables checking of client request body size.

12、fastcgi调优(配合PHP引擎动态服务)

官网文档:Module ngx_http_fastcgi_module

指定连接到后端fastCGI的超时时间:

fastcgi_connect_timeout 300;

Syntax: fastcgi_connect_timeout time;

Default: fastcgi_connect_timeout 60s;

Context: http, server, location

向FastCGI传送请求的超时时间,这个值是指已经完成两次握手后向FastCGI传送请求的超时时间:

fastcgi_send_timeout 300;

Syntax: fastcgi_send_timeout time;

Default: fastcgi_send_timeout 60s;

Context: http, server, location

指定接收FastcGI应答的超时时间,这个值是指己经完成两次握手后接收FastCGI应答的超时时间:

fastcgi_read_timeout 300;

Syntax: fastcgi_read_timeout time;

Default: fastcgi_read_timeout 60s;

Context: http, server, location

#指定读取FastCGI应答第一部分需要用多大的缓冲区,这个值表示将使用1个64KB的缓冲区读取应答的第一部分(应答头),可以置为fastcgi_buffers选项指定的缓冲区大小:

fastcgi_buffer_size 64k;

Syntax: fastcgi_buffer_size size;

Default: fastcgi_buffer_size 4k|8k;

Context: http, server, location

指定本地需要用多少和多大的缓冲区来缓冲FastCGI的应答请求:

fastcgi_buffers 4 64k;

如果一个PHP脚本所产生的页面大小为256KB,为其分配4个64KB的缓冲区来缓存;如果页面大小大于256KB,那么大于256KB的部分会缓存到fastcgi_temp指定的路径中,但是这并不是好方法,因为内存中的数据处理速度要快于硬盘。一般这个值应该为站点中PHP脚本所产生的页面大小的中间值,如果站点大部分脚本所产生的页面大小为256KB,那么可以把这个值设置为"16 16k"、"16 16k"。

Syntax: fastcgi_buffers number size;

Default: fastcgi_buffers 8 4k|8k;

Context: http, server, location

建议为fastcgi_buffers的两倍:

fastcgi_busy_buffers_size 128k;

在写入fastcgi_temp_path时将用多大的数据块,默认值是fastcgi_buffers的两倍,设置上述数值设置太小时若负载上来时可能报502 Bad Gateway:

fastcgi_temp_file_write_size 128k;

表示开启FastCGI缓存并为其指定一个名称:

fastcgi_cache oldboy_nginx;

开启缓存非常有用,可以有效降低CPU的负载,并且防止502错误的发生,但是开启缓存也可能会引起其它问题,要根据具体情况选择。

#用来指定应答代码的缓存时间,实例中的值表示将200和302应答缓存一个小时:

fastcgi_cache_valid 200 302 1h;

缓存在fastcgi_cache_path指令inactive参数值时间内的最少使用次数:

fastcgi_cache_valid 301 1d;fastcgi_cache_valid any 1m;fastcgi_cache_min_uses 1;

以上参数集合:

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 oldboy_nginx;fastcgi_cache_valid 200 302 1h;fastcgi_cache_valid 301 1d;fastcgi_cache_valid any 1m;fastcgi_cache_min_uses 1;

13、proxy.conf配置参数

        location ~ .*\.(php|php5)?$ {             root   /var/html/bbs;            fastcgi_pass  127.0.0.1:9000;            fastcgi_index index.php;            include fastcgi.conf;            include proxy.conf;        }

proxy.conf配置文件参数

vi /application/nginx/conf/proxy.conf        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;        client_max_body_size 50m;        client_body_buffer_size 256k;        proxy_connect_timeout 30;        proxy_send_timeout 30;        proxy_read_timeout 60;        proxy_buffer_size 4k;        proxy_buffers 4 32k;        proxy_busy_buffers_size 64k;        proxy_temp_file_write_size 64k;        proxy_next_upstream error timeout invalid_header http_500 http_503 http_404;        proxy_max_temp_file_size 128m;        proxy_store on;        proxy_store_access user:rw group:rw all:r;        #nginx cache        #client_body_temp_path /data/nginx_cache/client_body 1 2;        #proxy_temp_path /usr/local/nginx/proxy_temp 1 2;

14、改源码隐藏软件名称及版本号

修改header信息:

[root@nginx ~]# cd /server/tools/nginx-1.6.3[root@nginx nginx-1.6.3]# vi src/http/ngx_http_header_filter_module.c +48 static char ngx_http_server_string[] = "Server: nginx" CRLF;static char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;

修改上面两行的黑体为下面红色字体,然后编译安装:

static char ngx_http_server_string[] = "BWS" CRLF;static char ngx_http_server_full_string[] = "BWS" CRLF;

然后修改nginx.conf配置文件

server_tokens off;

修改403错误页信息:

cd /server/tools/nginx-1.6.3[root@nginx nginx-1.6.3]# vi src/http/ngx_http_special_response.c +29"
nginx" CRLF

修改为下面一行:

"
BWS(www.etiantian.org)" CRLF

然后重新编译安装。

15、配置nginx gzip压缩功能

nginx gzip压缩模块提供了对文件内容压缩的功能,允许nginx服务器将输出内容在发送到客户端之前根据具体的策略进行压缩,以节约网站带宽,同时提升用户访问体验。此功能同apache的mod_deflate压缩功能,依赖ngx_http_gzip_module模块,默认己安装,我们已经详细讲解过了压缩的功能。

要压缩的内容(js,css,html),不要压缩的内容(图片,视频,FLASH)

官方文档:Module ngx_http_gzip_module

gzip on;#开启压缩功能

Syntax: gzip on | off;

Default: gzip off;

Context: http, server, location, if in location

gzip_min_length 1k;

设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取。默认值是0,不管页面多大都进行压缩。建议设旦成大于1K。如果小于lK可能会越压越大。

Syntax: gzip_min_length length;

Default: gzip_min_length 20;

Context: http, server, location

gzip_buffers 4 16k;

压缩缓冲区大小。表示申请4个单位为16K的内存作为压缩结果流缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果

Syntax: gzip_buffers number size;

Default: gzip_buffers 32 4k|16 8k;

Context: http, server, location

gzip_http_version 1.0;

压缩版本(默认1. l,前端为squid2.5时使用1.0)用于设置识别HTTP协议版本,默认是1.1,目前大部分浏览器己经支持GZIP解压,使用默认即可。

Syntax: gzip_http_version 1.0 | 1.1;

Default: gzip_http_version 1.1;

Context: http, server, location

gzip_comp_level 2;

压缩比率;用来指定GZIP压缩比,1压缩比最小,处理速度最快;9压缩比最大,传输速度快,但处理最慢,也比较消耗CPU资源。

Syntax: gzip_comp_level level;

Default: gzip_comp_level 1;

Context: http, server, location

gzip_types text/plain application/javascript test/css text/xml;

用来指定压缩的类型,"text/html”类型.总是会被压缩。

提示:gzip_types类型不同的版本可能不同,可以查看:cat /application//nginx/conf/mime.types

Syntax: gzip_types mime-type ...;

Default: gzip_types text/html;

Context: http, server, location

gzip_vary on;

#vary header支持。该选项可以让前端的缓存服务器缓存经过GZIP压缩的页面,例如用Squid缓存经过Nginx压缩的数据。(让前端的缓存不解压缩发送给客户端)

Syntax: gzip_vary on | off;

Default: gzip_vary off;

Context: http, server, location

完整的配置如下:

gzip on;gzip_min_length 1k;gzip_buffers 4 32k;gzip_http_version 1.1;gzip_comp_level 9;gzip_types text/plain application/javascript test/css text/xml;gzip_vary on;

需要压缩的对象:

大于1K的纯文本文件html、js、css、xml、html;

图片视频等不要压缩。因为不但不会减小,在压缩时消耗CPU,MEM资源;

可以通过火狐的yslow插件查看是否压缩;

16、配置nginx expires功能

在网站开发和运营中,对于图片,css,js等元素更改机会较少,特别是图片,这时可以将图片设置在浏览器本地缓存365天或更长,CSS,JS,html等代码缓存10天,这样用户第一次打开页面后,会在本地的浏览器缓存相应的上述内容,这样的缓存可以提高下次用户打开类该页面的加载速度,并节省服务器端大量的带宽。此功能同apache的expires,我们己经详细讲解过了。这里通过location的功能,将需要缓存的扩展名列出来,然后指定缓存时间。

expires功能优点

Expires可以降低网站购买的带宽,节约成本,同时提升了用户访问体验,减轻服务器的压力,是web服务非常重要的功能

expire功能缺点

被缓存的页面或数据更新了,用户看到的可能还是旧的内容,反而影响用户体验

解决办法:

第一个 缩短缓存时间,例如:1天,不彻底,除非更新频率大于1天

第二个 对缓存的对象改名。图片,附件一般不会被用户修改,如果用户修改了,实际上也都是更改文件名里新传了而己。网站升级对于js,css元素,一般可以改名。把js,css推送到CDN

一般不希望被缓存的内容

1)广告图片

2)网站流量统计文件

3)更新频繁的文件

语法:

 expires [time|epoch|max|off]

默认值: expires off

作用域: http, server, location

例如:控制图片等过期时间为30天,当然这个时间可以设置的更长。具体视情况而定:

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

比如控制匹配/resource/或者/mediatorModule/里所有的文件缓存设置到最长时间:

location ~ /(resource|mediatorModule)/ {root /opt/demo;expires max;}

17、Nginx防蜘蛛爬虫处理

假定一个场景:某个网站它可能不希望被网络爬虫抓取,例如测试环境不希望被抓取,以免对用户造成误导,那么需要在该网站中申明,本站不希望被抓取。

有如下方法:

方法一:修改nginx.conf,禁止网络爬虫的ua,返回403。

server {listen 80;server_name 127.0.0.1;#添加如下内容即可防止爬虫if ($http_user_agent ~* "qihoobot|Baiduspider|Googlebot|Googlebot-Mobile|Googlebot-Image|Mediapartners-Google|Adsbot-Google|Feedfetcher-Google|Yahoo! Slurp|Yahoo! Slurp China|YoudaoBot|Sosospider|Sogou spider|Sogou web spider|MSNBot|ia_archiver|Tomato Bot"){return 403;}

方法2:网站更目录下增加Robots.txt,放在站点根目录下。

在http://tool.chinaz.com/robots/站点可以针对现在的搜索引擎按照想要的规则生成robots.txt文件。

robots.txt是搜索引擎中访问网站的时候要查看的第一个文件。robots.txt文件告诉蜘蛛程序在服务器上什么文件是可以被查看的。

当一个搜索蜘蛛访问一个站点时,它会首先检查该站点根目录下是否存在robots.txt,如果存在,搜索机器人就会按照该文件中的内容来确定访问的范围;如果该文件不存在,所有的搜索蜘蛛将能够访问网站上所有没有被口令保护的页面。百度官方建议,仅当您的网站包含不希望被搜索引擎收录的内容时,才需要使用robots.txt文件。如果您希望搜索引擎收录网站上所有内容,请勿建立robots.txt文件。

Robots协议是国际互联网界通行的道德规范,基于以下原则建立:

1. 搜索技术应服务于人类,同时尊重信息提供者的意愿,并维护其隐私权;

2. 网站有义务保护其使用者的个人信息和隐私不被侵犯;

当然,如果搜索引擎不遵守约定的Robots协议,那么通过在网站下增加robots.txt也是不起作用的。(在正式环境中,可以适当允许搜索引擎抓取收录)

18、nginx日志相关优化与安全

编写脚本实现Nginx access日志轮询。

自动切割访问日志脚本:

#!/bin/bashDataformat=`date +%F -d -1day`Basedir="/application/nginx"Nginxlogdir="$Basedir/logs"Logname="access"[ -d $Nginxlogdir ] && cd $Nginxlogdir||exit 1[ -f ${Logname}.log ] || exit 1/bin/mv ${Logname}.log ${Dataformat}_${Logname}.log$Basedir/sbin/nginx -s reload

添加定时任务实现每天定时切割

00 00 * * * /bin/sh /server/scripts/cut_log.sh &>/dev/null

日志相关工具:syslog、rsyslog、Awstats、flume、logstash、scribe、kafka、storm

不记录不需要的访问日志

对于健康检查或某些(图片、css、js)的日志,一般不需要记录,因为在统计PV时是按照页面计算。而且日志写入频繁会消耗磁盘IO,降低服务性能。

location ~ .*\.(js|JPG|JPEG|jpg|jpeg|png|gif|GIF|bmp|css)$ {access_log off;}

访问日志的权限设置

假如日志目录为/app/logs,则授权方法为:

chown -R root.root /app/logschmod -R 700 /app/logs

不需要在日志目录上给nginx用户读或者写许可,这个问题很多网友都没在意,直接给nginx或apache用户。因为apache或nginx的主进程都是以root用户运行的。

19、最小化nginx目录及文件权限设置

为了保证apache的网站不遭受木马入侵上传及修改文件

安全的权限:

  • 所有站点目录的用户和组都应该为root
  • 所有目录权限是默认的755
  • 所有文件权限是默认的644

以上的权限设置可以做到防止黑客上传木马,以及修改站点文件,但是,合理的用户上传的内容也被拒之门外了,那么如何解决可以让合法的用户传文件又不至于被黑客利用攻击呢?

这就是对业务进行分离,在比较好的网站业务架构中,应把资源文件,包括用户上传的图片,附件等的服务和程序服务分离,最好把上传程序服务也分离,这样就可以从容按照前面安全的标准授权了。

20、nqinx站点目录及文件URL访问控制

根据扩展名限制程序和文件访问:

location ~  ^/images/.*\.(php|php5)${     deny all;}location ~  ^/(static|js)/ {     deny all;}location ~  ^/(static|js)/ {     return 403;}location ~  ^/(static|js)/ {     allow 202.110.21.144;     allow 192.168.80.0/24;     deny all;}#http://nginx.org/en/docs/http/ngx_http_access_module.html

21、nginx错误页面及优雅显示

server   {   listen       80;   server_name  www.XXX.com ;   index index.html index.htm index.php;   root  /opt/www/;   location ~ .*.(php|php5)?$   {     #fastcgi_pass  unix:/tmp/php-cgi.sock;     fastcgi_pass  127.0.0.1:9000;     fastcgi_index index.php;     include fcgi.conf;   }   error_page  404 = /404.html;

创建自己的404.html页面,放在站点目录下面。

error_page   500 502 503 504 = /50x.html;   location = /50x.html {            root   /var/html;        }

门户网站nginx优雅显示配置案例:

error_page 400 http://err.tmall.com/error1.htmlerror_page 403 http://err.tmall.com/error2.html...

22、使用tmpfs文件系统替代频繁访问的目录

mkdir /opt/tmpmv /tmp/* /opt/tmpmount -t tmpfs -o size=16m tmpfs /tmp     #工作中一般给2-4Gecho "mount -t tmpfs -o size=16m tmpfs /tmp" >>/etc/rc.localvi /etc/fstab              #或者在fstab文件中挂载tmpfs         /tmp       tmpfs      size=2048m    0 0

23、使用普通用户启动nginx

本优化属架构优化(同样适合其他软件),通过nginx的-c参数指定不同的配置文件,以起多个实例使用。如果使用普通用户,则不能使用80端口:

/application/nginx/sbin/nginx -h #查看帮助