[关闭]
@Alex-Zhao 2018-04-25T16:59:44.000000Z 字数 6770 阅读 92

Nginx实现原理

负载均衡


本文主要从 Nginx 的进程模块、事件模块、http网络模块三方面介绍了 Nginx 的底层实现原理,希望你通过本文能对Nginx 的基本实现有一定了解。

进程模块


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

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

Master进程工作原理

Master 进程的工作包括

  1. 接收来自外界的信号

  2. 向各worker进程发送信号

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

惊群现象

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

  1. void
  2. ngx_process_events_and_timers(ngx_cycle_t *cycle)
  3. {
  4. ngx_uint_t flags;
  5. ngx_msec_t timer, delta;
  6. if (ngx_timer_resolution) {
  7. timer = NGX_TIMER_INFINITE;
  8. flags = 0;
  9. } else {
  10. timer = ngx_event_find_timer();
  11. flags = NGX_UPDATE_TIME;
  12. #if (NGX_THREADS)
  13. if (timer == NGX_TIMER_INFINITE || timer > 500) {
  14. timer = 500;
  15. }
  16. #endif
  17. }
  18. /* 检测是否启用mutex,多worker进程下一般都会启用 */
  19. if (ngx_use_accept_mutex) {
  20. if (ngx_accept_disabled > 0) {
  21. ngx_accept_disabled--;
  22. } else {
  23. if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
  24. /* 尝试获取锁,不管成功还是失败都会立即返回 */
  25. return;
  26. }
  27. if (ngx_accept_mutex_held) {
  28. /* 获取到锁之后添加flag */
  29. flags |= NGX_POST_EVENTS;
  30. } else {
  31. /* 如果获取不到锁需要结合timer事件设置下一次抢锁的时间 */
  32. if (timer == NGX_TIMER_INFINITE
  33. || timer > ngx_accept_mutex_delay)
  34. {
  35. timer = ngx_accept_mutex_delay;
  36. }
  37. }
  38. }
  39. }
  40. delta = ngx_current_msec;
  41. /* 开始epoll收集处理事件 */
  42. (void) ngx_process_events(cycle, timer, flags);
  43. /* delta就是epoll_wait消耗掉的时间 */
  44. delta = ngx_current_msec - delta;
  45. ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
  46. "timer delta: %M", delta);
  47. /* accept事件已经被加入到单独的任务队列并会被优先处理 */
  48. ngx_event_process_posted(cycle, &ngx_posted_accept_events);
  49. /* accept事件处理完之后先释放accept锁,因为其它事件的处理可能耗时较长,不要占着茅坑不睡觉 */
  50. if (ngx_accept_mutex_held) {
  51. ngx_shmtx_unlock(&ngx_accept_mutex);
  52. }
  53. if (delta) {
  54. ngx_event_expire_timers();
  55. }
  56. /* 之后可以放心处理其它事件了 */
  57. ngx_event_process_posted(cycle, &ngx_posted_events);
  58. }

Worker进程工作原理

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

采用这种方式的好处:

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

相关配置

  1. user nobody nobody;
  2. worker_processes 8;
  3. worker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;
  4. worker_rlimit_nofile 65535;
  5. error_log logs/error.log info;

事件模块


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

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

Epoll 模型

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

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

Select 模型

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

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

相关配置

  1. events {
  2. use epoll;
  3. worker_connections 1024;
  4. }

网络模块


最大连接数

当作为http服务器的时候:

  1. max_clients = worker_processes * worker_connections;

当作为反向代理的时候:

  1. max_clients = worker_processes * worker_connections/4

负载均衡

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

  1. upstream backserver {
  2. server host:port;
  3. server host:port;
  4. }
  1. upstream backserver {
  2. server host:port weight=10;
  3. server host:port weight=10;
  4. }
  1. upstream backserver {
  2. ip_hash;
  3. server host:port;
  4. server host:port;
  5. }
  1. upstream backserver {
  2. server server1;
  3. server server2;
  4. fair;
  5. }
  1. upstream backserver {
  2. server squid1:3128;
  3. server squid2:3128;
  4. hash $request_uri;
  5. hash_method crc32;
  6. }

在需要使用负载均衡的server中增加

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

代理缓存

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

访问控制

  1. location ~ /\.ht {
  2. deny all;
  3. }

相关配置

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

  1. http {
  2. include /xxx/nginx/conf/mime.types;
  3. default_type application/octet-stream;
  4. log_format main '$remote_addr^A $remote_user^A [$time_local]^A $request^A '
  5. '$status^A $body_bytes_sent^A $http_referer^A '
  6. '$http_user_agent^A $http_x_forwarded_for^A '
  7. '$request_body^A $http_X_Cache^A $upstream_http_X_Cache^A '
  8. '$upstream_cache_status^A $http_x_accel_expires^A $dna_device';
  9. access_log /xxx/nginx/logs/access.log main;
  10. sendfile on;
  11. tcp_nopush on;
  12. #server_names_hash_bucket_size 128;
  13. #client_header_buffer_size 8k;
  14. open_file_cache max=10240 inactive=20s;// max打开文件指定缓存数量,inactive指多长时间没请求就删除缓存
  15. open_file_cache_valid 30s; // 30s检查一次缓存的有效信息
  16. open_file_cache_min_uses 1;// 最少使用次数,如果超过这个数字,就一直在缓存中打开
  17. keepalive_timeout 60;
  18. # 代理缓冲区相关配置
  19. proxy_buffer_size 128k;
  20. proxy_buffers 4 256k;
  21. proxy_busy_buffers_size 256k;
  22. # 通过令牌桶原理实现用户访问次数限制
  23. limit_req_zone $http_x_forwarded_for zone=req_one:10m rate=30r/s;
  24. #设置Web缓存区名称为cache_web,内存缓存空间大小为300MB,1天没有被访问的内容自动清除,硬盘缓存空间
  25. #大小为3GB。
  26. proxy_temp_path /xxx/proxy_temp_dir 1 2;
  27. proxy_cache_path /xxx/proxy_cache_dir levels=1:2 keys_zone=cache_web:300m inactive=1d max_size=1g;
  28. upstream www_backend_server {
  29. server host:port;
  30. }
  31. upstream m_backend_server {
  32. server host:port;
  33. }
  34. server {
  35. listen 80;
  36. server_name www.xxx.com;
  37. access_log logs/xxx.access.log main;
  38. location ~ \.php {
  39. fastcgi_pass 127.0.0.1:9000;
  40. fastcgi_index /index.php;
  41. include /xxx/nginx/conf/fastcgi_params;
  42. fastcgi_buffer_size 128k;
  43. fastcgi_buffers 4 256k;
  44. fastcgi_busy_buffers_size 256k;
  45. fastcgi_split_path_info ^(.+\.php)(/.+)$;
  46. fastcgi_param PATH_INFO $fastcgi_path_info;
  47. fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
  48. fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  49. }
  50. location ~ /\.ht {
  51. deny all;
  52. }
  53. }
  54. server {
  55. listen 80;
  56. server_name www.xxx.com;
  57. access_log logs/xxx.access.log main;
  58. location / {
  59. index index.html;
  60. root /xxx/htdocs;
  61. }
  62. }
  63. }

其他


启动流程

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

Nginx和PHP交互

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

  1. fastcgi_pass unix:/xxx/php/var/php-cgi.sock;
  2. fastcgi_pass 127.0.0.1:9000;

Mail配置

  1. mail {
  2. auth_http 127.0.0.1:80/auth.php;
  3. pop3_capabilities "TOP" "USER";
  4. imap_capabilities "IMAP4rev1" "UIDPLUS";
  5. server {
  6. listen 110;
  7. protocol pop3;
  8. proxy on;
  9. }
  10. server {
  11. listen 25;
  12. protocol smtp;
  13. proxy on;
  14. smtp_auth login plain;
  15. xclient off;
  16. }
  17. }

作者:柳清的禅
链接:https://www.jianshu.com/p/b77482d4b670
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注