哈哈,我终于对nginx下手了嘛。第一篇不知道要起个什么名字,入门吧,又不是很小白。设计与架构吧,又不是很深刻,而且不知道哪天就被我的另外的博客给吞了,就无题吧。
或者说,如果写成了系列,那么这篇就会有名字了。
Nginx是一个 Web 服务器,也可以用作 反向代理,负载均衡器 和 HTTP 缓存。
我最开始使用 nginx 的时候就是用它来做反向代理,不过我用的是 tcp 负载均衡。
web 服务.
负载均衡 (反向代理)
web cache(web 缓存)
其中 web 服务和 web 缓存的使用我都将一笔带过,有所取舍嘛。不过如果在架构中遇到的话我还是要讲上一讲的。
点击前往
不是本篇重点,一笔带过。
需要在客户端配置代理服务器进行指定网站访问,通过代理服务器来访问服务器的过程 就叫 正向代理。
可以想一下爬虫。
反向代理,其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问。
我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器 IP 地址。
增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们所说的负载均衡。
为了加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速度。降低原来单个服务器的压力。
关于配置文件这里先不讲,下一篇再说。
平滑处理,有必要了解一下。好多地方都出现过平滑处理,不知道 nginx 的是哪种方式呢。像 redis 那种数据库的一致性,记不住记不住啊。
nginx主进程读取配置文件,如果发现配置文件变更,会创建一个新的主进程,然后同时旧的进程,及旧的子进程关闭,旧进程会拒绝新的连接,服务到自己的连接结束,然后关闭。
原来是跟共享内存一个样的处理方式啊。
(Apache select模型),题外话题外话。
Nginx高并发得益于其采用了 epoll 模型。
epoll,求知者离我近点,基础,小白,上手。
再探epoll,深入epoll,涉及源码,小白慎入。
Nginx启动后以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程:
主进程并不处理网络请求,主要负责调度工作进程,也就是图示的 3 项:加载配置、启动工作进程 及 平滑升级。所以,Nginx 启动以后,查看操作系统的进程列表,我们就能看到至少有两个 Nginx 进程。
服务器实际处理网络请求及响应的是工作进程(worker),在类 unix 系统上,Nginx 可以配置多个 worker,而每个 worker 进程都可以同时处理数以千计的网络请求。
再深入的实现等到后面几篇看源码了再说。
加载配置;
启动工作进程;
监控工作进程的运行状态;
当工作进程退出后(异常情况下),会自动重新启动新的工作进程;
平滑升级。
主要用来处理基本的网络事件。
多个worker进程之间是对等且相互独立的,他们同等竞争来自客户端的请求,一个请求,只可能在一个worker进程中处理。
worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致。更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,具有cpu绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。
对于每个worker进程来说,独立的进程无需加锁,省掉了锁带来的开销,同时在编程及问题查找时,也方便很多。其次,独立的进程可让相互间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程,这也是Nginx高效的另个原因。
关于这个连接数问题后边再说吧。
nginx 启动后,在 unix 系统中会以 daemon 方式在后台运行,后台进程包含个master进程和多个 worker 进程。当然 nginx 也是支持多线程的方式的,只是主流方式还是多进程方式,也是nginx默认方式。
(1)首先,每个 worker 进程都是从 master 进程 fork 过来,在 master 进程里面,先建立好需要 listen 的监听套接字之后,然后再 fork 出多个 worker 进程。
(2)所有 worker 进程的 listenfd 会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有 worker 进程会在注册 listenfd 读事件前抢 accept_mutex,抢到互斥锁的那个进程注册 listenfd 读事件,然后在读事件里调用 accept 接受该连接。(解决惊群)
(3)当一个 worker 进程在 accept 这个连接之后,就开始读取请求、解析请求、处理请求。产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。
每个 worker 里面只有一个主线程,那能够处理的并发数很有限啊,多少个 worker 就能处理多少个并发,何来高并发呢?这就是 Nginx 的高明之处,Nginx 采用了 异步非阻塞 的方式来处理请求,也就是说,Nginx 是可以同时处理成千上万个请求的。
在 Nginx 服务器的运行过程中,主进程和工作进程需要进程交互。交互依赖于 Socket 实现的管道来实现。
它是由主进程指向工作进程的单向管道,包含主进程向工作进程发出的 指令,工作进程 ID 等;同时主进程与外界通过信号通信;每个子进程具备接收信号,并处理相应的事件的能力。
这种交互是和主进程-工作进程交互是基本一致的,但是会通过主进程间接完成。工作进程之间是相互隔离的,所以当工作进程 W1 需要向工作进程 W2 发指令时,首先找到 W2 的进程 ID,然后将正确的指令写入指向 W2 的 通道。W2 收到信号采取相应的措施。
Nginx 真正的魅力在于它的模块,整个应用程序建立在一个模块化系统之上,在编译时,可以对每一个模块进行启用或者禁用,需要什么就定制什么。
至于模块的详解,后面几篇再说啦。
为了避免出现内存碎片,减少向操作系统申请内存的次数、降低各个模块的开发复杂度,Nginx 设计了简单的内存池,它的作用主要是把多次向系统申请内存的操作整合成一次,这大大减少了 CPU 资源的消耗,同时减少了内存碎片。
因此,通常每一个请求都有一个简易的独立内存池(如每个 TCP 连接都分配了一个内存池),而在请求结束时则会销毁整个内存池,把曾经分配的内存一次性归还给操作系统。这种设计大大提高了模块开发的简单些,因为在模块申请内存后不用关心它的释放问题;而且因为分配内存次数的减少使得请求执行的时延得到了降低。同时,通过减少内存碎片,提高了内存的有效利用率和系统可处理的并发连接数,从而增强了网络性能。
好啦,本系列基本的雏形已经初步具备了,后面就按照这篇文章将各个模块展开并落实到源码中。