「京东开涛」使⽤Nginx+Lua(OpenResty)开发⾼性能Web
应⽤
⼏乎所有互联⽹公司,Nginx可以说是标配组件,但是主要场景还是负载均衡、反向代理、代理缓存、限流等场景;⽽把Nginx作为⼀个Web容器使⽤的还不是那么⼴泛。Nginx的⾼性能是⼤家公认的,⽽Nginx开发主要是以C/C++模块的形式进⾏,整体学习和开发成本偏⾼;如果有⼀种简单的语⾔来实现Web应⽤的开发,那么Nginx绝对是把好的瑞⼠军⼑;⽬前Nginx团队也开始意识到这个问题,开发了nginxScript:可以在Nginx中使⽤JavaScript进⾏动态配置⼀些变量和动态脚本执⾏;⽽⽬前市⾯上⽤的⾮常成熟的扩展是由章亦春将Lua和Nginx粘合的ngx_lua模块,并且将Nginx核⼼、LuaJIT、ngx_lua模块、许多有⽤的Lua库和常⽤的第三⽅Nginx模块组合在⼀起成为OpenResty,这样开发⼈员就可以安装OpenResty,使⽤Lua编写脚本,然后部署到Nginx Web容器中运⾏。从⽽⾮常轻松就能开发出⾼性能的Web服务。
接下来我们就认识下Nginx、Lua、ngx_lua模块和ngx_lua到底能开发哪些类型的web应⽤。
⼀、ngx_lua简介
1、Nginx优点
Nginx设计为⼀个主进程多个⼯作进程的⼯作模式,每个进程是单线程来处理多个连接,⽽且每个⼯作进程采⽤了⾮阻塞I/O来处理多个连接,从⽽减少了线程上下⽂切换,从⽽实现了公认的⾼性能、⾼并发;因此在⽣成环境中会通过把CPU绑定给Nginx⼯作进程从⽽提升其性能;另外因为单线程⼯作模式的特点,内存占⽤就⾮常少了。
Nginx更改配置重启速度⾮常快,可以毫秒级,⽽且⽀持不停⽌Nginx进⾏升级Nginx版本、动态重载Nginx配置。
Nginx模块也是⾮常多,功能也很强劲,不仅可以作为http负载均衡,Nginx发布1.9.0版本还⽀持TCP负载均衡,还可以很容易的实现内容缓存、web服务器、反向代理、访问控制等功能。
2、Lua的优点
Lua是⼀种轻量级、可嵌⼊式的脚本语⾔,这样可以⾮常容易的嵌⼊到其他语⾔中使⽤。另外Lua提供了协程并发,即以同步调⽤的⽅式进⾏异步执⾏,从⽽实现并发,⽐起回调机制的并发来说代码更容易编写和理解,排查问题也会容易。Lua还提供了闭包机制,函数可以作为First Class Value 进⾏参数传递,另外其实现了标记清除垃圾收集。
因为Lua的⼩巧轻量级,可以在Nginx中嵌⼊Lua VM,请求的时候创建⼀个VM,请求结束的时候回收VM。
3、什么是ngx_lua
ngx_lua是Nginx的⼀个模块,将Lua嵌⼊到Nginx中,从⽽可以使⽤Lua来编写脚本,这样就可以使⽤Lua编写应⽤脚本,部署到Nginx中运⾏,即Nginx变成了⼀个Web容器;这样开发⼈员就可以使⽤Lua语⾔开发⾼性能Web应⽤了。
ngx_lua提供了与Nginx交互的很多的API,对于开发⼈员来说只需要学习这些API就可以进⾏功能开发,⽽对于开发web应⽤来说,如果接触过Servlet的话,其开发和Servlet类似,⽆外乎就是知道接收请求、参数解析、功能处理、返回响应这⼏步的API是什么样⼦的。
4、开发环境
我们可以使⽤OpenResty来搭建开发环境,OpenResty将Nginx核⼼、LuaJIT、许多有⽤的Lua库和Nginx第三⽅模块打包在⼀起;这样开发⼈员只需要安装OpenResty,不需要了解Nginx核⼼和写复杂的C/C++模块就可以,只需要使⽤Lua语⾔进⾏Web应⽤开发了。
5、OpenResty⽣态
OpenResty提供了⼀些常⽤的ngx_lua开发模块:如
lua-resty-memcached
lua-resty-mysql
lua-resty-redis
lua-resty-dns
lua-resty-limit-traffic
lua-resty-template
这些模块涉及到如mysql数据库、redis、限流、模块渲染等常⽤功能组件;另外也有很多第三⽅的ngx_lua组件供我们使⽤,对于⼤部分应⽤场景来说现在⽣态环境中的组件已经⾜够多了;如果不满⾜需求也可以⾃⼰去写来完成⾃⼰的需求。
6、场景
理论上可以使⽤ngx_lua开发各种复杂的web应⽤,不过Lua是⼀种脚本/动态语⾔,不适合业务逻辑⽐较重的场景,适合⼩巧的应⽤场景,代码⾏数保持在⼏⼗⾏到⼏千⾏。⽬前见到的⼀些应⽤场景:
web应⽤:会进⾏⼀些业务逻辑处理,甚⾄进⾏耗CPU的模板渲染,⼀般流程:mysql/redis/http获取数据、业务处理、产⽣JSON/XML/模板渲染内容,⽐如京东的列表页/商品详情页;
接⼊⽹关:实现如数据校验前置、缓存前置、数据过滤、API请求聚合、AB测试、灰度发布、降级、监控等功能,⽐如京东的交易⼤Nginx 节点、⽆线部门正在开发的⽆线⽹关、单品页统⼀服务、实时价格、动态服务;
Web防⽕墙:可以进⾏IP/URL/UserAgent/Referer⿊名单、限流等功能;
缓存服务器:可以对响应内容进⾏缓存,减少到后端的请求,从⽽提升性能;
其他:如静态资源服务器、消息推送服务、缩略图裁剪等。
⼆、基于Nginx+Lua的常⽤架构模式
1、负载均衡
如上图,我们⾸先通过LVS+HAProxy将流量转发给核⼼Nginx 1和核⼼Nginx 2,即实现了流量的负载均衡,此处可以使⽤如轮训、⼀致性哈希等调度算法来实现负载的转发;然后核⼼Nginx会根据请求特征如“Host:”,转发给相应的业务Nginx节点如单品页Nginx 1。此处为什么分两层呢?
1、核⼼Nginx层是⽆状态的,可以在这⼀层实现流量分组(内⽹和外⽹隔离、爬⾍和⾮爬⾍流量隔离)、内容缓存、请求头过滤、故障切换(机房故障切换到其他机房)、限流、防⽕墙等⼀些通⽤型功能;
2、业务Nginx如单品页Nginx,可以在在业务Nginx实现业务逻辑、或者反向代理到如Tomcat,在这⼀层可以实现内容压缩(放在这⼀层的⽬的是减少核⼼Nginx的CPU压⼒,将压⼒分散到各业务Nginx)、AB测试、降级;即这⼀层的Nginx跟业务有关联,实现业务的⼀些通⽤逻辑。
nginx和网关怎么配合使用
不管是核⼼Nginx还是业务Nginx,都应该是⽆状态设计,可以⽔平扩容。
业务Nginx⼀般会把请求直接转发给后端的业务应⽤,如Tomcat、PHP,即将请求内部转发到相应的业务应⽤;当有的Tomcat出现问题了,可以在这⼀层摘掉;或者有的业务路径变了在这⼀层进⾏rewrite;或者有的后端Tomcat压⼒太⼤也可以在这⼀层降级,减少对后端的冲击;或者业务需要灰度发布时也可以在这⼀层Nginx上控制。
2、单机闭环
所谓单机闭环即所有想要的数据都能从本服务器直接获取,在⼤多数时候⽆需通过⽹络去其他服务器获取。
如上所⽰,主要有三种应⽤模式:
⽬前有成熟的Nginx模块如nginx-http-concat进⾏静态资源合并;因为我们使⽤了OpenResty,那么我们完全可以使⽤Lua编写程序实现该功能,⽐如已经有⼈写了nginx-lua-static-merger来实现这个功能。
还⼀些业务型应⽤场景如下图所⽰:
商品页⾯是由商品框架和其他维度的页⾯⽚段(⾯包屑、相关分类、商家信息、规格参数、商品详情)组成;或者⾸页是由⾸页框架和⼀些页⾯⽚段(分类、轮播图、楼层1、楼层N)组成;分维度是因为不同的维度是独⽴变化的。对于这种静态内容但是需要进⾏框架内容嵌⼊的⽅式,Nginx⾃带的SSI(Server Side Include)可以很轻松的完成;也可以使⽤Lua程序更灵活的完成(读取框架、读取页⾯⽚段、合并输出)。
⽐如商品页⾯的架构我们可以这样:
⾸先接收到商品变更消息,商品页⾯同步Worker会根据消息维度⽣成相关的页⾯推送到Nginx服务器;Nginx应⽤再通过SSI输出。⽬前京东商品详情页没有再采⽤这种架构,具体架构可以参考《构建需求响应式亿级商品详情页》。
对于⾸页的架构是类似的,因为其特点(框架变化少,楼层变化较频繁)和个性化的要求,楼层⼀般实现为异步加载。
2.3、第三张图和第⼆张图的不同处是不再直接读取⽂件系统,⽽是读取本机的Redis或者Redis集或者如SSDB这种持久化存储或者其他存储系统都是可以的,⽐如直接说的商品页⾯可以使⽤SSDB进⾏存储实现。⽂件系统⼀个很⼤的问题是当多台服务器时需要Worker去写多台服务器,⽽这个过程可以使⽤SSDB的主从实现。
此处可以看到,不管是图⼆还是图三架构,都需要Worker去进⾏数据推送;假设本机数据丢了可怎么办?因此实际⼤部分应⽤不会是完全单机闭环的,⽽是会采⽤如下架构:
即⾸先读本机,如果没数据会回源到相应的Web应⽤从数据源拉取原始数据进⾏处理。这种架构的⼤部分场景本机都可以命中数据,只有很少⼀部分情况会回源到Web应⽤。如京东的实时价格/动态服务就是采⽤类似架构。
3、分布式闭环
单机闭环会遇到如下两个主要问题: 1、数据不⼀致问题(⽐如没有采⽤主从架构导致不同服务器数据不⼀致);2、遇到存储瓶颈(磁盘或者内存遇到了天花板)。
解决数据不⼀致的⽐较好的办法是采⽤主从或者分布式集中存储;⽽遇到存储瓶颈就需要进⾏按照业务键进⾏分⽚,将数据分散到多台服务器。
如采⽤如下架构,按照尾号将内容分布到多台服务器。
即第⼀步先读取分布式存储(JIMDB是京东的⼀个分布式缓存/存储系统,类似于Redis);如果不命中则回源到Tomcat集(其会调⽤数据库、服务总线获取相关数据)来获取相关数据。可以参考《构建需求响应式亿级商品详情页》来获取更详细的架构实现。
JIMDB集会进⾏多机房主从同步,各⾃机房读取⾃⼰机房的从JIMDB集,如下图:
4、接⼊⽹关
接⼊⽹关也可以叫做接⼊层,即接收到流量的⼊⼝,在⼊⼝我们可以进⾏如下事情:
4.1、核⼼接⼊Nginx会做如下事情:
1、动态负载均衡;1、普通流量⾛⼀致性哈希,提升命中率;热点流量⾛轮训减少单服务器压⼒;
2、根据请求特征将流量分配到不同分组并限流(爬⾍、或者流量⼤的IP);
3、动态流量(动态增加upstream或者减少upstream或者动态负载均衡)可以使⽤balancer_by_lua或者微博开源的upsync;
2、防DDOS攻击限流:可以将请求⽇志推送到实时计算集,然后将需要限流的IP推送到核⼼Nginx进⾏限流;
3、⾮法请求过滤:⽐如应该有Referer却没有,或者应该带着Cookie却没有Cookie;
5、请求头过滤:有些业务是不需要请求头的,因此可以在往业务Nginx转发时把这些数据过滤掉;
6、缓存服务:使⽤Nginx Proxy Cache实现内容页⾯的缓存;
4.2、业务Nginx会做如下事情:
1、缓存:对于读服务会使⽤⼤量的缓存来提升性能,我们在设计时主要有如下缓存应⽤:⾸先读取Nginx本地缓存  Shared Dict或者Nginx Proxy Cache,如果有直接返回内容给⽤户;如果本地缓存不命中,则会读取分布式缓存如Redis,如果有直接返回;如果还是不命中则回源到Tomcat应⽤读取DB或调⽤服务获取数据。另外我们会按照维度进⾏数据的缓存。
2、业务逻辑:我们会进⾏⼀些数据校验/过滤逻辑前置(如商品ID必须是数字)、业务逻辑前置(获取原⼦数据,然后在Nginx上写业务逻辑)。
3、细粒度限流:按照接⼝特征和接⼝吞吐量来实现动态限流,⽐如后端服务快扛不住了,那我们就需要进⾏限流,被限流的请求作为降级请求处理;通过lua-resty-limit-traffic可以通过编程实现更灵活的降级逻辑,如根据⽤户、根据URL等等各种规则,如降级了是让⽤户请求等待(⽐如sleep 100ms,这样⽤户请求就慢下来了,但是服务还是可⽤)还是返回降级内容。
4、降级:降级主要有两种:主动降级和被动降级;如请求量太⼤扛不住了,那我们需要主动降级;如后端挂了或者被限流了或者后端超时了,那我们需要被动降级。降级⽅案可以是:1、返回默认数据如库存默认有货;2、返回静态页如预先⽣成的静态页;3、部分⽤户降级,告诉部分⽤户等待下再操作;4、直接降级,服务没数据,⽐如商品页⾯的规格参数不展⽰;
5、只降级回源服务,即可以读取缓存的数据返回,实现部分可⽤,但是不会回源处理;
5、AB测试/灰度发布:⽐如要上⼀个新的接⼝,可以通过在业务Nginx通过Lua写复杂的业务规则实现不同的⼈看到不同的版本。
6、服务质量监控:我们可以记录请求响应时间、缓存响应时间、反向代理服务响应时间来详细了解到底哪块服务慢了;另外记录⾮200状态码错误来了解服务的可⽤率。
5、Web应⽤
此处所说的Web应⽤指的是页⾯模板渲染类型应⽤或者API服务类型应⽤;⽐如京东列表页/商品详情页就是⼀个模板渲染类型的应⽤,核⼼业务逻辑都是使⽤Lua写的,部署到Nginx容器。⽬前核⼼业务代码⾏数有5000多⾏,模板页⾯有2000多⾏,涉及到⼤量的计算逻辑,性能数据可以参考《构建需求响应式亿级商品详情页》。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。