【AICon】AI 基础设施、LLM运维、大模型训练与推理,一场会议,全方位涵盖! >>> 了解详情
写点什么

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:33690

评论

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

区块链支付系统开发,数字货币支付承兑商APP模式搭建

13530558032

有奖征文火热开赛,万元大奖等你来拿,准备好了吗?

InfoQ写作社区官方

程序员 开发者 音视频 随笔杂谈 RTC征文大赛

在5G智慧园区的“保龄球道”上,目标全垒打的征途

脑极体

经济适用的企业内外网互动直播方案

fumingwang

音视频 直播 视频会议 企业应用

深度解读:Apache DolphinScheduler 新架构与特性,性能提升2~3倍

代立冬

大数据 开源 工作流调度 开源社区

LeetCode题解:239. 滑动窗口最大值,双循环暴力,JavaScript,详细注释

Lee Chen

大前端 LeetCode

企业表格技术与风险指标补录系统

葡萄城技术团队

SpreadJS

Docker 搭建 Redis Cluster 集群环境

哈喽沃德先生

redis Docker 容器 集群 redis cluster

区块链数字货币交易所开发,数字交易平台搭建

13530558032

Centos7 mongodb安装全攻略

红泥

mongodb

区块链+公共安全 大有可为

CECBC

区块链 安全

缓存与数据库一致性问题深度剖析

Zhendong

数据库 缓存 秒杀系统

数字货币钱包软件开发方案,区块链数字货币钱包源码

13530558032

iWebExcel 协同数据填报和在线分析平台

葡萄城技术团队

SpreadJS

LeetCode题解:84. 柱状图中最大的矩形,循环+双指针暴力,JavaScript,详细注释

Lee Chen

大前端 LeetCode

全场景智慧:新工业革命必须拥抱的晨曦

脑极体

合约跟单系统开发,合约跟单软件定制开发

13530558032

oeasy教您玩转linux010204-figlet

o

Flink保存点-17

小知识点

scala 大数据 flink

学习笔记丨结构体中的内存管理

Liuchengz.

c Linux 学习

dubbo应用级服务发现初体验

捉虫大师

dubbo 注册中心

北京首台区块链政务终端亮相 一键“拉取”链上数据

CECBC

区块链技术

芯片破壁者(十五):仙童半导体和“八叛逆”所缔造的“硅谷模式”

脑极体

ARTS Week10

丽子

实战中学习浏览器工作原理 — 之 HTTP 请求与解析

三钻

CSS Java 大前端 浏览器

从每秒6000写请求谈起

架构师修行之路

程序员 架构师 高并发系统设计

区块链usdt承兑商支付系统开发 区块链应用开发

电微13828808271

USDT承兑支付系统开发

Apache Pulsar 8 月月报:里程碑一个接一个

Apache Pulsar

大数据 云原生 Apache Pulsar 消息系统 消息中间件

macos主流工作开发套件指南

久违

macos Docker 大前端 自动化部署

凤凰交易所 全球首个多元化生态交易平台震撼来袭

InfoQ_967a83c6d0d7

车队管理软件

samhuang

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