百度开源网关FE源代码阅读3之路由实战
如果你还是不懂怎么配置BFE
的路由,个人觉得,这是正常的,但是如果之前的文章让你有了Host
, HostTag
, Product
, Cluster
的概念就够了。本文尝试配置一些示例配置文件的来继续讲解BFE
的路由机制。
本文的配置文件及源代码都在: https://github.com/youerning/blog/tree/master/bfe
环境准备
BFE版本: v1.6.0
测试环境: linux
测试后端:
- 127.0.0.1:18001 响应内容:
ClusterA backend1 path:/
- 127.0.0.1:18002 响应内容:
ClusterA backend2 path:/
- 127.0.0.1:18003 响应内容:
ClusterB backend1 path:/
- 127.0.0.1:18004 响应内容:
ClusterB backend2 path:/
测试后端的代码如下:
package main
import (
"fmt"
"html"
"net/http"
)
func main() {
servers := []struct {
Cluster string
Name string
Port int
}{
{"ClusterA", "Backend1", 18001},
{"ClusterA", "Backend2", 18002},
{"ClusterB", "Backend1", 18003},
{"ClusterB", "Backend2", 18004},
}
for _, server := range servers {
mux := http.NewServeMux()
s := server
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s %s %s\n", s.Cluster, s.Name, html.EscapeString(r.URL.Path))
})
fmt.Printf("starting %s %s server at :%d\n", s.Cluster, s.Name, s.Port)
go http.ListenAndServe(fmt.Sprintf(":%d", s.Port), mux)
}
select {}
}
然后用curl测试结果如下
for i in `seq 1 4`;do curl "http://127.0.0.1:1800$i/test";done
ClusterA Backend1 /test
ClusterA Backend2 /test
ClusterB Backend1 /test
ClusterB Backend2 /test
案例1
假设本人搞了网站,域名是youerning.top
, 这个域名有两个后端127.0.0.1:18001, 127.0.0.1:18002
,基于这个需求,我们将其转换成BFE
能够理解的原语。
- Host -> youerning.top
- HostTag -> youerningWeb
- Product -> youerning_product
- Cluster -> cluster_youerning
- 子集群列表 -> [youerning.top]
相关配置如下:
host_rule.data
配置域名对应的HostTag
, 以及HostTag
对应的Product
。
{
"Version": "init version",
"DefaultProduct": null,
"Hosts": {
"youerningWeb":[
"youerning.top"
]
},
"HostTags": {
"youerning_product":[
"youerningWeb"
]
}
}
route_rule.data
配置Product
到集群的路由规则
{
"Version": "init version",
"ProductRule": {
"youerning_product": [
{
"Cond": "req_host_in(\"youerning.top\")",
"ClusterName": "cluster_youerning"
}
]
}
}
cluster_conf.data
集群配置,可以设置一个空对象,但是必须要配置。
{
"Version": "init version",
"Config": {
"cluster_youerning": {}
}
}
gslb.data
配置集群内子集群之间的路由,这里只有只有一个子集群example.com
。
{
"Clusters": {
"cluster_youerning": {
"youerning.top": 100
}
},
"Hostname": "",
"Ts": "0"
}
cluster_table.data
配置子集群example.com
对应的后端列表
{
"Config": {
"cluster_youerning": {
"youerning.top": [
{
"Addr": "127.0.0.1",
"Name": "backend1",
"Port": 18001,
"Weight": 10
},
{
"Addr": "127.0.0.1",
"Name": "backend2",
"Port": 18002,
"Weight": 10
}
]
}
},
"Version": "init version"
}
测试一下
for i in `seq 1 2`;do curl "http://127.0.0.1:8080/test" -H "Host: youerning.top";done
ClusterA Backend1 /test
ClusterA Backend2 /test
因为后端的路由权重一样,所以两个后端都会响应。
案例2
现在大家都搞k8s,本人觉得也不错(虽然我的网站是静态网站, 嘿嘿), 觉得应该搞个高可用,所以搞了两个k8s集群,两个集群在不同的地区,假设一个东莞,一个在深圳,这个服务的域名还是youerning.top
。基于这个需求,我们将其转换成BFE
能够理解的原语。
- Host -> youerning.top
- HostTag -> youerningWeb
- Product -> youerning_product
- Cluster -> cluster_youerning
- 子集群列表 -> [youerning.top.dongguan, youerning.top.shenzhen]
在不同地区还搞负载均衡?是专线太便宜了?
这个案例的配置跟上一个比较大的区别是后者子集群列表不止一个,所以主要的不同是后两个配置文件的配置,前面的配置一样,这里就不贴了, 只贴不同的配置文件。
gslb.data
{
"Clusters": {
"cluster_youerning": {
"youerning.top.dongguan": 50,
"youerning.top.shenzhen": 50
}
},
"Hostname": "",
"Ts": "0"
}
cluster_table.data
{
"Config": {
"cluster_youerning": {
"youerning.top.dongguan": [
{
"Addr": "127.0.0.1",
"Name": "backend1",
"Port": 18001,
"Weight": 10
},
{
"Addr": "127.0.0.1",
"Name": "backend2",
"Port": 18002,
"Weight": 10
}
],
"youerning.top.shenzhen": [
{
"Addr": "127.0.0.1",
"Name": "backend1",
"Port": 18003,
"Weight": 10
},
{
"Addr": "127.0.0.1",
"Name": "backend2",
"Port": 18004,
"Weight": 10
}
]
}
},
"Version": "init version"
}
测试结果如下:
for i in `seq 1 4`;do curl "http://127.0.0.1:8080/test" -H "Host: youerning.top";done
ClusterA Backend1 /test
ClusterA Backend2 /test
ClusterA Backend1 /test
ClusterA Backend2 /test
如果你仔细看会发现,只有集群A被转发了流量,这主要是因为默认GSLB的默认Hash策略是ClientIP
, 所以同一个客户端总会定位到同一个子集群,那么怎么看到在两个子集群间路由呢? 解决办法还是很多的,一是换hash策略,比如根据请求的路径hash,这样我们构造不同的路径就有可能会被hash到不同的子集群,二是用多个不同的客户端IP测试。
这里我们选择使用最简单的方法, 即第一个方法。使用第一个办法,需要修改cluster_conf.data
文件,修改后内容如下:
{
"Version": "init version",
"Config": {
"cluster_youerning": {
"GslbBasic": {
"CrossRetry": 0,
"RetryMax": 2,
"HashConf": {
"HashStrategy": 3
}
}
}
}
}
测试结果如下:
for i in `seq 1 4`;do curl "http://127.0.0.1:8080/test$i" -H "Host: youerning.top";done
ClusterA Backend1 /test1
ClusterA Backend2 /test2
ClusterB Backend1 /test3
ClusterB Backend2 /test4
测试符合预期, nice。
那么还有没有其他办法呢? 有的!!! 首先看看BFE
支持的hash策略。
const (
ClientIdOnly = iota
ClientIpOnly
ClientIdPreferred
RequestURI
)
一共四种hash策略,分别代表的意思是
- 只使用客户端ID作为Hash对象 这个策略需提供一个
HashHeader
的参数,比如"HashHeader": "Cookie:UID"
- 只使用客户端IP作为Hash对象
- 首先使用客户端ID, 如果没有的话就是用客户端IP
- 使用请求的路径
**如果使用客户端ID, 但是客户端ID的值没有设置怎么办?**那就随机生成一个, 代码如如下。
func (bal *BalanceGslb) getHashKey(req *bfe_basic.Request) []byte {
// 获取hash的值
// 如果为空 就设置一个随机数
if len(hashKey) == 0 {
hashKey = make([]byte, 8)
binary.BigEndian.PutUint64(hashKey, rand.Uint64())
}
return hashKey
}
所以回到上面的问题,我们可以设置hash策略是客户端ID,但是不设置任何值,这样也会在子集群之间路由,配置如下:
{
"Version": "init version",
"Config": {
"cluster_youerning": {
"GslbBasic": {
"CrossRetry": 0,
"RetryMax": 2,
"HashConf": {
"HashStrategy": 0,
"HashHeader": "CustomID"
}
}
}
}
}
测试如下:
for i in `seq 1 4`;do curl "http://127.0.0.1:8080/test" -H "Host: youerning.top" ;done
ClusterA Backend2 /test
ClusterB Backend2 /test
ClusterA Backend1 /test
ClusterA Backend2 /test
值得注意的是因为hash的key是随机生成的,所以少次数测试并不稳定。
当然了,我们也可以指定我们设置的ClentID
for i in `seq 1 4`;do curl "http://127.0.0.1:8080/test" -H "Host: youerning.top" -H "CustomID:$i";done
ClusterA Backend1 /test
ClusterB Backend1 /test
ClusterB Backend2 /test
ClusterA Backend2 /test
总结
BFE
在域名之上多了一层封装Product
,这个Product
最开始叫做产品线,后面叫做租户,现在BFE
一般称其为租户,之所以现在感受不到Product
的作用,是因为还没有使用插件或者说扩展模块,模块一般是在租户级别设置规则,这样一组域名就能共享同样的配置,后续的文章看看模块的是用和开发。