斗鱼 API 网关演进之路

2019年5月11日,OpenResty社区联合又拍云,举办OpenResty×OpenTalk全国巡回沙龙武汉站,斗鱼资深工程师张壮壮在活动上做了《斗鱼API网关演进之路》的分享。OpenRestyxOpenTalk全国巡回沙龙是由OpenResty社区、又拍云发起,邀请业内资深的OpenResty技术专家,分享OpenResty实战经验,增进OpenResty使用者的交流与学习,推...

斗鱼 API 网关演进之路

2019 年 5 月 11 日,OpenResty 社区联合又拍云,举办 OpenResty × Open Talk 全国巡回沙龙武汉站,斗鱼资深工程师张壮壮在活动上做了《 斗鱼 API 网关演进之路 》的分享。

OpenResty x Open Talk 全国巡回沙龙是由 OpenResty 社区、又拍云发起,邀请业内资深的 OpenResty 技术专家,分享 OpenResty 实战经验,增进 OpenResty 使用者的交流与学习,推动 OpenResty 开源项目的发展。活动已先后在深圳、北京、武汉举办,后续还将陆续在上海、广州、杭州等城市巡回举办。

张壮壮,斗鱼数据平台部资深工程师,负责打点、API 网关及后端服务架构建设。

以下是分享全文:

大家下午好,先简单做下自我介绍,我是来自斗鱼的张壮壮,曾就职于拉勾网和滴滴出行,2017 年 3 月加入斗鱼,主要负责 API 网关和数据采集等工作。

今天给大家带来斗鱼 API 网关的一些细节,在分享之前,先感谢刚才的邵海杨老师,因为我们的 API 网关基于 Slardar 二次开发的,刚才海杨老师已经详细介绍了 Slardar 的基础原理,所以这块内容我不再重复介绍,直接介绍斗鱼在此基础上做的更细节的工作。

今天我主要从三个方面来分享:

  • 斗鱼使用 API 网关的背景
  • 网关的架构&功能
  • 斗鱼 API 网关远期规划


为什么是 API 网关?

为什么是 API 网关?这要从微服务化遇到的两个问题说起,第一,怎样保证服务的无宕机更新部署;第二,怎样保证服务的自动扩容及故障恢复。这两个问题,又拍云已经有了解决方案:只需要在服务之上做服务路由,让路由支持服务的无宕机更新部署,保证服务的扩容及故障恢复,我们也是按照这个思路来实现的。

但是只有这个不能解决所有问题,比如服务的性能监控、系统的资源调度等问题,还需要其他基础设施来支撑。所以 Docker 和 Kubernetes 进入了我们的视野。由于本次活动主题是 OpenResty,对容器技术选型就不做展开了。服务上容器,在更新和迁移中 IP 和 Port 是变化的、不确定的,这是必须要解决的问题。

服务路由

服务路由要支持服务注册、服务发现和负载均衡。经过多方调研,我们发现又拍云开源的动态负载均衡组件 Slardar 非常适合业务场景,主要解决了容器环境服务 IP 和 Port 均变化的问题。

  • 服务注册是服务需要主动上报服务相关信息,最重要的是 IP 和端口;
  • 服务发现是把服务注册的信息集中起来,最好能持久化;
  • 为了避免单点故障,服务会启动多个实例,因此还需要做负载均衡。

服务发现有很多开源的工具可以用,比如 Consul、etcd 和 Apache Zookeeper。由于我们选型是 K8S,所以服务发现选择 etcd。

负载均衡,基本上就是三个:LVS、HAProxy 和 Nginx。

接下来我们简单了解下 Slardar,它由四个部分组成:

  • 第一部分是官方的 Nginx,没有任何改动;
  • 第二部分是 Nginx Lua 模块,核心是 Lua 版本的负载均衡算法 balance_by_lua
  • 第三部分是 lua-resty-checkups,它是把 Nginx upstream 模块常用的功能单独抽出来,用 lua 重新实现了一遍;
  • 第四部分是 luacocket,用于加载配置信息。

Slardar 在启动过程中先拉取服务配置,拉取完配置就可以对外进行服务了。如果我们的服务因为扩容或者异常宕机又起了一个新的实例,此时 IP和 Port 都会变化,需要把服务 IP 和 Port 等信息注册到 Slardar 和 Consul。逻辑清晰,结构简单,这是选择 Slardar 的原因,不过想要真正应用,仅仅有动态负载均衡远远不够,还需要解决以下的问题:

  • 服务如何在启动后自动上报信息到 Consul;
  • Slardar 如何解决自身单点问题;
  • 怎样应对Consul 集群故障、或者网络故障;
  • 没有可视化管理;
  • 灰度测试、AB 测试、流量复制等等功能的实现(考虑未来使用场景)。

我们拿到了 Slardar 进行了大刀阔斧地调整:

  1. 取消 Consul,实现注册中心,并持久化配置到数据库;
  2. 开发 Java agent,实现 Java 服务自动上报;
  3. 对接 Kubernetes API,实现非 Java 服务自动上报;
  4. 提供可视化管理后台;
  5. 配置定时落盘,当网络故障时作为托底;
  6. 定时全量拉取配置,增加集群(机房)概念,可一键切换流量;
  7. 支持集群部署,应对单点问题;
  8. upstrem 列表 key 由 Host 改为 Host URI(前缀);
  9. 引入插件模式,新增了很多功能,如灰度测试、AB测试、流量复制、服务限流等。

斗鱼 API 网关的架构&功能

下面介绍斗鱼的 API 网关的部署架构,以及内部功能细节。

抽象来看,服务接入 API 网关的架构非常清晰,和原生 Nginx 架构一样,所有流量必须经过 API 网关后,才能访问到真实的后端。经服务端处理,并将响应返回给 API 网关之后,再交给客户端。这是单机房的部署架构。实际上使用 Nginx 作为入口网关,API 网关作为内网网关,Nginx 负责处理复杂的 location 逻辑、SSL 认证等,API 网关负责抽象后台服务间通用功能。

这是多机房部署架构,上游使用 CDN 来做流量分配,方便机房故障时进行一键流量切换。

这张图很好的展示了斗鱼 API 网关生态体系。

上图左侧是 API 网关的内部功能,绿色部分是已实现的功能,包括限流、OA 认证、请求限制、AB 测试、灰度测试、流量复制、蓝绿发布、API 开放平台等。灰色部分是即将实现的功能,蓝色部分是 Slardar 原生的功能,当然我们也做了大量的优化工作,比如:路由算法支持动态的权重更新。

上图右侧 API 网关的支撑服务,其中网关管理 MIS 系统是可视化管理后台。日志聚合提供了接入服务的性能图表,比如状态码 4XX,5XX 统计,以及请求不同水位线耗时分布,俗称 P90,P99。天眼是 Java agent,负责实现服务对接下面的注册中心、实现服务优雅停机。

上图下侧是 API 网关代理的服务,如搜索、推荐、风控、流量分发等等。

以上整个罗列了我们已经实现且在线使用的重要功能。因为时间关系,后面我会挑选其中三个功能详细介绍实现原理。

OA 认证:为了解决后端服务各自对接 OA 认证繁琐,比较典型的是内部使用的开源系统,如Kibana、zabbix、Dubbo Admin 等,这些开源组件我们不可能投入人力去二次开发,但是需要接 OA,还需要进行一些 ACL 权限控制。

QPS 限流:用的是非常简单的计数器模式,是单机版的,限流是为了保证斗鱼的核心服务,比如视频拉流,还有刚才提到的推荐、搜索免受洪峰攻击。

服务兜底:这个功能想必大家非常了解,它能保证上游的数据服务永不消失,它与 QPS 限流其实是结合在一起的,触发限流的请求会直接返回兜底数据,这可以保证在极端情况下,我们的后端服务不会被瞬时流量打垮,同时保证友好的用户体验。典型的场景就是 S 级主播的首秀,例如 3 月份PDD 的首播就给我们带来了非常大的流量冲击,通过 QPS 限流保证了我们的核心服务不受影响。

流量复制:在不影响用户正常请求的前提下,将原始请求复制一份或者多份,供开发人员在线对服务进行功能测试、性能测试和压力测试。功能上支持自身复制及跨域名复制,流量的放大和缩小。

AB测试、灰度测试和蓝绿发布,可以归为一类,均是维护多套 upstream 列表,通过某种策略,将不同的请求代理到不同 upstream 。

签名认证:对外暴露的接口,需要一套签名算法,避免服务直接裸露在外,所以这里面做了一个功能抽象。

服务高可用

我们保证服务高可用其实就是要处理两个场景:第一个场景是它的更新部署;第二个场景就是运行期间故障。

我们从服务更新部署和服务下线来看考虑第一个场景。

更新部署

想要避免服务更新部署导致请求异常,其实满足两个条件即可:

  • 第一保证注册到注册中心的服务都是已经可以对外提供服务的
  • 第二下线之前,先从反向代理列表中剔除

第一个是保证服务注册到注册中心的实例,一定是可以对外服务的状态,即某些服务一定要在启动完成后才能加入到中心。另一个场景是一些服务需要热加载,启动完了后不能立即对外进行服务,这时服务有一个预热的过程,一定是预热完了后才能注册到注册中心。

第二个是下线时通过 trap 命令,在接收到程序结束(terminate)和程序终止(interrupt)时执行 shutdown 函数,这里 shutdown 函数会先通知注册中心下线改实例,然后 hang 住 5-10 秒,等待下线事件更新到网关。

这里特别说明一下服务更新流程,因为 API 网关是基于 Nginx 的,所以控制单个 worker 进程全量从注册中心拉取 upstream 配置信息,并写入 lua_shared_dict,其他的 worker 定时同步lua_shared_dict 配置信息到本地缓存。为了反向代理的高性能,网关是从本地缓存获取 upstream 信息,而不是从 lua_shared_dict 。

运行期

接下来我们看运行期故障怎么处理。

源文地址:https://www.guoxiongfei.cn/cntech/18349.html
0