Silentdoer

导航

Nginx原理和配置总结

一:前言

  Nginx是一款优秀的HTTP服务器和反向代理服务器,除却网上说的效率高之类的优点,个人的切身体会是Nginx配置确实简单而且还好理解,和redis差不多,比rabbitmq好理解太多了;

这里Nginx可以作为Http服务器在我们安装部署启动后,访问http://localhost/就能看到这一特性;这里主要来说说反向代理:

1.正向代理

  举个最简单的例子,FQ;我们正常情况下是不能访问google等外国网站的,这时候可以通过代理服务器来实现,即我们主动配置一个proxy地址并启动成功,然后我们在浏览器上输入的所有访问请求

如http://188.155.88.3/testb,这时候浏览器不会去访问这个ip,而是去连接我们配置的代理服务,成功后将数据发送给代理服务,代理服务通过判断header中的host等值从而知道我们真正要访问的是

哪个host,然后代理服务器内部创建一个socket以tcp协议去连接我们想访问的host将我们原本的数据发送过去(一般会在header里额外加点东西)。。。扯远了,正向代理最大的特点是客户端主动配置

代理服务器,由代理服务器完成客户端和客户端本身想访问的服务之间的数据传输;

2.反向代理

  反向代理特点则是:客户端不知道自己真正要沟通的是谁,只是别人提供了一个代理服务器的ip和port,然后客户端就只需要访问这个地址即可;这里我们暂认为反向代理服务器就是nginx;然而nginx

在配置了proxy_pass后成为反向代理,它会主动将客户端的数据通过ip+port数据转接的方式发送给在nginx里配置好的服务,这一步客户端是不清楚的,他以为反向代理服务器就是真正处理自己请求的服务

,而不像正向代理一样客户端事先知道自己只是通过这个正向代理服务实现FQ而以(或者说正向代理是客户端可以指定要访问的服务由代理服务实现转接,而反向代理客户端无法指定转接规则);

3.流量转发原理

  无论是正向代理还是反向代理,都需要通过流量转发来实现代理功能;它的原理是:1)正/反代理服务器先bind一个ip和端口;2)客户端对此地址发起连接并发送数据;

3)代理服务器收到数据后根据规则(常用规则有1.根据host将client的数据转发到host:80里;2.根据代理服务器内部事先规定好的规则如queryPath/host/固定端口-ip:端口[tcp nat]等)找出要转发的ip:port;

4)代理服务器创建一个如socket对象并连接3步骤中找到的地址,成功后将client发给代理服务的数据(可以进行一定修饰)通过创建的socket对象[代理服务主机的另一个port,这也是为什么有可同时接收连接数量配置的存在理由之一,太多了端口没那么多]将

其转发给3中匹配到的真正的服务器[但是这个真正的服务器指不定还是个代理服务器,会只需去找,有点像链表的感觉];

5)处理服务器对转发过来的数据进行处理(一般http请求的转发是会在header里添加origin的,故处理服务器能依次判断要不要对此请求进行处理,否则处理服务器只知道代理服务器向自己发起连接并发送了请求而不知道真正的源),将处理结果返回给

代理服务器;

6)代理服务器的socket[不一定是socket只是以此举例]收到数据后(此socket不是bind的socket它们端口也不一样),将数据提取出来(可能有一定的修饰)并让另一个socket(这个socket是客户端连接代理服务器,代理服务器接收请求后产生的port和serversocket一致

的那个)将此数据send给客户端;

至此,一条完整的代理流程就弄完了,这里原理其实不难,难在各种特殊情况的控制,比如如果Client->Proxy发送数据后会发送协定的结束标识或事先双方有格式规约,且发送完后会先等待Proxy返回数据然后才发第二个消息,那么其实非常好实现(http协议的代理

比较容易,因为可以通过content-length来判断数据是否发送完毕而且一般也是发送一次数据后会等结果才发第二次数据,如果是短连接更好办了),最难的是stream的代理,它可能存在Client发送数据后Proxy将数据转发给server,还没处理client又来了数据,这里

就需要有比较好的设计才能支撑这种较为复杂的业务;目前我的理解是proxy将client发来的数据都串行化放入队列,然后由一个单线程按自己的能力去从队列里取数据并转发;

二:Nginx配置整理

  上面讲了很多啰嗦的话,这里正式通过配置文件来说下自己对Nginx配置的理解:

user nginx nginx;  # 这个配置不是很重要,可以不配置,不过一般都会为后台进程创建一个nologin的账号,如mysql,我这里为nginx创建的是nginx账号,group也是一样[用www貌似较多],这里第一个是user,第二个是group
worker_processes  4;  # 允许生成的进程数,默认为1,按原作者的话一个足够只需要把连接数设置到足够大即可,但是如果存在同时对多个文件的占用,使用gzip等可以增加(似乎是因为os会限制进程可同时占用的资源数),一般不超过cpu个数的2倍,但设置为cpu核数即可

# 这里用的是相对的是nginx的安装目录,即logs在/usr/local/nginx下,可以配置绝对路径将日志写到其它地方 # 级别:debug
|info|notice|warn|error|crit|alert|emerg error_log logs/error.log crit; # 日志级别,不要配太小,毕竟这是高并发的服务,如果什么都要记录日志肯定影响性能; # 可要可不要,就是方便kill时能获取其pid,原理是nginx启动后会找到自己的pid然后将其写入此文件里,但是还是通过./nginx -s quit|stop就可以了;注意nginx是daemon进程 pid logs/nginx.pid; worker_rlimit_nofile 50000; # nginx可以同时打开的文件个数 events { multi_accept on; # 设置一个进程是否同时接受多个网络连接,默认off # 事件驱动模型,select/poll/kqueue/epoll/resig/dev-poll/eventport use epoll; worker_connections 60000; # 同时连接数,即代理时可同时创建多少个中间socket; }
# 注意,这里写了http表示内部的都是根http协议相关的,后面还会加个stream的,它表示普通的tcp数据 http { # 文件扩展名与文件类型映射表(可以去看conf目录,里面有个mime.types文件,里面的内容为:
    # types {
      #  text/html                             html htm shtml;
    #  text/css                  css;
    #  .....等等 } include mime.types;
  # 默认的类型,即流数据 default_type application
/octet-stream; # 这个应该是记录日志时的pattern,名为main,下面的 access_log logs/access.log main;就用到了
  
 # 1.$remote_addr记录的是直接连接nginx的ip,它可能是个代理服务;  而$http_x_forwarded_for的记录格式是:clientip,proxy1,proxy2...故能获取origin和中间节点信息; 2.$remote_user:用来记录客户端用户名称;
  # 3.$time_local: 用来记录访问时间与时区;4.$request: 用来记录请求的url与http协议; 5.$status: 用来记录请求状态;成功是200;
  # 6.$body_bytes_sent:记录发送给客户端文件主体内容大小;7.$http_referer: 用来记录从那个页面链接访问过来的(这个由浏览器实现,浏览器能知道当前页的地址); 8.$http_user_agent: 记录客户端浏览器的相关信息(还能记录手机类型); 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; # 访问日志,比如application如果没有获取到参数可以查nginx的访问日志看客户端是否提供了参数从而缩小排查范围
    server_names_hash_bucket_size 128; # 据说是根保存服务器名字的hash表有关,还有另一个协同的配置项server_names_hash_max_size;貌似是如果添加了很多的域名后nginx如果报错可以增大此值(以32的倍数增大)
# 配置关于客户端数据限制的一些参数
client_header_buffer_size 32k; # 指http请求header的缓存大小,默认是1k large_client_header_buffers 4 32k; # 当上面配置的缓存不够大时这个时新的缓存大小 client_max_body_size 8m;  # 这个可以考虑再加大,毕竟body是存储数据的主要地方(如果是多文件则考虑multipart)
  # nodelay是指服务器处理好后立刻就将结果数据发送给客户端而不等待数据到达一定量(tcp是有这方面优化的,比如我返回数据指有5b,那么tcp默认会等待达到1k时才一次性发送,这里就是为了禁用这种选项),毕竟处理结果数据少是正常的
  tcp_nodelay on; sendfile on; # 允许sendfile方式传输文件,默认off tcp_nopush on;  # 和nodelay有些类似,也是阻止立刻发送的
keepalive_timeout 65; # 空闲状态长连接保持的时间,并不是说header里设置了keepalive就一直保持长连接,还要看是否长时间空闲
    # 经过看了conf下的多个fastcgi相关的文件,都有写PHP only,故如果我们不用php应该可以不配置; # cgi 是common gateway interface通用网关接口的意思 #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;
    # 注意,个人不建议在nginx里开启gzip,因为gzip功能完全可以由真正的处理服务器去做,nginx只需要进行数据传输和流量转发即可,这样可以保证nginx的高效性(看情况,如果nginx负载的机器在3台左右那么也可以由nginx来gzip) # gzip 最好不要在nginx里配置,毕竟nginx最好只是做流量转发不要有\ # 任何业务操作或其它耗资源的操作   #gzip on; #gzip_min_length 1k; #gzip_buffers
4 16k; #gzip_http_version 1.0; # 注意只能是1.0不能1.1,因为CDN抓取目前只能1.0 #gzip_comp_level 2; # 压缩等级 #gzip_types text/plain application/x-javascript text/css application/xml; #gzip_vary on; # 这里是nginx实现负载均衡的关键,这个是定义一个服务器名,但它可以对应多个服务,这里可以配置1.热备[下面的就是];2.轮询[将backup去掉就是]即内部的server权重一样会循环的分别转发给server;3.加权轮询,ip后加上weight=n即可,n越大权重越大
# 最好用加权轮训,比如要更新服务时,可以先将某台的权值设置为0(或者该台需要重启),这样nginx就不会负载到那台上,这样就可以比较平滑的一台一台的升级上去;
     # 4.ip_hash,最下面加个ip_hash;即可,nginx会让相同的客户端都由一个server处理(或说ip具有共性的客户端由一个server处理,这个共性就包括了ip一样)
upstream myhost1 { server 127.0.0.1:8080; server 127.0.0.1:8081 backup; # 热备,当127.0.0.1:8080无法访问时启用 } # 注意这个server是可以配置多个的,但则是一个虚拟主机,即可以有多个server它的监听端口都是80,但是前提是server_name不一样(host),即A:http://test1.com/api和B:http://test2.com/api传到同一个nginx服务里是可以被区分的(尽管换成ip后他们是一样的) # 顺便HTTPS和HTTP的端口不一样的前者默认是443,后者默认是80 server { keepalive_requests 1024; # 此服务同时保持keepalive的客户端的数量; listen 80; # 此虚拟主机监听的端口 # 注意,很重要的参数,如果nginx转发多个80端口的数据到不同tomcat就靠此区分 server_name localhost; # 域名,虽然连接是靠ip,但header里还是有域名的 #charset koi8-r; #access_log logs/host.access.log main; # 注意,nginx配置里location有多种模式,优先级是:(location =) > (location 完整路径) > (location ^~ 路径) > (location ~,~* 正则顺序) > (location 部分起始路径) > (/)
    # 这个有点像tomcat的url-pattern;其中/的优先级最小但是可以匹配任何情况;而=的优先级最大但是只能绝对匹配;而location /aaa可以匹配如http:../aaab但是如果不是完全匹配则其优先级比后面的正则表达式的要小(~匹配大小写,~*不匹配大小写)
    # 均不匹配queryString部分,即?xx=bb&nn之类的不作为匹配(但据说可以自己升级此部分的处理模块来支持更多的匹配);详情可以看:https://www.cnblogs.com/cheyunhua/p/7927674.html
location / { #root html; # 可以改到srv的www目录内,这两个配置是不转发时的配置 #index index.html index.htm; # 设置默认页 proxy_pass http://myhost1; # 用于流量转发,注意这个myhost1和upstream的对应 #deny 192.168.0.88; # 拒绝的ip #allow 192.168.0.14; # 允许的ip
        # 还可以配置proxy_connect_timeout和read/write的超时时间,即nginx创建一个socket和处理服务建立连接将客户端的数据发送给处理服务,但是可以配置此socket和处理服务之间的超时关系; } # 正则表达式的匹配,开头
~表示区分大小写,而~*则不区分 # 这是个perl类型正则表达式,可以将静态资源由一个服务统一处理,也可以由nginx之间转发,或者根据后缀规则转发各个系统共用的静态资源(其它的资源可以通过url映射实现无后缀访问) location ~^.*\.(gif|jpg|jpeg|png|bmp|swf|ico|js|css)$ { root html; # 这个则可以转发给一个专门处理静态资源的处理器处理,或则nginx直接处理 #proxy_pass http://127.0.0.1:8080; expires 30d;  # 设置静态资源在缓存里的过期时间,如果不经常更新可以将值弄大一点; # access_log off; }

        access_log off; # 不记录访问日志 #error_page 404 /404.html;
    # 配置error_page 中间的是具体的error,后面的则是表示转发的地址(有点像java的requestDispatcher.forward(..),然后后面的location就是为了和此forward匹配从而从html目录里找到此文件并返回给客户端
#error_page 500 502 503 504 /50x.html; #location = /50x.html { # root html; #} # 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; # } #} }

 这里还有一种stream的配置,和http{...}同级,针对的是更加底层的tcp协议,要用到ngx_stream_core_module模块,这个模块需要在安装时指定--with-stream;具体配置如下(和events平级)

events{
    worker_connections 1024;
}

stream{
    upstream host1{
        server 192.168.0.11:9090;  // 可以只填一个server
        server 192.168.0.12:9090;
        ip_hash;
    }

    // 注意tcp协议没有uri的概念,故server内不存在location的说法   
    server {
        listen 7778;
        proxy_pass host1;  // 注意这里和http不一样的地方,http这里是写http://host1;
} }

 

posted on 2018-04-09 17:07  Silentdoer  阅读(1242)  评论(0编辑  收藏  举报