【ArchSummit】如何通过AIOps推动可量化的业务价值增长和效率提升?>>> 了解详情
写点什么

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

评论

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

中移链合约常用开发介绍 (二)多索引表的使用

BSN研习社

电脑音视频暂停再继续,声音突然变大

代码的路

windows

Linux安装与卸载软件

代码的路

Linux

再获殊荣!图数据库 NebulaGraph 获得 ITPUB 2022 创新产品奖

最新动态

互联网医疗月度观察:规范化、合法化的网络售药新时代到来

易观分析

互联网医疗

【1.6-1.13】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动

版本控制 | 设计师和美术人员的理想版本控制软件是?

龙智—DevSecOps解决方案

版本控制 版本控制软件

智能图像处理:基于边缘去除和迭代式内容矫正的复杂文档图像校正

合合技术团队

图像处理 图像预处理 人工智能’

数维图可视化编辑器超10项功能升级,您的需求就在其中

2D3D前端可视化开发

数据可视化 数字孪生 三维可视化 web3d web组态软件

如何使用免适配云鹰模组实现多网可切?——实践类

阿里云AIoT

安全 物联网 物联网安全 技术标签

2022年IAA行业品类年度表现总结

易观分析

视频 IAA

微信小程序实验案例:简易成语小词典

TiAmo

小程序 微信小程序

PyFlink 最新进展解读及典型应用场景介绍

Apache Flink

大数据 flink 实时计算

如何使用滑块实现切换图片功能?

Towify

10分钟玩转阿里云物联网平台设备接入、管理、运维——实践类

阿里云AIoT

安全 物联网 物联网安全 技术标签

收官!OceanBase第五届技术征文大赛获奖名单公布!

OceanBase 数据库

数据库 oceanbase

企业真的需要一个私有化的即时通讯吗?

WorkPlus

企业移动应用APP是否能实现统一整合与管理呢?

WorkPlus

Getaverse入选KuCoin Labs首批孵化项目

Geek_Web3

#区块链# 元宇宙 web3

软件测试/测试开发 | 单元测试体系集成

测试人

软件测试 单元测试 自动化测试 JUnit 测试开发

Vue实现登录功能

代码的路

Vue

35张图,直观理解Stable Diffusion

OneFlow

人工智能 深度学习 Stable Diffusion

JDBC的基本概念

代码的路

Java

粒子滤波 PF(Particle filter)算法

代码的路

机器学习

如何理解鲁棒性?为什么robustness会翻译为鲁棒性?

九章云极DataCanvas

火山引擎DataTester:一次A/B测试,帮助产品分享率提升超20%

字节跳动数据平台

大数据 AB testing实战

如何使用企业账户进行协作?

Towify

Win10桌面图标显示问题

代码的路

windows

mmdetection训练数据遇到的问题

代码的路

Python 机器学习

软件测试/测试开发 | 静态扫描体系集成

测试人

软件测试 持续集成 jenkins 自动化测试 测试开发

【UE虚幻引擎】手把手教学,UE新手打包全攻略!

3DCAT实时渲染

游戏开发 虚幻引擎 虚幻引擎5 UE5 游戏开发引擎

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