文章原创于公众号:程序猿周先森。本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号。
之前其实写过一篇文章具体介绍过:最基础的Nginx教学,当时有提到过Nginx有一个重要的功能:负载均衡。所以这篇文章主要讲讲Nginx如何实现反向代理以及在Nginx中负载均衡的参数使用。
一、代理
正向代理
正向代理也是大家最常接触的到的代理模式,那究竟什么是正向代理呢?我们都知道Google在国内是无法正常访问的,但是某些时候我们由于技术问题需要去访问Google时,我们会先找到一个可以访问Google的代理服务器,我们将请求发送到代理服务器,代理服务器去访问Google,然后将访问到的数据返回给我们,这样的过程就是正向代理。
正向代理的特点
正向代理最大的特点是客户端需要明确知道要访问的服务器地址,Google服务器只清楚请求来自哪个代理服务器,而不清楚来自哪个具体的客户端,正向代理可以隐藏真实客户端的具体信息。
客户端必须设置正向代理服务器,而且需要知道正向代理服务器的IP地址以及代理程序的端口。一句话来概括就是正向代理代理的是客户端,是一个位于客户端和Google服务器之间的服务器,为了从Google服务器取得数据,客户端向代理服务器发送一个请求并指定目标(Google服务器),然后代理向原始服务器转交请求并将获得的数据返回给客户端。
正向代理的使用:
- 访问国外无法访问的网站
- 做缓存,加速访问资源
- 对客户端访问授权,上网进行认证
- 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息
反向代理
说完了什么是正向代理,我们接下来看看什么叫做反向代理,如果我们网站每日访问量达到某个上限,单个服务器远远不能符合我们日常需求,这时候我们首先会想到分布式部署。通过部署多台服务器来解决访问人数限制的问题,然后我们功能其实大部分都是通过Nginx反向代理来实现的。我们可以看下图:
反向代理的特点
我们可以清楚的看到,多个客户端给服务器发送的请求,Nginx服务器接收到请求以后,按照一定的规则转发到不同的服务器进行业务逻辑处理。此时请求来源于哪个客户端是确定的,但是请求由哪台服务器处理的并不明确,Nginx扮演的就是一个反向代理角色。可以这样来理解,反向代理对外都是透明的,访问者并不知道自己访问的是一个代理。反向代理代理的是服务端,主要用于服务器集群分布式部署的情况下,反向代理隐藏了服务器的信息。
反向代理的使用:
- 保证内网的安全,通常将反向代理作为公网访问地址,Web服务器是内网
- 负载均衡,通过反向代理服务器来优化网站的负载
- 在正向代理中,隐藏了请求来源的客户端信息;
- 在反向代理中,隐藏了请求具体处理的服务端信息;
服务端中我们最常使用的反向代理的工具就是Nginx。
二、基本架构
Nginx在启动后以daemon的方式在后台运行,会有一个master进程和多个worker进程:
- Nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程。
- 接收来自外界的信号,向各worker进程发送信号,每个进程都有可能来处理这个连接。
- master 进程能监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动启动新的 worker 进程。
master进程:主要用来管理worker进程,包含:
- 接收来自外界的信号
- 向各worker进程发送信号
- 监控worker进程的运行状态
- 当worker进程异常退出后,会自动重新启动新的worker进程。
worker进程:处理基本的网络事件了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求只能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,或者直接设置参数worker_processes auto;
我们可以输入nginx -s reload来重启Nginx,nginx -s stop来停止Nginx的运行,执行这些命令时其实会启动一个新的Nginx进程,而新的Nginx进程在解析到reload参数后,其实就可以知道用户执行这个命令是控制Nginx重新加载配置文件,于是向master进程发送信号。master进程接到信号会先重新加载配置文件,然后启动新的worker进程并向所有旧worker进程发送信号提示老进程可以停止运行了。新的worker启动成功后就开始接收新的请求,而旧worker在收到来自master的信号后停止接收新的请求,在未处理完的请求处理完成后进程就会退出。所以说使用nginx -s reload命令重启Nginx的时候服务是不中断的。
三、Nginx处理客户端请求方式
刚才有讲到过每个worker进程都是从master进程分支的,所以在master进程里面需要先建立好需要监听的socket然后再分支出多个worker进程。所有worker进程listenfd事件会在新连接时变成可读,为保证只有一个进程处理该连接,所以需要设置互斥锁,所有worker进程需要抢互斥锁,抢到互斥锁的work进程注册listenfd读事件,在listenfd读事件里调用accept接受该连接。当Nginx监听80端口时,一个客户端的连接请求过来的时候,每个worker进程都会去抢互斥锁注册listenfd读事件。当一个worker进程在accept这个连接之后,就开始处理请求获取数据,再将数据返回给客户端,然后断开连接,到这里一个请求结束。
一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。
我下面贴一个简单的配置:
server {
listen 80;server_name aaa.com www.aaa.com;
}
server {
listen 80;server_name aaa.cn www.aaa.cn;
}
server {
listen 80;server_name aaa.org www.aaa.org;
}
当接收到客户端http请求,Nginx根据请求头的Host字段决定请求应该由哪一台服务器处理,如果Host字段的值没有匹配的服务器或者请求中没有Host字段,Nginx会将请求路由至这个端口的默认服务器。没有显示配置默认服务器,则默认服务器则为第一个配置。当然我们还可以使用default_server参数指定默认服务器。
server {
listen 80 default_server;
server_name aaa.com www.aaa.com;
}
这里需要注意一下:配置默认服务器是监听端口号,而不是服务器名称。
四、Nginx实现高并发
Nginx内部采用了异步非阻塞的方式处理请求,使用了epoll和大量的底层代码优化。可以同时处理成千上万个请求的。
异步非阻塞:每进来一个request,会有一个worker进程去处理。但不是全程的处理,处理到什么程度呢?处理到可能发生阻塞的地方,比如向后端服务器转发request,并等待请求返回。这个处理的worke会在发送完请求后注册一个事件:“如果upstream返回了,再进行执行接下来的工作”。此时,如果再有request 进来,他就可以很快再按这种方式处理。而一旦后端服务器返回了,就会触发这个事件,worker进程会来接手request接着往下执行。
而Nginx采用一个master进程,多个woker进程的模式。master进程主要负责收集、分发请求。每当一个请求过来时,master就拉起一个worker进程负责处理这个请求。同时master进程也负责监控woker的状态,保证高可靠性,woker进程一般设置为跟cpu核心数一致。Nginx的woker进程在同一时间可以处理的请求数只受内存限制,可以处理多个请求。Nginx 的异步非阻塞工作方式可以把当中的进程空闲等待时间利用起来,因此表现为少数几个进程就解决了大量的并发问题。
Nginx中以epoll为例子,当事件没准备好时,放到epoll里面,事件准备好了,Nginx就去读写,当读写返回EAGAIN时,就将它再次加入到epoll里面。这样,只要有事件准备好了,Nginx就可以去处理它,只有当所有事件都没准备好时,才在epoll里面等着。这样便实现了所谓的并发处理请求,但是线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已。
Nginx单线程机制与多线程相比优势:
- 在于不需要创建线程。
- 每个请求占用的内存也很少。
- 没有上下文切换。
- 事件处理非常的轻量级。
- 并发数再多也不会导致无谓的资源浪费。
五、Nginx负载均衡的算法及参数
- weight轮询(默认):接收到的请求按照请求顺序逐一分配到不同的后端服务器,如果在使用过程中,某一台服务器宕机,Nginx会自动将该服务器剔除出队列,请求受理情况不会受到任何影响。这种方式下,可以给不同的后端服务器设置一个权重值,权重数据越大,服务器被分配到请求的几率越大。
- ip_hash:每个请求按照发起客户端的ip的hash结果进行匹配,这样的算法下一个固定ip地址的客户端总会访问到同一个后端服务器。
- fair:智能调整调度算法,动态的根据后端服务器的请求响应时间进行均衡分配,响应时间短处理效率高的服务器分配到请求的概率高,响应时间长处理效率低的服务器分配到的请求少。
- url_hash:按照访问的url的hash结果分配请求,每个请求的url会指向后端固定的某个服务器,可以在Nginx作为静态服务器的情况下提高缓存效率。
上面是最基本的4种算法,我们还可以通过改变参数来自行配置负载均衡:
upstream localhost{
ip_hash;
server 127.0.0.1:9090 down;
server 127.0.0.1:8080 weight=2;
server 127.0.0.1:6060;
server 127.0.0.1:7070 backup;
}
- down表示当前的服务器停止参与负载。
- weight默认为1,weight越大,负载的权重就越大。
- backup表示其它所有的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。