网关路由规则设置(网关系统就该这么设计(万能通用),稳的一批)

来根烤肠 资讯 16
一种是接入层网关(access gateway),主要负责路由,WAF(防止SQL Injection, XSS, 路径遍历, 窃取敏感数据,CC攻击等),限流,日志,缓存等,这一层的网关主要承载着将请求路由到各个应用层网关的功能另一种是应用层网关,比如现在流行的微服务,各个服务可能是用不同的语言写的,如 PHP,Java 等,那么接入层就要将请求路由到相应的应用层集群,再由相应的应用层网关进行鉴权等处理,处理完之后再调用相应的微服务进行处理,应用层网关也起着路由,超时,重试,熔断等功能。

目前市面上比较流行的系统架构如下

如图示,两个请求对应的路由规则是不一样的,它们对应的路由规则(限流,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.详尽的文档和测试用例」

「2.同步非阻塞」

OpenResty 在诞生之初就支持了协程,并且基于此实现了同步非阻塞的编程模型。

「画外音:协程(coroutine)我们可以将它看成一个用户态的线程,只不过这个线程是我们自己调度的,而且不同协程的切换不需要陷入内核态,效率比较高。(一般我们说的线程是要指内核态线程,由内核调度,需要从用户空间陷入内核空间,相比协程,对性能会有不小的影响)」

啥是同步非阻塞呢。假设有以下两个两行代码:

local res, err = query-mysql(sql)local value, err = query-redis(key)

「同步」:必须执行完查询 mysql,才能执行下面的 redis 查询,如果不等 mysql 执行完成就能执行 redis 则是异步。

那么 OpenResty 的工作原理是怎样的呢,又是如何实现同步非阻塞的呢。

OpenResty 原理剖析

工作原理剖析

由于 OpenResty 基于 Nginx 实现的,我们先来看看 Nginx 的工作原理

Nginx 启动后,会有一个 master 进程和多个 worker 进程 , master 进程接受管理员的信号量(如 Nginx -s reload, -s stop)来管理 worker 进程,master 本身并不接收 client 的请求,主要由 worker 进程来接收请求,不同于 apache 的每个请求会占用一个线程,且是同步IO,Nginx 是异步非阻塞的,每个 worker 可以同时处理的请求数只受限于内存大小,这里就要简单地了解一下 nginx 采用的 epoll 模型:

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 事件机制是如何配合的了。

事实上,由 OpenResty 提供的所有 API,都是非阻塞的,下文提到的与 MySQL,Redis 等交互,都是非阻塞的,所以性能很高。

各个阶段 *_by_lua 的解释如下

# 明文协议版本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 dict

worker 既然是互相独立的进程,就需要考虑其共享数据的问题, OpenResty 提供了一种高效的数据结构: shared dict ,可以实现在 worker 间共享数据,shared dict 对外提供了 20 多个 Lua API,都是原子操作的,避免了高并发下的竞争问题。

路由策略插件化实现

有了以上 OpenResty 点的铺垫,来看看上文提的网关核心功能 「路由策略插件化」,「后端集群的动态变更」如何实现

首先针对某个请求的路由策略大概是这样的

整个插件化的步骤大致如下

1、每条策略由 url ,action, cluster 等组成,代表请求 url 在打到后端集群过程中最终经历了哪些路由规则,这些规则统一在我们的路由管理平台配置,存在 db 里。

经过路由规则确定好每个请求对应要打的后端集群后,就需要根据 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 的知识点远不止以上这些,大家如有兴趣,可以深入学习,相信大家会有不少启发的。

标签: 协程 进程 网关

抱歉,评论功能暂时关闭!