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 。

运行期

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

我们是这样做的:心跳探活+状态回溯,我们知道只有心跳探活是不能完全保证服务的高可用的,于是加上了“状态回溯”。

另外说一句:“状态回溯”是我自己想的,不知道用的对不对,即一次请求过来,如果反向代理失败,就将该服务标记为不可用。

加上之前启动和停止的操作,就保证了在任何情况下,正常的发布或者某台实例异常故障不会出现服务不可用的瞬间。这里就解决了之前提到的保证服务的无宕机更新部署和保证服务的自动扩容和故障恢复这两个问题。

AB 测试

AB 测试我们只是做了一个通用的功能。我们的 AB 测试是基于 Nginx rewrite 命令,主要使用场景有:

  • 需测试的 ABCD…策略(或算法,或模型,或服务)会长期且同时存在于生产环境;
  • 不想同时维护多套代码分支;
  • 客户端无需改造。

目前网关对外提供 AB Test 功能是面向 HTTP 服务的,其实现原理是:后台服务提供一个默认接口,同时提供多种需要在线验证的其他接口,请求到达API网关,根据命中规则,重定向至对应的接口。其中,源 URI 作为对外暴露的接口,保持固定不变。

AB 测试规则支持白名单、尾数、轮询策略——一致性哈希等等。当然,业界还是另外一种实现方式,比如马蜂窝根据策略访问不同的 upstream 列表。

服务兜底

请求到达网关之后,会反向代理给后端,如果是错误状态码,比如 500、502、503,我们直接拿到兜底数据,返回给客户端就可以了。由于 OpenResty 的限制,无法在 header_filter_by_lua* 和 body_filter_by_lua 中使用发起非阻塞的 HTTP 请求或者其他依赖 TCP 的协议(如 Redis)去查询兜底数据(原因请点击https://github.com/openresty/lua-Nginx-module),有过 OpenResty 或者 Nginx 模块开发经验的同学应该都知道:阻塞 API 的使用将会大大的降低 Nginx 的性能。

业界是怎么处理这个问题的?我见到的有基于 OpenResty 实现的兜底功能,均是使用ngx.location.capture 来主动发起请求给上游服务,而非使用 Nginx 原生的命令 proxy_pass 来实现反向代理。ngx.location.capture 的返回结果包含了响应状态码,如果状态码属于 Error Code,则去查询兜底数据,并返回给 C 端用户。

--子查询代理完成请求
local res = ngx.location.capture('/backend' .. request_uri, {
method = method,
always_forward_body = true
}) if 504 == res.status then
return bottom_value
end

使用 ngx.location.capture 替换 proxy_pass 的问题在于,HTTP 协议的应用场景非常广泛,Nginx 实现反向代理时已经做了大量的适配和场景覆盖,使用 ngx.location.capture 相当于重新 Nginx 的反向代理模块,需要考虑诸如:文件上传下载、静态资源和动态资源、是否传递 Cookie 等等场景。任何一个应用场景的遗漏都将是一个线上 BUG。

我们要做服务兜底的时候,网关已经上线了一年多,这个时候如果想要重写,无异于火中取栗。比较幸运的是,在查看 Nignx 官方文档的时候,发现原生命令 error_page。

示例中请求 location / 如果上游服务返回 404,则会内部重定向至 @fallback,而 @fallback 接口中可以发起二次 HTTP 请求,例如获取兜底数据,并且是可以使用非阻塞类库的。最后实现的核心代码如下:

location / {
proxy_pass http://backend;
error_page 500 502 503 504 =200 @janus_bottom;
}
location @janus_bottom {
content_by_lua '
local bottom = require "bottom";
--非阻塞获取兜底数据,并返回给client
bottom.bottom();
';
}

总结一下斗鱼的 API 网关,就是运行在所有的 HTTP 服务上,提供通用、可抽象的服务治理功能。

斗鱼 API 网关的远期规划

2018 年我参加了杭州的 OpenResty 大会,当时受到了很大的启发。

现在 API 网关已经全面应用到斗鱼整个后端架构,我们对其做了一些规划。首先网关是分集群的,由于请求量非常大,磁盘的性能不高,反而会反向影响 API 网关吞吐性能。因此我们做了很多优化,比如上 SSD,精简日志等,这些功能都已经上线了,日志精简完后大概是原来体量的 2/3,效果非常明显,当然 SSD 才是“银弹”。

我们后期的一个想法是将网关日志直接写入 MQ,后续用专门的服务消费 MQ,把日志落地到本地磁盘。另外一个想法是从 MQ 里面直接消费到 ES 里面,做一些其他的分析。

然后,就是将请求分同步和异步两条线路。客户端请求到达 API 网关,网关会经过如限流,服务兜底,AB测试,灰度测试等功能,这些功能都是在同步的逻辑线路中。显然,同步逻辑功能越多,势必会影响客户端请求 HTTP 的时延。所以我们在 API 网关这一块,将请求日志放到 MQ,由一些模块直接消费这个 MQ,比如经过风控、流量控制、限流分析,产生一些 API 网关可以使用的配置,写入到 DB 里。网关通过拉取 DB 的配置,对后续的请求做一些限制。我们现在的同步链路已经比较完善,下一步重点是异步链路,而且我相信异步链路应该是整个 API 网关体系中更加庞大的生态链。

观看视频和 ppt 下载,请点击:

斗鱼 API 网关演进之路 - 又拍云

斗鱼 API 网关演进之路的更多相关文章

  1. 企点微服务网关演进之路 IT大咖说 - 大咖干货,不再错过

      http://www.itdks.com/dakashuo/new/dakalive/detail/2297

  2. NET Core微服务之路:基于Ocelot的API网关Relay实现--RPC篇

    前言 我们都知道,API网关是工作在应用层上网关程序,为何要这样设计呢,而不是将网关程序直接工作在传输层.或者网络层等等更底层的环境呢?让我们先来简单的了解一下TCP/IP的五层模型.     (图片 ...

  3. SpringCloud升级之路2020.0.x版-3.Eureka Server 与 API 网关要考虑的问题

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ 之前我们提到了 ...

  4. 【IT名人堂】何云飞:阿里云数据库的架构演进之路

    [IT名人堂]何云飞:阿里云数据库的架构演进之路 原文转载自:IT168 ​ 如果说淘宝革了零售的命,那么DT革了企业IT消费的命.在阿里巴巴看来,DT时代,企业IT消费的模式变成了“云服务+数据”, ...

  5. Net分布式系统之六:微服务之API网关

    本人建立了个人技术.工作经验的分享微信号,计划后续公众号同步更新分享,比在此更多具体.欢迎有兴趣的同学一起加入相互学习.基于上篇微服务架构分享,今天分享其中一个重要的基础组件“API网关”. 一.引言 ...

  6. Asp.Net Core API网关Ocelot

    首先,让我们简单了解下什么是API网关? API网关是一个服务器,是系统的唯一入口.从面向对象设计的角度看,它与外观模式类似.API网关封装了系统内部架构,为每个客户端提供一个定制的API.它可能还具 ...

  7. 【微服务】之六:轻松搞定SpringCloud微服务-API网关zuul

    通过前面几篇文章的介绍,我们可以轻松搭建起来微服务体系中比较重要的几个基础构建服务.那么,在本篇博文中,我们重点讲解一下,如何将所有微服务的API同意对外暴露,这个就设计API网关的概念. 本系列教程 ...

  8. .NET Core开源API网关 – Ocelot中文文档

    Ocelot是一个用.NET Core实现并且开源的API网关,它功能强大,包括了:路由.请求聚合.服务发现.认证.鉴权.限流熔断.并内置了负载均衡器与Service Fabric.Butterfly ...

  9. 从一张图开始,谈一谈.NET Core和前后端技术的演进之路

    从一张图开始,谈一谈.NET Core和前后端技术的演进之路 邹溪源,李文强,来自长沙.NET技术社区 一张图 2019年3月10日,在长沙.NET 技术社区组织的技术沙龙<.NET Core和 ...

随机推荐

  1. modbus读输入状态与读线圈状态的区别?

    01 读线圈状态 描述 读从机离散量输出口的 ON/OFF 状态,不支持广播.附录B列出由不同控制器型号支持最大的参数清单. 查询 查询信息规定了要读的起始线圈和线圈量,线圈的起始地址为零,1-16个 ...

  2. excel2003, 2007最大行列、sheet数

    1.excel2003版本一个工作表最多可有65536行,行用数字1—65536表示;     256列,列用英文字母A—Z,AA—AZ,BA—BZ,……,IA—IV表示.2.一个工作簿中最多含有25 ...

  3. javaScript之事件处理程序

    事件就是用户或浏览器自身执行的某个动作,JavaScript与HTML的交互也是通过事件实现的.而相应某个事件的函数就叫做事件处理函数.包括以下几种: 1.HTML事件处理程序    某个元素支持的每 ...

  4. web安全之XSS和CSRF

    XSS 跨站脚本攻击(cross site script),本来缩写CSS单位了和层叠样式(Cascading Style Sheet,CSS)有所区别,所以在安全领域叫做“XSS”. XSS攻击,通 ...

  5. 基于Docker部署私有npm

    NPM作为前端最cool及最烂的包管理器,它解决困扰前端工程化发展中代码模块管理的大问题.但是随着业务需求的发展,我们的代码从以前的单项目复用,延伸出了多项目复用的需求.本来项目之间代码复用管理的情景 ...

  6. Http协议以及模拟http请求发送数据

    1 为什么要使用http协议 假设我现在有两个客户端浏览器,一个是google,一个是IE浏览器:我现在有两个服务器,一个是tomcat,一个是JBoss;在最初的情况下是:如果google要往tom ...

  7. Arcane Numbers 1

    Vance and Shackler like playing games. One day, they are playing a game called "arcane numbers& ...

  8. HTML5新增的结构元素

    HTML5的结构 一:新增的主体结构元素 在HTML5中,为了使文档的结构更加清晰明确,追加了几个与页眉,页脚内容区块等文档结构相关联的结构元素. 1.1article元素 article元素代表文档 ...

  9. php学习笔记-php中把浮点数转化为整数

    在php中有时候会遇到比如 14.6%3这种操作,php是会先把14.6转化为整数再做其它的操作,那么这个转化为整数的操作是floor(14.6)还是ceil(14.6)还是round(14.6)呢? ...

  10. 1、转载 bwa的使用方法

    http://bio-bwa.sourceforge.net/bwa.shtml http://www.plob.org/?p=25 bwa的使用需要两中输入文件: Reference genome ...