go-zero 是如何做路由管理的?
原文链接: 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.goconst (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(route) > 0 && route[0] == colon {return nd.children[1]}return nd.children[0]
}
路由添加
// Add adds item to associate with route.
func (t *Tree) Add(route string, item interface{}) error {// 需要路由以 / 开头if len(route) == 0 || route[0] != slash {return errNotFromRoot}if item == nil {return errEmptyItem}// 把去掉 / 的路由作为参数传入err := add(t.root, route[1:], item)switch err {case errDupItem:return duplicatedItem(route)case errDupSlash:return duplicatedSlash(route)default:return err}
}func add(nd *node, route string, item interface{}) error {if len(route) == 0 {if nd.item != nil {return errDupItem}nd.item = itemreturn nil}// 继续判断,看看是不是有多个 /if route[0] == slash {return errDupSlash}for i := range route {// 判断是不是 /,目的就是去处两个 / 之间的内容if route[i] != slash {continue}token := route[:i]// 看看有没有子节点,如果有子节点,就在子节点下面继续添加children := nd.getChildren(token)if child, ok := children[token]; ok {if child != nil {return add(child, route[i+1:], item)}return errInvalidState}// 没有子节点,那么新建一个child := newNode(nil)children[token] = childreturn add(child, route[i+1:], item)}children := nd.getChildren(route)if child, ok := children[route]; ok {if child.item != nil {return errDupItem}child.item = item} else {children[route] = newNode(item)}return nil
}
主要部分代码都已经加了注释,其实这个过程就是树的构建,如果读过之前那篇文章,那这里还是比较好理解的。
路由查找
先来看一段 match 代码:
func match(pat, token string) innerResult {if pat[0] == colon {return innerResult{key: pat[1:],value: token,named: true,found: true,}}return innerResult{found: pat == token,}
}
这里有两个参数:
pat:路由树中存储的路由token:实际请求的路由,可能包含参数值
还是刚才的例子 /api/:user,如果是 api,没有以 : 开头,那就不会走 if 逻辑。
接下来匹配 :user 部分,如果实际请求的 url 是 /api/zhangsan,那么会将 user 作为 key,zhangsan 作为 value 保存到结果中。
下面是搜索查找代码:
// Search searches item that associates with given route.
func (t *Tree) Search(route string) (Result, bool) {// 第一步先判断是不是 / 开头if len(route) == 0 || route[0] != slash {return NotFound, false}var result Resultok := t.next(t.root, route[1:], &result)return result, ok
}func (t *Tree) next(n *node, route string, result *Result) bool {if len(route) == 0 && n.item != nil {result.Item = n.itemreturn true}for i := range route {// 和 add 里同样的提取逻辑if route[i] != slash {continue}token := route[:i]return n.forEach(func(k string, v *node) bool {r := match(k, token)if !r.found || !t.next(v, route[i+1:], result) {return false}// 如果 url 中有参数,会把键值对保存到结果中if r.named {addParam(result, r.key, r.value)}return true})}return n.forEach(func(k string, v *node) bool {if r := match(k, route); r.found && v.item != nil {result.Item = v.itemif r.named {addParam(result, r.key, r.value)}return true}return false})
}
以上就是路由管理的大部分代码,整个文件也就 200 多行,逻辑也并不复杂,通读之后还是很有收获的。
大家如果感兴趣的话,可以找到项目更详细地阅读。也可以关注我,接下来还会分析其他模块的源码。
以上就是本文的全部内容,如果觉得还不错的话欢迎点赞,转发和关注,感谢支持。
推荐阅读:
- 使用 Go 语言实现二叉搜索树
- HTTP Router 算法演进
相关文章:
go-zero 是如何做路由管理的?
原文链接: go-zero 是如何做路由管理的? go-zero 是一个微服务框架,包含了 web 和 rpc 两大部分。 而对于 web 框架来说,路由管理是必不可少的一部分,那么本文就来探讨一下 go-zero 的路由管理是怎么做的,…...
Springboot集成ip2region离线IP地名映射-修订版
title: Springboot集成ip2region离线IP地名映射 date: 2020-12-16 11:15:34 categories: springboot description: Springboot集成ip2region离线IP地名映射 1. 背景2. 集成 2.1. 步骤2.2. 样例2.3. 响应实例DataBlock2.4. 响应实例RegionAddress 3. 打开浏览器4. 源码地址&…...
智能驾驶系列报告之一:智能驾驶 ChatGPT时刻有望来临
原创 | 文 BFT机器人 L3 功能加速落地,政策标准有望明确 L2 发展日益成熟,L3 功能加速落地。根据市场监管总局发布的《汽车驾驶自动化分级》与 SAE发布的自动驾驶分级标准,自动驾驶主要分为 6 个级别(0 级到 5 级,L0 …...
设计HTML5文档结构
定义清晰、一致的文档结构不仅方便后期维护和拓展,同时也大大降低了CSS和JavaScript的应用难度。为了提高搜索引擎的检索率,适应智能化处理,设计符合语义的结构显得很重要。 1、头部结构 在HTML文档的头部区域,存储着各种网页元…...
vue echarts中按钮点击后修改值 watch数据变化后刷新图表
1 点击按钮 {feature: {myBtn1: {show: true,title: 反转Y轴,showTitle: true,icon: path://M512 0A512 512 0 1 0 512 1024A512 512 0 0 0 512 0M320 320V192h384v128zM128 416V288h256v128zM320 704V576h384v128zM128 800V672h256v128z,onclick: () > {dataSetting.rever…...
React antd tree树组件 - 父子节点没有自动关联情况下 - 显示半选、全选状态以及实现父子节点互动
实现的效果图如下: 如Ant Design Vue 中所示,并没有提供获取半选节点的方法,当设置checked和checkStrictly时,父子节点也不再自动关联了 前提:从后端可以获取的数据分别是完整的树型数据、所有选中的节点数据&#…...
优漫动游 大厂需要什么样的ui设计师呢?
通常来说大公司UI设计的流程主要是这样的:创意-头脑风暴-策划方案-交互设计&评审-美术设计&评审-开发实施,不过实际上大多数公司都有自己的一套流程,源于公司的基因、公司组织体系、公司领导风格。一起了解大厂需要什么样的ui设计师呢…...
TiDB Bot:用 Generative AI 构建企业专属的用户助手机器人
本文介绍了 PingCAP 是如何用 Generative AI 构建一个使用企业专属知识库的用户助手机器人。除了使用业界常用的基于知识库的回答方法外,还尝试使用模型在 few shot 方法下判断毒性。 最终,该机器人在用户使用后,点踩的比例低于 5%࿰…...
uniapp 使用canvas画海报(微信小程序)
效果展示: 项目要求:点击分享绘制海报,并实现分享到好友,朋友圈,并保存 先实现绘制海报 <view class"data_item" v-for"(item,index) in dataList" :key"index"click"goDet…...
TiDB 应急运维脚本,更加方便的管理TiDB集群
TiDB 应急运维脚本,更加方便的管理TiDB集群 使用方法 使用方法:[tidblocalhost ~]$ which tiup ~/.tiup/bin/tiup编辑脚本,MYSQL_PASSWD 和 PORT 根据实际替换 [tidblocalhost ~]$ vi ~/.tiup/bin/ti#version 1.1 #author guanguanglei ##…...
第二部分:AOP
一、AOP简介 AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。 AOP是OOP(面向对象编程)的进阶版。 作用:在不改变原始设计的基础上为其进行功能增强。 spring理念&#x…...
分享一组天气组件
先看效果: CSS部分代码(查看更多): <style>:root {--bg-color: #E9F5FA;--day-text-color: #4DB0D3;/* 多云 */--cloudy-background: #4DB0D3;--cloudy-temperature: #E6DF95;--cloudy-content: #D3EBF4;/* 晴 */--sunny-b…...
微服务中RestTemplate访问其他服务返回值转换问题
背景: 接收一个springcloud项目,UI模块访问其他服务的接口,返回数据统一都是使用fastjson进行转换,但是新开发了几个新模块之后发现fastjson很多bug(各种内存溢出),但是很多地方已经重度依赖fa…...
Centos7.9上(离线)安装Gitlab
1、下载Gitlab的rpm安装包Index of /gitlab-ce/yum/el7/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror 2、安装rpm -i gitlab-ce-10.0.0-ce.0.el7.x86_64.rpm,如果依赖缺失,yum安装即可 3、vi /etc/gitlab/gitlab.rb 配置external_url&…...
pikachu中RCE出现乱码的解决的方案
exec “ping” 输入127.0.0.1 这种乱码的解决办法就是在pikachu/vul/rce/rce_ping.php目录里面的第18行代码 header("Content-type:text/html; charsetgbk");的注释打开即可。 BUT但是吧!又出现了其他的乱码!但是搞完这个再把它给注释掉就行了…...
airflow是什么
Airflow 简介 Airflow是一个基于有向无环图(DAG)的可编程、调度和监控的工作流平台,它可以定义一组有依赖的任务,按照依赖依次执行。airflow提供了丰富的命令行工具用于系统管控,而其web管理界面同样也可以方便的管控调度任务,并…...
训练用于序列分类任务的 RoBERTa 模型的适配器
介绍 NLP当前的趋势包括下载和微调具有数百万甚至数十亿参数的预训练模型。然而,存储和共享如此大的训练模型非常耗时、缓慢且昂贵。这些限制阻碍了 RoBERTa 模型开发更多用途和适应性更强的 NLP 技术,该模型可以从多个任务中学习并针对多个任务进行学习;在本文中,我们将重…...
Linux之awk判断和循环
echo zhaoy 70 72 74 76 74 72 >> score.txt echo wangl 70 81 84 82 90 88 >> score.txt echo qiane 60 62 64 66 65 62 >> score.txt echo sunw 80 83 84 85 84 85 >> score.txt echo lixi 96 80 90 95 89 87 >> score.txt把下边的内容写入到s…...
Django入门
Day1 django环境安装 创建虚拟环境 # step1 创建虚拟环境 python3 -m venv datawhale_django # step2 mac进入虚拟环境 source ./datawhale_django/bin/activate # step3 退出虚拟环境 deactivate安装包 pip3 install django pip3 install djangorestframework pip3 …...
uniapp 格式化时间刚刚,几分钟前,几小时前,几天前…
效果如图: 根目录下新建utils文件夹,文件夹下新增js文件,文件内容: export const filters {dateTimeSub(data) {if (data undefined) {return;}// 传进来的data必须是日期格式,不能是时间戳//将字符串转换成时间格式…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
