百度开源网关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策略,分别代表的意思是

  1. 只使用客户端ID作为Hash对象 这个策略需提供一个HashHeader的参数,比如"HashHeader": "Cookie:UID"
  2. 只使用客户端IP作为Hash对象
  3. 首先使用客户端ID, 如果没有的话就是用客户端IP
  4. 使用请求的路径

**如果使用客户端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的作用,是因为还没有使用插件或者说扩展模块,模块一般是在租户级别设置规则,这样一组域名就能共享同样的配置,后续的文章看看模块的是用和开发。

参考链接