3.01 路由树-Beego&GIN&Echo实现与设计总结
Last updated
Last updated
我们分成以下步骤来实现一颗路由树:
全静态匹配
支持通配符匹配
支持参数路由
作业:支持正则匹配
在之前的框架概览部分讲到过,Beego的ControllerRegister
结构体负责路由相关的功能.
ControllerRegister
:类似于容器,存储了所有的路由树.
ControllerRegister.routers
即为路由树.可以看到其类型为map[string]*Tree
,其中的key表示HTTP动词.也就是说,Beego的路由树是按照HTTP动词来组织的,每个HTTP动词对应一棵路由树.因此说ControllerRegister
存储了所有的路由树.
Tree
:它代表的就是路由树,在Beego中,一棵路由树可以被看做由子树组成.Tree
实际上是一种多叉树的数据结构.
其中:
fixrouters
:静态路由
wildcard
:通配符路由
leaves
:叶子节点
Beego的路由树中区分了叶子节点和非叶子节点,但可能实际意义并不大.leafInfo
结构体表示了叶子节点:
如下图示,Beego的树定义,并没有采用children式的定义,而是采用递归式的定义,即:一棵树由根节点+子树组成.也就是说实际上ControllerRegister.routers
实际上是路由森林而非路由树
在之前的框架概览部分讲到过,GIN的路由树功能本质上是依赖于Engine
结构体的trees
字段的
methodTrees的定义:
methodTrees
类型是methodTree
的切片.实际上GIN的路由树也是按照HTTP动词来组织的,每个HTTP动词对应一棵路由树.只是相比于Beego使用了map[string]*Tree
的方式存储路由森林,GIN使用了[]methodTree
,也就是切片的方式来存储路由森林,而实际上这个切片中元素的数量只是和HTTP动词的数量(9个)相等.该切片中的每个元素表示了一棵路由树.
methodTree
定义了单棵路由树.树在GIN里边采用的是children的定义方式,即树由节点构成.
methodTree.method
:HTTP动词
methodTree.root
:根节点
node
结构体表示树上的1个节点:
其中:
children
字段表示该节点的子节点
nType
:实际上是一个uint8类型,用于标记节点类型
wildChild
:标记特殊节点
handlers
:责任链
GIN利用路由的最长公共前缀来构造路由树.
图中的??
表示特殊字符
所谓的最长公共前缀,指的是:
/
c
o
ntact
m
a
b
doc/
go(注意go_faq.html
和go1.html
的最长公共前缀是go
而不是g
,因此基址是go
)
_faq.html
1.html
??(表示路由中的特殊字符)
α
β
hi
这个数据结构也被称为前缀树.
在之前的框架概览部分讲到过,Echo结构体中的routers
字段表示根据Host将路由树隔离,可以看做是类似namespace之类的概念,既是一种组织方式,也是一种隔离机制;而router
字段表示路由树
注意和Beego的HTTP动词不同,Echo.routers
字段的map[string]*Router
中,key表示Host
而Router
结构体表示路由注册中心,其中维护了路由树(routes
字段)
Route
结构体表示具体的路由:
node
结构体也是常规的树节点设计,内部也采用的是children
字段来表示子节点的数据
与GIN的node
结构体基本类似,只是相比于GIN,Echo的node
结构体多维护了一个parent
字段,即指向父节点的指针.其中:
staticChildren
:静态子节点
paramChild
:参数子节点
anyChild
:任意子节点(通配符匹配)
有一种观点认为:Echo的设计不如GIN的设计,也是因为Echo多引入了一些抽象,但是实际上它们功能都差不多.
开源,讲究的是一个以简为美;但是工作,讲究刷KPI唬人为要.因此开源要克制,而工作要泛滥.
当然,我现在连刷KPI的机会都没有~
归根结底就是设计一颗多叉树
我们的设计也是按照HTTP方法来组织路由树,每个HTTP方法一棵树
每个节点维护自己的子节点
以下这两种形态的路由树组织,性能差异不大,但是第二种的实现要简单很多
第1种:最长公共前缀树
GIN的路由树不好读懂的原因就在于提取公共前缀.因为每次注册路由时,最长公共前缀都会发生变化.举个不太恰当的例子:
5 10 15 18
这4个数字的最大公约数为3;而将18换为45,则5 10 15 45
的最大公约数为5.每当有一个数字被替换或添加,都要重新计算这一组数字的最大公约数.
在这里打一个断点就能看到他实现的树的结构
第2种:按/
"分层"的树
大体上理解,Beego的路由树,是直接将叶子节点append到fixrouters和wildcard上的;而GIN的路由树,是通过递归来维护树的内部节点与内部节点的关系的
也就是说Beego的路由树,实际上只有3层:
根节点
静态路由&通配符匹配
根节点