成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go-Zero 是如何做路由管理的?

開發(fā) 前端
對于 Web 框架來說,路由管理是必不可少的一部分,那么本文就來探討一下 go-zero 的路由管理是怎么做的,具體采用了哪種技術(shù)方案。

go-zero 是一個(gè)微服務(wù)框架,包含了 web 和 rpc 兩大部分。

而對于 web 框架來說,路由管理是必不可少的一部分,那么本文就來探討一下 go-zero 的路由管理是怎么做的,具體采用了哪種技術(shù)方案。

路由管理方案

路由管理方案有很多種,具體應(yīng)該如何選擇,應(yīng)該根據(jù)使用場景,以及實(shí)現(xiàn)的難易程度做綜合分析,下面介紹常見的三種方案。

注意這里只是做一個(gè)簡單的概括性對比。

標(biāo)準(zhǔn)庫方案

最簡單的方案就是直接使用 map[string]func() 作為路由的數(shù)據(jù)結(jié)構(gòu),鍵為具體的路由,值為具體的處理方法。

// 路由管理數(shù)據(jù)結(jié)構(gòu)

type ServeMux struct {
    mu    sync.RWMutex          // 對象操作讀寫鎖
    m     map[string]muxEntry   // 存儲路由映射關(guān)系
}

這種方案優(yōu)點(diǎn)就是實(shí)現(xiàn)簡單,性能較高;缺點(diǎn)也很明顯,占用內(nèi)存更高,更重要的是不夠靈活。

Trie Tree

Trie Tree 也稱為字典樹或前綴樹,是一種用于高效存儲和檢索、用于從某個(gè)集合中查到某個(gè)特定 key 的數(shù)據(jù)結(jié)構(gòu)。

圖片

Trie Tree 時(shí)間復(fù)雜度低,和一般的樹形數(shù)據(jù)結(jié)構(gòu)相比,Trie Tree 擁有更快的前綴搜索和查詢性能。

和查詢時(shí)間復(fù)雜度為 O(1) 常數(shù)的哈希算法相比,Trie Tree 支持前綴搜索,并且可以節(jié)省哈希函數(shù)的計(jì)算開銷和避免哈希值碰撞的情況。

最后,Trie Tree 還支持對關(guān)鍵字進(jìn)行字典排序。

Radix Tree

Radix Tree(基數(shù)樹)是一種特殊的數(shù)據(jù)結(jié)構(gòu),用于高效地存儲和搜索字符串鍵值對,它是一種基于前綴的樹狀結(jié)構(gòu),通過將相同前綴的鍵值對合并在一起來減少存儲空間的使用。

圖片

Radix Tree 通過合并公共前綴來降低存儲空間的開銷,避免了 Trie Tree 字符串過長和字符集過大時(shí)導(dǎo)致的存儲空間過多問題,同時(shí)公共前綴優(yōu)化了路徑層數(shù),提升了插入、查詢、刪除等操作效率。

比如 Gin 框架使用的開源組件 HttpRouter 就是采用這個(gè)方案。

go-zero 路由規(guī)則

在使用 go-zero 開發(fā)項(xiàng)目時(shí),定義路由需要遵守如下規(guī)則:

  1. 路由必須以 / 開頭
  2. 路由節(jié)點(diǎn)必須以 / 分隔
  3. 路由節(jié)點(diǎn)中可以包含 :,但是 : 必須是路由節(jié)點(diǎn)的第一個(gè)字符,: 后面的節(jié)點(diǎn)值必須要在結(jié)請求體中有 path tag 聲明,用于接收路由參數(shù)
  4. 路由節(jié)點(diǎn)可以包含字母、數(shù)字、下劃線、中劃線

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

go-zero 源碼實(shí)現(xiàn)

首先需要說明的是,底層數(shù)據(jù)結(jié)構(gòu)使用的是二叉搜索樹,還不是很了解的同學(xué)可以看這篇文章:使用 Go 語言實(shí)現(xiàn)二叉搜索樹。

節(jié)點(diǎn)定義

先看一下節(jié)點(diǎn)定義:

// core/search/tree.go

const (
    colon = ':'
    slash = '/'
)

type (
    // 節(jié)點(diǎn)
    node struct {
        item     interface{}
        children [2]map[string]*node
    }

    // A Tree is a search tree.
    Tree struct {
        root *node
    }
)

重點(diǎn)說一下 children,它是一個(gè)包含兩個(gè)元素的數(shù)組,元素 0 存正常路由鍵,元素 1 存以 : 開頭的路由鍵,這些是 url 中的變量,到時(shí)候需要替換成實(shí)際值。

舉一個(gè)例子,有這樣一個(gè)路由 /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
    }

    // 把去掉 / 的路由作為參數(shù)傳入
    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
    }

    // 繼續(xù)判斷,看看是不是有多個(gè) /
    if route[0] == slash {
        return errDupSlash
    }

    for i := range route {
        // 判斷是不是 /,目的就是去處兩個(gè) / 之間的內(nèi)容
        if route[i] != slash {
            continue
        }

        token := route[:i]
        
        // 看看有沒有子節(jié)點(diǎn),如果有子節(jié)點(diǎn),就在子節(jié)點(diǎn)下面繼續(xù)添加
        children := nd.getChildren(token)
        if child, ok := children[token]; ok {
            if child != nil {
                return add(child, route[i+1:], item)
            }

            return errInvalidState
        }

        // 沒有子節(jié)點(diǎn),那么新建一個(gè)
        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
}

主要部分代碼都已經(jīng)加了注釋,其實(shí)這個(gè)過程就是樹的構(gòu)建,如果讀過之前那篇文章,那這里還是比較好理解的。

路由查找

先來看一段 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,
    }
}

這里有兩個(gè)參數(shù):

  • pat:路由樹中存儲的路由。
  • token:實(shí)際請求的路由,可能包含參數(shù)值。

還是剛才的例子 /api/:user,如果是 api,沒有以 : 開頭,那就不會走 if 邏輯。

接下來匹配 :user 部分,如果實(shí)際請求的 url 是 /api/zhangsan,那么會將 user 作為 key,zhangsan 作為 value 保存到結(jié)果中。

下面是搜索查找代碼:

// 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 中有參數(shù),會把鍵值對保存到結(jié)果中
            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
    })
}

以上就是路由管理的大部分代碼,整個(gè)文件也就 200 多行,邏輯也并不復(fù)雜,通讀之后還是很有收獲的。

大家如果感興趣的話,可以找到項(xiàng)目更詳細(xì)地閱讀。也可以關(guān)注我,接下來還會分析其他模塊的源碼。

責(zé)任編輯:姜華 來源: AlwaysBeta
相關(guān)推薦

2023-08-10 08:00:42

令牌限流器計(jì)數(shù)器

2023-08-28 08:00:45

2025-05-26 04:00:00

2024-04-28 14:46:55

gozero微服務(wù)技巧

2012-03-12 16:42:54

測試

2024-01-15 07:42:37

Figma協(xié)同編輯算法

2024-04-22 08:26:37

協(xié)同編輯FigmaOT 算法

2011-08-01 09:08:49

程序員

2019-11-06 09:39:42

云成本企業(yè)云計(jì)算

2015-08-20 11:09:53

準(zhǔn)入控制盈高

2022-12-07 11:21:30

Reactdiff

2017-11-16 21:21:18

DevOps測試軟件開發(fā)

2021-07-06 10:03:05

軟件開發(fā) 技術(shù)

2021-05-13 08:00:00

軟件測試程序IT

2017-11-23 19:14:00

CRM數(shù)字化軟件開發(fā)

2019-09-15 14:07:49

2015-07-30 11:21:16

代碼審查

2022-08-03 09:11:31

React性能優(yōu)化

2022-08-29 08:08:58

SQLOracleCPU

2023-01-18 23:52:07

RTA用戶粒度運(yùn)營
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 中文字幕欧美一区 | 国产精品视频一区二区三 | 视频一区二区在线观看 | 国产精品福利在线观看 | 毛片免费视频 | 精品成人一区二区 | 日韩欧美手机在线 | 红色av社区 | 国产精品中文字幕在线观看 | 欧美视频网 | 久久国产精品一区二区三区 | 久久久久国 | 精品在线观看一区二区 | 一区二区三区亚洲 | 羞羞视频一区二区 | 日韩午夜在线观看 | 精品一区二区三区日本 | 国产精品亚洲成在人线 | 成人在线免费网站 | www.天天操| 久久精品福利 | 国产日韩欧美在线一区 | 久久精品国产一区二区电影 | 日韩在线一区二区 | 91麻豆产精品久久久久久 | 久久亚洲综合 | 天天宗合网 | 国产视频久久 | 麻豆hd| 久久久久久国模大尺度人体 | ww 255hh 在线观看 | 91视频久久久久 | 亚洲人人 | 精品免费国产一区二区三区四区介绍 | 中文字幕一区二区三区四区五区 | 蜜桃臀av一区二区三区 | 亚洲a视频| 国产精品日韩一区二区 | 欧美成人激情视频 | 香蕉久久久久久 | 国产91亚洲精品 |