一、Nginx特性
Nginx使用可扩展的事件驱动,不是传统的过程驱动架构。
在传统的Web服务器体系结构中,每个客户端连接作为一个单独的进程或线程处理,随着网站的流行度增加,并发连接数量的增加,Web服务器减慢,延迟了对用户的响应。
从技术角度来看,产生一个单独的进程/线程需要将CPU切换到新的任务并创建一个新的运行时上下文,消耗额外的内存和CPU时间,从而对性能产生负面影响。
Nginx开发的目标是实现10倍以上的性能,优化服务器资源的使用,同时也能够扩展和支持网站的动态增长。因此,Nginx成为最知名的模块化,时间驱动,异步,单线程Web服务器和Web代理之一。
Nginx是一个高性能的Web和反向代理服务器,它具有很多非常优越的特性:
1)作为Web服务器
相比 Apache, Nginx 使用更少的资源,支持更多的并发连接,体现更高的效率,这点使 Nginx尤其受到虚拟主机提供商的欢迎。能够支持高达 50,000 个并发连接数的响应,感谢 Nginx为我们选择了 epoll and kqueue 作为开发模型。
2)作为负载均衡服务器
Nginx 既可以在内部直接支持 Rails 和 PHP,也可以支持作为 HTTP 代理服务器 对外进行服务。 Nginx 用 C 编写, 不论是系统资源开销还是 CPU 使用效率都比 Perlbal 要好的多。
3)作为邮件代理服务器
Nginx 同时也是一个非常优秀的邮件代理服务器(最早开发这个产品的目的之一也是作为邮
件代理服务器), Last.fm 描述了成功并且美妙的使用经验。
Nginx 安装非常的简单,配置文件 非常简洁(还能够支持 perl 语法), Bugs 非常少的服务器。
Nginx 启动特别容易,并且几乎可以做到 7*24 不间断运行,即使运行数个月也不需要重新启动。
你还能够在 不间断服务的情况下进行软件版本的升级。
二、Nginx架构
nginx 大量使用复用和事件通知,并专门用于分离进程的特定任务。 连接在有限数量的单线程进程称为工作(worker)的高效运行循环中处理。 在每个工作(worker)中, nginx 可以处理每秒数千个并发连接和请求。
2.1 代码结构
Nginx工作(worker)码包括核心和功能模块。 nginx 的核心是负责维护严格的运行循环,并在请求处理的每个阶段执行模块代码的适当部分。 模块构成了大部分的演示和应用层功能。 模块读取和写入网络和存储,转换内容,执行出站过滤,应用服务器端包含操作,并在代理启动时将请求传递给上游服务器。
nginx 的模块化架构通常允许开发人员扩展一组 Web 服务器功能,而无需修改 nginx 内核。 nginx 模块略有不同,即核心模块,事件模块,阶段处理程序,协议,可变处理程序,过滤器,上游和负载平衡器。 nginx 不支持动态加载的模块; 即在构建阶段将模块与核心一起编译。
在处理与接受,处理和管理网络连接和内容检索相关的各种操作时,nginx 在基于 Linux,Solaris 和 BSD 的操作系统中使用事件通知机制和一些磁盘 I/O 性能增强,如: kqueue, epoll,和事件端口。 目标是为操作系统提供尽可能多的提示,以便及时获取入站和出站流量,磁盘操作,读取或写入套接字,超时等异步反馈。 对于每个基于 Unix 的 nginx 运行的操作系统,大量优化了复用和高级 I/O 操作的不同方法的使用。
2.2 工作模式
Nginx不会为每一个连接生成一个进程或线程。相反,worker进程接受来自共享“listen”套接字的新请求,并在每个worker内执行高效的运行循环,以处理每个worker中的数千个连接。
worker是由操作系统内核机制完成。
启动后,将创建一组初始侦听套接字,然后worker在处理HTTP请求和响应时不断接受、读取和写入套接字。运行循环是 nginx worker代码中最复杂的部分。 它包括全面的内部调用,并且在很大程度上依赖异步任务处理的想法。 异步操作通过模块化,事件通知,广泛使用回调函数和微调定时器来实现。 总体而言,关键原则是尽可能不阻塞。
nginx 仍然可以阻塞的唯一情况是worker进程没有足够的磁盘存储。
内存:绝大多数情况下,内存使用非常保守,非常有效,因为nginx不会连接一个进程或线程。
CPU:节省CPU周期,因为进程或线程没有持续的 ”创建-销毁“ 模式。
nginx 的作用是检查网络和存储的状态,初始化新连接,将其添加到运行循环中,并异步处理直到完成,此时连接被重新分配并从运行循环中删除。 结合仔细使用系统调用(syscall)和精确实现支持接口(如 pool 和 slab 内存分配器), nginx 通常可以在极端工作负载下实现中到低的CPU 使用。
在一些磁盘使用和 CPU 负载模式,应调整 nginx 工作(worker)的数量。建议:
1)如果负载模式是CPU密集型,如需处理大量TCP/IP,执行SSL或压缩,worker数量应与CPU内核数量匹配。
2)如果负载模式是磁盘IO绑定,如从存储或重代理服务不同的内容集合,worker的数量可能是核心数量的1到2倍。
现有worker模式问题,下面两种类型的行为将立即导致工作(worker)挂起的情况,同时影响到数千个连接。
- 问题1:磁盘 I/O 上的大多数阻塞。如果没有足够的存储性能来提供特定工作(worker)生成的磁盘操作,该工作(worker)可能仍然阻止从磁盘读取/写入。
- 问题2:与嵌入式脚本的有限支持有关。一个使用标准的 nginx 分发,只支持嵌入 Perl 脚本。一个简单的解释:关键问题是嵌入式脚本阻止任何操作或意外退出的可能性。
2.3 进程角色
nginx 在内存中运行多个进程; 有一个主进程和几个工作(worker)进程。 还有一些特殊用途的过程,特别是缓存加载器和缓存管理器。 所有进程都是单线程版本为 1.x 的 nginx。
所有进程主要使用共享内存机制进行进程间通信。主进程作为root用户运行。缓存加载器,缓存管理器和worker则无权限制用户运行。
》》主进程负责以下任务:
- 读取和验证配置
- 创建,绑定和关闭套接字
- 启动,终止和维护配置的worker进程数
- 重新配置,无需中断服务
- 控制不间断的二进制升级(如果需要,启动新的二进制并回滚)
- 重新打开日志文件
- 编译嵌入式Perl脚本
》》worker进程接受和处理来自客户端的连接,提供反向代理和过滤功能,并执行几乎所有其他nginx能力。关于监视nginx实例的行为,系统管理员应该关注worker进程,因为它们是反应Web服务实际日常操作的过程。
》》缓存加载器进程负责检查磁盘缓存项目,并使用缓存元数据填充nginx的内存数据库。本质上,缓存加载器准备nginx实例来处理已经存储在磁盘上的特定分配的目录结构中的文件。它遍历目录,检查缓存内容元数据,更新共享内存的相关条目,然后在所有内筒清洁并准备使用时退出。
》》缓存管理器主要负责缓存到期和无效。在正常的nginx操作期间它保持在内存中,并且在失败的情况下由主进程重新启动。
2.4 缓存
在nginx中的缓存以文件系统上的分层数据存储的形式实现。
缓存密钥是可以配置的,并可以使用不同的请求特定参数来控制进入缓存的内筒。缓存密钥和缓存元数据存储在共享存储器段中,高速缓存加载,缓存管理器和worker可以访问它们。层次结构(级别和命名细节)通过 nginx 配置指令进行控制。 当响应写入缓存目录结构时,文件的路径和名称将从代理 URL 的 MD5 哈希导出。
将内容放入缓存的过程:
- nginx从上游服务器读取响应时,内容首先写入缓存目录结构之外的临时文件。
- nginx完成处理请求时,它重命名临时文件并将其转移到缓存目录。
若用于代理的临时文件目录位于另一个文件系统上,则该文件将被复制,因此建议将临时文件目录和缓存目录保存在同一个文件系统上。因此建议将临时文件目录和缓存目录保存在同一文件系统上。
三、Nginx应用
3.1 Nginx安装和操作的一些说明
1、安装
Nginx安装的时候同时需要安装一些依赖库。
- pcre:负责做正则表达式。
- openssl:用作MD5验证、NTPS加密。
- zlib:用于压缩。
2、操作
- 启动:sudo ./sbin/nginx -c ./conf/nginx.conf
- 快速关闭服务:./sbin/nginx -s stop
- 正常关闭服务:./sbin/nginx -s quit
- 重新加载配置文件:./sbin/nginx -s reload
- 重新打开日志文件:./sbin/nginx -s reopen
3.2 四层与七层负载均衡
分层模型
1)四层负载均衡
定义:
四层负载均衡工作在OSI模型的传输层,由于在传输层,只有TCP/UDP协议,这两种协议中除了包含源IP、目标IP以外,还包含源端口号及目的端口号。四层负载均衡服务器在接受到客户端请求后,以后通过修改数据包的地址信息(IP+端口号)将流量转发到应用服务器。
2)七层负载均衡
定义:
七层负载均衡工作在OSI模型的应用层,应用层协议较多,常用http、radius、dns等。七层负载就可以基于这些协议来负载。这些应用层协议中会包含很多有意义的内容。比如同一个Web服务器的负载均衡,除了根据IP加端口进行负载外,还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡。
3)四层和七层的区别
- 四层通过虚拟IP+端口接收请求,然后再分配到真实的服务器
- 七层通过虚拟的URL或主机名接收请求,然后再分配给真实的服务器
- 四层交换机主要分析IP层及TCP/UCP层,实现四层流量负载均衡
- 七层交换机除了支持四层负载均衡外,还有分析应用层的信息,如HTTP协议,URI或Cookie信息
- 四层负载均衡的本质是转发,七层负载均衡的本质是内容交换和代理
如果是http选择nginx做代理,如果是tcp使用haproxy。Nginx可以用于七层负载均衡。
3.3 应用
3.3.1 做负载均衡
服务器拓扑结构:
工作流程:
访问机器先访问路由器,然后再到Nginx,Nginx再把数据分发给同一局域网内的其他服务。
路由器也称为网关,而nginx为应用网关。路由器到nginx是通过端口映射(通过路由器或者交换机直接配置),当外部机器访问到路由器端口的时候直接对应的是nginx子网的机器。因此给人的感觉是nginx是对接在公网上。比如:路由器对外提供的访问地址是112.93.116.78.8080,映射到nginx服务192.168.1.123.80。
DNS做负载均衡
一个域名映射多个IP地址,比如www.xzq.com–>通过域名解析的时候可以拿到多个IP地址(跟端口没关系),第一次响应的可能是第一个IP地址,第二次响应的可能是第二个IP地址,这里做的就是负载均衡。
该负载均衡跟开发没有什么关系,只需要配置号就行,其他不用管。
特点:响应没有那么及时,假设有一台机器宕机,需要同步到其他域名中的时候有可能会耗费10分钟之久。
》conf文件
在开发中核心的最常用的是conf文件。
conf文件的解析原理:解析conf文件中的worker_processes参数,通过源码搜索找到源码ngx_core_module函数,所有的模块都放到ngx_modules数组中。
conf文件配置说明
#每个worker_processes进程对应的连接数--每一个连接,可读可写的时候就是事件
events {
worker_connections 1024;
}
#http协议模块,如果是自己封装的通信协议,也可以在配置文件中作为一个模块
http {
server {
#80为http默认端口,自定义端口最好大于1024
listen 8080;
server_name localhost;
#客户端请求body最大数量 网页设置小些,视频文件设置大些
client_max_body_size 100m;
#要访问的网页或者文件
location / {
root /usr/local/nginx/html/www;
}
location /images/ {
root /usr/local/nginx/html;
}
}
}
》http请求
http请求比较大的文件,有一个头range: 0–1024,都有一个对应的请求位置,如果服务器中的文件大小为500M,每次可以取25M,确定了开始和结束位置(位置由请求的客户端定义),就可以使用20个线程同时下载。比如第一个线程请求0-25,第二个线程请求26-50M…
问:20个线程,写入到同一个文件是不是回到了单线程处理?
事先已经分配好文件大小,每个线程写入到确定的位置即可。
问:磁头只有一个,写到盘里还是单线程?
磁盘SSD写入可以达到1G/s,软件写入一般比较快的100M/s。这里的瓶颈就不是磁盘的问题了。
问:断点续传是否可以分片,如果可以如何保证顺序?
断点续传也是需要分片的,但并非可以一个字节一个字节的续传:
1)多线程同时下载一个文件,需要确定每个线程的开始和结束位置。
2)断点续传,记住上一次的结束位置作为下一次的开始。
3.3.2 做代理
1、代理单台
A请求123,实际123会把数据传给168进行处理。123就是一个代理。
实例:
123服务器上IP地址为192.168.139.132,168服务器地址为192.168.139.133。
1)123服务的nginx.conf配置文件为:
events {
worker_connections 1024;
}
http {
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://192.168.139.133;
}
}
}
2)168服务的nginx.conf配置文件为:
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
}
3)启动123服务器Nginx,168服务器Nginx
./sbin/nginx -c ./conf/nginx.conf
4)使用网页访问123服务
可以看到显示的网页数据为168服务的数据。
2、代理多台
nginx.conf配置文件可以修改如下:
events {
worker_connections 1024;
}
http {
upstream backend{
server 192.168.139.133 weight=2;
server 192.168.139.134 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http:// backend;
}
}
}
在网页请求Nginx服务,可以看到显示的网页不断在192.168.139.133和192.168.139.134服务之间进行切换。
指定轮询几率,weight 和访问比率成正比,用于后端服务器性能不均的情况。
那么 3次一般只会有 1 次会访问到 192.168.139.134,而有2 次会访问到192.168.139.133。
3.3.3 访问图片
1)在Nginx新建images文件夹,文件夹存放1.jpg 2.jpg 3.jpg三张图片。
2)修改nginx.conf 配置文件
events {
worker_connections 1024;
}
http {
upstream backend{
server 192.168.139.133 weight=2;
server 192.168.139.134 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http:// backend;
}
location /images/ {
root ./;
}
}
}
3)网页访问图片:
3.3.4 访问视频
1)在Nginx新建media文件夹,文件夹存放视频文件RB.mp4。
2)修改nginx.conf配置文件
events {
worker_connections 1024;
}
http {
upstream backend{
server 192.168.139.133 weight=2;
server 192.168.139.134 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http:// backend;
}
location /images/ {
root ./;
}
location ~ \.(mp3|mp4) {
root media;
}
}
}
静态资源都可以使用上面两种方式进行配置,比如css、js、png。
3.3.5 CGI公共网关接口
CGI是一个进程,当一个客户端请求Web服务器的时候,服务器把一些响应的数据转换给CGI,计算出来的结果返回给Web服务器。
问:CGI与后端的Server有什么区别?
CGI是对外提供输入输出流,比如std cout,printf,直接打印到网页上。Server的协议是http协议。
问:CGI到底用在哪里?在哪种场景下必须要用到CGI?
在线编译工具,当你提交代码的时候编译工具可以给你实时输出结果,这是使用CGI实现。
问:CGI与fastcgi的区别?
CGI是一请求一进程方式,fastcgi:启动进程池等待。
问:Nginx如何做CGI?
需要用到fcgi和spawn-fcgi两个工具。其中:
fcgi:CGI的库文件,我们可以通过这里的接口开发CGI。
spawn-fcgi:启动CGI的工具。
CGI编程
1)spawn-fcgi编译:解压后进到文件夹执行./configure,然后执行make,再把src文件里的spawn-fcgi拷贝到Nginx的sbin文件夹。
2)fcgi编译:解压后进到文件夹执行./configure,然后执行make。
这个时候可能会出现以下编译错误提示:
我们可以通过在include/fcgio.h头文件中增加#include <stdio.h>即可解决。
最后执行 sudo make install即可。
3)源码示例
- 编码
#include <stdio.h>
#include <fcgi_stdio.h>
int main()
{
while(0 <= FCGI_Accept())
{
printf("Content-type:text/html\r\n");
printf("\r\n");
printf("<title> Fast CGI Hello!</title>");
printf("<h1>ZVoice cgi</h1>");
printf("Thank you cgi\n");
}
}
- 编译
编译CGI程序:gcc –o zvoice_cgi zvoice_cgi.c -lfcgi
- 运行
./zvoice_cgi
发现程序运行失败,报找不到对应库文件错误:
于是从头开始检查cgi库文件的安装,从./configure到make到sudo make install,都没有问题,但是依旧报错。
这个时候我们思考一下,库文件已经安装但是调用失败,应该是系统没有识别到,这个时候我们设置动态库为系统共享,执行指令:sudo ldconfig
,然后再运行程序发现程序运行成功。
ldconfig:动态库管理,让动态库为系统共享,把新安装的so,为系统共享。
- 启动CGI程序:
./spawn-fcgi -a 127.0.0.1 -p 9002 -f /home/ubuntu/nginx/zvoice_cgi
- 配置Nginx的conf文件
events {
worker_connections 1024;
}
http {
upstream backend{
server 192.168.139.133 weight=2;
server 192.168.139.134 weight=1;
}
server {
listen 80;
server_name localhost;
location / {
proxy_pass http:// backend;
}
location /images/ {
root ./;
}
location ~ \.(mp3|mp4) {
root media;
}
}
server{
listen 9000;
location ~ \.cgi{
fastcgi_pass 127.0.0.1:9002;
fastcgi_index index.cgi;
fastcgi_param SCRIPT_FILENAME cgi$fastcgi_script_name;
include ../conf/fastcgi_params;
}
}
}
- 重启Nginx
- 访问网页:http://192.168.139.132:9000/zvoice_cgi.cgi