共计 4207 个字符,预计需要花费 11 分钟才能阅读完成。
文章原创于公众号:程序猿周先森。本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号。
之前其实写过一篇文章具体介绍过:最基础的 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 基本架构如下:
我们可以输入 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 机器。所以这台机器压力会最轻。
欢迎关注我的个人公众号:程序猿周先森