用OpenResty构建网关(3)—— 数据访问
场景与挑战
作为API网关,我们有很多数据访问的需求,这些需求内包含两类数据:
-
网关的配置数据:数据更新不频繁,但是要求准实时生效,主要包含各种网关规则、API信息。
-
网关动态数据:数据写入频繁,吞吐量大,主要是限流的实时数据、API的接口调用时长收集的数据。
数据源与缓存思路
既然网关对数据读取效率上有着严格的要求,我们不能选择传统的关系数据库来作为运行时数据源。那么,在原始的界面配置读写的数据库与网关运行态之间:
用redis(对于短小的数据,吞吐能力强)来做运行时数据源,并且在网关与redis之间做一个缓存。
此思路带来两个问题:
-
redis数据与数据库数据间存在同步的问题,缓存与redis之间存在同步问题。
-
redis写入动态实时的限流、API收集类数据时,需要考虑分摊压力,减少写入时延。
那么我来各个击破,做一个权衡(Tradeoff)。
方案细节
1.配置数据同步
网关的配置数据不是业务数据,没有事务问题,对实时性有一定的容忍力。但是,网关需要在一定的时间内获取到最新的配置,这个时间最坏做到在5秒以内。这样,数据更新的解决办法是:以通知形式告知所有网关节点进行缓存失效,然后去重新加载;
如何通知?有两种选择:1、实现gossip协议,让收到数据
老规矩,先上图:

2.redis cluster 数据分片控制
-
初始阶段(Initialization Phase):全局启动时初始化init_by_lua,nginx worker启动时的初始化init_worker_by_lua;
-
重写/接入阶段(Rewrite/Access Phase):set_by_lua流程分支处理判断变量初始化,rewrite_by_lua转发、重定向、缓存等功能(例如特定请求代理到外网),access_by_lua*IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙);
-
内容阶段(Content Phase):content_by_lua内容生成,balancer_by_lua代理内容,header_filter_by_lua响应头部过滤处理(例如添加头部信息),body_filter_by_lua响应体过滤处理(按chunk获取到内容,无法获取整体,例如完成应答内容统一成大写);
-
日志阶段(Log Phase):log_by_lua*会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)。
lua语言本身、绑定变量不赘述,openresty官网有详细说明,我讲一下在网关开发过程中一些 trick and trap:
-
使用lua_shared_dict 这个LRU缓存配合Redis的pub/sub机制,做配置数据加载/读取/更新;
-
需要做集群,开发gossip协议太复杂,节点先为自己生成UUID,再用Lua脚本向Redis竞争成为Master,先到先得,定时续命;
-
API的质量数据收集、动态限流均采用init阶段的定时器向Redis执行Lua脚本来做,发现超阈值立即发通知,进行限流规则的变更;
-
尽可能使用local变量、函数,尽可能使用lua Module,用require来组织模块链接;
-
Redis Cluster分片需要考虑业务数据分片,这里采用了按时间片分片。执行redis lua时,需要对redis的lua客户端改造,支持lua脚本load到所有cluster节点,支持按key的hash tag的slot结果寻找对应的cluster node;
-
采用蓄水池算法进行采样,防止内存不够用。
网关架构
整个API网关主要为了做如下几件事情:
-
限流:根据规则对特定的请求进行限流;
-
分流:根据规则对特定的请求进行分流;
-
数据收集:对所有请求数据进行按API标识和时间片收集采样;
-
降级(动态限流)
网关的整体架构如下图:

后续
在这个专题的后几篇,讲把网关实现的细节关键点一一讲述。