【运维分享】软负载与nginx那些强大的不可不说的功能

当咱们打开手机访问点评客户端的时候,访问商户的请求是如何到达对应某台应用服务器的?javascript

当有不少XX宽带的用户投诉说我大点评某某域名没法打开可是咱们却找不出任何问题的时候,咱们就想到会不会是宽带运营商的问题php

今天与你们分享的话题,主要是跟咱们的软负载集群和Nginx这个强大的开源应用有关系。css

   当咱们准备上线一个新的业务,或者新的功能时候,除了把代码发布的线上生产环境的应用服务器外,还须要作什么工做才能让咱们的资深吃货的用户们能够访问到咱们高端大气上档次的服务呢?html

   用户是不可能直接跑到咱们的IDC机房插根网线就来访问咱们的内部服务器的,咱们答应,电信管理IDC的怪叔叔们也不会答应啊。前端

   首先,咱们很清楚用户是经过dianping.com的域名来访问咱们的站点,同时经过咱们对外开放的url连接来访问一些新站点或者新功能的页面。而后,用户访问的域名会经过DNS服务被替换为咱们对外的IP地址,这样才能被网络设备识别,而后将用户的请求按照一个一个网络包发给咱们的网络设备,最后网络设备收到这些网络数据包后,会将这些数据整理后转化为应用程序能够理解的数据。java

  数据到了咱们的核心网络设备被转换为应用层的数据后,是如何到咱们具体某一台应用服务器来处理呢,这就牵涉到咱们要讲的负载均衡器了。负载均衡器若是是硬件设备的话,那就是咱们常常提到的负载均衡设备,若是是linux服务器上运行的负载均衡软件,那就是软负载了,若是是集群而不是单机的话,那就是传说中的负载均衡集群了。node

以下图是咱们 线上生产环境的用户请求走向图:linux

  当一个吃货经过浏览器或手机APP访问咱们网站的时候,不管是访问商户,添加点评,购买团购,仍是在社区经过私信功能与妹子聊天,全部请求都会通过咱们的F5负载均衡设备按照设定的转发策略(随机,权重,最小链接等)转发到特定的某台应用服务器来处理,而后再将处理结果返回给用户。nginx

wKiom1NhAT3xIqdpAAD43rtCzhQ827.jpg

好吧,当咱们说了这个硬件设备时候,是否是要谈谈以软件实现的负载均衡功能呢,其实目前在咱们PPE环境(xx机房,之后的双IDC运行后另外一个生产环境)运行着这样一套软负载集群来处理用户的请求(固然,如今都是伪造的用户请求)。web

wKioL1NhASOBUE4oAAEaWwRzzCQ111.jpg

   网络设备和Nginx负载均衡集群中间的F5做为流量管理设备,作4层(链接层,tcp)流量分发。

wKiom1NhAWqBgZGNAAE70_ipu7w168.jpg

软负载实质上是一组nginx集群以及容许用户管理nginx配置文件的一个web端。

wKiom1NhAXrBLo_1AAEhYUrHQmM672.jpg

Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx因稳定性、丰富的功能集和低系统资源的消耗而闻名。

 从中咱们能够看出,nginx至少能够作web服务器,同时能够作反向代理服务器,同时又能够作邮件代理服务器,功能仍是很是丰富的,稍后咱们会对nginx的功能模块作简要的介绍。

做为web服务器,nginx因为自身的优点,在处理静态文件上有着绝对的优点,因此也是自然的优秀web服务器软件。

什么是反向代理服务器,和咱们平时说的用代理服务器上国外网站又有什么区别?

有图有真相,看图说话

代理服务器呢,就是当我想访问某个网站的时候由于各类缘由不能直接访问,我能够主动或者被动用一台能够访问目标站点的服务器作代理去访问我想要访问的站点。

    当我主动去找代理服务器去访问是一种状况,还有一种比较悲催的状况,当咱们使用了某些无良宽带运营商提供的物美价廉,缩水严重,还不断搞各类潜规则的宽带时候,就会碰到咱们这些吃货去访问点评网站的时候,首先是去访问宽带运营商局域网的代理服务器,而后代理服务器去访问点评的网站。这样作对于宽带运营商来讲,能够缓存一些数据,这样就能节省点带宽,可是对于咱们这些使用宽带的用户而言,一则数据不安全,运营商的代理服务器上可能有咱们的艳照也说不定,二则,当点评站点可正常访问,宽带运营商代理服务器出现问题的时候,就会收到各类用户投诉,点评又跪了,这让吃货怎么活?问题是点评活的好好的,用户却访问不到。

wKioL1NhAV7jqiPLAACXJkGloDQ338.jpg

    反向代理(Reverse Proxy)是指以代理服务器来接受internet上的链接请求,而后将请求转发给内部网络上的服务器,并将从服务器上获得的结果返回给internet上请求链接的客户端,此时代理服务器对外就表现为一个服务器。

 反向代理服务器能够看下面图,nginx就是反向代理的角色,用户的请求是先发到nginx,而后转发到后端的tomcat。

wKiom1NhAZbSut7FAACZukjMSpM461.jpg

固然,代理服务器和反向代理服务器的类型不仅web服务一种。

以下是一个简单的反向代理的配置:

server_name   qunying.dianping.com;
location / {
proxy_pass test.qunying.liu.dianping.com; //反向代理站。
index index.html index.htm;
}

当用户访问qunying.dianping.com/helloword.jsp的时候,这台服务器上的nginx就会将用户的请求转发到qunying.liu.dianping.com/helloword.jsp,固然proxy_pass转向的地址也能够是内部地址,好比127.0.0.1:8080

固然对于咱们线上的环境,nginx不是做为典型的反向代理在使用,目前点评java相关web业务服务器上采用的是 nginx (缓存和压缩,日志)+tomcat(java容器),充分利用了nginx低系统占用以及高并发处理的优点。

  不少人会有疑问,tomcat也能够作web容器的啊,改个端口不就能够直接给用户提供服务了,并且tomcat也能记录日志,不必再放一个nginx啊。

tomcat 前面有没有必要放一个nginx呢?

  术业有专攻,tomcat作web服务器是兼职,作java容器是专职。nginx服务器是专职作web服务器,支持高并发,响应快,擅长处理静态内容,并且能够把动态内容交给tomcat处理。用tomcat作web容器响应用户请求,有可能1分钟只能处理10个请求,可是用nginx+tomcat一分钟就可能能够处理100个请求。

nginx为何能够这么快处理用户的请求呢?

nginx的进程模型以及系统事件机制

nginx启动后的进程,如图所示:

wKioL1NhAYuDpxoQAAFduYmboQ4730.jpg

咱们能够看到master进程,是以root身份启动,执行内容为:/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

worker进程都是以咱们指定的nobody身份运行,其中有一个worker进程是旧的worker进程奔溃后,自动从新建立的,你能找到他吗?

wKioL1NhAZmwwXNPAADogK6xWTc871.jpg

    nginx在启动后,会有一个master进程多个worker进程master进程主要用来管理worker进程,包含:接收来自外界的信号,向各个worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常状况下),会自动从新启动新的worker进程。基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是能够设置的,通常咱们会设置与机器cpu核数一致。

   咱们要控制nginx,只须要经过kill向master进程发送信号就好了。好比kill -HUP pid,则是告诉nginx,从容地重启nginx,咱们通常用这个信号来重启nginx,或从新加载配置,由于是从容地重启,所以服务是不中断的。master进程在接收到HUP信号后是怎么作的呢?首先master进程在接到信号后,会先从新加载配置文件,而后再启动新的进程,并向全部老的进程发送信号,告诉他们能够光荣退休了。新的进程在启动后,就开始接收新的请求,而老的进程在收到来自master的信号后,就再也不接收新的请求,而且在当前进程中的全部未处理完的请求处理完成后,再退出。固然,直接给master进程发送信号,这是比较老的操做方式,nginx在0.8版本以后,引入了一系列命令行参数,来方便咱们管理。好比,./nginx -s reload,就是来重启nginx,./nginx -s stop,就是来中止nginx的运行。如何作到的呢?咱们仍是拿reload来讲,咱们看到,执行命令时,咱们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道咱们的目的是控制nginx来从新加载配置文件了,它会向master进程发送信号,而后接下来的动做,就和咱们直接向master进程发送信号同样了。

 worker进程是如何处理咱们的http请求的?

    master(master进程会先创建好须要listen的socket)--------fork生成子进程workers,继承socket(此时workers子进程们都继承了父进程master的全部属性,固然也包括已经创建好的socket,固然不是同一个socket,只是每一个进程的这个socket会监控在同一个ip地址与端口,这个在网络协议里面是容许的)------当一个链接进入,产生惊群现象(惊群现象:指一个fd的事件被触发后,等候这个fd的全部线程/进程都被唤醒。虽然都被唤醒,可是只有一个会去响应。)。

Nginx对惊群现象的处理共享锁

    nginx提供了一个accept_mutex这个东西,从名字上,咱们能够看这是一个加在accept上的一把共享锁。有了这把锁以后,同一时刻,就只会有一个进程在accpet链接,这样就不会有惊群问题了。accept_mutex是一个可控选项,咱们能够显示地关掉,默认是打开的。

worker进程工做

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

    采用这种方式的好处:

    1)节省锁带来的开销。对于每一个worker进程来讲,独立的进程,不须要加锁,因此省掉了锁带来的开销,同时在编程以及问题查上时,也会方便不少

    2)独立进程,减小风险。采用独立的进程,可让互相之间不会影响,一个进程退出后,其它进程还在工做,服务不会中断,             master进程则很快从新启动新的worker进程。固然,worker进程的异常退出,确定是程序有bug了,异常退出,会致使当前worker上的全部请求失败,不过不会影响到全部请求,因此下降了风险。

    Nginx的事件处理机制,采用异步非阻塞事件处理机制,一个worker进程只有一个主线程,经过异步非阻塞的事件处理机制,实现了循环处理多个准备好的事件,从而实现轻量级和高并发。      

异步非阻塞事件处理机制:

同步和异步的概念,这两个概念与消息的通知机制有关.同步的状况下,是由处理消息者本身去等待消息是否被触发,而异步的状况下是由触发机制来通知处理消息者。

阻塞和非阻塞,这两个概念与程序等待消息(无所谓同步或者异步)时的状态有关.

 当读写事件没有准备好时,就放入epoll里面。若是有事件准备好了,那么就去处理;若是事件返回的是EAGAIN,那么继续将其放入epoll里面。从而,只要有事件准备好了,咱们就去处理,只有当全部时间都没有准备好时,才在epoll里面等着。这样,咱们就能够并发处理大量的并发了,固然,这里的并发请求,是指未处理完的请求,线程只有一个,因此同时能处理的请求固然只有一个了,只是在请求间进行不断地切换而已,切换也是由于异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你能够理解为循环处理多个准备好的事件。

    与多线程相比,这种事件处理方式是有很大的优点的,不须要建立线程,每一个请求占用的内存也不多,没有上下文切换,事件处理很是的轻量级。并发数再多也不会致使无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已.

以前咱们提到nginx的负载均衡功能,那么和LVS的负载均衡有什么区别呢?

负载均衡分为:

    L4 switch(四层交换),即在OSI第4层工做,就是TCP层啦。此种Load Balance不理解应用协议(如HTTP/FTP/MySQL等等)。例子:LVS,F5

    L7 switch(七层交换),OSI的最高层,应用层。此时,该Load Balancer能理解应用协议。例子:haproxy,MySQL Proxy

不少Load Balancer(例如F5)既能够作四层交换,也能够作七层交换。

LVS 工做在网络4层仅作请求分发之用没有流量,可配置性低,几乎可对全部应用作负载均衡,对网络依赖大,没有健康检查机制。

nginx的7层(应用层),因此它能够针对http应用自己来作分流策略,好比针对域名、目录结构等,对网络依赖小,可检测服务器内部错误。


nginx能够根据URL进行负载均衡的请求转发,而LVS只能根据ip:port进行请求转发

通常状况下,LVS会被放在最前端作负载均衡,nginx可做为lvs的节点服务器。

前面咱们也提到过nginx实现邮件代理服务器的功能,通常使用nginx作邮件代理服务器的场景很少。

很不幸,nginx最先也是被看成邮件代理服务器来开发的。

--with-mail - 启用 IMAP4/POP3/SMTP 代理模块

安装时须要注意的库依赖:

gzip模块须要 zlib 库  
rewrite模块须要 pcre 库  
ssl 功能须要openssl库

咱们nginx通常安装在:/usr/local/nginx 目录,nginx的安装目录结构以下图所示

/usr/local/nginx

├── conf(配置文件目录)

│   ├── fastcgi.conf

│   ├── fastcgi.conf.default

│   ├── fastcgi_params

│   ├── fastcgi_params.default

│   ├── hosts

│   ├── koi-utf

│   ├── koi-win

│   ├── mime.types

│   ├── mime.types.default

│   ├── nginx_app.conf(应用相关配置段 server段)

│   ├── nginx.conf(nginx公用配置信息 events,http段

│   ├── nginx.conf.default

│   ├── nginx_status.conf

│   ├── proxy.conf

│   ├── scgi_params

│   ├── scgi_params.default

│   ├── uwsgi_params

│   ├── uwsgi_params.default

│   └── win-utf

├── html

│   ├── 50x.html

│   └── index.html

├── logs -> /data/applogs/nginx

├── sbin(nginx程序目录)

│   └── nginx

└── tmpdir

   ├── client_body_temp

   ├── fastcgi_temp

   ├── proxy_temp

   │   ├── 0

   │   │   └── 01

   │   ├── 1

   │   │   ├── 00

   │   │   └── 01

   │   ├── 2

   │   │   ├── 00

   │   │   └── 01

   │   ├── 3

   │   │   ├── 00

   │   │   └── 01

   │   ├── 4

   │   │   ├── 00

   │   │   └── 01

   │   ├── 5

   │   │   ├── 00

   │   │   └── 01

   │   ├── 6

   │   │   ├── 00

   │   │   └── 01

   │   ├── 7

   │   │   ├── 00

   │   │   └── 01

   │   ├── 8

   │   │   ├── 00

   │   │   └── 01

   │   └── 9

   │       └── 00

   ├── scgi_temp

   └── uwsgi_temp

nginx基本配置文件

#运行用户(worker进程属主)
user nobody;
#启动进程,设置成和cpu的数量相等
过多的worker数,只会致使进程相互竞争cpu资源,从而带来没必要要的上下文切换
worker_processes  4;
#全局错误日志及PID文件
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;
#工做模式及链接数上限
events {
    #epoll是多路复用IO(I/O Multiplexing)中的一种方式,
    #仅用于linux2.6以上内核,能够大大提升nginx的性能
    use   epoll;
    #单个后台worker process进程的最大并发连接数   
    worker_connections  1024;
    # 并发总数是 worker_processes 和 worker_connections 的乘积
    # 即 max_clients = worker_processes * worker_connections
    # worker_connections 值的设置跟物理内存大小有关
    # 由于并发受IO约束,max_clients的值须小于系统能够打开的最大文件数
    # 系统能够打开的最大文件数和内存大小成正比
    # $ cat /proc/sys/fs/file-max
    # 并发链接总数小于系统能够打开的文件句柄总数,这样就在操做系统能够承受的范围以内
    # worker_connections 的值需根据 worker_processes 进程数目和系统能够打开的最大文件总数进行适当地进行设置
    # 根据主机的物理可用CPU和可用内存进行配置
}
http {
    #设定mime类型,类型由mime.type文件定义
    include    mime.types;
    default_type  application/octet-stream;
    #设定日志格式
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  logs/access.log  main;
    #sendfile 指令指定 nginx 是否调用 sendfile 函数(zero copy 方式)来输出文件,
    #对于普通应用,必须设为 on,
    #若是用来进行下载等应用磁盘IO重负载应用,可设置为 off,以平衡磁盘与网络I/O处理速度,下降系统的uptime.
    sendfile     on;
    #tcp_nopush     on;
    #链接超时时间
    #keepalive_timeout  0;
    keepalive_timeout  65;
    tcp_nodelay     on;
    #开启gzip压缩
    gzip  on;
    gzip_disable "MSIE [1-6].";
    #设定请求缓冲
    client_header_buffer_size    128k;
    large_client_header_buffers  4 128k;
    #设定虚拟主机配置
    server {
        #侦听80端口
        listen    80;
        #定义使用 www.nginx.cn访问
        server_name  www.nginx.cn;
        #定义服务器的默认网站根目录位置
        root html;
        #设定本虚拟主机的访问日志
        access_log  logs/nginx.access.log  main;
        #默认请求
        location / {
                    
            #定义首页索引文件的名称
            index index.php index.html index.htm;  
        }
        # 定义错误提示页面
        error_page   500 502 503 504 /50x.html;
        location = /50x.html {
        }
        #静态文件,nginx本身处理
        location ~ ^/(p_w_picpaths|javascript|js|css|flash|media|static)/ {
                    
            #过时30天,静态文件不怎么更新,过时能够设大一点,
            #若是频繁更新,则能够设置得小一点。
            expires 30d;
        }
        #PHP 脚本请求所有转发到 FastCGI处理. 使用FastCGI默认配置.
        location ~ .php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
        #禁止访问 .htxxx 文件
            location ~ /.ht {
            deny all;
        }
    }
}


线上的一个应用的配置文件:

user                              nobody nobody;
worker_processes                  4;
worker_rlimit_nofile              51200;
error_log                         logs/error.log  notice;
pid                               /var/run/nginx.pid;
events {
  use                             epoll;
  worker_connections              51200;
}
http {
  server_tokens                   off;
  include                         mime.types;
  include                         proxy.conf;
  default_type                    application/octet-stream;
  charset                         utf-8;
  client_body_temp_path           /usr/local/nginx/tmpdir/client_body_temp 1 2;
  proxy_temp_path                 /usr/local/nginx/tmpdir/proxy_temp 1 2;
  fastcgi_temp_path               /usr/local/nginx/tmpdir/fastcgi_temp 1 2;
  uwsgi_temp_path                 /usr/local/nginx/tmpdir/uwsgi_temp 1 2;
  scgi_temp_path                  /usr/local/nginx/tmpdir/scgi_temp 1 2;
  ignore_invalid_headers          on;
  server_names_hash_max_size      256;
  server_names_hash_bucket_size   64;
  client_header_buffer_size       8k;
  large_client_header_buffers     4 32k;
  connection_pool_size            256;
  request_pool_size               64k;
  output_buffers                  2 128k;
  postpone_output                 1460;
  client_header_timeout           1m;
  client_body_timeout             3m;
  send_timeout                    3m;
  sendfile                        on;
  tcp_nodelay                     on;
  tcp_nopush                      off;
  reset_timedout_connection       on;
  keepalive_timeout               20 5;
  keepalive_requests              100;
  gzip                            on;
  gzip_http_version               1.1;
  gzip_vary                       on;
  gzip_proxied                    any;
  gzip_min_length                 1024;
  gzip_comp_level                 6;
  gzip_buffers                    16 8k;
  gzip_proxied                    expired no-cache no-store private auth no_last_modified no_etag;
  gzip_types                      text/plain application/x-javascript text/css application/xml application/json;
  gzip_disable                    "MSIE [1-6]\.(?!.*SV1)";
  include                         nginx_app.conf; #与应用有关的server配置
  include                         nginx_status.conf; #单机nginx访问统计配置


Nginx配置文件分为好多块,常见的从外到内依次是「http」、「server」、「location」等等,缺省的继承关系是从外到内,也就是说内层块会自动获取外层块的值做为缺省值。

root和alias的区别

当用户访问http://ip:port/nginx/qunying/helloword.jsp时,nginx上能够进行以下配置:

    location  /nginx/qunying/ {

       alias  /home/qunying.liu/;

       }

 实际访问的文件是:/home/qunying.liu/helloword.jsp

或      

    location /nginx/qunying/ {

       root  /home/;

       }

 实际访问的文件是:/home/qunying/helloword.jsp

在location /中配置root,在location /other中配置alias,推荐如此配置

nginx的目录浏览功能:

在server里加上以下三行便可。  
autoindex on;  
autoindex_localtime on;  
autoindex_exact_size off;

nginx登陆验证

在location段内添加:  
auth_basic "phoenix slb admin ";  
auth_basic_user_file /data/appdatas/phoenix-slb-passwd;

最后说下:

nginx与tengine

官网介绍:http://tengine.taobao.org/

Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了不少高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等获得了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。

咱们的软负载其实是基于tengine 开发的我的认为 tengine实际上就是nginx,只不过功能比nginx多,属于Nginx的超集。

  固然,咱们的科普之路才刚刚开始,更多的还须要你们自行研究,快乐每每都是自找的。


书籍推荐:

《深刻理解nginx:模块开发与架构解析》 做者:陶辉  阿里巴巴资深nginx技术专家

参考资料:


http://blog.csdn.net/syhd142/article/details/8440667

http://blog.csdn.net/yankai0219/article/details/8018275

http://blog.csdn.net/yankai0219/article/details/8018232

http://www.cppblog.com/converse/archive/2009/05/13/82879.html

http://www.alidata.org/archives/1208

http://www.oschina.net/translate/nginx-setup


本文原创做者为大众点评高级运维工程师:刘群英   转载请注明出处!