深入理解高性能代理服务器——Nginx
前言
本文会对Nginx的介绍、使用、原理等知识作由浅入深的剖析。如果你只想对Nginx作为反向代理的使用作初步的了解,那么可以只看第一、二章节;如果想对Nginx工作原理、扩展使用等进行深入研究,可以看第三章节。相信本文会对你有所帮助
一、Nginx 基本介绍
1. 关于 Nginx
Nginx(“engine x”)是一个高性能的HTTP和反向代理服务器,特点是占有内存少,并发能力强,事实上Nginx的并发能力确实在同类型的网页服务器中表现较好。 Nginx 专为性能优化而开发,性能是其最重要的考量,实力上表现注重效率,能经受高负载的考验,有报告表明Nginx能支持高达50000个并发连接数。
- 俄罗斯程序员Igor Sysoev于2002年开始
- Nginx是增长最快的Web服务器,市场份额已达33.3%
- 全球使用量排行第二2011年成立商业公司
2. 关于反向代理
Nginx不仅可以做反向代理,实现负载均衡。还能用做正向代理来进行上网等功能。
2.1 正向代理
正向代理:如果把局域网的Internet想象成一个巨大的资源库,则局域网中的客户端要访问Internet,则需要通过代理服务器来访问,这种代理服务就称为正向代理。在客户端(浏览器)配置代理服务,通过代理服务器进行互联网访问。
2.3 反向代理
反向代理:其实客户端对代理是无感知的,因为客户端不需要任何配置就可以访问,我们只需要将请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,再返回给客户端,此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址。
2.4 正向代理与反向代理的区别
正向代理与反向代理的区别在于代理的对象不一样,正向代理代理的对象是客户端,反向代理代理的对象是服务端。
- 正向代理即是客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端
- 反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端
3. 负载均衡
客户端发送多个请求到服务器,服务器处理请求,有一些可能要与数据库进行交互,服务器处理完毕后,再将结果返回给客户端。
这种架构模式对于早期的系统相对单一,并发请求相对较少的情况下是比较适合的,成本也低。但是随着访问量和数据量的飞速增长,以及系统业务的复杂度增加,这种架构会造成服务器响应客户端的请求日益缓慢,并发量特别大的时候,还容易造成服务器直接崩溃。很明显这是由于服务器性能的瓶颈造成的问题,那么如何解决这种情况呢?
我们首先想到的可能是升级服务器配置,比如提高CPU执行频率,加大内存等提高服务器的物理性能来解决此问题,但我们知道摩尔定律的日益失效,硬件的性能提升已经不能满足日益提升的需求了。最明显的一个例子,天猫双十一当天,某个热销商品的瞬时访问量是极其庞大的,那么类似上面的系统架构,将机器都增加到现有的顶级物理配置,都是不能够满足需求的,那么怎么办呢?
上面的分析我们去掉了增加服务器物理配置来解决问题的办法,也就是说纵向解决问题的办法行不通了,那么横向增加服务器的数量呢?这时候集群的概念产生了,单个服务器解决不了,我们增加服务器的数量,然后将请求分发到各个服务器上,将原先请求集中到单个服务器上的情况改为将请求分发到多个服务器上,将负载分发到不同的服务器,也就是我们所说的负载均衡。
4. 动静分离
原部署方式:动态资源与静态资源都部署在同一服务器中,这样的方式会使Tomcat有较大压力
动静分离:为了加快网站的解析速度,可以把动态页面和静态页面由不同的服务器来解析,加快解析速度。降低原来单个服务器的压力。 在动静分离下,Tomcat 有比较明显的优化,因为 Tomcat 解析静态很慢。其实原理很好理解,简单来说,使用正则表达式匹配过滤,然后交给不同的服务器。
静态页面一般直接由Nginx来处理,动态页面则是通过反向代理,代理到后端的 Tomcat动态资源服务器,然后再做负载均衡,是选择本地静态页面还是后端Tomcat,这由负载均衡配置决定。
动静分离是在负载均衡后做的,例如静态web有多台,动态web有多台,先动静分离,然后在各自集群里做负载均衡、权重等。
关于动静分离的Nginx配置,可见文章:Nginx动静分离详细配置
二、Nginx 基本使用
1. Nginx 常用命令
使用Nginx操作命令前提条件:进入到 nginx/sbin 目录 /usr/local/nginx/sbin
-
查看Nginx版本号:./nginx -v
-
测试Nginx配置:./nginx -t
-
启动Nginx:./nginx
-
关闭Nginx:./nginx -s stop
-
重新加载Nginx配置文件:./nginx -s reload
2. Nginx 配置文件
(1)Nginx配置文件目录:/nginx/conf/nginx.conf
(2)Nginx配置文件的组成
Nginx的配置文件由三部分组成,分别为全局块、events块、http块
2.1 第一部分:全局块
从配置文件开始到 events 块之间的内容,主要会设置一些影响 Nginx 服务器整体运行的配置指令,主要包括配置运行 Nginx 服务器的用户(组)、允许生成的 worker process 数,进程 PID 存放路径、日志存放路径和类型以及配置文件的引入等。
比如上面第一行配置的:worker_processes 1
,这是 Nginx 服务器并发处理服务的关键配置,worker_processes 值越大,可以支持的并发处理量也越多,但是会受到硬件、软件等设备的制约。
2.2 第二部分:events块
events 块设计的指令主要影响 Nginx 服务器与用户的网络连接,常用的设置包括是否开启对多 work process 下的网络连接进行序列化,是否允许同时接收多个网络连接,选取哪种事件驱动模型来处理连接请求,每个 word process 可以同时支持的最大连接数等。
上图中的例子标识每个 work process 支持的最大连接数为 1024。这部分的配置对 Nginx 的性能影响较大,在实际中应该灵活配置。
2.3 第三部分:http块
这一模块是 Nginx 服务器配置中最频繁的部分,代理、缓存和日志定义等绝大多数功能和第三方模块的配置都在这里。需要注意的是:http 块也可以包括 http 全局块、server 块。
① http 全局块
http 全局块配置的指令包括文件引入、MIME-TYPE定义、日志自定义、连接超时时间、单链接请求数上限等。
② server 块
这块和虚拟主机有密切关系,虚拟主机从角度看,和一台独立的硬件主机是完全一样的,该技术的产生是为了节省互联网服务器硬件成本。
每个 http 块可以包括多个 server 块,而每个 server 块就相当于一个虚拟主机。
每个 server 块也分为全局 server 块,以及可以同时包含多个 location 块。
- 全局 server 块
最常见的配置是本虚拟主机的监听配置和本虚拟主机的名称或IP配置 - location 块
一个 server 块可以配置多个 location 块。
这块的主要作用是基于 Nginx 服务器接收到的请求字符串(例如server_name/uri-string),对虚拟主机名称(也可以是IP别名)之外的字符串(例如 前面的/uri-string)进行匹配,对特定的请求进行处理。地址定向、数据缓存和应答控制等功能,还有许多第三方模块的配置也在这里进行。
三、Nginx 进阶
1. Nginx 详述
1.1 Nginx 社区分支
- Openresty:作者@agentzh(章宜春)开发的,最大特点是引入了ngx_lua模块,支持使用lua开发插件,且集合了很多丰富的模块以及lua库
- Tengine:主要由淘宝团队开发。特点是融入了因淘宝自身的一些业务带来的新功能(统计QPS、主动健康检查等)
- Nginx官方版本:更新迭代快,且提供免费版本和商业版本。目前大多数使用的是免费版本,核心版本为1.6
1.2 Nginx 源码结构
Nginx 大约含11万行C代码,源代码目录结构:
- core:主干和基础设置,包括链表、数组、字符串解析、MD5库等。定制化开发时候可以直接使用此目录的数据结构
- event:事件驱动模型和不同的IO复用模块
- http:HTTP服务器和模块
- mail:邮件代理服务器和模块
- os:操作系统相关的实现
- misc:杂项
如果我们要开发新http模块,可参考下http中内容,调用core模块的库函数来实现新模块开发
1.3 Nginx 特点
- 反向代理,负载均衡:用的最多也是最基础的特点。最常用的负载均衡算法是轮询,另外还有IP哈希、URL哈希、基于服务器后端响应时间的负载均衡
- 高可靠性,架构模型基于单master多worker模式:master管理所有worker。当worker意外call down,master立即拉取新worker
- 高可扩展性、高度模块化:Nginx 有主流程11个阶段,每个阶段都可以进行独立的专注扩展
- 非阻塞:Nginx非常快,基于纯C实现,即收即发的非阻塞模式
- 事件驱动:支持多种事件驱动,默认采用epoll(最流行最火的)
- 低内存消耗:在刚开始创建内存池,每次请求会取用其中一小块内存,用完即回收(减少了因系统不断开辟内核态内存产生的内存碎片,防止内存泄漏)
- 热部署:不停机的状态下,更新二进制文件与配置,完成升级
1.4 Nginx 更多应用场景
- 静态文件服务器:将静态页面资源(图片、CSS、样式表)与动态服务器分离,存储于不同服务器上,即动静分离
- 反向代理、负载均衡:作为反向代理,以负载多台服务集群
- 安全防御:带有限流、限速等功能
- 智能路由:比如进行灰度测试,A、B两个版本,测A与B的功能,看用户更喜爱哪个。,另外A机房出现问题也可以切换到B机房
- 灰度发布:上面所说的AB测试
- 静态化:动态服务器性能无法承担,
- 消息推送:成本较高,不作多介绍
- 图片实时压缩
- 防盗链:根据配置实现防盗链,比如图片打水印等方式
1.5 Nginx 进程组件角色
- master 进程
- 监视worker进程的状态
- 当worker进程挂掉后,重启一个新的
- 处理信号和通知worker进程
- worker 进程
- 处理客户端请求
- 从master进程处获得信号做相应的事情
- cache loader进程
- 加载缓存索引文件信息,然后退出
- cache manager进程
- 管理磁盘的缓存大小,超过预定值大小后,根据LRU淘汰数据
2. Nginx 框架模型
-
Client 发送请求到 Nginx:Nginx中存在多个Worker抢占该连接请求,仅有一个会抢占成功并处理该连接,处理完成后返回响应给Client
-
管理员(运维人员) 发送请求到Nginx:比如发送一个reload信号,该请求会被Master进程接收,并交给所有Worker执行相应操作
Client与Nginx交互的详细流程:Master进程启动时,创建socket绑定端口并监听,然后创建多个Worker进程。Worker进程accept来自Client的连接,Client发送请求包至Worker,因为是非阻塞,创建连接后可能由于网络延迟发包较慢而receive多次
2.1 Master 进程的初始化
Master初始化启动时会加载配置文件,创建全局数据结构、共享内存、连接池等,并创建Workers,然后等待接收信号。(特别解释下reload命令,它会使Workers处理完任务后退出,并fork一批新的Workers接收新的请求)
2.2 Worker 进程的初始化
Worker中有各个模块与11个阶段,初始化各个模块,包括进行回调函数、事件模块、定时器的初始化
3. 静态文件请求流程
Client发送请求到Nginx,Nginx初始化连接并建立读事件的回调函数,该函数从TCP中读取客户端发送的数据,读取完成后解析请求行,检验该请求的HTTP合法性(是否有非法字符等),然后解析请求,并执行Nginx的11个阶段。此时进入static_handler函数,在Nginx配置的目录中找寻相应静态资源文件读取进内存,响应Client
4. Upstream设计
Upstream用来解决跨进程访问第三方Server服务器问题,具有以下特点:
- 底层HTTP通信框架非常完善,采用异步非阻塞模式
- 上下游内存零拷贝,节省内存,提升效率(Nginx接收到Client的请求直接发送给后端Server)
- 支持自定义模块开发
5. 反向代理流程
Nginx接收到Client发送的请求,进行解析并进行upstream:初始化upstream请求,连接后端Server发送请求,接收来自Server的响应,最终返回响应给Client
四、Nginx 定制化开发与接入层
1. Nginx 模块化设计特点
- 高度抽象的模块接口
- 模块接口简单,具有很高的灵活性
- 配置模块的设计
- 多层次、多类别的模块设计
Handler 模块:接收来自客户端的请求并构建响应头和响应体。(例:读取服务器图片并压缩,就可以自己开发一个Handler 模块,读取数据源到内存并使用压缩算法压缩,最终响应)
Filter 模块:过滤模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理,它处理时间在获取回复内容后,向用户发送响应前。比如gzip模块(例:统计该网站所有请求量,在响应回去时候去添加统计代码)
Upstream 模块:使Nginx跨越单机限制,完成网络数据的接收、处理和转发,纯异步访问后端服务
Load-Balancer 模块:负载均衡模块,实现特定的算法,在众多后端服务器中选择一个服务器,作为某请求的转发服务器
Ngx_Lua 模块:不需要对Nginx源码进行理解,只需要了解Lua脚本语言,内存开销小、运行速度快、强大的Lua协程、非阻塞、开发成本低
2. 关于接入层
接入层简称DFE(Didi FrontEnd),负责公司类HTTP流量(http、https、h2、websocket、grpc)和thrift流量的接入,同时提供强大的流量管理/治理能力。
接入层主要分为Router和Inrouter两个系统:
- 【Router】:主要负责南北向流量的接入,可以简单理解为外网流量接入。
- 【Inrouter】:主要负责东西向流量的接入,可以简单理解为内网流量接入,对内部服务间流量调用进行治理。
接入层具备如下的特点:
- 【极高的稳定性】:SLA > 99.99%,超一级服务稳定性要求。
- 【极致的性能】:极快的响应时间和高并发处理能力
- 转发平均延时 < 1ms
- HTTP QPS > 2W
- HTTPS QPS > 1W
- 【专业团队支持】:有专业的研发/运维团队支持
- 【丰富的功能】:
- 负载均衡: 支持多种负载均衡策略,包括rr、wrr、一致性hash等
- 多协议支持:支持http、https、grpc、websocket、thrift等多种协议
- SSL卸载:支持SSL卸载能力,HTTPS一站式接入
- 应用层防火墙(WAF):联合安全部,提供应用层防火墙能力
- 健康检查:同时提供主动和被动健康检查能力
- 服务发现:支持服务发现能力
- 一键限流:打通911,支持一键限流
- 一键切流:打通911,提供一键切流
- IP黑白名单:支持IP黑白名单控制
- 控制台能力:提供控制台进行配置管控(目前部分开放)
服务相关指标
- 服务当前可用性指标:99.990%
- 接口延迟2ms以内
- 单机容量2W QPS
滴滴流量接入一览图:
3. 接入层的智能路由
滴滴的接入层插件模块:
对于智能路由模块做下详细解释:主要用于解决用户灵活的进行灰度测试(A/B Test)、动态分流、智能分流等各种需求。
根据请求header头中header-hint-content字段,匹配对应的策略模式动态的计算集群lidc名称,从而进行动态的分流到不同的集群。
支持的策略:
- 字段绝对值
- 字段范围值
- 字段枚举值
- 字段权重值
- 多种策略组合
基于Tengine与可配置的Apollo(阿波罗)平台实现的统一接入层,将请求转发到对应集群的流程:
统一接入层接收到来自请求方的请求,会检查智能路由模块开关是否初始化,如果已初始化则检查header合法性,并获取header中的字段header-hint-content,然后解析,根据解析后的结果按策略匹配到相应的集群名称lidc_name。
业务方使用阿波罗平台配置相应header匹配策略规则(比如要求city_id值小于等于1000的请求发送到hna集群),并推送到智能路由模块,该模块每秒根据配置文件更新缓存,在下次策略匹配时会拿到缓存信息,走最新的匹配规则。
相应流程图:
4. 关于灰度发布、配置同步、A/B测试与Apollo
Apollo是一个适用于多种场景的A/B实验与灰度发布、配置同步的平台,通过科学合理的AB实验数据辅助业务决策与效果分析,利用简单易用的灰度、配置功能提升开发效率,降低上线风险。Apollo已经成为策略和业务效果评价体系中的重要一环。
滴滴Apollo平台官网地址:http://ab.intra.xiaojukeji.com/
4.1 配置同步
配置同步:针对不同的服务、部署环境,实时将配置下发到目标机器,提供小流量测试及全量发布功能;配置修改无需上线,实现配置修改快速生效。
使用场景:微服务应用架构下的配置管理;分布式架构下的服务治理;应用业务数据动态推送;基于模版的可视化配置;配置通道
应用项目中都会有一些配置信息,这些配置信息数据量少,一般会保存到内存、文件或者数据库,有时候需要动态更新。当需要在多个应用服务器中修改这些配置文件时,需要做到快速、简单、不停止应用服务器的方式修改并同步配置信息到所有应用中去。此时,就需要一个平台来集中管理所有应用环境中的配置,实现配置的动态同步。
Apollo 配置服务能帮您集中管理所有应用环境中的配置,降低系统中管理配置的成本和因错误的配置变更带来可用性下降甚至发生故障的风险。配置服务的具体使用场景如下:
- 在传统架构的应用发布过程中,修改一个应用配置,需要将整个应用重新打包发布。apollo 配置服务可以集中管理配置,不再依赖于配置打包。
- 在各类分布式架构下,如何基于某类 RPC 框架做好服务治理是非常关键的。其中,服务治理的服务限流、服务降级、开关策略控制等,均可通过配置中心实现。
- 对于有复杂业务需求的场景,业务方通常需要定制符合业务需求的页面,此时,apollo 配置服务作为配置下发通路,负责把配置数据下发到业务机器,并提供 SDK 供使用方读取配置。
4.2 A/B 实验
A/B 实验:为一个目标制定两个(或两个以上)方案,将用户流量对应分成几组,让用户看到不同的方案设计,并设定几个要关注的指标,根据几组用户的真实数据反馈,通过计算每组人群的业务指标(转化率、成交率等)来评估策略或功能的实际效果。
支持场景:策略算法、产品优化、通过运营系统上线、广告投放
分流方式:随机分流、时间片分流、城市分片
较直觉而言, 我们通常更相信用实验来验证我们的新想法。 在互联网企业中, 在线实验特别是A/B测试在产品创新和企业发展的过程中扮演着非常重要的角色。我们通常利用A/B Tests来验证一些新特性或者调整, 并以此来支撑我们在产品开发过程的决策过程。 这些需要测试的变化可以是UI界面的调整, 也可以是一个排序模型的迭代, 甚至也可以是整个首页的重建。通常来说, 如果规模不大, 我们可以实现一些ad hoc的测试来分析。 可是当我们每天需要同时进行上百的实验时, ad hoc方法就不能满足要求了。 这个时候, 我们就需要一个平台来允许我们能够科学的开展实验, 并且能够容易的对A/B tests的影响进行量化。
因此, 我们开发了Apollo实验平台。 这个平台允许我们能够轻松地设计并部署实验, 并且也提供自动化流程来分析实验结果。 这是一个通用且可扩展的平台, 包含对各个平台如iOS, Android和算法平台的支持。同时,这个平台被设计成可以支持成千的实验并发进行。
4.3 灰度发布
灰度发布:通过选择目标流量,进行功能投放;持续观测数据并逐步放量,直到全量部署的过程。通过灰度发布可以找到容错度高、反馈意愿高的用户来尽早获取数据,可以在问题到达所有用户之前及时止损,客观的比较新旧方案的受欢迎程度驱动正确的产品决策。
- 在端上出现一个Bug,快速修复之后提交审核会需要5-7个工作日,发布给用户之后需要很长的时间用户升级APP才能完成修复。
- 一次重大的改版,如果无法得到用户的青睐,将流失大量用户
- 新上了一个功能,用户非常喜欢,需求突然暴增,相关资源、客服、经费等暴露出严重问题
互联网产品通常伴随着短而频繁的升级,升级在给用户带来新功能的同时,也会带来一系列的风险:例如新功能和旧功能是否存在兼容问题、用户会不会喜欢我们的设计、会不会引入BUG。而对于移动互联网产品还要面对App Store发布的版本审核周期,手机用户的升级习惯等更为复杂的场景。在竞争如此激烈的市场,如果没有有效的手段规避这些风险,将会对企业造成巨大的损失。
灰度发布(或者灰度放量)是一种业内通用的规避版本迭代风险的手段, 它是一种通过选择目标流量,进行功能投放,通过持续观测数据逐步放量,直到全量部署的过程。相比传统发布模式,通过灰度发布可以找到容错度高、反馈意愿高的用户来尽早获取数据, 可以在问题到达所有用户之前及时止损,可以客观的比较新旧方案的受欢迎程度驱动正确的产品决策。