可以看到接入层网关承载着公司的所有流量,对性能有很高的哀求,它的设计决定着全体系统的上限。以是我们今天主要谈谈接入层网关的设计。
接入层网关架构设计与实现首先我们要明白接入层网关的核心功能是:「根据路由规则将要求分发到对应的后端集群」,以是要实现如下几个功能模型 。
1、 路由:根据要求的 host, url 等规则转发到指定的上游(相应的后端集群) 2、 路由策略插件化:这是网关的「灵魂所在」,路由中会有身份认证,限流限速,安全防护(如 IP 黑名单,refer非常,UA非常,需第一韶光谢绝)等规则,这些规则以插件的形式相互组合起来以便只对某一类的要求生效,每个插件都即插即用,互不影响,这些插件该当是「动态可配置」的,动态生效的(无须重启做事),为啥要可动态可配置呢,由于每个要求对应的路由逻辑,限流规则,终极要求的后端集群等规则是不一样的

如图示,两个要求对应的路由规则是不一样的,它们对应的路由规则(限流,rewrite)等通过各个规则插件组合在一起,可以看到,光两个要求 url 的路由规则就有挺多的,如果一个别系大到一定程度,url 会有不少,就会有不少规则,这样每个要求的规则就必须「可配置化」,「动态化」,最好能在管理端集中掌握,统一下发。
3、后端集群的动态变更
路由规则的运用是为了确定某一类要求经由这些规则后终极到达哪一个集群,而我们知道要求肯定是要打到某一台集群的 ip 上的,而机器的扩缩容实在是比较常见的,以是必须支持动态变更,总不能我每次高下线机器的时候都要重启系统让它生效吧。
4、监控统计,要求量、缺点率统计等等
这个比较好理解,在接入层作所有流量的要求,缺点统计,便于打点,告警,剖析。
要实现这些需求就必须对我们采取的技能:OpenResty 有比较详细的理解,以是下文会大略先容一下 OpenResty 的知识点。
技能选型有人可能第一眼想到用 Nginx,没错,由于 Nginx 采取了 epoll 模型(非壅塞 IO 模型),确实能知足大多数场景的需求(经由优化 100 w + 的并发数不是问题),但是 Nginx 更适宜作为静态的 Web 做事器,由于对付 Nginx 来说,如果发生任何变革,都须要修正磁盘上的配置,然后重新加载才能生效,它并没有供应 API 来掌握运行时的行为,而如上文所述,动态化是接入层网关非常主要的一个功能。以是经由一番调研,我们选择了 OpenResty,啥是 OpenResty 呢,来看下官网的定义:
OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 运用、Web 做事和动态网关。OpenResty® 的目标是让你的Web做事直接跑在 Nginx 做事内部,充分利用 Nginx 的非壅塞 I/O 模型,不仅仅对 HTTP 客户端要求,乃至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行同等的高性能相应。
可以大略理解为,OpenResty = Nginx + Lua, 通过 Lua 扩展 Nginx 实现的可伸缩的 Web 平台 。它利用了 Nginx 的高性能,又在其根本上添加了 Lua 的脚本措辞来让 Nginx 也具有了动态的特性。通过 OpenResty 中 lua-Nginx-module 模块中供应的 Lua API,我们可以动态地掌握路由、上游、SSL 证书、要求、相应等。乃至可以在不重启 OpenResty 的条件下,修正业务的处理逻辑,并不局限于 OpenResty 供应的 Lua API。
关于静态和动态有一个很得当的类比:如果把 Web 做事器当做是一个正在高速公路上飞驰的汽车,Nginx 须要停车才能改换轮胎,改换车漆颜色,而 OpenResty 中可以边跑边换轮胎,改换车漆,乃至改换发动机,直接让普通的汽车变成超跑!
除了以上的动态性,还有两个特性让 OpenResty 独出一格。
「1.详尽的文档和测试用例」
作为开源项目,文档和测试毫无疑问是其是否靠谱的关键,它的文档非常详细,作者把每个把稳的点都写在文档上了,多数时候只要看文档即可,每一个测试案例都包含完全的 Nginx 配置和 lua 代码。以及测试的输入数据和预期的输出数据。
「2.同步非壅塞」
OpenResty 在出身之初就支持了协程,并且基于此实现了同步非壅塞的编程模型。
「画外音:协程(coroutine)我们可以将它算作一个用户态的线程,只不过这个线程是我们自己调度的,而且不同协程的切换不须要陷入内核态,效率比较高。(一样平常我们说的线程是要指内核态线程,由内核调度,须要从用户空间陷入内核空间,比较协程,对性能会有不小的影响)」
啥是同步非壅塞呢。假设有以下两个两行代码:
local res, err = query-mysql(sql)local value, err = query-redis(key)
「同步」:必须实行完查询 mysql,才能实行下面的 redis 查询,如果不等 mysql 实行完造诣能实行 redis 则是异步。
「壅塞」:假设实行 sql 语句须要 1s,如果在这 1s 内,CPU 只能干等着不能做其它任何事,那便是壅塞,如果在 sql 实行期间可以做其他事(把稳由于是同步的,以是不能实行以下的 redis 查询),则是非壅塞。
同步关注的是语句的先后实行顺序,如果上一个语句必须实行完才能实行下一个语句便是同步,如果不是,便是异步,壅塞关注的是线程是 CPU 是否须要在 IO 期间干等着,如果在 IO(或其他耗时操作期间)期间可以做其他事,那就是非壅塞,不能动,则是壅塞。
那么 OpenResty 的事情事理是若何的呢,又是如何实现同步非壅塞的呢。
OpenResty 事理阐发事情事理阐发由于 OpenResty 基于 Nginx 实现的,我们先来看看 Nginx 的事情事理
Nginx 启动后,会有一个 master 进程和多个 worker 进程 , master 进程接管管理员的旗子暗记量(如 Nginx -s reload, -s stop)来管理 worker 进程,master 本身并不吸收 client 的要求,紧张由 worker 进程来吸收要求,不同于 apache 的每个要求会占用一个线程,且是同步IO,Nginx 是异步非壅塞的,每个 worker 可以同时处理的要求数只受限于内存大小,这里就要大略地理解一下 nginx 采取的 epoll 模型:
epoll 采取多路复用模型,即同一韶光虽然可能会有多个要求进来, 但只会用一个线程去监视,然后哪个要求数据准备好了,就调用相应的线程去处理,就像图中所示,犹如拨开关一样,同一韶光只有一个线程在处理, Nginx 底层便是用的 epoll ,基于事宜驱动模型,每个要求进来注册事宜并注册 callback 回调函数,等数据准入好了,就调用回调函数进行处理,它是异步非壅塞的,以是性能很高。
打个大略的比方,我们都有订票的履历,当我们委托酒店订票时,接待员会先把我们的电话号码和干系信息等记下来(注册事宜),挂断电话后接待员在操作期间我们就可以去做其他事了(非壅塞),当接待员把手续搞好后会主动打电话给我们关照我们票订好了(回调)。
worker 进程是从 master fork 出来的,这意味着 worker 进程之间是相互独立的,这样不同 worker 进程之间处理并发要求险些没有同步锁的限定,好处便是一个 worker 进程挂了,不会影响其他进程,我们一样平常把 worker 数量设置成和 CPU 的个数,这样可以减少不必要的 CPU 切换,提升性能,每个 worker 都是单线程实行的。那么 LuaJIT 在 OpenResty 架构中的位置是若何的呢。
首先启动的 master 进程带有 LuaJIT 的机虚拟,而 worker 进程是从 master 进程 fork 出来的,在 worker 内进程的事情紧张由 Lua 协程来完成,也便是说在同一个 worker 内的所有协程,都会共享这个 LuaJIT 虚拟机,每个 worker 进程里 lua 的实行也是在这个虚拟机中完成的。
同一个韶光点,worker 进程只能处理一个用户要求,也便是说只有一个 lua 协程在运行,那为啥 OpenResty 能支持百万并发要求呢,这就须要理解 Lua 协程与 Nginx 事宜机制是如何合营的了。
如图示,当用 Lua 调用查询 MySQL 或 网络 IO 时,虚拟机会调用 Lua 协程的 yield 把自己挂起,在 Nginx 中注册回调,此时 worker 就可以处理其余的要求了(非壅塞),等到 IO 事宜处理完了, Nginx 就会调用 resume 来唤醒 lua 协程。
事实上,由 OpenResty 供应的所有 API,都是非壅塞的,下文提到的与 MySQL,Redis 等交互,都是非壅塞的,以是性能很高。
OpenResty 要求生命周期Nginx 的每个要求有 11 个阶段,OpenResty 也有11 个 _by_lua 的指令,如下图示:
各个阶段 _by_lua 的阐明如下
set_by_lua:设置变量;rewrite_by_lua:转发、重定向等;access_by_lua:准入、权限等;content_by_lua:天生返回内容;header_filter_by_lua:应答头过滤处理;body_filter_by_lua:应答体过滤处理;log_by_lua:日志记录。
这样分阶段有啥好处呢,假设你原来的 API 要求都是明文的
# 明文协议版本location /request { content_by_lua '...'; # 处理要求}
现在须要对其加上加密和解密的机制,只须要在 access 阶段解密, 在 body filter 阶段加密即可,原来 content 的逻辑无需做任务改动,有效实现了代码的解藕。
# 加密协议版本location /request { access_by_lua '...'; # 要求体解密 content_by_lua '...'; # 处理要求,不须要关心通信协议 body_filter_by_lua '...'; # 应答体加密}
再比如我们不是要要上文提到网关的核心功能之一不是要监控日志吗,就可以统一在 log_by_lua 上报日志,不影响其他阶段的逻辑。
worker 间共享数据利器: shared dictworker 既然是相互独立的进程,就须要考虑其共享数据的问题, OpenResty 供应了一种高效的数据构造: shared dict ,可以实现在 worker 间共享数据,shared dict 对外供应了 20 多个 Lua API,都是原子操作的,避免了高并发下的竞争问题。
路由策略插件化实现有了以上 OpenResty 点的铺垫,来看看上文提的网关核心功能 「路由策略插件化」,「后端集群的动态变更」如何实现
首先针对某个要求的路由策略大概是这样的
全体插件化的步骤大致如下
1、每条策略由 url ,action, cluster 等组成,代表要求 url 在打到后端集群过程中终极经历了哪些路由规则,这些规则统一在我们的路由管理平台配置,存在 db 里。
2、OpenResty 启动时,在要求的 init 阶段 worker 进程会去拉取这些规则,将这些规则编译成一个个可实行的 lua 函数,这一个个函数就对应了一条条的规则。
须要把稳的是为了避免重复去 MySQL 中拉取数据,某个 worker 从 MySQL 拉取完规则(此步须要加锁,避免所有 worker 都去拉取)或者后端集群等配置信息后要将其保存在 shared dict 中,这样之后所有的 worker 要求只要从 shared dict 中获取这些规则,然后将其映射成对应模块的函数即可,如果配置规则有变动呢,配置后台通过接口关照 OpenResty 重新加载一下即可
经由路由规则确定好每个要求对应要打的后端集群后,就须要根据 upstream 来确定终极打到哪个集群的哪台机器上,我们看看如何动态管理集群。
后端集群的动态配置在 Nginx 中配置 upstream 的格式如下
upstream backend { server backend1.example.com weight=5; server backend2.example.com; server 192.0.0.1 backup;}
以上这个示例是按照权重(weight)来划分的,6 个要求进来,5个要求打到 backend1.example.com, 1 个要求打到 backend2.example.com,如果这两台机器都不可用,就打到 192.0.0.1,这种静态配置的办法 upstream 的办法确实可行,但我们知道机器的扩缩容有时候比较频繁,如果每次机器高下线都要手动去改,并且改完之后还要重新去 reload 无疑是不可行的,出错的概率很大,而且每次配置都要 reload 对性能的损耗也是挺大的,为理解决这个问题,OpenResty 供应了一个 dyups 的模块来办理此问题, 它供应了一个 dyups api,可以动态增,删,创建 upsteam,以是在 init 阶段我们会先去拉取集群信息,构建 upstream,之后如果集群信息有变动,会通过如下形式调用 dyups api 来更新 upstream
-- 动态配置 upstream 接口站点server { listen 127.0.0.1:81; location / { dyups_interface; }}-- 增加 upstream:user_backendcurl -d "server 10.53.10.191;" 127.0.0.1:81/upstream/user_backend-- 删除 upstream:user_backendcurl -i -X DELETE 127.0.0.1:81/upstream/user_backend
利用 dyups 就办理了动态配置 upstream 的问题
网关终极架构设计图通过这样的设计,终极实现了网关的配置化,动态化。
总结网关作为承载公司所有流量的入口,对性能有着极高的哀求,以是技能选型上还是要慎重,之以是选择 OpenResty,一是由于它高性能,二是目前也有小米,阿里,腾讯等大公司在用,是久经由市场磨练的,本文通过对网关的总结简要先容了 OpenResty 的干系知识点,相信大家对其紧张功能点该当有所理解了,不过 OpenResty 的知识点远不止以上这些,大家如有兴趣,可以深入学习,相信大家会有不少启示的。