**本人博客网站 **IT小神 www.itxiaoshen.com
Nginx官网 最新版本为1.21.3
Nginx (engine x) 是一个开源的、高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务,由俄罗斯的程序设计师IgorSysoev所开发,官方测试nginx能够支撑5万并发连接,并且cpu、内存等资源消耗却非常低,运行非常稳定,支持热部署,几乎可以实现7*24小时不间断运行。
可以说只要有网站或者后台服务的企业就会需要用到Nginx,其使用极为广泛。
#以RHEL/CentOS为例 #安装先决条件 sudo yum install yum-utils #在新机器上第一次安装nginx之前,需要设置nginx包存储库。之后,您可以从存储库安装和更新nginx,要设置yum存储库,创建文件/etc/yum.repos.d/nginx [nginx-stable] name=nginx stable repo baseurl=http://nginx.org/packages/centos/$releasever/$basearch/ gpgcheck=1 enabled=1 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true [nginx-mainline] name=nginx mainline repo baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/ gpgcheck=1 enabled=0 gpgkey=https://nginx.org/keys/nginx_signing.key module_hotfixes=true #默认情况下,使用稳定nginx包的存储库。如果你想使用主线nginx包,请运行以下命令: sudo yum-config-manager --enable nginx-mainline #使用实例安装nginx: sudo yum install nginx
如果一些特殊的功能是必需的,而不是包和端口提供的,nginx也可以从源文件编译。虽然这种方法更灵活,但对于初学者来说可能比较复杂,下面我们进行Nginx源码安装
#编译前提,需要安装必要的包 yum install gcc pcre-devel openssl-devel zlib-devel -y #从Nginx官网下载源码文件 wget https://nginx.org/download/nginx-1.21.3.tar.gz #解压下载的压缩文件 tar -xvf nginx-1.21.3.tar.gz #进入解压的nginx目录 cd nginx-1.21.3 #参数使用示例(所有这些都需要在一行中输入),需要定制特别参数可以详细参考官网源代码安装说明,https://nginx.org/en/docs/configure.html ./configure --prefix=/usr/local/nginx \ --with-http_ssl_module \ --with-http_v2_module \ --with-http_realip_module \ --with-http_stub_status_module \ --with-http_gzip_static_module \ --with-pcre \ --with-stream \ --with-stream_ssl_module \ --with-stream_realip_module #配置完成后,使用make编译并安装nginx,-j 8 表示CPU八核,可以按照实际CPU核数定义,越多越快 make -j 8 && make install
修改/usr/local/nginx/conf/nginx.conf,将监听端口改为8082,
启动nginx和访问本机的8082端口,http://localhost:8082,返回nginx欢迎页面,nginx部署完毕
cd /usr/local/nginx/sbin/ #启动nginx ./nginx #停止nginx ./nginx -s stop #安全退出nginx ./nginx -s quit #重新加载配置文件启动nginx,相当于热启动 ./nginx -s reload #查看nginx进程 ps aux | grep nginx
关于nginx https://nginx.org/en/ ,详细可以查阅这个官网文档
从HTTP缓存看Nginx进程架构
nginx是C语言编写的,采用的多进程架构模式,一个master和多个worker(worker数量一般为cpu核数),Master负责管理worker进程,worker进程负责处理网络事件,整个框架被设计为一种依赖事件驱动、异步、非阻塞的模式。这样设计有点如下:
如果有兴趣要深入了解nginx源码可研究nginx的upstream(工作方式)、线程池(线程管理,)、内存池(建立一个大块内存缓冲,不需要每次使用去申请)、网络IO、进程间通信如共享内存、epoll、多进程、日志处理、conf配置文件管理、原子操作、http模块、链表、红黑树以及进一步理解Nginx业务流程架构;Nginx提供高度灵活性,通常我们可以自己编写C模块嵌入到nginx程序中,可以基于nginx模块化开发实现类似限流、黑白名单、认证鉴权验证等功能。
Nginx官方功能和特性描述如下:
正向代理作用在客户端的,类似一个跳板机,代理访问外部资源,比如我们国内访问谷歌,直接访问访问不到,我们可以通过一个正向代理服务器,请求发到代理服,代理服务器能够访问谷歌,这样由代理去谷歌取到返回数据,再返回给我们,这样我们就能访问谷歌了。正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端。
反向代理是作用在服务器端的,代理服务器对外就表现为一个服务器,实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端;反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端。
反向代理的作用:
Nginx本身就是一个非常强劲的静态资源服务器,如通过location配置实现动态页面转发后端tomcat服务器,在nginx站点下存放静态资源文件或站点,加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速度,降低原来单个服务器的压力。 简单来说,就是使用正则表达式匹配过滤,然后交个不同的服务器。
动静分离的原理很简单,通过location对请求url进行匹配即可,在/usr/local/nginx/html/(任意目录)下创建 /static/imgs 配置如下:
###静态资源访问 server { listen 80; server_name static.itxiaoshen.com; location /static/imgs { root /usr/local/nginx/html/imgs; index index.html index.htm; } } ###动态资源访问 server { listen 80; server_name www.itxiaoshen.com; location / { proxy_pass http://127.0.0.1:8080; //这里如果有多台也可以通过配置upstream然后在这里引用,详细可以看下一节 index index.html index.htm; } }
Nginx提供丰富的负载均衡算法,包括轮询、加权轮询、最少连接、响应时间最小、iphash、urlhash。
nginx默认负载均衡算法,可以配合权重使用,默认情况权重是1。
upstream backend { #没有负载均衡算法定义则默认为Round Robin server backend1.example.com; server backend2.example.com; }
upstream backend { server backend1.example.com weight=10; server backend2.example.com weight=10; }
请求会转发到当前有效连接最少的服务器,可以配合权重使用。
upstream backend { least_conn; server backend1.example.com; server backend2.example.com; }
由请求客户端的IP地址决定请求发往哪台服务器,可以保证同一个IP地址的请求可以转发到同一台服务器。IPV4的前三位或者IPV6的全部地址参与哈希运算。
upstream backend { ip_hash; server backend1.example.com; server backend2.example.com; }
如果想要暂时移除当前负载服务器组中的某一台服务器,可以用down
参数标记服务器,已经通过IP哈希到当前服务器的请求会暂时保持,并自动被转移到组中的下一台服务器。
upstream backend { server backend1.example.com; server backend2.example.com; server backend3.example.com down; }
请求会根据自定义的字符串、变量或者二者的组合作为key进行哈希,决定发往哪台服务器。比如按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身是不支持url_hash的,如果需要使用这种调度算法,必须安装Nginx 的hash软件包。
upstream backend { hash $request_uri consistent; hash_method crc32; server backend1.example.com; server backend2.example.com; }
hash指令中consistent 参数是可选的,如果指定了consistent参数,负载均衡会使用一致性哈希中的ketama算法,请求会根据定义的哈希键值哈希均匀的发往组中的所有服务器上。给服务器组中增加新服务器或者移除要原来的服务器,只会有少数的哈希键会重新映射。
比上面两个更加智能的负载均衡算法。根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身是不支持fair的,如果需要使用这种调度算法,必须下载Nginx的upstream_fair模块。或者使用商业版本的Nginx Plus,此外在在upstream中每个主机后面配置 max_conns可以限制每个后端服务器的处理连接数。
upstream backend { server server1; server server2; fair; }
nginx配置缓存的优点:可以在一定程度上,减少服务器的处理请求压力。比如对一些图片,css或js做一些缓存,那么在每次刷新浏览器的时候,就不会重新请求了,而是从缓存里面读取。这样就可以减轻服务器的压力。详细可参考官网说明
nginx可配置的缓存有2种:
worker_processes 1; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; include nginx_proxy.conf; proxy_cache_path /data/nuget-cache levels=1:2 keys_zone=nuget-cache:20m max_size=50g inactive=168h; #gzip on; server { listen 8081; server_name xxx.abc.com; location / { proxy_pass http://localhost:7878; add_header Cache-Control max-age=no-cache; } location ~* \.(css|js|png|jpg|jpeg|gif|gz|svg|mp4|ogg|ogv|webm|htc|xml|woff)$ { access_log off; add_header Cache-Control "public,max-age=30*24*3600"; proxy_pass http://localhost:7878; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
#指定缓存位置、缓存名称、内存中缓存内容元数据信息大小限制、缓存总大小限制。缓存位置是一个目录应该先创建好,nginx并不会帮我们创建这个缓存目录 proxy_cache_path /data/nginx/cache keys_zone=one:10m max_size=10g; #指定使用前面设置的缓存名称 proxy_cache one;
URL重写有利于网站首选域的确定,对于同一资源页面多条路径的301重定向有助于URL权重的集中,rewrite的组要功能是实现URL地址的重定向。Nginx的rewrite功能需要PCRE软件的支持,即通过perl兼容正则表达式语句进行规则匹配的。默认参数编译nginx就会支持rewrite的模块,但是也必须要PCRE的支持,rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到replacement,结尾是flag标记。详细可参考官网说明
Nginx限流的实现主要是ngx_http_limit_conn_module和ngx_http_limit_req_module这两个模块,按请求速率限速模块使用的是漏桶算法,即能够强行保证请求的实时处理速度不会超过设置的阈值。Nginx官方版本限制IP的连接和并发分别有两个模块:zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 “leaky bucket”。limit_req_conn 用来限制同一时间连接数,即并发限制。
算法思想:
算法思想:
#先安装压测工具,当前我们java开发在windows熟悉的jmeter有相似的功能 yum install -y httpd-tools #编辑 vim /usr/local/nginx/conf/nginx.conf #将limit_req_zone放在http中 limit_req_zone $binary_remote_addr zone=MyReqZone:10m rate=1r/s; #将limit_req放在server中 limit_req zone=MyReqZone burst=5 nodelay; #更新nginx配置 ./nginx -s reload #执行访问测试 ab -c 1 -n 50 http://localhost:8082/
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPS50iJd-1633588453688)(F:\creation\markdown\article\Nginx大厂面试需要掌握多少\Nginx大厂面试需要掌握多少.assets\image-20211007124839533.png)]
#将limit_conn_zone放在http中,limit_conn perip 10:对应的key是 $binary_remote_addr,表示限制单个IP同时最多能持有10个连接。limit_conn perserver 100:对应的key是 $server_name,表示虚拟主机(server) 同时能处理并发连接的总数。注意,只有当 request header 被后端server处理后,这个连接才进行计数。 limit_conn_zone $binary_remote_addr zone=MyPerIp:10m; limit_conn_zone $server_name zone=MyPerServer:10m; #将limit_conn放在server中 limit_conn MyPerIp 10; limit_conn MyPerServer 100;
我们通常使用开源Nginx版本,基于Nginx开源版本至上还衍生其他版本,包括商业收费版本的Nginx Plus、淘宝开源的TEngine、OpenResty。
NGINX Plus官网 https://www.nginx.com/resources/datasheets/nginx-plus-datasheet/
NGINX Plus以先进的功能和屡获殊荣的支持扩展了NGINX开源,为客户提供了完整的应用交付解决方案。NGINX Plus将负载平衡器、内容缓存、web服务器、安全控制和丰富的应用程序监控和管理整合到一个易于使用的软件包中。
TEngine官网 http://tengine.taobao.org/
Tengine是由淘宝网发起的Web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。Tengine的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的Web平台。
从2011年12月开始,Tengine成为一个开源项目,Tengine团队在积极地开发和维护着它。Tengine团队的核心成员来自于淘宝、搜狗等互联网企业。Tengine是社区合作的成果,我们欢迎大家参与其中,贡献自己的力量。
TEngine特性:
OpenResty官网 http://openresty.org/cn/
OpenResty® 是一个基于Nginx 与 Lua 的高性能Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
OpenResty® 通过汇聚各种设计精良的 Nginx 模块(是国人章亦春发起和创建,目前主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。
OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。
OpenResty强大之处就是提供许许多多的组件,开箱即用
通过git编译成机器码缓存起来,直接执行机器码,lua git效率更高,lua git将lua虚拟机vm嵌入到进程中,lua nginx可以在设置阶段中通过lua命令嵌入到nginx中并回显。Cosocket实际上也是些协程,采用异步事件网络IO,发送和接收数据是两个流程,Cosocket实现了一个同步非阻塞的模式
#在redis存储hello的String类型key,值为heihei,下面这句代码OpenResty->redis发送并挂起当前的协程,等redis server返回数据的时候激活挂起的协程返回数据 Local val = redis:get("hello")
在Open Resty四个部分,lua nginx module 、测试集、resty lrucache redis 、工具集。lua可以随心所欲的做复杂的访问控制和安全检查,防止一些注入和xss的攻击。比如可以在Open Resty实现黑白名单(黑名单数据可以存储在mysql或者redis中,通过lua访问后端存储服务)、身份授权,还有比如很多做电商行业通过在Open Resty中将一些并发访问极大的商品详细页嵌入到nginx实现,通过Open Resty以使用同步编程方式但实际上内部确是异步非阻塞的模式访问redis获取库存数据等并构建成静态页面返回给用户,lua_shared_dict、lua_resty_lrucache、lua_resty_lock 解决缓存失效风暴问题。
Nginx实质上也即是一个网关,Kong网关也是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的,能提供易于使用的RESTful API来操作和配置API管理系统,所以它可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。
配置文件与模块化设计
OpenResty中,Nginx 的模块化设计使得每一个 HTTP 模块可以仅专注于完成一个独立的、简单的功能,而一个请求的完整处理过程可以使由无数个 HTTP 模块共同合作完成。
# add the yum repo: wget https://openresty.org/package/centos/openresty.repo sudo mv openresty.repo /etc/yum.repos.d/ # update the yum index: sudo yum check-update sudo yum install -y openresty sudo yum install -y openresty-resty #安装完找到/usr/local/openresty
#进入/usr/local/openresty/nginx/conf目录,编辑下面这段嵌入一段简单lua脚本,可以执行代码块或者脚本文件 worker_processes 1; error_log logs/error.log; events { worker_connections 1024; } http { server { listen 8084; location / { default_type text/html; content_by_lua_block { ngx.say("<p>hello, world</p>") } } } } ##进入到openresty的nginx bin目录 cd /usr/local/openresty/nginx/sbin ##启动openresty的nginx ./nginx #访问nginx,出现我们使用lua代码块回显的内容,至此可以开启openresty的编程大门 curl http://localhost:8084
由于worker都是由master进程fork产生,所以worker都会同时监听相同端口,有IO事件所有进程同时被唤醒然后挂起,但只有一个进程能否获取到,这样多个子进程在accept建立连接时会发生争抢,带来著名的“惊群”问题。
nginx采取在同一个时刻只有一个进程在监听端口的机制,实现如下ngx_shmtx_t 是共享变量、共享内存是多进程通信最快方式,所有worker进程使用同一把锁,采用无锁的CAS实现。
Nginx使用业务场景主要是一个web服务器,有处理无状态的如http请求,如果采用多线程就需要加锁和同步问题,采用多进程可以对每次请求做到最大的隔离。
我们知道使用nginx -s reload就可以不停止nginx服务情况下更新配置,master负责解析配置文件、监控worker进程,worker进程负责listen,一个worker进程同时监听多个server端口,worker之间是竞争的关系,通过master将内存配置结构更新写入共享内存中下一次就可以获取最新的配置信息。
Nginx支持配置热更新和程序热更新,Nginx热更配置时,可以保持运行中平滑更新配置,具体流程如下:
Worker进程在处理网络事件时,依靠epoll模型,来管理并发连接,实现了事件驱动、异步、非阻塞等特性。通常海量并发连接过程中,每一时刻(相对较短的一段时间),往往只需要处理一小部分有事件的连接即活跃连接。基于以上情况,epoll通过将连接管理与活跃连接管理进行分离,实现了高效、稳定的网络IO处理能力。其中,epoll利用红黑树高效的增删查效率来管理连接,利用一个双向链表来维护活跃连接。
可以通过基于VRRP虚拟路由器冗余协议的keepalived方案实现多台nginx故障自动切换,核心也即是控制同一个VIP虚拟IP在多台主机漂移。
多进程实现并发编程强调的是稳定性,每个进程有自己独立的地址空间,一个进程挂了不影响其他的进程,但进程间的通信方式实现还是比较麻烦的,比如管道、有名管道、信号量、消息队列、信号、共享内存、套接字等。
多线程实现并发编程主要是共享进程的地址空间,一个线程挂了或者写乱数据有可能影响其他线程甚至整个应用程序,也即是常说线程安全问题,多线程交换数据比较方便,线程之间的通信也可以直接通过内存来实现。多线程其实并不是多个线程一起执行,而是因为线程之间切换的速度非常的快,所以我们看起来像不间断的执行。
并发编程充分利用多核CPU或者多处理器的计算能力,从设计上方便进行业务拆分以提升应用程序性能。并发指的是多个事情,在同一时间段内同时发生了; 而并行指的是多个事情在同一时间点上同时发生了;并发的多个任务之间是互相抢占资源的; 并行的多个任务之间是不互相抢占资源的,只有在多CPU的情况中,才会发生并行;否则,看似同时发生的事情,其实都是并发执行的。
#第一种 for(i=0;i++;i<n) for(j=0;j++;j<n) array[i][j] = 0; #第二种 for(i=0;i++;i<n) for(j=0;j++;j<n) array[j][i] = 0;
经过测试,第一种比第二种的执行时间要快好几倍甚至几十倍
需要先确定下是属于行布局还是列布局的内存布局方式,二维数组内存是行布局,因为二维数组 array 所占用的内存是连续的,内存中的数组元素的布局顺序如下图所示
第一种方式缓存行批量读取优势。用 array【i】【j】访问数组元素的顺序,正是和内存中数组元素存放的顺序一致。当 CPU 访问 array[0][0] 时,由于该数据不在 Cache 中,于是会「顺序」把跟随其后的 3 个元素从内存中加载到 CPU Cache,这样当 CPU 访问后面的 3 个数组元素时,就能在 CPU Cache 中成功地找到数据,这意味着缓存命中率很高,缓存命中的数据不需要访问内存,这便大大提高了代码的性能
第一种方式充分利用缓存行大小64k的局部性原理。访问 array【i】【j】元素时,CPU 具体会一次从内存中加载多少元素到 CPU Cache 呢?这跟 CPU Cache Line 有关,它表示 CPU Cache 一次性能加载数据的大小,可以在 Linux 里通过 coherency_line_size 配置查看 它的大小,通常是 64 个字节。
定义:f ( 0 ) = f ( 1 ) = 1 , f ( n ) = f ( n − 1 ) + f ( n − 2 ) ( n ≥ 2 )
第一种:递归普通方法,能体现递归思维能力,算法时间复杂度是指数级增加,根据递归数而定。
int Fibnacci(int n) { if(n == 0){ return 1; }else if(n == 1){ return 1; }else { return Fibnacci(n-1) + Fibnacci(n-2); } }
第二种: 记忆化搜索优化后的递归算法,开辟一块空间,利用-1赋值方式,采用记忆化搜索的功能,此外动态规划基础也是记忆化搜索。
我们从节点5开始,依次来到节点4,节点3,节点2,节点1(上图红色节点)。并且在递归返回过程中,节点2,节点3的值被计算出。此时递归返回来到节点4,f ( 4 ) = f ( 3 ) + f ( 2 ) f(4)=f(3)+f(2)f(4)=f(3)+f(2),如果是一般的递归,此时还需要重新计算f ( 2 ) f(2)f(2);但在记忆化搜索中,f ( 2 ) f(2)f(2)的值已经被m e m o memomemo记录下来了,实际并不需要继续向下递归重复计算,函数可以直接返回。最后,递归返回来到了节点5,本应重复计算的f ( 3 ) f(3)f(3)由于m e m o memomemo的记录,也不需要重复计算了。所以,记忆化搜索可以起到剪枝的作用,对于已经保存的中间结果 ,由于其记忆能力,并不需要重新递归计算了。
int dp[] = new int[100]; int Fibnacci(int n) { if (n == 0 || n == 1) return 1; if (dp[n] != -1){ return dp[n]; }else{ dp[n] = Fibnacci(n-1) + Fibnacci(n-2); return dp[n]; } }
第三种:降低复杂度,o(n)复杂度,倒着去推,从0开始计算,记忆化
int dp[] = new int[100]; int Fibnacci(int n) { if (n == 1 || n == 2){ dp[n] = n; } else { if (dp[n] == -1){ dp[n] = Fibnacci(n-1) + Fibnacci(n-2); } } return dp[n]; }
第四种:最优方法也即是数学公式计算,变成O(1)的时间复杂度:F(n)=(1/√5)*{[(1+√5)/2]^n - [(1-√5)/2]^n},如果能回答出这个那就非常不错了
后续我们再找时间深入分析Nginx源码、Nginx的C模块化开发、Nginx高阶及高可用实战,OpenResty编程实战。