关于网关:如何在几百万qps的网关服务中实现灵活调度策略

62次阅读

共计 3754 个字符,预计需要花费 10 分钟才能阅读完成。

作者 | 加纳斯

导读

说起百度的 BFE 可能不少人都据说过,然而其实在百度外部还有一个几百万 qps 的通用网关服务:Janus。截止以后,Janus 服务不仅笼罩了百度外部 FEED、评论、点赞、关注、直播等十多个中台服务的内网流量,而且为百度 app、晓得、教训、passport、百科、问一问等业务提供了外网流量服务。

在百度已有 BFE 且 BFE 开源的状况下,为什么要建设 Janus 网关?Janus 网关区别于其余网关的外围点有哪些?面对泛滥的接入方,如何实现既能通用又能个性化的流量调度呢?

来本文一探到底吧。

全文 3802 字,预计浏览工夫 10 分钟。

01 为什么要建设 Janus

在百度已有 BFE 且 BFE 开源的状况下,为什么要建设 Janus 网关?

从场景上看,与 BFE 面向通用性能的流量网关场景不同,Janus 不仅能够作为流量网关,也能够作为业务网关、混合网关;不仅面向通用性能,也反对个性化需要。从实现上看,Janus 的局部技术参考了 BFE,然而与厂内 BFE 次要提供 saas 类的服务不同,Janus 提供的是一个通用技术计划,谁应用谁部署,谁有个性化需要谁本人定制插件。因而,Janus 网关的利用场景绝对更广一些,以后的应用场景次要蕴含流量网关、业务网关、混合网关三种模式,流量拓扑如下:

部署拓扑:

02 外围问题

从流量调度规定为例,大部分的应用方的转发规定都绝对比较简单,然而局部业务的转发规定来自于原来的 nginx 配置,绝对比较复杂,更有些应用方会有偏业务的逻辑在外面,例如:

  1. 从某时刻后,将 API1 的 A 机房和 B 机房的流量切 30% 到 C 机房;
  2. 将某个 APP 的某个版本之上的 android 流量切到新的路由规定;
  3. cookie 有某些特色或者 query 中有某些特色的流量转发到预览环境。

那么在调度阶段如何更好地解决如下两个问题呢:

  1. 如何让简略的路由规定配置起来特地简略,性能较高?
  2. 如何实现简单甚至业务个性化的调度策略实现?

把问题放大到网关的全局利用场景来看,如何既能通用,写个插件大家都能用;又能反对个性化,尽量能通过通用插件满足业务特例的问题;还能灵便,流量网关、业务网关都能胜任。既要、又要、还要的问题通常是应用 tradeoff 的形式加以均衡解决,然而 Janus 的解决方案同时满足了下面的三个需要:通过插件机制满足通用化需要 + 通过可动静下发编程能力的形式进行差异化配置 + 通过 SDK 集成到业务部署的形式反对灵便应用。

03 流量调度方案设计

3.1 计划思路概述

为了将服务的转发规定更加清晰,Janus 将路由分为了三级(与 nginx 相似):

由下面的挑战剖析可知:

  1. 对于大多数的简略路由规定须要绝对简略,性能绝对高,通过域名匹配 + 树路由实现的 url 匹配即可;
  2. 对于大量的简单路由规定须要扩展性足够强,能够在特色匹配阶段引入一个极简的脚本语言来实现。

3.2 根底路由规定反对

通过树路由反对的局部规定如下:

3.3 进阶路由规定反对

上述的简略路由规定能够满足 90%+ 的业务需要,然而对于相似 \` 从某时刻后,将 API1 的 A 机房和 B 机房的流量切 30% 到 C 机房 \` 这种需要是满足不了的。因而,在特色匹配阶段能够通过 \` 变量表达式 \`+\` 条件表达式 \` 进行精细化匹配。

变量表达式

为了能依据零碎外面的常见特色进行精细化匹配,首先咱们要对系统外面的常见特色进行形容。例如:

  1. 通过 ${idc} 示意以后所属的机房
  2. 通过 ${time} 示意以后工夫
  3. 通过 ${query} 示意 get 参数
  4. 通过 ${header} 示意 header 外面的数值

然而当特色越来越多的时候,就会略显臃肿,存在的特色变量越来越多,这时候 Janus 引入了分级的概念,比方:

如图所示,就能够用 ${request.query.id} 来示意本次申请中 key 为 id 的 query 值。并且如上的特色变量是能够裁减的,每个应用方能够依据本人的零碎差别、环境差别定义本人的特色变量体系。

条件表达式

有了下面实现的变量表达式,咱们就能够用 $ 形容咱们须要的特色变量了,然而如何对这些特色变量进行操作呢?

Janus 的计划是定义一门极简的语言(无论是用 yacc 等一类的生成语法分析的工具,还是本人做词法剖析、语法分析,实现都比较简单,这里不再赘述实现细节),只反对逻辑运算 + 函数调用,局部例子如下:

函数调用:

逻辑运算:

Janus 在有变量表达式来示意零碎特色的根底上,增加了条件表达式来对系统特色进行操作、判断。因为能够一直裁减变量表达式和条件表达式,因而 Janus 简直能够满足用户的任意需要。

性能比照

通过如上计划介绍能够看出,采纳从管制面下发表达式的形式,能够满足绝大部分场景的需要,然而,对性能影响如何呢?

在数据面接管到管制面下发转发规定时,首先会对变量表达式和条件表达式进行编译,映射成 go 的代码,在后续运行时,与间接调用原生的 go 语言差别并不大。比照数据如下:

条件表达式:

“random(0,100) || random(100,100)”

对应的 benchmark 数据:

goos: windows
goarch: amd64
cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz
BenchmarkRandom-8       35817918            34.52 ns/op        0 B/op          0 allocs/op

原生 go 代码:

(0 > rand.Intn(100)) || (100 > rand.Intn(100))

对应的 benchmark 数据:

goos: windows
goarch: amd64
cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHz
BenchmarkRawRandom-8     39136900          31.63 ns/op         0 B/op         0 allocs/op

能够看到应用表达式与应用原生 go 代码在性能上相差不到 10%,区别并不是特地大。

04 计划泛化

通过下面的变量表达式 + 条件表达式的形式,很好地解决了流量调度问题。实际上,该计划能够作为一个通用解决方案解决很多相似问题。以 Janus 网关为例,在很多中央都大量存在这个变量表达式和条件表达式。

4.1 插件的运行条件

以容灾插件为例,用户能够把容灾插件配置在任意路由规定上,然而大家认定的触发容灾的规定可能不一样,比方:

  1. 有些业务认为 :只有后端的 http 协定返回 5xx 才须要容灾
  2. 有些业务认为 :后端的 http 协定返回 5xx 或者 返回值的 json 外面 errno != 0 须要容灾
  3. 更有些业务认为 :后端的 http 协定返回 5xx 或者 header 外面的 sla\_status= 0 须要容灾

一方面,咱们想做一个通用的容灾插件,另一方面,大家的触发规定的规范又千奇百怪、各不相同。怎么解决这个矛盾呢?

Janus 的答案是:把控制权交给用户,用户配置容灾插件的时候同时配置一个条件表达式,只有条件表达式返回 true,才会运行容灾逻辑。

下面的问题对应的下发配置如下:

  1. num\_gt(${response.code}, 499)
  2. num\_gt($ {response.code}, 499) || (!str\_equal($ {response.jsonbody.errno}, 0))
  3. num\_gt($ {response.code}, 499) || (!str\_equal(${response.header.sla\_status}, 0))

这样就做到了既是一个通用容灾插件,又能够做到个性化的触发逻辑。

4.2 通用缓存插件的设计

当咱们想做一个通用的 redis 缓存插件时,存取逻辑比较简单:

// 申请上游前
if data, ok := redis.Get(key); ok {return data}

// 申请上游
data := reqeust(xxx)

// 申请上游后
redis.Set(key, data)

然而,与下面的插件面临的问题相似,通用缓存插件的 key 怎么定义呢?

  1. 评论接口只有 id 一样就认为是同一个申请
  2. 我的粉丝接口不仅须要 id 一样,还须要 uk 一样才是同一个申请
  3. 主页接口须要 uk 一样才认为是同一个申请

解决思路是用变量表达式来把 key 的定义交给用户,用户配置缓存插件的时候同时配置 key 的规定,比方:

  1. comment\_${request.query.id}
  2. fans\_$ {request.query.id}\_${request.query.uk}
  3. homepage\_${request.query.uk}

这样就解决了通用缓存插件中的通用与个性化之间的矛盾。

05 瞻望

在 Janus 网关服务中,通过惯例路由规定 + 变量表达式 + 条件表达式的形式实现了各种流量调度策略,并将计划泛化到了各种其余性能的实现上,撑持了几百万 QPS 的流量及泛滥应用方的接入。通过曾经实现的零碎变量及规定的组合,根本能够实现任意性能,然而当须要新的规定时,则须要在 Janus 中上线新的条件表达式实现。为了进一步强化 Janus 中的动静配置体现能力,Janus 正在进行示意式与 Go 官网规范库的无缝买通。这样就能够在管制面进行更加灵便的配置下发动静编程能力,满足更宽泛的需要。

——END——

举荐浏览:

深入浅出 DDD 编程

百度 APP iOS 端内存优化实际 - 内存管控计划

Ernie-SimCSE 比照学习在内容反作弊上利用

品质评估模型助力危险决策程度晋升

合约广告平台架构演进实际

AI 技术在基于危险测试模式转型中的利用

正文完
 0