【ArchSummit架构师峰会】探讨数据与人工智能相互驱动的关系>>> 了解详情
写点什么

Oceanus:美团 HTTP 流量定制化路由的实践

  • 2020-02-25
  • 本文字数:4904 字

    阅读完需:约 16 分钟

Oceanus:美团HTTP流量定制化路由的实践

背景

Oceanus 是美团基础架构部研发的统一 HTTP 服务治理框架,基于 Nginx 和 ngx_lua 扩展,主要提供服务注册与发现、动态负载均衡、可视化管理、定制化路由、安全反扒、session ID 复用、熔断降级、一键截流和性能统计等功能。本文主要讲述 Oceanus 如何通过策略抽象、查询、渲染和分组动态更新,实现 HTTP 请求的定制化路由。


随着公司业务的高速发展,路由场景也越来越复杂。比如:


  • 团购秒杀要灵活控制压测流量,实现线上服务单节点、各机房、各地域等多维度的压测。

  • 外卖业务要做流量隔离,把北方地域的流量转发到分组 a,南方地域的流量转发到分组 b。

  • 酒旅业务要对 App 新版本进行灰度,让千分之一的用户试用新版本,其他用户访问老版本。

  • QA 部门要通过请求的自定义参数指定转发分组,构建稳定且高可用的测试环境。


由于公司早期的业务场景相对比较简单,所以均通过 Nginx if 指令支持。比如某业务要把来源 IP 为 10.4.242.16 的请求转发到后端节点 10.4.232.110,其它请求转发到后端节点 10.4.232.111 和 10.4.232.112,就可以进行如下配置:


upstream backend_aaa {  server 10.4.232.110:8080 weight=10;}upstream backend_bbb {  server 10.4.232.111:8080 weight=10;  server 10.4.232.112:8080 weight=10;}location /abc {  if($remote_ip = "10.4.242.16") {    proxy_pass http://backend_aaa; #路由到backend_aaa集群  }  proxy_pass http://backend_bbb; #路由到backend_bbb集群}
复制代码


上述方式虽然不需要额外开发,性能方面也接近原生的 Nginx 框架,但是使用场景比较受限,因为 if 指令仅支持比较简单的 condition 类型,官方描述如下:



如果该业务要把 IP 段 10.4.242.16/34 的请求转发到 10.4.232.110 时,if 指令勉强还可以支持。但对于上述的复杂业务场景,if 指令均无法支持。除此之外,这种方式还存在以下两点不足:


  • 规则调整不支持动态化:如果要把客户端 10.4.242.16 调整为 10.4.242.17,需要对 Nginx 进行 reload,而 reload 操作会使 Nginx 的并发能力下降,业务高峰时甚至会导致请求 504 或 502。

  • 指令坑太多:if 指令和 set、rewrite 指令等一起使用时,很多时候会出现不符合预期的行为,严重时甚至会导致段错误,最好的方法就是避免使用。


为了解决上述问题,Oceanus 开始探索如何实现 HTTP 流量的定制化路由。

业界调研

通过初步调研,发现业界有一套开源的 ABTestingGateway(以下简称 AB)框架:



由上图所示,AB 框架使用 Redis 存储策略数据,key 是 Host 字段,value 是策略对象,包括策略类型、匹配区间和要分发的 Upstream。策略的增删改查可以通过基于 Nginx 搭建的 Web 服务的 API 实现,运行时根据请求的 Host 字段从 lua-shared-dict 或 Redis 获取关联的策略,根据策略类型(iprange/uidrange/uidsuffix/uidappoint)选择对应的 Lua 脚本从请求中获取相关参数(IP、UID)查询是否匹配策略,若匹配,就修改请求的 Upstream 上下文完成分流的目的。


相比 if 指令的方式,AB 框架有下面两个优点:


  • 策略调整动态生效:已有策略类型中的策略变更均可以通过 HTTP API 进行动态管理。

  • 分流策略丰富:支持 IP 段、UID 段等策略,也可以通过新增策略类型对策略库进行扩展。


由于 AB 框架只支持 4 种策略类型,对于业务要根据请求 Cookie、自定义 header 控制转发的情况,均需要开发新的策略类型和发布上线。另外,策略类型和业务场景紧密相关,导致 AB 系统的扩展性极差,很难快速支持新业务的路由需求。


无论是 Nginx if 指令,还是 AB 框架,要么需要 reload 重新加载才能生效,要么无法支持某些业务场景下的分流需求,所以都很难作为解决公司级分流框架的有效手段。针对它们所存在的不足,Oceanus 开发了一套应用级、高可扩展的动态分流框架,不仅动态支持各种业务场景的分流需求,而且保证了请求转发的性能,下文将阐述我们如何解决分流机制的几个核心问题。

Oceanus 定制化路由的核心设计 &实现

关于分流机制,我们主要从以下四个方面来讲述:


  • 策略抽象:合理定义策略结构,适用尽可能多的业务场景。

  • 策略的高效查询:接口粒度关联,应用维度管理。

  • 运行时策略渲染:渲染策略模板,判断是否匹配策略,实现动态路由。

  • 分组动态更新:分组数据增删改,均不需要 reload。

策略的结构定义

以 AB 框架为例,只支持 iprange、uidrange、uidsuffix、uidappoint 四种场景,对策略类型和匹配方式太具体化,导致无法支持更多普适性的业务场景。从分流的本质出发,即根据请求特征完成流量的定制化路由。结合 Nginx if 指令的几个组成部分:条件判断依赖的变量、条件判断要匹配的 value、条件表达式、匹配后要执行的 proxy_pass,一个策略必须要包含请求特征描述、定制化路由描述以及两者的关系描述。其中请求特征描述包含特征关键字、关键字的上下文传输方式,定制化路由描述通过 Upstream 表示,Upstream 可以预先设置,也可以动态指定,两者的关系通过泛型表达式表示。那么一个策略就需要包含下面几个属性:


  • name:策略名,没有实际意义,可以根据业务场景进行定义。

  • key:分流时依赖的关键字,比如要根据城市地域进行分发路由时,key 就是 regionid。

  • passway:关键字在 HTTP 协议中的传输方式,可以是 Parameter、Cookie、header、body 中的一种。

  • condition:表达式模板,支持四则运算/取模、关系运算符、逻辑运算符等。

  • group:后端服务集群,即匹配策略后,转发请求的目标节点,一般是策略所属应用集群中的部分节点。

  • category:策略类型,如果为 1,表示某个服务的私有策略;如果为 2,表示公共策略,主要用于策略数据管理。

  • switch:策略开关,用于控制当前策略是在线还是离线。

  • graylist:灰度列表,用于策略变更的线上灰度校验。


其中 switch、graylist 字段主要用于策略的上下线操作,这里不做过多讨论。下面重点介绍上面的策略定义是如何表述业务场景的:



备注:应用 apk1 和 apk2 分别配置 2 个私有策略,apk3 使用公共策略。


如上图所示,无论业务根据请求的哪些特征进行分流,策略结构均可以支持。


以私有策略 gray-deploy 为例,在 Oceanus 管理平台进行添加,如下图所示:



备注:这里省略了策略的非核心字段比如 switch、graylist 等。

如何实现策略的高效查询?

策略拓扑关系

分流策略分为私有策略和公共策略。私有策略是面向服务的,而且和该服务创建的分组紧密相关。不同服务的私有策略完全独立,可以相同,也可以不同。一个服务可以配置多个私有策略,也可以关联多个 Host 的 Location,Location 之间的策略使用完全独立,一个 Location 可以启用该服务的一个或者多个私有策略。如果通过 Host+location_path 直接关联策略数据,不同 Location 关联同一个私有策略时,会存在大量的数据冗余。所以我们通过服务标识(appkey,唯一标识一个应用服务)关联具体的策略数据,Host+location_path 只关联当前 Location 使用的策略名列表,策略之间支持指定顺序。


公共策略与具体服务无关,策略名全局唯一,可以使用策略名关联策略数据即可。综上,策略的拓扑关系描述如下:



StrategyTopology


如上图所示,以应用 apk1 为例,关联了两个 Location 接口,分别为/api 和/list,总共部署了 8 个节点,创建了 2 个分组 ups-cq 和 ups-gray,其中节点 10.5.23.6 和 10.5.24.72 属于分组 ups-cq,节点 10.7.46.32 和 10.7.72.232 属于分组 ups-gray。应用配置了两个私有策略 stress-testing 和 gray-deploy,其中策略 stress-testing 被接口/api 启用,匹配策略的流量路由到分组 ups-cq,策略 gray-deploy 被接口/list 启用,匹配策略的流量路由到 ups-gray。

运行时获取 Location path

Nginx 在解析 Location 配置时,通过不同的字段区分不同类型的 Location,没有记录配置中的 Location path。如果要运行时获取,一般有两种方式:一种是根据相关字段逆向还原 path,另一种是为框架新增变量。由于 Nginx 在处理正则 Location 时,对于是否忽略大小写的情况,并没有做标记,即解析的过程是不可逆的,所以我们选择了第二种方式。在核心模块的变量数组 ngx_http_core_variables 中新增了内置变量,记录下原始的 Location path,变量属性定义如下:


{ngx_string("loc_mod"), NULL, ngx_http_variable_loc_mod, 0, NGX_HTTP_VAR_NOCACHEABLE, 0},{ngx_string("loc_name"), NULL, ngx_http_variable_loc_name, 0, NGX_HTTP_VAR_NOCACHEABLE, 0}
复制代码


loc_mod 和 loc_name 之间用一个空格符连接,格式和 Oceanus 管理平台保持一致。

异步更新机制

为了保证运行时获取策略数据的高效性,我们通过异步定时拉取,把策略数据全量同步到本地的共享内存中。基于稳定性和灵活性的考虑,我们采用了关系型数据库 MySQL 存储策略。


更新机制如下图所示:



  1. Oceanus 在 init_worker 阶段随机选择某个 worker 进程,嵌入 timer。

  2. 被选中的 worker 会异步非阻塞地从 MySQL 定时拉取策略数据。

  3. timer worker 把拉取到的策略数据解析,按照策略的拓扑关系,更新到当前共享内存中的写缓存区,完成更新后,切换读写缓存区,保证最新的策略立即生效。

  4. worker 进程在处理请求时,从当前共享内存中的读缓存区获取策略数据。


为了解决 timer worker 和其它 worker 在读写策略数据时的竞态关系,我们采用了双 buffer 机制,实现了业务层策略数据的无锁读写。另外,通过设置 timer 的时间为 0,保证在所有 worker 处理请求前,策略数据已经在共享内存中完成初始化。

策略查询机制

查询算法如下图所示:



  1. worker 进程从 request 上下文中获取请求的 Host,以及所匹配 Location 的 location_path。

  2. 根据 Host+location_path,到共享内存中查询所开启的策略名。

  3. 如果是公共策略,直接根据策略名去查询策略数据。

  4. 如果是私有策略,从 request 上下文获取 Location 关联的 Upstream,即应用标识 appkey,到共享内存读缓存区获取具体的策略数据。


备注:公共策略以”oceanus”开头,区别于私有策略的命名。

运行时策略渲染

查询到请求开启的策略后,Oceanus 需要运行时判断是否匹配,以私有策略为例,执行流如下图所示:



  1. 在 rewrite phase,Oceanus 通过 rewrite_by_lua_file 嵌入回调,触发请求处理,进入分流框架的主流程。

  2. 通过上面的策略查询机制获取请求的策略,进行解析,获取策略的 key 和 passway。

  3. 根据 passway 从请求对应的上下文获取 key 的 value。

  4. 用 3 获取到的 value 渲染策略的 condition,把 condition 中的占位符替换为 value。

  5. 基于 Lua VM,通过 load 计算 condition 的结果,即 true 或 false。

  6. 从策略中获取 condition 的 value 和 group 数据。

  7. 如果 condition 为 true,就用 group 覆盖请求的 Upstream 上下文,否则,不做处理。

分组动态更新

分组列表的动态化是分流框架的重要一环。更新机制如下图所示:



  1. 分组数据使用 ZooKeeper 存储,变更通过 watcher 机制实现增量同步。

  2. Oceanus 也会定时拉取,进行全量同步。

  3. Oceanus 把所有变更都通过本地的 HTTP 调用同步到 Nginx 内存。

  4. worker 处理变更请求前,会先抢锁,读取共享内存中的消息队列,同步其它 worker 进行的历史变更。

  5. 把这次变更同步到当前 worker 的 Upstream main 上下文中,完成当前 worker 的更新。

  6. 把变更封装成消息,加入到共享内存中的队列。

  7. 其它 worker 通过 timer 或者自己处理变更消息前读取消息队列,完成更新。

总结

通过 Oceanus 分流机制在美团外卖、酒旅、到店餐饮等多个业务线的广泛使用,基础架构部帮助业务同胞解决了多个定制化路由的需求,比如服务 set 化、链路压测、灰度发布、泳道环境建设等等。目前,Oceanus 分流机制只关注了流量转发方向,还不支持更复杂的转发动作,比如根据策略调整请求的 Parameter、header、Cookie,也不支持根据请求的 URL 实现动态路由等,未来我们还将逐一完善这些问题,当然也欢迎大家跟我们一起交流,共同进步。

作者简介

  • 周峰,美团高级工程师,2015 年 7 月加入美团基础架构部,先后负责统一密钥管理服务、智能反爬服务和 HTTP 负载均衡,目前主要负责 HTTP 服务治理 Oceanus 的相关工作,致力于探索和研究服务的自动化、智能化、和高性能等方向。

参考文献


2020-02-25 20:33684

评论

发布
暂无评论
发现更多内容

汇丰坠落:世间已无「日不落」

钛禾产业观察

汇丰 财经

架构师训练营第九周作业

一剑

读书笔记:Google软件测试之道【二】

Man

测试 测试文化

读书笔记:Google软件测试之道【三】

Man

测试 测试文化

实时计算的业务劣势、思维误区和改进之道

KAMI

大数据 flink 方法论 实时计算

什么是零代码?零代码开发可以带来的好处

代码制造者

可视化 零代码 编程效率

自主管理——对人性的假设

zhongzhq

自主管理 组织

从0到1搭建大数据平台之数据采集系统

数据社

大数据 数据采集

VIPKID 在线教育场景下的实时计算技术落地和实践

Apache Flink

flink

一位区块链产品经理讲述“区块链”的通知 重点方向包括区块链安全

CECBC

物联网 区块链技术 联盟链

我是如何写读书笔记的

dd多了个多

读书笔记

读书,区分一二三四手知识

dd多了个多

读书笔记

30秒,2种方法解决SQL Server的内存管理问题

华为云开发者联盟

数据库 sql 内存 服务器 华为云

读书笔记:Google软件测试之道【一】

Man

测试 测试文化

区块链在这些生活场景中悄然落地了......

CECBC

区块链 落地应用

周子衡 | 数字资产、数字支付及跨境活动——以美元数字化为例

CECBC

加密货币 数字资产

湾区金科沙龙,华青融天技术总监吴伟平详解旁路式应用性能监控

DT极客

话题讨论 | 哪本极具影响力的书,是每位程序员都应该读的?

InfoQ写作社区官方

写作平台 话题讨论

读书时,如何提炼文章架构形成思维导图

dd多了个多

读书笔记 读书感悟

作业1

chenzt

从0到1搭建大数据平台之调度系统

数据社

大数据 工作流调度

成功的9大步骤:从手动测试转为自动化测试

禅道项目管理

测试 自动化测试

糟糕,你写的 BUG 要被存1000年了!

华为云开发者联盟

GitHub 开源 代码 bug 卤化银胶片

前端面试vue部分(1)——谈谈你对MVVM的理解

dd多了个多

面试 Vue 大前端 Web

如何从0到1搭建大数据平台

数据社

大数据 中台

从0到1搭建大数据平台之计算存储系统

数据社

大数据 中台 计算引擎

写作社区划线笔记新功能全新上线!给你带来不一样的写作学习体验~

InfoQ写作社区官方

写作平台 玩转写作平台 热门活动

Java 垃圾回收

dongge

Flink x Zeppelin ,Hive Streaming 实战解析

Apache Flink

flink hive Zeppelin

LeetCode 328. Odd Even Linked List

liu_liu

算法 LeetCode

原创 | 使用JPA实现DDD持久化- O:对象的世界(1/3)

编程道与术

Java hibernate DDD JDBC jpa

Oceanus:美团HTTP流量定制化路由的实践_文化 & 方法_美团技术团队_InfoQ精选文章