go-zero 是如何做路由管理的?

来源:https://www.cnblogs.com/alwaysbeta/archive/2023/08/09/17618125.html
-Advertisement-
Play Games

**原文鏈接:** [go-zero 是如何做路由管理的?](https://mp.weixin.qq.com/s/uTJ1En-BXiLvH45xx0eFsA) go-zero 是一個微服務框架,包含了 web 和 rpc 兩大部分。 而對於 web 框架來說,路由管理是必不可少的一部分,那麼本文 ...


原文鏈接: 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 開發項目時,定義路由需要遵守如下規則:

  1. 路由必須以 / 開頭
  2. 路由節點必須以 / 分隔
  3. 路由節點中可以包含 :,但是 : 必須是路由節點的第一個字元,: 後面的節點值必須要在結請求體中有 path tag 聲明,用於接收路由參數
  4. 路由節點可以包含字母、數字、下劃線、中劃線

接下來就讓我們深入到源碼層面,相信看過源碼之後,你就會更懂這些規則的意義了。

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(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 = item
        return 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] = child
        return 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 作為 keyzhangsan 作為 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 Result
    ok := 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.item
        return 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.item
            if r.named {
                addParam(result, r.key, r.value)
            }

            return true
        }

        return false
    })
}

以上就是路由管理的大部分代碼,整個文件也就 200 多行,邏輯也並不複雜,通讀之後還是很有收穫的。

大家如果感興趣的話,可以找到項目更詳細地閱讀。也可以關註我,接下來還會分析其他模塊的源碼。

以上就是本文的全部內容,如果覺得還不錯的話歡迎點贊轉發關註,感謝支持。


推薦閱讀:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Avue 是一個基於Element-plus低代碼前端框架,它使用JSON 配置來生成頁面,可以減少頁面開發工作量,極大提升效率; 雖然Avue官網上面都有這些配置說明,但是如果剛開始接觸不熟悉框架的話需要很久才找到自己需要的參數配置,為了方便自己今後查找使用,現將一些開發中常用的配置梳理在下 一、 ...
  • 這幾天在學vue3, 用Element-plus 加 vue3 搭了個框架,在這裡記錄一下項目搭建中遇到的問題。 1、使用 Element-plus 的 icon 圖標,顯示不出來 首先,用命令行中安裝 Element-plus 的圖標: npm install @element-plus/icon ...
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:景明 ## 升級背景 目前公司產品有關 react 的工具版本普遍較低,其中 react router ...
  • 在現代的 Web 應用程式中,頁面訪問攔截是非常重要的一個方面。它可以用於確保用戶只能訪問他們有許可權的頁面,提高應用程式的安全性和用戶體驗。本篇博文將介紹如何使用 Vue 框架來實現頁面訪問攔截的功能。 ...
  • 本文主要做推薦系統淺析,主要介紹推薦系統的定義,推薦系統的基礎框架,簡單介紹設計推薦的相關方法以及架構。適用於部分對推薦系統感興趣的同學以及有相關基礎的同學,本人水平有限,歡迎大家指正。 ...
  • 不知道大家在開發的時候,有沒有想過(遇到)這些問題: 1、大家都是按需要開發,都是一個職級的同事,為什麼有些人的思路就很清晰,代碼也很整潔、易懂;而自己開發,往往不知道怎麼下手設計,寫完了也是bug一堆,codeReview的時候更是頻頻被懟... 2、感覺每天都是CURD,寫重覆的代碼,做類似的需... ...
  • ## 10.1、概念 - AOP(Aspect Oriented Programming)是一種設計思想,是軟體設計領域中的面向切麵編程 - AOP是面向對象編程(OOP)的一種補充和完善,OOP是縱向繼承機制,AOP是橫向抽取機制 - AOP能通過預編譯方式和運行期動態代理方式,實現在不修改源代碼 ...
  • 逆向過程分析與js代碼扣取 請求頭U-Sign數據 通過瀏覽器開發者工具可以看到返回數據的介面/youzy.dms.basiclib.api.college.query 添加XHR斷點刷新瀏覽器會自動進入斷點 查看具體請求數據,將斷點定位到r = r.then(t.shift(), t.shift( ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...