Nginx 之实现原理

来源:互联网 发布:七天网络查分账号注册 编辑:程序博客网 时间:2024/06/17 18:07

144 作者 happy江柳清 关注
2015.10.26 17:23* 字数 2536 阅读 1753评论 2喜欢 8
本文主要从 Nginx 的进程模块、事件模块、http网络模块三方面介绍了 Nginx 的底层实现原理,希望你通过本文能对Nginx 的基本实现有一定了解。

进程模块

Nginx 默认采用守护模式启动,守护模式让master进程启动后在后台运行,不在窗口上卡住。

Nginx 启动后会有一个 Master 进程和多个Worker 进程,Master 进程主要用来管理 Worker 进程,对网络事件进程进行收集和分发,调度哪个模块可以占用 CPU 资源,从而处理请求。一般配置Worker进程的个数与机器cpu个数一致,从而打到cpu资源的最大化利用,也避免由于进程资源分配带来的额外开销。

进程 icon

图片来自网络

Master进程工作原理

Master 进程的工作包括

接收来自外界的信号

向各worker进程发送信号

监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程

惊群现象

惊群现象 是指请求到来的时候,只有可以成功accept的那个进程会惊醒,其他进程会继续阻塞。nginx 采用accept-mutex来解决惊群问题,当一个请求到达的时候,只有竞争到锁的worker进程才能处理请求,其他进程结合timer_solution 配置的最大的超时时间,再去获取监听锁
来看源码

void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;

if (NGX_THREADS)

if (timer == NGX_TIMER_INFINITE || timer > 500) {    timer = 500;}

endif

}/* 检测是否启用mutex,多worker进程下一般都会启用 */if (ngx_use_accept_mutex) {    if (ngx_accept_disabled > 0) {        ngx_accept_disabled--;    } else {        if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {        /* 尝试获取锁,不管成功还是失败都会立即返回 */            return;        }        if (ngx_accept_mutex_held) {            /* 获取到锁之后添加flag */            flags |= NGX_POST_EVENTS;        } else {            /* 如果获取不到锁需要结合timer事件设置下一次抢锁的时间 */            if (timer == NGX_TIMER_INFINITE                || timer > ngx_accept_mutex_delay)            {                timer = ngx_accept_mutex_delay;            }        }    }}delta = ngx_current_msec;/* 开始epoll收集处理事件 */(void) ngx_process_events(cycle, timer, flags);/* delta就是epoll_wait消耗掉的时间 */delta = ngx_current_msec - delta;ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,               "timer delta: %M", delta);/* accept事件已经被加入到单独的任务队列并会被优先处理 */ngx_event_process_posted(cycle, &ngx_posted_accept_events);/* accept事件处理完之后先释放accept锁,因为其它事件的处理可能耗时较长,不要占着茅坑不睡觉 */if (ngx_accept_mutex_held) {    ngx_shmtx_unlock(&ngx_accept_mutex);}if (delta) {    ngx_event_expire_timers();}/* 之后可以放心处理其它事件了 */ngx_event_process_posted(cycle, &ngx_posted_events);

}
Worker进程工作原理

当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。

采用这种方式的好处:

节省锁带来的开销。对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查上时,也会方便很多
独立进程,减少风险。采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快重新启动新的worker进程
在一次请求里无需进程切换
相关配置

user nobody nobody;
worker_processes 8;
worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
worker_rlimit_nofile 65535;
error_log logs/error.log info;
事件模块

对于一个基本的 WEB 服务器来说,事件通常有三种类型,网络事件、信号和定时器。

一个请求的基本过程:建立连接 - 接受连接 - 发送数据,在系统底层就是读写事件。

Epoll 模型

Epoll出现在 linux2.6以后,Nginx采用 Epoll 这种异步非阻塞的事件处理机制。这种机制的原理就是把一个完整的请求,划分成多个事件,比如accept(), recv(),磁盘I/O,send(),每个事件都有不同的模块进行处理。一个请求由一个worker进程处理,在请求多的时候,无需频繁的切换进程。

事件模块 icon

图片来自网络

master进程先建好需要listen的socket后,然后再fork出多个woker进程,这样每个work进程都可以去accept这个socket
当一个client连接到来时,所有accept的work进程都会受到通知,但只有一个进程可以accept成功,其它的则会accept失败,Nginx提供了一把共享锁accept_mutex来保证同一时刻只有一个work进程在accept连接,从而解决惊群问题
当一个worker进程accept这个连接后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完成的请求就结束了
Epoll 是基于一个进程处理多个连接、非阻塞IO的策略,Nginx多使用这种策略。
Select 模型

Select 模型在启动的时候创建多个进程,放在一个进程池里,并且进程池里的进程数会随着请求数目的增加而增加,对于每一个连接,都是在一个进程内处理完毕。所以Select模型能接收的并发量受到所能开启的进程数影响,进程之间是互相阻塞的,且频繁的切换进程造成大量开销。

Select 是基于一个线程处理一个请求的非阻塞IO的策略,Apache使用这种策略。
相关配置

events {
use epoll;
worker_connections 1024;
}
网络模块

最大连接数

当作为http服务器的时候:

max_clients = worker_processes * worker_connections;
当作为反向代理的时候:

max_clients = worker_processes * worker_connections/4
负载均衡

nginx的upstream目前支持的5种方式的分配

轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

upstream backserver {
server host:port;
server host:port;
}
weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况

upstream backserver {
server host:port weight=10;
server host:port weight=10;
}
ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题

upstream backserver {
ip_hash;
server host:port;
server host:port;
}
fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。

upstream backserver {
server server1;
server server2;
fair;
}
url_hash(第三方)
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效

upstream backserver {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
在需要使用负载均衡的server中增加

proxy_pass http://backserver/ ;
upstream backserver{
ip_hash;
server host:port down; (down 表示单前的server暂时不参与负载)
server host:port weight=2; (weight 默认为1.weight越大,负载的权重就越大)
server host:port
server host:port backup; (其它所有的非backup机器down或者忙的时候,请求backup机器)
}
代理缓存

代理缓冲区相关配置

proxy_buffer_size   128k;proxy_buffers   4 256k;proxy_busy_buffers_size   256k;# 通过令牌桶原理实现用户访问次数限制limit_req_zone  $http_x_forwarded_for zone=req_one:10m rate=30r/s;#设置Web缓存区名称为cache_web,内存缓存空间大小为300MB,1天没有被访问的内容自动清除,硬盘缓存空间#大小为3GB。proxy_temp_path   /xxx/proxy_temp_dir 1 2;proxy_cache_path  /xxx/proxy_cache_dir levels=1:2 keys_zone=cache_web:300m inactive=1d max_size=1g;

访问控制

location ~ /.ht {
deny all;
}
相关配置

一个http指令下可以配置多个server指令块,一个server指令块里可以根据不同的url做配置

http {
include /xxx/nginx/conf/mime.types;
default_type application/octet-stream;

log_format    main  '$remote_addr^A $remote_user^A [$time_local]^A $request^A '                    '$status^A $body_bytes_sent^A $http_referer^A '                    '$http_user_agent^A $http_x_forwarded_for^A '        '$request_body^A $http_X_Cache^A $upstream_http_X_Cache^A '        '$upstream_cache_status^A $http_x_accel_expires^A $dna_device';access_log    /xxx/nginx/logs/access.log  main;sendfile      on;tcp_nopush    on;#server_names_hash_bucket_size 128;#client_header_buffer_size 8k;open_file_cache max=10240 inactive=20s;// max打开文件指定缓存数量,inactive指多长时间没请求就删除缓存open_file_cache_valid 30s; // 30s检查一次缓存的有效信息open_file_cache_min_uses 1;// 最少使用次数,如果超过这个数字,就一直在缓存中打开keepalive_timeout  60;# 代理缓冲区相关配置proxy_buffer_size   128k;proxy_buffers   4 256k;proxy_busy_buffers_size   256k;# 通过令牌桶原理实现用户访问次数限制limit_req_zone  $http_x_forwarded_for zone=req_one:10m rate=30r/s;#设置Web缓存区名称为cache_web,内存缓存空间大小为300MB,1天没有被访问的内容自动清除,硬盘缓存空间#大小为3GB。proxy_temp_path   /xxx/proxy_temp_dir 1 2;proxy_cache_path  /xxx/proxy_cache_dir levels=1:2 keys_zone=cache_web:300m inactive=1d max_size=1g;upstream www_backend_server {    server   host:port;} upstream m_backend_server {    server   host:port;}server {    listen          80;    server_name     www.xxx.com;    access_log      logs/xxx.access.log main;    location ~ \.php {        fastcgi_pass 127.0.0.1:9000;        fastcgi_index /index.php;        include /xxx/nginx/conf/fastcgi_params;        fastcgi_buffer_size 128k;        fastcgi_buffers 4 256k;        fastcgi_busy_buffers_size 256k;        fastcgi_split_path_info       ^(.+\.php)(/.+)$;        fastcgi_param PATH_INFO       $fastcgi_path_info;        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;    }    location ~ /\.ht {        deny all;        }}server {    listen          80;    server_name     www.xxx.com;    access_log      logs/xxx.access.log main;    location / {        index index.html;        root  /xxx/htdocs;    }}

}
其他

启动流程

时间、正则、错误日志、ssl等初始化
读入命令行参数
OS相关初始化
读入并解析配置
核心模块初始化
创建各种暂时文件和目录
创建共享内存
打开listen的端口
所有模块初始化
启动worker进程
Nginx和PHP交互

通过fastcgi模块进行交互,交互模式有两种:

fastcgi_pass unix:/xxx/php/var/php-cgi.sock;

fastcgi_pass 127.0.0.1:9000;
Mail配置

mail {
auth_http 127.0.0.1:80/auth.php;
pop3_capabilities “TOP” “USER”;
imap_capabilities “IMAP4rev1” “UIDPLUS”;

server {    listen     110;    protocol   pop3;    proxy      on;}server {    listen      25;    protocol    smtp;    proxy       on;    smtp_auth   login plain;    xclient     off;}

}
下期预告: Nginx 之扩展模块的开发

0 0
原创粉丝点击