前言:
最近在部署项目时要求实现负载均衡, 有趣的是发现网上一搜全部都是以下类似的配置文件
- upstream localhost{
- server 127.0.0.1:8080 weight=1;
- server 127.0.0.1:8081 weight=1;
- }
- server {
- listen 80;
- server_name localhost;
- location / {
- proxy_pass http://localhost;
- index index.html index.htm index.jsp;
- }
- }
所以打算来看看 Nginx 内部原理, 这篇博客主要介绍 Nginx 如何实现反向代理以及在 Nginx 中负载均衡的参数使用
一, 正向代理与反向代理
正向代理是代理客户端, 也就是客户端能真正接触到的, 比如访问外网时需要使用 VPN 软件, 在这个软件中用户可以选择连接哪里的服务器.
反向代理则是代理服务端, 用户感知不到, 只是客户端把请求发到服务端的端口时, Nginx 监听到了便把该端口的请求转发到不同的服务器上. 就以上面配置文件来讲解, 当在网址中输入 http://localhost:80 / 时 (不加 80 一样时默认进入 80 端口, 这里为了表示清楚), 而后 Nginx 监听到 80 端口的请求之后, 就会查找对应的 location 来执行. 由上面的配置文件我们可以看出是将请求转发到了不同的端口. 这是在服务器中执行的, 用户不可见.
而服务端中我们最常使用的反向代理的工具就是 Nginx.
二, Nginx 内部基本架构
nginx 在启动后以 daemon 的方式在后台运行, 会有一个 master 进程和多个 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 的时候服务是不中断的.
三, Nginx 如何处理客户端请求
首先来解释一下上面的架构图: 每个 worker 进程都是从 master 进程分支过来的, 在 master 进程里面, 先建立好需要监听的 socket 之后, 然后再分支出多个 worker 进程. 所有 worker 进程的 listenfd(socket 中 listenfd 是指客户端连接本机时的 fd, 是用来和客户端通信用的) 会在新连接到来时变得可读, 为保证只有一个进程处理该连接, 所有 worker 进程在注册 listenfd 读事件前抢 accept_mutex, 抢到互斥锁的那个进程注册 listenfd 读事件, 在读事件里调用 accept 接受该连接.
在 Nginx 中 worker 进程之间是平等的, 每个进程, 处理请求的机会也是一样的. 当 Nginx 监听 80 端口时, 一个客户端的连接请求过来, 每个进程都有可能处理这个连接, 上面说到是每个 worker 进程都会去抢注 listenfd 读事件. 当一个 worker 进程在 accept 这个连接之后, 就开始读取请求, 解析请求, 处理请求, 产生数据后, 再返回给客户端, 最后才断开连接, 这样一个完整的请求就是这样的了. 这里需要注意的是一个请求, 完全由 worker 进程来处理, 而且只在一个 worker 进程中处理.
下面两幅流程图能很好的帮我们理解
四, Nginx 如何处理事件并且实现高并发
Nginx 内部采用了异步非阻塞的方式来处理请求, 也就是说, Nginx 是可以同时处理成千上万个请求的.
异步非阻塞: 当一个网络请求过来时, 我们并不依赖于这个请求才能做后续操作, 那么这个请求就是异步操作, 也就是调用者在没有得到结果之前同样可以执行后续的操作. 非阻塞就是当前进程 / 线程没有得到请求调用的结果时也不会妨碍到进程 / 线程后续的操作. 可以看出异步和非阻塞的对象是不同的.
回到 Nginx 中, 首先, 请求过来, 要建立连接, 然后再接收数据, 接收数据后, 再发送数据. 具体到系统底层, 就是读写事件, 而当读写事件没有准备好时, 必然不可操作, 如果不用非阻塞的方式来调用, 那就得阻塞调用了, 事件没有准备好, 那就只能等了, 等事件准备好了, 再继续. 而阻塞调用会进入内核等待, CPU 就会让出去给别人用了, 对单线程的 worker 来说, 显然不合适, 当网络事件越多时, 大家都在等待, CPU 空闲下来没人用, CPU 利用率自然上不去了, 更别谈高并发了. 而非阻塞就是, 事件没有准备好, 马上返回 EAGAIN, 告诉调用者, 事件还没准备好, 过会再来. 过一会, 再来检查一下事件, 直到事件准备好了为止, 在这期间, Nginx 可以处理其他调用者的读写事件. 但是虽然不阻塞了, 但 Nginx 得不时地过来检查一下事件的状态, Nginx 可以处理更多请求了, 但带来的开销也是不小的. 所以, 才会有了异步非阻塞的事件处理机制, 具体到系统调用就是像 select/poll/epoll/kqueue 这样的系统调用. 它们提供了一种机制, 让你可以同时监控多个事件, 以 epoll 为例子, 当事件没准备好时, 放到 epoll 里面, 事件准备好了, Nginx 就去读写, 当读写返回 EAGAIN 时, 就将它再次加入到 epoll 里面. 这样, 只要有事件准备好了, Nginx 就可以去处理它, 只有当所有事件都没准备好时, 才在 epoll 里面等着. 这样便实现了所谓的并发处理请求, 但是线程只有一个, 所以同时能处理的请求当然只有一个了, 只是在请求间进行不断地切换而已, 切换也是因为异步事件未准备好, 而主动让出的. 这里的切换是没有任何代价, 你可以理解为循环处理多个准备好的事件, 事实上就是这样的. 与多线程相比, 这种事件处理方式是有很大的优势的, 不需要创建线程, 每个请求占用的内存也很少, 没有上下文切换, 事件处理非常的轻量级. 并发数再多也不会导致无谓的资源浪费 (上下文切换). 更多的并发数, 只是会占用更多的内存而已. 现在的网络服务器基本都采用这种方式, 这也是 nginx 性能高效的主要原因.
五, Nginx 负载均衡的算法及参数
round robin(默认): 轮询方式, 依次将请求分配到后台各个服务器中, 适用于后台机器性能一致的情况, 若服务器挂掉, 可以自动从服务列表中剔除
weight: 根据权重来分发请求到不同服务器中, 可以理解为比例分发, 性能较高服务器分多点请求, 较低的则分少点请求
IP_hash: 根据请求者 ip 的 hash 值将请求发送到后台服务器中, 保证来自同一 ip 的请求被转发到固定的服务器上, 解决 session 问题
- upstream localhost {
- ip_hash;
- server 127.0.0.1:8080;
- server 127.0.0.1:8080;
- }
上面是最基本的三种算法, 我们还可以通过改变参数来自行配置负载均衡
- 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 | 表示单前的 server 暂时不参与负载 |
weight | 默认为 1.weight 越大,负载的权重就越大 |
max_fails | 允许请求失败的次数默认为 1. 当超过最大次数时,返回 proxy_next_upstream 模块定义的错误 |
fail_timeout | max_fails 次失败后,暂停的时间 |
backup | 其它所有的非 backup 机器 down 或者忙的时候,请求 backup 机器。所以这台机器压力会最轻 |
参考:
理解 Nginx 工作原理 https://www.jianshu.com/p/6215e5d24553
Nginx 从入门到精通
来源: https://www.cnblogs.com/Cubemen/p/11387975.html