1.Tengine简介
2.Tengine特性
3.Tengine工作原理和用途
- 3.1 Tengine处理HTTP请求流程
- 3.2 Tengine的模块化
- 3.3 Tengine进程模型
- 3.4 Tengine事件模型
- 3.5 Tengine信号
- 3.6 Tengine定时器
4.Tengine安装部署
- 4.1 安装依赖
- 4.2 编译
5.Tengine 文件目录结构
6.Tengine 配置文件详解
- 6.1 tengine主配置文件
- 6.2 状态检测配置文件
- 6.3 tengine常用配置
7.Tengine 安全防护
- 7.1 禁止web服务IP直接访问
- 7.2 连接和请求数限制
- 7.3 访问全站IP黑名单限制
- 7.4 下载防盗链配置
8.Tengine 重要模块
- 8.1 upstream负载均衡模块
- 8.2 rewrite重写模块
9.location 在匹配中的优先级
10.Tengine root 和 alias 的区别
11.Tengine TCP转发配置
12.Tengine 常用维护脚本或命令
13.参考文档
1.Tengine简介
Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。从2011年12月开始,Tengine成为一个开源项目,Tengine团队在积极地开发和维护着它。Tengine团队的核心成员来自于淘宝、搜狗等互联网企业。Tengine是社区合作的成果,我们欢迎大家参与其中,贡献自己的力量。
2.Tengine特性
继承Nginx-1.8.1的所有特性,兼容Nginx的配置
动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个Tengine
支持HTTP/2协议,HTTP/2模块替代SPDY模块
流式上传到HTTP后端服务器或FastCGI服务器,大量减少机器的I/O压力
更加强大的负载均衡能力,包括一致性hash模块、会话保持模块,还可以对后端的服务器进行主动健康检查,根据服务器状态自动上线下线,以及动态解析upstream中出现的域名
输入过滤器机制支持,通过使用这种机制Web应用防火墙的编写更为方便
支持设置proxy、memcached、fastcgi、scgi、uwsgi在后端失败时的重试次数
动态脚本语言Lua支持。扩展功能非常高效简单
支持按指定关键字(域名,url等)收集Tengine运行状态
组合多个CSS、JavaScript文件的访问请求变成一个请求
自动去除空白字符和注释从而减小页面的体积
自动根据CPU数目设置进程个数和绑定CPU亲缘性
监控系统的负载和资源占用从而对系统进行保护
显示对运维人员更友好的出错信息,便于定位出错机器
更强大的防攻击(访问速度限制)模块
更方便的命令行参数,如列出编译的模块列表、支持的指令等
可以根据访问文件类型设置过期时间
3.Tengine工作原理和用途
3.1 Tengine处理HTTP请求流程
http请求是典型的请求-响应类型的的网络协议。http是文件协议,所以我们在分析请求行与请求头,以及输出响应行与响应头,往往是一行一行的进行处理。通常在一个连接建立好后,读取一行数据,分析出请求行中包含的method、uri、http_version信息。然后再一行一行处理请求头,并根据请求method与请求头的信息来决定是否有请求体以及请求体的长度,然后再去读取请求体。得到请求后,我们处理请求产生需要输出的数据,然后再生成响应行,响应头以及响应体。在将响应发送给客户端之后,一个完整的请求就处理完了。处理流程图:


3.2 Tengine的模块化
Nginx由内核和模块组成。Nginx的模块从结构上分为核心模块、基础模块和第三方模块:
核心模块:HTTP模块、EVENT模块和MAIL模块
基础模块:HTTP Access模块、HTTP FastCGI模块、HTTP Proxy模块和HTTP Rewrite模块,
第三方模块:HTTP Upstream Request Hash模块、Notice模块和HTTP Access Key模块。
用户根据自己的需要开发的模块都属于第三方模块。正是有了这么多模块的支撑,Nginx的功能才会如此强大,Nginx的模块从功能上分为如下三类。
Handlers(处理器模块):此类模块直接处理请求,并进行输出内容和修改headers信息等操作。Handlers处理器模块一般只能有一个。
Filters (过滤器模块):此类模块主要对其他处理器模块输出的内容进行修改操作,最后由Nginx输出。
Proxies (代理类模块):此类模块是Nginx的HTTP Upstream之类的模块,这些模块主要与后端一些服务比如FastCGI等进行交互,实现服务代理和负载均衡等功能。
Nginx模块常规的HTTP请求和响应的过程:

Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块)。handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。
3.3 Tengine进程模型
Nginx默认采用多进程工作方式,Nginx启动后,会运行一个master进程和多个worker进程。其中master充当整个进程组与用户的交互接口,同时对进程进行监护,管理worker进程来实现重启服务、平滑升级、更换日志文件、配置文件实时生效等功能。worker用来处理基本的网络事件,worker之间是平等的,他们共同竞争来处理来自客户端的请求。
nginx的进程模型如图所示:

请求到来时,如何分配均分worker进程来处理他们?
在创建master进程时,先建立需要监听的socket(listenfd),然后从master进程中fork()出多个worker进程,如此一来每个worker进程多可以监听用户请求的socket。一般来说,当一个连接进来后,所有在Worker都会收到通知,但是只有一个进程可以接受这个连接请求,其它的都失败,这是所谓的惊群现象。nginx提供了一个accept_mutex(互斥锁),有了这把锁之后,同一时刻,就只会有一个进程在accpet连接,这样就不会有惊群问题了。
先打开accept_mutex选项,只有获得了accept_mutex的进程才会去添加accept事件。nginx使用一个叫ngx_accept_disabled的变量来控制是否去竞争accept_mutex锁。ngx_accept_disabled = nginx单进程的所有连接总数 / 8 -空闲连接数量,当ngx_accept_disabled大于0时,不会去尝试获取accept_mutex锁,ngx_accept_disable越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去accept,每个worker进程的连接数就控制下来了,其它进程的连接池就会得到利用,这样,nginx就控制了多进程间连接的平衡。
每个worker进程都有一个独立的连接池,连接池的大小是worker_connections。这里的连接池里面保存的其实不是真实的连接,它只是一个worker_connections大小的一个ngx_connection_t结构的数组。并且,nginx会通过一个链表free_connections来保存所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。一个nginx能建立的最大连接数,应该是worker_connections 乘以 worker_processes。当然,这里说的是最大连接数,对于HTTP请求本地资源来说,能够支持的最大并发数量是worker_connections 乘以 worker_processes,而如果是HTTP作为反向代理来说,最大并发数量应该是worker_connections 乘以 worker_processes/2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。
3.4 Tengine事件模型
对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号的处理。Nginx每个worker里面只有一个主线程,多少个worker就能处理多少个并发,何来高并发呢?请求流程:首先,请求到来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,不可操作。apache的常用工作方式:每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求了。这对操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的cpu开销很大,自然性能就上不去了,而这些开销完全是没有意义的。
Nginx采用异步非阻塞的方式来支持用户请求。Nginx支持select/poll/epoll/kqueue等事件模型。拿epoll为例,当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,当事件都没有完全准备好时,就在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换),更多的并发数,只是会占用更多的内存而已。 现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。
推荐设置worker的个数为cpu的核数,因为更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。
3.5 Tengine信号
对nginx来说,有一些特定的信号,代表着特定的意义。信号会中断掉程序当前的运行,在改变状态后,继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重新进行一次。如果nginx正在等待事件(epoll_wait时),如果程序收到信号,在信号处理函数处理完后,epoll_wait会返回错误,然后程序可再次进入epoll_wait调用。
3.6 Tengine定时器
由于epoll_wait等函数在调用的时候是可以设置一个超时时间的,所以nginx借助这个超时时间来实现定时器。nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入epoll_wait前,先从该红黑树里面拿到所有定时器事件的最小时间,在计算出epoll_wait的超时时间后进入epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件。由此可以看出,当我们写nginx代码时,在处理网络事件的回调函数时,通常做的第一个事情就是判断超时,然后再去处理网络事件
4.Tengine安装部署
4.1 安装依赖
1
| yum -y install zlib zlib-devel openssl openssl-devel pcre pcre-devel gcc gcc-c++ autoconf automake jemalloc jemalloc-devel
|
4.2 编译
1 2 3 4 5 6 7 8 9 10 11 12
| tar -zxvf tengine-2.2.0.tar.gz cd tengine-2.2.0 ./configure --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-http_concat_module --with-jemalloc --with-http_v2_module --with-http_secure_link_module make && make install
./configure --with-http_stub_status_module --with-http_ssl_module --with-http_gzip_static_module --with-http_concat_module --with-jemalloc --with-http_v2_module --with-http_secure_link_module
|
5.Tengine 文件目录结构

1 2 3 4 5 6 7 8 9 10 11 12 13
| |---certs :存放域名证书位置,自建的规范目录 |---client_body_temp:如果客户端POST一个比较大的文件,长度超过了nginx缓冲区的大小,需要把这个文件的部分或者全部内容暂存到client_body_temp目录下的临时文件 |---conf:主配置文件存放目录 |---conf.d:存放虚拟主机的配置文件,自建规范目录 |---fastcgi_temp:对于来自 FastCGI Server 的 Response,Nginx 将其缓冲到内存中,然后依次发送到客户端浏览器。缓冲区的大小由 fastcgi_buffers 和 fastcgi_buffer_size 两个值控制,fastcgi_buffers 控制 nginx 最多创建 8 个大小为 4K 的缓冲区,而 fastcgi_buffer_size 则是处理 Response 时第一个缓冲区的大小,不包含在前者中,超出部分存在这个目录 |---html:静态文件默认存放位置 |---include:存放编译代码头文件目录 |---logs:默认存放日志目录 |---modules:存放模块目录 |---proxy_temp:后端返回数据的临时存放目录 |---sbin:nginx二进制文件存放目录 |---scgi_temp:客户端可能会向服务器端请求大量的数据,服务器端收到的请求报文中的body中可能会有很多的数据,而这些数据都会存放内存中,倘若有很多的用户并发发出请求,服务器端内存无法存放,因此就会把数据临时存放在磁盘上的这些临时文件内 |---uwsgi_temp:代理服务器时缓存文件的存放路径
|
6.Tengine 配置文件详解
6.1 tengine主配置文件
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 96 97 98 99 100 101 102 103 104 105 106 107 108
| user nobody nobody; worker_processes auto; worker_cpu_affinity auto; worker_rlimit_nofile 102400; pid logs/nginx.pid;
events { use epoll; worker_connections 65535; }
http { req_status_zone server "$server_addr:$server_port" 10M; include mime.types; default_type application/octet-stream; check_shm_size 20M; sendfile on; tcp_nopush on; tcp_nodelay on; server_tokens off; server_info off; keepalive_timeout 20s; keepalive_requests 1000;
gzip on; gzip_min_length 1024; gzip_buffers 16 8k; gzip_comp_level 6; gzip_proxied any; gzip_types text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript; gzip_vary on;
fastcgi_intercept_errors on; fastcgi_connect_timeout 75s; fastcgi_send_timeout 300s; fastcgi_read_timeout 300s; fastcgi_buffer_size 16k; fastcgi_buffers 4 16k; fastcgi_busy_buffers_size 32k;
client_body_timeout 90s; client_max_body_size 20m; client_body_buffer_size 1m; client_header_buffer_size 128k; large_client_header_buffers 256 16k;
proxy_buffer_size 16k; proxy_buffers 8 32k; proxy_busy_buffers_size 64k; proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s;
map $upstream_addr $short_address { ~^\d+\.\d+\.(.*) ''; } add_header X-from $short_address$1;
proxy_set_header Host $host; proxy_set_header X-User-IP $clientRealIp; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass_header User-Agent; proxy_set_header X-Forwarded-Proto $scheme;
map $http_x_forwarded_for $clientRealIp { "" $remote_addr; ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr; }
log_format main '{ "@timestamp": "$time_iso8601", ' '"remote_addr": "$remote_addr", ' '"upstream_addr": "$upstream_addr", ' '"server_addr": "$server_addr", ' '"http_host": "$http_host",' '"request_time": $request_time, ' '"upstream_response_time": $upstream_response_time, ' '"request_uri": "$request_uri", ' '"status": "$status", ' '"request": "$request", ' '"request_method": "$request_method", ' '"http_referer": "$http_referer", ' '"body_bytes_sent": $body_bytes_sent, ' '"http_x_forwarded_for": "$http_x_forwarded_for", ' '"request_length": $request_length, ' '"http_user_agent": "$http_user_agent", ' '"scheme": "$scheme",' '"uri": "$uri",' '"clientRealIp": "$clientRealIp"}';
include /usr/local/nginx/conf.d/*.conf; }
|
6.2 状态检测配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| server { listen 8000; location ~ ^/(phpfpm_status|ping)$ { fastcgi_pass 127.0.0.1:9000; include fastcgi.conf; } location = /nginx_status { stub_status on; access_log off; } location = /nginx_status_detail { req_status_show; } location = /nstatus { check_status; access_log off; }
}
|
6.3 tengine常用配置
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 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| server { listen 80 ; listen 443 ssl; server_name www.test1.com www.test2.com; server_tag off; req_status server;
ssl_certificate /usr/local/nginx/certs/test.crt; ssl_certificate_key /usr/local/nginx/certs/test.key; ssl_session_timeout 5m; ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; ssl_prefer_server_ciphers on; access_log logs/host.access.log main;
location /example {
root html; index index.html index.htm;
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_http_version 1.1; proxy_set_header Connection "";
proxy_pass http://192.168.31.11:7111/ ; allow 192.168.31.69; deny all; deny 192.168.31.70; allow all;
limit_rate 20k;
concat on; concat_max_files 20; concat_unique off; concat_delimiter "\r\n"; concat_ignore_file_error on;
proxy_pass http://192.168.31.11; 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_cache cache_one; add_header Nginx-Cache $upstream_cache_status; proxy_cache_valid 200 304 301 302 8h; proxy_cache_valid 404 1m; proxy_cache_valid any 2d; proxy_cache_key $host$uri$is_args$args; expires 30d; }
location ~ /purge(/.*) { allow 127.0.0.1; allow 192.168.31.0/24; deny all; proxy_cache_purge cache_one $host$1$is_args$args; error_page 405 =200 /purge$1; }
location ~* \.(gif|jpg|png|bmp)$ { root html; valid_referers none blocked *.feidee.com server_names ~\.google\. ~\.baidu\.; if ($invalid_referer) { return 403; } }
location /realip { 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_pass http://192.168.31.11/realip/; }
error_page 404 /404.html; error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } }
|
7.Tengine 安全防护
7.1 禁止web服务IP直接访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
server { listen 80 default; return 403; }
server { listen 443 default; ssl_certificate /usr/local/nginx/certs/test.crt; ssl_certificate_key /usr/local/nginx/certs/test.key; return 403; }
|
7.2 连接和请求数限制
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
|
map $http_x_forwarded_for $clientRealIp { "" $remote_addr; ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr; }
geo $clientRealIp $whiteiplist { default 1; 192.168.31.241 1; 192.168.31.251 0; 192.168.31.236 0; 192.168.31.0/24 0; } map $whiteiplist $limit { 1 $clientRealIp; 0 ""; }
limit_conn_zone $limit zone=TotalConnLimitZone:20m ; limit_conn TotalConnLimitZone 50; limit_conn_log_level notice;
limit_req_zone $limit zone=ConnLimitZone:20m rate=20r/s; limit_req zone=ConnLimitZone burst=10 nodelay; limit_req_log_level notice;
|
7.3 访问全站IP黑名单限制
1 2 3 4 5 6 7
|
deny 192.168.31.69; deny 192.168.31.70;
allow all;
|
7.4 下载防盗链配置
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
| nginx 配置如下: server { listen 80; server_name www.testchao.com access_log ../logs/ www.testchao.com.access.log main; location / { root html; secure_link $arg_st,$arg_e; secure_link_md5 ttlsa.com$uri$arg_e;
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 403; } }
location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name; include fastcgi_params; } }
<html> <br>nginx下载页面 <br> <a href="/test.php" class="btn btn-danger go-link" role="button" target="_blank" rel="nofollow" _hover-ignore="1">下载地址</a> </html>
<?php $secret = 'qingye'; $path = '/web/nginx-1.4.2.tar.gz.jpg'; $expire = time()+10;
$md5 = base64_encode(md5($secret . $path . $expire .$_SERVER['REMOTE_ADDR'], true)); $md5 = strtr($md5, '+/', '-_'); $md5 = str_replace('=', '', $md5);
$url = http://www.qingye.info/web/nginx-1.4.2.tar.gz.jpg?st='.$md5.'&e='.$expire; #echo '<a href=http://www.qingye.info/web/nginx-1.4.2.tar.gz.jpg?st='.$md5.'&e='.$expire.'>nginx-1.4.2</a>'; #echo '<br>http://www.qingye.info/web/nginx-1.4.2.tar.gz.jpg?st='.$md5.'&e='.$expire; header("Location: $url"); ?>
|
8.Tengine 重要模块
8.1 upstream负载均衡模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| upstream backend {
ip_hash; server 192.168.31.225:8080 weight 2; server 192.168.31.226:8080 weight=1 max_fails=2 fail_timeout=30s ; server 192.168.31.227:8080 backup; } server { location / { proxy_pass http://backend; proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; }
|
关于nginx使用负载均衡会话跟踪问题(ngx_http_upstream_session_sticky_module 模块,tenginx默认已经安装)
这个模块的作用是通过cookie黏贴的方式将来自同一个客户端(浏览器)的请求发送到同一个后端服务器上处理,这样一定程度上可以解决多个backend servers的session同步的问题 —— 因为不再需要同步,
而RR轮询模式(nginx的默认后端调度模式)必须要运维人员自己考虑session同步的实现。
语法:session_sticky [cookie=name] [domain=your_domain] [path=your_path] [maxage=time] [mode=insert|rewrite|prefix] [option=indirect] [maxidle=time] [maxlife=time] [fallback=on|off] [hash=plain|md5]
默认值:session_sticky cookie=route mode=insert fallback=on
上下文:upstream
说明:
本指令可以打开会话保持的功能,下面是具体的参数:
cookie设置用来记录会话的cookie名称
domain设置cookie作用的域名,默认不设置
path设置cookie作用的URL路径,默认不设置
maxage设置cookie的生存期,默认不设置,即为session cookie,浏览器关闭即失效
mode设置cookie的模式:
insert: 在回复中本模块通过Set-Cookie头直接插入相应名称的cookie。
prefix: 不会生成新的cookie,但会在响应的cookie值前面加上特定的前缀,当浏览器带着这个有特定标识的cookie再次请求时,模块在传给后端服务前先删除加入的前缀,后端服务拿到的还是原来的cookie值,这些动作对后端透明。如:”Cookie: NAME=SRV~VALUE”。
rewrite: 使用服务端标识覆盖后端设置的用于session sticky的cookie。如果后端服务在响应头中没有设置该cookie,则认为该请求不需要进行session sticky,使用这种模式,后端服务可以控制哪些请求需要sesstion sticky,哪些请求不需要。
option 设置用于session sticky的cookie的选项,可设置成indirect或direct。indirect不会将session sticky的cookie传送给后端服务,该cookie对后端应用完全透明。direct则与indirect相反。
maxidle设置session cookie的最长空闲的超时时间
maxlife设置session cookie的最长生存期
fallback设置是否重试其他机器,当sticky的后端机器挂了以后,是否需要尝试其他机器
hash 设置cookie中server标识是用明文还是使用md5值,默认使用md5
maxage是cookie的生存期。不设置时,浏览器或App关闭后就失效。下次启动时,又会随机分配后端服务器。所以如果希望该客户端的请求长期落在同一台后端服务器上,可以设置maxage。
hash不论是明文还是hash值,都有固定的数目。因为hash是server的标识,所以有多少个server,就有等同数量的hash值。
一些例外:
同一客户端的请求,有可能落在不同的后端服务器上## 如果客户端启动时同时发起多个请求。由于这些请求都没带cookie,所以服务器会随机选择后端服务器,返回不同的cookie。当这些请求中的最后一个请求返回时,客户端的cookie才会稳定下来,值以最后返回的cookie为准。
cookie不一定生效## 由于cookie最初由服务器端下发,如果客户端禁用cookie,则cookie不会生效。
客户端可能不带cookie## Android客户端发送请求时,一般不会带上所有的cookie,需要明确指定哪些cookie会带上。如果希望用sticky做负载均衡,请对Android开发说加上cookie。
注意事项:
cookie名称不要和业务使用的cookie重名。Sticky默认的cookie名称是route,可以改成任何值。但切记,不可以与业务中使用的cookie重名。
客户端发的第一个请求是不带cookie的。服务器下发的cookie,在客户端下一次请求时才能生效
另外内置的 ip_hash 也可以实现根据客户端IP来分发请求,但它很容易造成负载不均衡的情况,而如果nginx前面有CDN网络或者来自同一局域网的访问,它接收的客户端IP是一样的,容易造成负载不均衡现象。
这个模块并不合适不支持 Cookie 或手动禁用了cookie的浏览器,此时默认session_sticky就会切换成RR。它不能与ip_hash同时使用。
1 2 3 4 5 6 7 8 9 10 11 12
| upstream backend { check interval=3000 rise=2 fall=5 timeout=1000 type=http; check_http_send "HEAD / HTTP/1.0\r\n\r\n"; check_http_expect_alive http_2xx http_3xx; server 192.168.31.226:8080 weight=1; server 192.168.31.227:8080 weight=1; session_sticky;
}
|
负载均衡其它调度方案
轮询(默认) : 每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某台服务器宕机,故障系统被自动剔除,使用户访问不受影响。Weight 指定轮询权值,Weight值越大,分配到的访问机率越高,主要用于后端每个服务器性能不均的情况下。
ip_hash : 每个请求按访问IP的hash结果分配,这样来自同一个IP的访客固定访问一个后端服务器,有效解决了动态网页存在的session共享问题。当然如果这个节点不可用了,会发到下个节点,而此时没有session同步的话就注销掉了。
least_conn : 请求被发送到当前活跃连接最少的realserver上。会考虑weight的值。
url_hash : 此方法按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身是不支持url_hash的,如果需要使用这种调度算法,必须安装Nginx 的hash软件包 nginx_upstream_hash 。
fair : 这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。
Nginx本身是不支持fair的,如果需要使用这种调度算法,必须下载Nginx的 upstream_fair 模块
8.2 rewrite重写模块
rewrite模块rewrite功能就是,使用nginx提供的全局变量或自己设置的变量,结合正则表达式和标志位实现url重写以及重定向。rewrite只能放在server{},location{},if{}中,
并且只能对域名后边的除去传递的参数外的字符串起作用,例如 http://seanlook.com/a/we/index.php?id=1&u=str 只对/a/we/index.php重写。语法rewrite regex replacement [flag];
flag标志位
1 2 3 4
| last : 相当于Apache的[L]标记,表示完成rewrite break : 停止执行当前虚拟主机的后续rewrite指令集 redirect : 返回302临时重定向,地址栏会显示跳转后的地址 permanent : 返回301永久重定向,地址栏会显示跳转后的地址
|
if指令与全局变量,if判断指令:
1 2 3 4 5 6 7 8
| 语法为if(condition){...},对给定的条件condition进行判断。如果为真,大括号内的rewrite指令将被执行,if条件(conditon)可以是如下任何内容: 当表达式只是一个变量时,如果值为空或任何以0开头的字符串都会当做false 直接比较变量和内容时,使用=或!= ~正则表达式匹配,~*不区分大小写的匹配,!~区分大小写的不匹配 -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 20 21 22 23 24 25
| if ($http_user_agent ~ MSIE) { rewrite ^(.*)$ /msie/$1 break; } //如果UA包含"MSIE",rewrite请求到/msid/目录下 if ($http_cookie ~* "id=([^;]+)(?:;|$)") { set $id $1; } //如果cookie匹配正则,设置变量$id等于正则引用部分 if ($request_method = POST) { return 405; } //如果提交方法为POST,则返回状态405(Method not allowed)。return不能返回301,302 if ($slow) { limit_rate 10k; } //限速,$slow可以通过 set 指令设置 if (!-f $request_filename){ break; proxy_pass http://127.0.0.1; } //如果请求的文件名不存在,则反向代理到localhost 。这里的break也是停止rewrite检查 if ($args ~ post=140){ rewrite ^ http://example.com/ permanent; } //如果query string中包含"post=140",永久重定向到example.com location ~* \.(gif|jpg|png|swf|flv)$ { valid_referers none blocked www.jefflei.com www.leizhenfang.com; if ($invalid_referer) { return 404; } //防盗链 }
|
下面是可以用作if判断的全局变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $args : $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 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
| . : 匹配除换行符以外的任意字符 ? : 重复0次或1次 + : 重复1次或更多次 * : 重复0次或更多次 \d :匹配数字 ^ : 匹配字符串的开始 $ : 匹配字符串的介绍 {n} : 重复n次 {n,} : 重复n次或更多次 [c] : 匹配单个字符c [a-z] : 匹配a-z小写字母的任意一个
break 语法:break 默认值:none 使用字段:server, location, if 完成当前设置的重写规则,停止执行其他的重写规则。
return 语法:return code 默认值:none 使用字段:server, location, if 停止处理并为客户端返回状态码。非标准的444状态码将关闭连接,不发送任何响应头。可以使用的状态码有:204,400,402-406,408,410, 411, 413, 416与500-504。如果状态码附带文字段落,该文本将被放置在响应主体。相反,如果状态码后面是一个URL,该URL将成为location头补值。没有状态码的URL将被视为一个302状态码。
rewrite 语法:rewrite regex replacement flag 默认值:none 使用字段:server, location, if 按照相关的正则表达式与字符串修改URI,指令按照在配置文件中出现的顺序执行。可以在重写指令后面添加标记。 注意:如果替换的字符串以http://开头,请求将被重定向,并且不再执行多余的rewrite指令。 尾部的标记(flag)可以是以下的值: last - 停止处理重写模块指令,之后搜索location与更改后的URI匹配。 break - 完成重写指令。 redirect - 返回302临时重定向,如果替换字段用http://开头则被使用。 permanent - 返回301永久重定向。 rewrite_log 语法:rewrite_log on | off 默认值:rewrite_log off 使用字段:server, location, if 变量:无 启用时将在error log中记录notice级别的重写日志。
set 语法:set variable value 默认值:none 使用字段:server, location, if 为给定的变量设置一个特定值。 uninitialized_variable_warn 语法:uninitialized_variable_warn on|off 默认值:uninitialized_variable_warn on 使用字段:http, server, location, if 控制是否记录未初始化变量的警告信息。
|
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| location ~ ^/best/ { rewrite ^/best/(.*) /test/$1 break; proxy_pass http://www.taob.com }
server{ server_name www.taob.com; rewrite ^/(.*)$ http://www.tb.com/$1 permanent; }
server { server_name www.tb.com www.taob.com; if ($host != 'www.tb.com') rewrite ^/(.*)$ http://www.tb.com/$1 permanent; }
|
9.location 在匹配中的优先级
配置文件示例如下:
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
| location = / {
[ configuration A ]
}
location / {
[ configuration B ]
}
location /documents/ {
[ configuration C ]
}
location ^~ /images/ {
[ configuration D ]
}
location ~* \.(gif|jpg|jpeg)$ {
[ configuration E ]
}
|
匹配结果:
URL |
匹配结果 |
原因 |
/ |
configuration A |
=优先级最高,匹配到结束 |
/index.html |
configuration B |
路径匹配 |
/documents/document.html |
configuration C |
第二个匹配到,往后继续匹配,发现第三个匹配最确 |
/images/1.gif |
configuration D |
同时匹配第二个,第四个,和第五个。但是由于优先级问题 |
/documents/1.jpg |
configuration E |
同时匹配第二个,第三个,和第五个。第五个是正则表达式 |
优先级如下:
(location =) > (location 完整路径) > (location ^~ 路径) > (location ,* 正则顺序) > (location 部分起始路径) > (/)
注: ~ 和 ~都是正则匹配 其中 ~ 不区分大小写 ~ 区分大小写
10.Tengine root 和 alias 的区别
nginx配置下有两个指定目录的执行,root和alias
例如有以下配置文件:
1 2 3
| location /img/ { alias /var/www/image/; }
|
若按照上述配置的话,则访问/img/目录里面的文件时,ningx会自动去/var/www/image/目录找文件
1 2 3
| location /img/ { root /var/www/image; }
|
若按照这种配置的话,则访问/img/目录下的文件时,nginx会去/var/www/image/img/目录下找文件。alias是一个目录别名的定义,root则是最上层目录的定义
还有一个重要的区别是alias后面必须要用“/”结束,否则会找不到文件的。。。而root则可有可无
11.Tengine TCP转发配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| tcp { timeout 1d; proxy_read_timeout 10d; proxy_send_timeout 10d; proxy_connect_timeout 30; upstream api_server { server 192.168.31.212:389 weight=5 max_fails=1 fail_timeout=10s; } server { listen 1389; proxy_connect_timeout 1s; proxy_pass api_server; } }
|
12.Tengine 常用维护脚本或命令
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
|
/usr/local/nginx/sbin/nginx -m
/usr/local/nginx/sbin/nginx -t
/usr/local/nginx/sbin/nginx
/usr/local/nginx/sbin/nginx -s reload
/usr/local/nginx/sbin/nginx -s stop
logs_path="/usr/local/nginx/logs/"
pid_path="/usr/local/nginx/logs/nginx.pid"
cut_path="/usr/local/nginx/logs/bak/"
[ -e $cut_path ] || mkdir -p $cut_path cd $logs_path for log_name in `ls *.log`;do mv ${logs_path}${log_name} ${cut_path}${log_name}_$(date +"%Y-%m-%d").log done
if [[ -s $pid_path ]]; then kill -USR1 `cat ${pid_path}` fi
find ${cut_path} -type f -name "*.log" -mtime +7 | xargs rm -f
|
13.参考文档