作者 | 加纳斯
导读
说起百度的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配置,绝对比较复杂,更有些应用方会有偏业务的逻辑在外面,例如:
- 从某时刻后,将API1的A机房和B机房的流量切30%到C机房;
- 将某个APP的某个版本之上的android流量切到新的路由规定;
- cookie有某些特色或者query中有某些特色的流量转发到预览环境。
那么在调度阶段如何更好地解决如下两个问题呢:
- 如何让简略的路由规定配置起来特地简略,性能较高?
- 如何实现简单甚至业务个性化的调度策略实现?
把问题放大到网关的全局利用场景来看,如何既能通用,写个插件大家都能用;又能反对个性化,尽量能通过通用插件满足业务特例的问题;还能灵便,流量网关、业务网关都能胜任。既要、又要、还要的问题通常是应用tradeoff的形式加以均衡解决,然而Janus的解决方案同时满足了下面的三个需要:通过插件机制满足通用化需要+通过可动静下发编程能力的形式进行差异化配置+通过SDK集成到业务部署的形式反对灵便应用。
03 流量调度方案设计
3.1 计划思路概述
为了将服务的转发规定更加清晰,Janus将路由分为了三级(与nginx相似):
由下面的挑战剖析可知:
- 对于大多数的简略路由规定须要绝对简略,性能绝对高,通过域名匹配+树路由实现的url匹配即可;
- 对于大量的简单路由规定须要扩展性足够强,能够在特色匹配阶段引入一个极简的脚本语言来实现。
3.2 根底路由规定反对
通过树路由反对的局部规定如下:
3.3 进阶路由规定反对
上述的简略路由规定能够满足90%+的业务需要,然而对于相似\`从某时刻后,将API1的A机房和B机房的流量切30%到C机房\`这种需要是满足不了的。因而,在特色匹配阶段能够通过\`变量表达式\`+\`条件表达式\`进行精细化匹配。
变量表达式
为了能依据零碎外面的常见特色进行精细化匹配,首先咱们要对系统外面的常见特色进行形容。例如:
- 通过${idc}示意以后所属的机房
- 通过${time}示意以后工夫
- 通过${query}示意get参数
- 通过${header}示意header外面的数值
然而当特色越来越多的时候,就会略显臃肿,存在的特色变量越来越多,这时候Janus引入了分级的概念,比方:
如图所示,就能够用${request.query.id}来示意本次申请中key为id的query值。并且如上的特色变量是能够裁减的,每个应用方能够依据本人的零碎差别、环境差别定义本人的特色变量体系。
条件表达式
有了下面实现的变量表达式,咱们就能够用$形容咱们须要的特色变量了,然而如何对这些特色变量进行操作呢?
Janus的计划是定义一门极简的语言(无论是用yacc等一类的生成语法分析的工具,还是本人做词法剖析、语法分析,实现都比较简单,这里不再赘述实现细节),只反对逻辑运算+函数调用,局部例子如下:
函数调用:
逻辑运算:
Janus在有变量表达式来示意零碎特色的根底上,增加了条件表达式来对系统特色进行操作、判断。因为能够一直裁减变量表达式和条件表达式,因而Janus简直能够满足用户的任意需要。
性能比照
通过如上计划介绍能够看出,采纳从管制面下发表达式的形式,能够满足绝大部分场景的需要,然而,对性能影响如何呢?
在数据面接管到管制面下发转发规定时,首先会对变量表达式和条件表达式进行编译,映射成go的代码,在后续运行时,与间接调用原生的go语言差别并不大。比照数据如下:
条件表达式:
"random(0,100) || random(100,100)"
对应的benchmark数据:
goos: windowsgoarch: amd64cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHzBenchmarkRandom-8 35817918 34.52 ns/op 0 B/op 0 allocs/op
原生go代码:
(0 > rand.Intn(100)) || (100 > rand.Intn(100))
对应的benchmark数据:
goos: windowsgoarch: amd64cpu: 11th Gen Intel(R) Core(TM) i5-1145G7 @ 2.60GHzBenchmarkRawRandom-8 39136900 31.63 ns/op 0 B/op 0 allocs/op
能够看到应用表达式与应用原生go代码在性能上相差不到10%,区别并不是特地大。
04 计划泛化
通过下面的变量表达式+条件表达式的形式,很好地解决了流量调度问题。实际上,该计划能够作为一个通用解决方案解决很多相似问题。以Janus网关为例,在很多中央都大量存在这个变量表达式和条件表达式。
4.1 插件的运行条件
以容灾插件为例,用户能够把容灾插件配置在任意路由规定上,然而大家认定的触发容灾的规定可能不一样,比方:
- 有些业务认为:只有后端的http协定返回5xx才须要容灾
- 有些业务认为:后端的http协定返回5xx 或者 返回值的json外面errno != 0须要容灾
- 更有些业务认为:后端的http协定返回5xx 或者 header外面的sla\_status=0须要容灾
一方面,咱们想做一个通用的容灾插件,另一方面,大家的触发规定的规范又千奇百怪、各不相同。怎么解决这个矛盾呢?
Janus的答案是:把控制权交给用户,用户配置容灾插件的时候同时配置一个条件表达式,只有条件表达式返回true,才会运行容灾逻辑。
下面的问题对应的下发配置如下:
- num\_gt(${response.code}, 499)
- num\_gt($ {response.code}, 499) || (!str\_equal($ {response.jsonbody.errno}, 0))
- 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怎么定义呢?
- 评论接口只有id一样就认为是同一个申请
- 我的粉丝接口不仅须要id一样,还须要uk一样才是同一个申请
- 主页接口须要uk一样才认为是同一个申请
解决思路是用变量表达式来把key的定义交给用户,用户配置缓存插件的时候同时配置key的规定,比方:
- comment\_${request.query.id}
- fans\_$ {request.query.id}\_${request.query.uk}
- homepage\_${request.query.uk}
这样就解决了通用缓存插件中的通用与个性化之间的矛盾。
05 瞻望
在Janus网关服务中,通过惯例路由规定+变量表达式+条件表达式的形式实现了各种流量调度策略,并将计划泛化到了各种其余性能的实现上,撑持了几百万QPS的流量及泛滥应用方的接入。通过曾经实现的零碎变量及规定的组合,根本能够实现任意性能,然而当须要新的规定时,则须要在Janus中上线新的条件表达式实现。为了进一步强化Janus中的动静配置体现能力,Janus正在进行示意式与Go官网规范库的无缝买通。这样就能够在管制面进行更加灵便的配置下发动静编程能力,满足更宽泛的需要。
——END——
举荐浏览:
深入浅出DDD编程
百度APP iOS端内存优化实际-内存管控计划
Ernie-SimCSE比照学习在内容反作弊上利用
品质评估模型助力危险决策程度晋升
合约广告平台架构演进实际
AI技术在基于危险测试模式转型中的利用