原文链接: go-zero 是如何做路由管理的?
go-zero 是一个微服务框架,包含了 web 和 rpc 两大部分。
而对于 web 框架来说,路由管理是必不可少的一部分,那么本文就来探讨一下 go-zero 的路由管理是怎么做的,具体采用了哪种技术方案。
路由管理方案
路由管理方案有很多种,具体应该如何选择,应该根据使用场景,以及实现的难易程度做综合分析,下面介绍常见的三种方案。
注意这里只是做一个简单的概括性对比,更加详细的内容可以看这篇文章:HTTP Router 算法演进。
标准库方案
最简单的方案就是直接使用 map[string]func()
作为路由的数据结构,键为具体的路由,值为具体的处理方法。
// 路由管理数据结构
type ServeMux struct {
mu sync.RWMutex // 对象操作读写锁
m map[string]muxEntry // 存储路由映射关系
}
这种方案优点就是实现简单,性能较高;缺点也很明显,占用内存更高,更重要的是不够灵活。
Trie Tree
Trie Tree 也称为字典树或前缀树,是一种用于高效存储和检索、用于从某个集合中查到某个特定 key 的数据结构。
Trie Tree 时间复杂度低,和一般的树形数据结构相比,Trie Tree 拥有更快的前缀搜索和查询性能。
和查询时间复杂度为 O(1)
常数的哈希算法相比,Trie Tree 支持前缀搜索,并且可以节省哈希函数的计算开销和避免哈希值碰撞的情况。
最后,Trie Tree 还支持对关键字进行字典排序。
Radix Tree
Radix Tree(基数树)是一种特殊的数据结构,用于高效地存储和搜索字符串键值对,它是一种基于前缀的树状结构,通过将相同前缀的键值对合并在一起来减少存储空间的使用。
Radix Tree 通过合并公共前缀来降低存储空间的开销,避免了 Trie Tree 字符串过长和字符集过大时导致的存储空间过多问题,同时公共前缀优化了路径层数,提升了插入、查询、删除等操作效率。
比如 Gin 框架使用的开源组件 HttpRouter 就是采用这个方案。
go-zero 路由规则
在使用 go-zero 开发项目时,定义路由需要遵守如下规则:
- 路由必须以
/
开头 - 路由节点必须以
/
分隔 - 路由节点中可以包含
:
,但是:
必须是路由节点的第一个字符,:
后面的节点值必须要在结请求体中有path tag
声明,用于接收路由参数 - 路由节点可以包含字母、数字、下划线、中划线
接下来就让我们深入到源码层面,相信看过源码之后,你就会更懂这些规则的意义了。
go-zero 源码实现
首先需要说明的是,底层数据结构使用的是二叉搜索树,还不是很了解的同学可以看这篇文章:使用 Go 语言实现二叉搜索树
节点定义
先看一下节点定义:
// core/search/tree.go
const (
colon = ':'
slash = '/'
)
type (
// 节点
node struct {
item interface{
}
children [2]map[string]*node
}
// A Tree is a search tree.
Tree struct {
root *node
}
)
重点说一下 children
,它是一个包含两个元素的数组,元素 0
存正常路由键,元素 1
存以 :
开头的路由键,这些是 url 中的变量,到时候需要替换成实际值。
举一个例子,有这样一个路由 /api/:user
,那么 api
会存在 children[0]
,user
会存在 children[1]
。
具体可以看看这段代码:
func (nd *node) getChildren(route string) map[string]*node {
// 判断路由是不是以 : 开头
if len