Contents

用OpenResty构建网关(3)—— 数据访问

场景与挑战

作为API网关,我们有很多数据访问的需求,这些需求内包含两类数据:

  1. 网关的配置数据:数据更新不频繁,但是要求准实时生效,主要包含各种网关规则、API信息。

  2. 网关动态数据:数据写入频繁,吞吐量大,主要是限流的实时数据、API的接口调用时长收集的数据。

数据源与缓存思路

既然网关对数据读取效率上有着严格的要求,我们不能选择传统的关系数据库来作为运行时数据源。那么,在原始的界面配置读写的数据库与网关运行态之间:

用redis(对于短小的数据,吞吐能力强)来做运行时数据源,并且在网关与redis之间做一个缓存。

此思路带来两个问题:

  1. redis数据与数据库数据间存在同步的问题,缓存与redis之间存在同步问题。

  2. redis写入动态实时的限流、API收集类数据时,需要考虑分摊压力,减少写入时延。

那么我来各个击破,做一个权衡(Tradeoff)。

方案细节

1.配置数据同步

网关的配置数据不是业务数据,没有事务问题,对实时性有一定的容忍力。但是,网关需要在一定的时间内获取到最新的配置,这个时间最坏做到在5秒以内。这样,数据更新的解决办法是:以通知形式告知所有网关节点进行缓存失效,然后去重新加载;

如何通知?有两种选择:1、实现gossip协议,让收到数据

老规矩,先上图:

/apigateway_conf_pubsub.png

2.redis cluster 数据分片控制

  1. 初始阶段(Initialization Phase):全局启动时初始化init_by_lua,nginx worker启动时的初始化init_worker_by_lua;

  2. 重写/接入阶段(Rewrite/Access Phase):set_by_lua流程分支处理判断变量初始化,rewrite_by_lua转发、重定向、缓存等功能(例如特定请求代理到外网),access_by_lua*IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙);

  3. 内容阶段(Content Phase):content_by_lua内容生成,balancer_by_lua代理内容,header_filter_by_lua响应头部过滤处理(例如添加头部信息),body_filter_by_lua响应体过滤处理(按chunk获取到内容,无法获取整体,例如完成应答内容统一成大写);

  4. 日志阶段(Log Phase):log_by_lua*会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)。

lua语言本身、绑定变量不赘述,openresty官网有详细说明,我讲一下在网关开发过程中一些 trick and trap:

  1. 使用lua_shared_dict 这个LRU缓存配合Redis的pub/sub机制,做配置数据加载/读取/更新;

  2. 需要做集群,开发gossip协议太复杂,节点先为自己生成UUID,再用Lua脚本向Redis竞争成为Master,先到先得,定时续命;

  3. API的质量数据收集、动态限流均采用init阶段的定时器向Redis执行Lua脚本来做,发现超阈值立即发通知,进行限流规则的变更;

  4. 尽可能使用local变量、函数,尽可能使用lua Module,用require来组织模块链接;

  5. Redis Cluster分片需要考虑业务数据分片,这里采用了按时间片分片。执行redis lua时,需要对redis的lua客户端改造,支持lua脚本load到所有cluster节点,支持按key的hash tag的slot结果寻找对应的cluster node;

  6. 采用蓄水池算法进行采样,防止内存不够用。

网关架构

整个API网关主要为了做如下几件事情:

  1. 限流:根据规则对特定的请求进行限流;

  2. 分流:根据规则对特定的请求进行分流;

  3. 数据收集:对所有请求数据进行按API标识和时间片收集采样;

  4. 降级(动态限流)

网关的整体架构如下图: /GATEWAY_ARCH.png

后续

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