系统相关
首页 > 系统相关> > Nginx后端开发人员必学神器-并发编程经典之作剖析和名企热点面试

Nginx后端开发人员必学神器-并发编程经典之作剖析和名企热点面试

作者:互联网

概述

**本人博客网站 **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

image-20211006133414806

源码安装

如果一些特殊的功能是必需的,而不是包和端口提供的,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

image-20211006142949217

image-20211006143105583

修改/usr/local/nginx/conf/nginx.conf,将监听端口改为8082,

image-20211006144127074

启动nginx和访问本机的8082端口,http://localhost:8082,返回nginx欢迎页面,nginx部署完毕

image-20211006144252762

常用命令

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常用特性

概述

关于nginx https://nginx.org/en/ ,详细可以查阅这个官网文档

image-20211006175317457

从HTTP缓存看Nginx进程架构

image-20211006180920191

nginx是C语言编写的,采用的多进程架构模式,一个master和多个worker(worker数量一般为cpu核数),Master负责管理worker进程,worker进程负责处理网络事件,整个框架被设计为一种依赖事件驱动、异步、非阻塞的模式。这样设计有点如下:

如果有兴趣要深入了解nginx源码可研究nginx的upstream(工作方式)、线程池(线程管理,)、内存池(建立一个大块内存缓冲,不需要每次使用去申请)、网络IO、进程间通信如共享内存、epoll、多进程、日志处理、conf配置文件管理、原子操作、http模块、链表、红黑树以及进一步理解Nginx业务流程架构;Nginx提供高度灵活性,通常我们可以自己编写C模块嵌入到nginx程序中,可以基于nginx模块化开发实现类似限流、黑白名单、认证鉴权验证等功能。

Nginx官方功能和特性描述如下:

image-20211006145950083

image-20211006150051696

反向代理

正向代理

正向代理作用在客户端的,类似一个跳板机,代理访问外部资源,比如我们国内访问谷歌,直接访问访问不到,我们可以通过一个正向代理服务器,请求发到代理服,代理服务器能够访问谷歌,这样由代理去谷歌取到返回数据,再返回给我们,这样我们就能访问谷歌了。正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端。

image-20211006190622482

反向代理

反向代理是作用在服务器端的,代理服务器对外就表现为一个服务器,实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端;反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端

反向代理的作用:

image-20211006190137447

动静分离

Nginx本身就是一个非常强劲的静态资源服务器,如通过location配置实现动态页面转发后端tomcat服务器,在nginx站点下存放静态资源文件或站点,加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速度,降低原来单个服务器的压力。 简单来说,就是使用正则表达式匹配过滤,然后交个不同的服务器。

image-20211006194339511

动静分离的原理很简单,通过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。

image-20211007102601758

轮询(Round Robin)

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;
}

最少连接(Least Connections)

请求会转发到当前有效连接最少的服务器,可以配合权重使用。

upstream backend {
    least_conn;
    server backend1.example.com;
    server backend2.example.com;
}

IP哈希(IP Hash)

由请求客户端的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;
}

URL哈希

请求会根据自定义的字符串、变量或者二者的组合作为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算法,请求会根据定义的哈希键值哈希均匀的发往组中的所有服务器上。给服务器组中增加新服务器或者移除要原来的服务器,只会有少数的哈希键会重新映射。

响应时间最小fair(第三方)

比上面两个更加智能的负载均衡算法。根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身是不支持fair的,如果需要使用这种调度算法,必须下载Nginx的upstream_fair模块。或者使用商业版本的Nginx Plus,此外在在upstream中每个主机后面配置 max_conns可以限制每个后端服务器的处理连接数。

upstream backend {    
    server server1;    
    server server2;    
    fair;    
}

缓存

nginx配置缓存的优点:可以在一定程度上,减少服务器的处理请求压力。比如对一些图片,css或js做一些缓存,那么在每次刷新浏览器的时候,就不会重新请求了,而是从缓存里面读取。这样就可以减轻服务器的压力。详细可参考官网说明

image-20211007115934714

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;

Rewrite 配置功能

URL重写有利于网站首选域的确定,对于同一资源页面多条路径的301重定向有助于URL权重的集中,rewrite的组要功能是实现URL地址的重定向。Nginx的rewrite功能需要PCRE软件的支持,即通过perl兼容正则表达式语句进行规则匹配的。默认参数编译nginx就会支持rewrite的模块,但是也必须要PCRE的支持,rewrite是实现URL重写的关键指令,根据regex(正则表达式)部分内容,重定向到replacement,结尾是flag标记。详细可参考官网说明

image-20211007120455550

限流

概述

Nginx限流的实现主要是ngx_http_limit_conn_module和ngx_http_limit_req_module这两个模块,按请求速率限速模块使用的是漏桶算法,即能够强行保证请求的实时处理速度不会超过设置的阈值。Nginx官方版本限制IP的连接和并发分别有两个模块:zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 “leaky bucket”。limit_req_conn 用来限制同一时间连接数,即并发限制。

限流算法

令牌桶算法

算法思想:

image-20211007123221870

漏桶算法

算法思想:

image-20211007123402818

请求处理数限流

#先安装压测工具,当前我们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/

image-20211007125528244

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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的生态

image-20211006170918240

我们通常使用开源Nginx版本,基于Nginx开源版本至上还衍生其他版本,包括商业收费版本的Nginx Plus、淘宝开源的TEngine、OpenResty。

NGINX Plus

NGINX Plus官网 https://www.nginx.com/resources/datasheets/nginx-plus-datasheet/

NGINX Plus以先进的功能和屡获殊荣的支持扩展了NGINX开源,为客户提供了完整的应用交付解决方案。NGINX Plus将负载平衡器、内容缓存、web服务器、安全控制和丰富的应用程序监控和管理整合到一个易于使用的软件包中。

TEngine

TEngine官网 http://tengine.taobao.org/

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

从2011年12月开始,Tengine成为一个开源项目,Tengine团队在积极地开发和维护着它。Tengine团队的核心成员来自于淘宝搜狗等互联网企业。Tengine是社区合作的成果,我们欢迎大家参与其中,贡献自己的力量。

TEngine特性:

OpenResty

概述

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强大之处就是提供许许多多的组件,开箱即用

image-20211007105825045

通过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,来应对大批量的网络请求。

配置文件与模块化设计

60ccf064d064e2d352b2a805ae31bde8

OpenResty中,Nginx 的模块化设计使得每一个 HTTP 模块可以仅专注于完成一个独立的、简单的功能,而一个请求的完整处理过程可以使由无数个 HTTP 模块共同合作完成。

c5a6e0098ec0a75d22627b90f9427eed

beddb076d1a44a0413f673716b1543c1

CentOS安装OpenResty

# 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

image-20211007135633404

HelloWorld入门示例

image-20211007140434235

#进入/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

image-20211007140818835

大厂面试题

惊群效应是怎么产生的?Nginx解决思路?

由于worker都是由master进程fork产生,所以worker都会同时监听相同端口,有IO事件所有进程同时被唤醒然后挂起,但只有一个进程能否获取到,这样多个子进程在accept建立连接时会发生争抢,带来著名的“惊群”问题。

nginx采取在同一个时刻只有一个进程在监听端口的机制,实现如下ngx_shmtx_t 是共享变量、共享内存是多进程通信最快方式,所有worker进程使用同一把锁,采用无锁的CAS实现。

为什么Nginx使用多进程不使用多线程?

Nginx使用业务场景主要是一个web服务器,有处理无状态的如http请求,如果采用多线程就需要加锁和同步问题,采用多进程可以对每次请求做到最大的隔离。

Nginx热启动是如何实现?

我们知道使用nginx -s reload就可以不停止nginx服务情况下更新配置,master负责解析配置文件、监控worker进程,worker进程负责listen,一个worker进程同时监听多个server端口,worker之间是竞争的关系,通过master将内存配置结构更新写入共享内存中下一次就可以获取最新的配置信息。

Nginx支持配置热更新和程序热更新,Nginx热更配置时,可以保持运行中平滑更新配置,具体流程如下:

image-20211007112327121

谈谈Nginx的事件驱动模型?

Worker进程在处理网络事件时,依靠epoll模型,来管理并发连接,实现了事件驱动、异步、非阻塞等特性。通常海量并发连接过程中,每一时刻(相对较短的一段时间),往往只需要处理一小部分有事件的连接即活跃连接。基于以上情况,epoll通过将连接管理与活跃连接管理进行分离,实现了高效、稳定的网络IO处理能力。其中,epoll利用红黑树高效的增删查效率来管理连接,利用一个双向链表来维护活跃连接。

image-20211007113101508

Nginx如何实现高可用?

可以通过基于VRRP虚拟路由器冗余协议的keepalived方案实现多台nginx故障自动切换,核心也即是控制同一个VIP虚拟IP在多台主机漂移。

附带十个并发编程小面试题

多进程和多线程实现并发编程各自优势和劣势是什么?

协程为什么能够实现更高的并发?

下面两种访问数据的方式那种更快,为什么?

#第一种
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;    

经过测试,第一种比第二种的执行时间要快好几倍甚至几十倍

Fibonacci数列F(n)=F(n-1)+F(n-2),实现F(n)函数?

定义:f ( 0 ) = f ( 1 ) = 1 , f ( n ) = f ( n − 1 ) + f ( n − 2 ) ( n ≥ 2 )

image-20211005223827497

第一种:递归普通方法,能体现递归思维能力,算法时间复杂度是指数级增加,根据递归数而定。

int Fibnacci(int n)
{
    if(n == 0){
        return 1;
    }else if(n == 1){
        return 1;
    }else {
        return Fibnacci(n-1) + Fibnacci(n-2);
    }
}

第二种: 记忆化搜索优化后的递归算法,开辟一块空间,利用-1赋值方式,采用记忆化搜索的功能,此外动态规划基础也是记忆化搜索。

image-20211005224417471

我们从节点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},如果能回答出这个那就非常不错了

image-20211005222244466

哈希表和二叉树相比,各自优缺点是什么?

解决哈希表冲突有哪些方法?各自优缺点是什么?

自旋锁有哪些特点,不适用于哪些场景?

读写锁用于解决什么问题?读优先和写优先是指什么?

怎样将文件快速发送客户端?

image-20211005231948486

相比堆,为什么栈上分配的对象速度更快?

后续我们再找时间深入分析Nginx源码、Nginx的C模块化开发、Nginx高阶及高可用实战,OpenResty编程实战。

标签:缓存,http,nginx,必学,server,Nginx,location,开发人员
来源: https://www.cnblogs.com/itxiaoshen/p/15668820.html