3.12 路由树-总结与面试要点
本节课工程结构如下:
PART1. 注册路由的注意事项
已经注册了的路由,无法被覆盖.例如
/user/home
注册两次,会冲突path
必须以/
开始并且结尾不能有/
,中间也不允许有连续的/
不能在同一个位置注册不同的参数路由.例如
/user/:id
和/user/:name
冲突不能在同一个位置同时注册通配符路由和参数路由.例如
/user/:id
和/user/*
冲突同名路径参数,在路由匹配的时候,值会被覆盖.例如
/user/:id/abc/:id
,那么/user/123/abc/456
,最终id = 456
TODO:这里是可以实现把2个id都记录下来的,只需要将
matchNode.pathParams
的类型修改为map[string][]string
即可.下去自己试一下TODO:这种情况也可以考虑panic掉
把以上内容作为注释放在addRoute()
方法上,那么但凡用户发现panic的时候看一眼这个注释,都知道出了什么问题
router.go
:
PART2. 为什么在注册路由用panic
?
panic
?可以看到在addRoute()
方法中有大量的panic出现.
那么问题来了:
可不可以返回
error
?应不应该返回
error
?
2.1 可不可以返回error
?
error
?可以返回,但没必要.因为框架的使用者根本不会去处理的.注册路由都失败了,谁还有空去处理注册路由的报错信息呢?
2.2 应不应该返回error
?
error
?其实不应该.从框架使用者的角度上来看,用户必须完成注册路由的步骤,才能启动HTTPServer.那么在注册路由时,采用panic
就是没问题的,因为这时应用还没运行.
注册路由时panic
总好过应用启动了再panic
.
PART3. 路由树是线程安全的吗?
显然不是线程安全的.我们要求用户必须要注册完路由才能启动HTTPServer
.而正常的用法都是在启动之前依次注册路由,不存在并发场景.
换言之,这是一个顺序写并发读的场景.并不会出问题.并发读写才会引入线程安全问题.
至于运行期间动态注册路由,没必要支持.这是典型的为了解决1%的问题,引入99%的代码.
之所以不将路由树做成线程安全的,也是为了性能,因为一旦涉及到加锁/原子操作的情况,性能就下来了.
PART4. 面试要点(一)
4.1 路由树算法的实现?
核心就是前缀树.前缀的意思就是,两个节点共同的前缀,将会被抽取出来作为父节点.在我们的实现中是按照/
来切割,每一段作为一个节点.
4.2 路由匹配的优先级?
本质上这是和Web框架相关的.在我们的设计里面是静态匹配 > 路径参数 > 通配符匹配
.但实际上这个规则意义不大,因为路径参数和通配符匹配是互斥的
4.3 路由查找会回溯吗?
这也是和Web框架相关的,我们在课程上是不支持的.在这里可以简单描述可回溯和不可回溯之间的区别,可以是用课程中的例子/user/123/home
和/user/*/*
.课程中不支持是因为这个特性非常鸡肋
4.4 Web框架是怎么组织路由树的?
一个HTTP动词就是一颗路由树.也可以考虑所有HTTP动词共用一颗路由树,每个节点标记自己支持的HTTP方法.前者是比较主流的
TODO:第二种可以尝试去写
PART5. 面试要点(二)
5.1 路由查找的性能受什么影响? 或者说怎么评估路由查找的性能?
核心是看路由树的高度,次要因素是路由树的宽度(想想我们的node.children
字段).
但是我们实现的时候node.children
字段用的是map
来实现的,所以宽度对我们的实现影响不大.但是如果使用slice
实现,宽度影响就比较大了.
GIN的最长前缀树,则树会更高;我们的实现中,树会更宽.
如果你不是为了装逼,不要写GIN的那种路由树,因为代码很复杂,且很难读懂.
5.2 路由树是线程安全的吗?
严格来说也是跟Web框架相关的.大多数都不是线程安全的,这是为了性能.所以才要求大家一定要先注册路由,后启动Web服务器.如果你有运行期间动态添加路由的需求,只需要利用装饰器模式,就可以将一个线程不安全的封装为线程安全的路由树.
5.3 具体匹配方式的实现原理?
课程上我们实现了静态匹配、通配符匹配和路径匹配,作业里面要求大家照着实现一个正则匹配.其实核心就是划定优先级,然后一种种匹配方式挨个匹配过去.
Last updated