欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Golang官方中的一致性哈希組件

 更新時間:2023年04月03日 10:28:24   作者:jxwu  
這篇文章主要為大家詳細介紹了Golang官方中的一致性哈希組件的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下

背景

在分布式緩存中,我們需要通過一組緩存節(jié)點來提高我們的緩存容量。比如我們有3個Redis節(jié)點:

最簡單的路由規(guī)則是我們計算`Key`的哈希值,然后取模計算目標節(jié)點,比如我們有5個Key,計算出以下哈希值及對應的目標節(jié)點:

Key的哈希值模3的余目標節(jié)點
101Redis1
41Redis1
60Redis0
82Redis2
150Redis0

如果我們這時候加入一個新的Redis節(jié)點,這時候路由變化如下:

Key的哈希值模3的余目標節(jié)點(舊)模4的余目標節(jié)點(新)是否變化
101Redis12Redis2
41Redis10Redis0
60Redis02Redis2
82Redis20Redis0
150Redis03Redis3

可以看到,我們只是加入了一個節(jié)點,就導致了所有Key的目標節(jié)點被改變了,這樣會導致大量緩存失效,這時請求可能就會都打到數(shù)據(jù)庫里,可能會導致數(shù)據(jù)庫被擊垮,這也就是緩存雪崩問題。

為了解決這個問題,一般我們會使用一致性哈希:

一致性哈希算法

一致性哈希算法經(jīng)常被用于請求路由中,在處理節(jié)點不變的情況下,它能夠把相同的請求路由到相同的處理節(jié)點上。同時還能在處理節(jié)點變動時,讓相同請求盡可能的打到原先相同的處理節(jié)點上。

原理

一致性哈希的原理是把處理節(jié)點通過哈希映射到一個哈希環(huán)上,哈希環(huán)可以理解為一個連續(xù)編號的循環(huán)鏈表,一般會使用長度為32位的哈希值,也就是哈希環(huán)可以映射2^32個值。如下圖所示:

圖中有三個Redis節(jié)點,通過哈希映射到環(huán)上的某個位置。Key也是通過哈希映射到環(huán)上的某個位置,然后向前尋找計算節(jié)點,第一個遇到的就是Key的目標節(jié)點。

這時候如果我們加入一個新的Redis3節(jié)點,可以看到只有Key4的路由改變了,其他的Key的路由都保持不變:

也就是我們新加入的處理節(jié)點,只會影響前面的處理節(jié)點的路由。

改進

可以看到上面的Redis節(jié)點在環(huán)上分布得并不均勻,這樣會導致每個節(jié)點的負載差距過大。為了讓Redis節(jié)點在環(huán)上分布得更加均勻,我們還可以再加入虛擬節(jié)點。讓一個Redis節(jié)點能夠映射到哈希環(huán)上的多個位置,這樣節(jié)點的分布會更加均勻。

可以看到因為每個Redis節(jié)點的映射位置變多了,因此更有可能會分布得更加均勻。圖里每個Redis節(jié)點只有兩個虛擬節(jié)點,主要是不太好畫,實際上我們可能會給每個Redis節(jié)點分配幾十個虛擬節(jié)點,這樣基本上就很均勻了。

實現(xiàn)方式

Golang官方的groupcache庫是一個嵌入式的分布式緩存庫,它里面有一個一致性哈希的實現(xiàn):https://github.com/golang/groupcache/blob/master/consistenthash/consistenthash_test.go

下面的代碼對這個實現(xiàn)有一些修改。

結(jié)構(gòu)和接口

第一件需要做的事情,就是我們需要把節(jié)點進行哈希得到一個整數(shù)值,這里默認是使用crc32計算一個字節(jié)序列的哈希值,當然也可以自己指定。

哈希環(huán)的結(jié)構(gòu)里面有一個ring數(shù)組,我們使用這個數(shù)組模擬一個哈希環(huán),當然數(shù)組并不會把最后一個元素鏈接到第一個元素,因此我們需要在邏輯上模擬。里面的nodes則是保存了哈希值到真實節(jié)點字符串的映射,這樣我們在ring數(shù)組里面找到對應的哈希值時才能反過來找到真實節(jié)點。

// 哈希函數(shù)
type Hash func(data []byte) uint32

// 哈希環(huán)
// 注意,非線程安全,業(yè)務需要自行加鎖
type HashRing struct {
	hash Hash
	// 每個真實節(jié)點的虛擬節(jié)點數(shù)量
	replicas int
	// 哈希環(huán),按照節(jié)點哈希值排序
	ring []int
	// 節(jié)點哈希值到真實節(jié)點字符串,哈希映射的逆過程
	nodes map[int]string
}

添加節(jié)點

可以看到這個方法是把節(jié)點添加到哈希環(huán)里面,這里會為每個節(jié)點創(chuàng)建虛擬節(jié)點,這樣可以分布的更加均勻。

當然這個方法存在一個問題,就是它沒有判斷加入的節(jié)點是否已經(jīng)存在,這樣可能會導致Ring上面存在相同的節(jié)點。

// 添加新節(jié)點到哈希環(huán)
// 注意,如果加入的節(jié)點已經(jīng)存在,會導致哈希環(huán)上面重復,如果不確定是否存在請使用Reset
func (m *HashRing) Add(nodes ...string) {
	for _, node := range nodes {
		// 每個節(jié)點創(chuàng)建多個虛擬節(jié)點
		for i := 0; i < m.replicas; i++ {
			// 每個虛擬節(jié)點計算哈希值
			hash := int(m.hash([]byte(strconv.Itoa(i) + node)))
			// 加入哈希環(huán)
			m.ring = append(m.ring, hash)
			// 哈希值到真實節(jié)點字符串映射
			m.nodes[hash] = node
		}
	}
	// 哈希環(huán)排序
	sort.Ints(m.ring)
}

重置節(jié)點

為了解決上面的問題,我們額外實現(xiàn)了一個重置方法,也就是先清空哈希環(huán),再添加。當然這樣就必須每次都指定完整的節(jié)點列表。

// 先清空哈希環(huán)再設置
func (r *HashRing) Reset(nodes ...string) {
	// 先清空
	r.ring = nil
	r.nodes = map[int]string{}
	// 再重置
	r.Add(nodes...)
}

獲取Key對應的節(jié)點

這個方法的功能是查詢Key應該路由到哪個節(jié)點,也就是計算Key的哈希值,然后找到哈希值對應的處理節(jié)點(這里需要考慮ring數(shù)組邏輯上是一個環(huán)),然后再根據(jù)這個哈希值去尋找真實處理節(jié)點的字符串。

// 獲取Key對應的節(jié)點
func (r *HashRing) Get(key string) string {
	// 如果哈希環(huán)位空,則直接返回
	if r.Empty() {
		return ""
	}

	// 計算Key哈希值
	hash := int(r.hash([]byte(key)))

	// 二分查找第一個大于等于Key哈希值的節(jié)點
	idx := sort.Search(len(r.ring), func(i int) bool { return r.ring[i] >= hash })

	// 這里是特殊情況,也就是數(shù)組沒有大于等于Key哈希值的節(jié)點
	// 但是邏輯上這是一個環(huán),因此第一個節(jié)點就是目標節(jié)點
	if idx == len(r.ring) {
		idx = 0
	}

	// 返回哈希值對應的真實節(jié)點字符串
	return r.nodes[r.ring[idx]]
}

總結(jié)

這個一致性哈希的實現(xiàn)非常簡單,功能上也非常簡單(官方的實現(xiàn)甚至沒有Reset()方法),可以通過這個實現(xiàn)理解一致性哈希的原理。也可以直接在業(yè)務中使用它,如果功能不夠再根據(jù)需求進行擴展。

上面代碼地址:https://github.com/jiaxwu/gommon/blob/main/consistenthash/consistenthash.go

到此這篇關(guān)于詳解Golang官方中的一致性哈希組件的文章就介紹到這了,更多相關(guān)Golang一致性哈希內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Golang中omitempty關(guān)鍵字的具體實現(xiàn)

    Golang中omitempty關(guān)鍵字的具體實現(xiàn)

    本文主要介紹了Golang中omitempty關(guān)鍵字的具體實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • go語言go?func(){select{}}()的用法

    go語言go?func(){select{}}()的用法

    本文主要介紹了go語言go?func(){select{}}()的用法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-02-02
  • Go語言學習之new函數(shù)的用法詳解

    Go語言學習之new函數(shù)的用法詳解

    這篇文章主要為大家詳細介紹了Go語言中new()函數(shù)的相關(guān)知識以及具體用法,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的小伙伴可以了解一下
    2023-05-05
  • go語言題解LeetCode228匯總區(qū)間示例詳解

    go語言題解LeetCode228匯總區(qū)間示例詳解

    這篇文章主要為大家介紹了go語言題解LeetCode228匯總區(qū)間示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-12-12
  • golang中tar壓縮和解壓文件詳情

    golang中tar壓縮和解壓文件詳情

    這篇文章主要給大家介紹golang中tar壓縮和解壓文件,文章以查看官方文檔自帶的給大家演習一下golang的archive/tar壓縮和解壓功能,需要的朋友可以參考一下
    2021-11-11
  • 詳解Golang中SQLX庫的高級操作

    詳解Golang中SQLX庫的高級操作

    sqlx是Golang中的一個知名三方庫,其為Go標準庫database/sql提供了一組擴展支持,下面就來和大家分享一下SQLX庫的高級操作吧,希望對大家有所幫助
    2023-06-06
  • 使用Viper處理Go應用程序的配置方法

    使用Viper處理Go應用程序的配置方法

    Viper是一個應用程序配置解決方案,用于Go應用程序,它支持JSON、TOML、YAML、HCL、envfile和Java properties配置文件格式,這篇文章主要介紹了使用Viper處理Go應用程序的配置,需要的朋友可以參考下
    2023-09-09
  • Go快速開發(fā)一個RESTful API服務

    Go快速開發(fā)一個RESTful API服務

    這篇文章主要為大家介紹了Go快速開發(fā)一個RESTful API服務,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-06-06
  • Go 語言中 20 個占位符的整理

    Go 語言中 20 個占位符的整理

    這篇文章主要介紹了Go 語言中 20 個占位符的整理,看完本篇文章講學會什么是占位符?哪些函數(shù)支持?如何使用占位符?不同的占位符的作用?配合占位符的幾個標記符號用法?
    2021-10-10
  • Go語言中節(jié)省內(nèi)存技巧方法示例

    Go語言中節(jié)省內(nèi)存技巧方法示例

    這篇文章主要為大家介紹了Go語言中節(jié)省內(nèi)存技巧方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-01-01

最新評論