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

Golang使用Zookeeper實(shí)現(xiàn)分布式鎖

 更新時間:2024年02月22日 09:32:13   作者:awk  
分布式鎖是一種在分布式系統(tǒng)中用于控制并發(fā)訪問的機(jī)制,ZooKeeper?和?Redis?都是常用的實(shí)現(xiàn)分布式鎖的工具,本文就來使用Zookeeper實(shí)現(xiàn)分布式鎖,希望對大家有所幫助

什么是分布式鎖

分布式鎖是一種在分布式系統(tǒng)中用于控制并發(fā)訪問的機(jī)制。在分布式系統(tǒng)中,多個客戶端可能會同時對同一個資源進(jìn)行訪問,這可能導(dǎo)致數(shù)據(jù)不一致的問題。分布式鎖的作用是確保同一時刻只有一個客戶端能夠?qū)δ硞€資源進(jìn)行訪問,從而避免數(shù)據(jù)不一致的問題。

分布式鎖的實(shí)現(xiàn)通常依賴于一些具有分布式特性的技術(shù),如 ZooKeeper、Redis、數(shù)據(jù)庫等。這些技術(shù)提供了在分布式環(huán)境中實(shí)現(xiàn)互斥訪問的機(jī)制,使得多個客戶端在競爭同一個資源時能夠有序地進(jìn)行訪問。

通過使用分布式鎖,可以確保分布式系統(tǒng)中的數(shù)據(jù)一致性和并發(fā)訪問的有序性,從而提高系統(tǒng)的可靠性和穩(wěn)定性。

Zookeeper 與 Redis 的分布式鎖對比

ZooKeeper 和 Redis 都是常用的實(shí)現(xiàn)分布式鎖的工具,但它們在實(shí)現(xiàn)方式、特性、適用場景等方面有一些區(qū)別。以下是 ZooKeeper 分布式鎖與 Redis 分布式鎖的比較:

實(shí)現(xiàn)方式

  • ZooKeeper 分布式鎖主要依賴于其臨時節(jié)點(diǎn)和順序節(jié)點(diǎn)的特性??蛻舳嗽?nbsp;ZooKeeper 中創(chuàng)建臨時順序節(jié)點(diǎn),并通過監(jiān)聽機(jī)制來實(shí)現(xiàn)鎖的獲取和釋放。
  • Redis 分布式鎖通常使用 SETNX(set if not exists) 命令來嘗試設(shè)置一個 key,如果設(shè)置成功則獲取到鎖。也可以通過設(shè)置過期時間和輪詢機(jī)制來防止死鎖和提高鎖的可靠性。

特性

  • ZooKeeper 分布式鎖具有嚴(yán)格的順序性和公平性,保證了鎖的獲取順序與請求順序一致,避免了饑餓問題。
  • Redis 分布式鎖的性能通常更高,因?yàn)樗且粋€內(nèi)存數(shù)據(jù)庫,讀寫速度非???。然而,它可能存在不公平性和死鎖的風(fēng)險,需要額外的機(jī)制來避免這些問題。

適用場景

  • ZooKeeper 分布式鎖適用于對順序性和公平性要求較高的場景,如分布式調(diào)度系統(tǒng)、分布式事務(wù)等。
  • Redis 分布式鎖適用于對性能要求較高的場景,如緩存系統(tǒng)、高并發(fā)訪問的系統(tǒng)等。Redis 的高性能使得它在處理大量并發(fā)請求時具有優(yōu)勢。

可靠性

  • ZooKeeper 分布式鎖具有較高的可靠性,因?yàn)樗蕾囉?nbsp;ZooKeeper 的高可用性和強(qiáng)一致性保證。即使部分節(jié)點(diǎn)宕機(jī),ZooKeeper 也能保證鎖的正確性和一致性。
  • Redis 分布式鎖的可靠性取決于其實(shí)現(xiàn)方式和配置。在某些情況下,如 Redis 節(jié)點(diǎn)宕機(jī)或網(wǎng)絡(luò)故障,可能會導(dǎo)致鎖失效或死鎖。因此,需要合理配置 Redis 和采取額外的措施來提高鎖的可靠性。

綜上所述,ZooKeeper 分布式鎖和 Redis 分布式鎖各有優(yōu)缺點(diǎn),具體選擇哪種方式取決于實(shí)際業(yè)務(wù)場景和需求。在需要保證順序性和公平性的場景下,ZooKeeper 分布式鎖可能更適合;而在需要高性能和快速響應(yīng)的場景下,Redis 分布式鎖可能更合適。

為什么 Zookeeper 可以實(shí)現(xiàn)分布式鎖

ZooKeeper 可以實(shí)現(xiàn)分布式鎖,主要得益于其以下幾個特性:

  • 臨時節(jié)點(diǎn):ZooKeeper 支持創(chuàng)建臨時節(jié)點(diǎn),這些節(jié)點(diǎn)在創(chuàng)建它們的客戶端會話結(jié)束時會被自動刪除。這種特性使得 ZooKeeper 的節(jié)點(diǎn)具有生命周期,可以隨著客戶端的存活而存在,客戶端斷開連接后自動消失,非常適合作為鎖的標(biāo)識。
  • 順序節(jié)點(diǎn):ZooKeeper 的另一個重要特性是支持創(chuàng)建順序節(jié)點(diǎn)。在創(chuàng)建節(jié)點(diǎn)時,ZooKeeper 會在節(jié)點(diǎn)名稱后自動添加一個自增的數(shù)字,確保節(jié)點(diǎn)在 ZNode 中的順序性。這個特性使得 ZooKeeper 可以實(shí)現(xiàn)分布式鎖中的公平鎖,按照請求的順序分配鎖。
  • Watcher 機(jī)制:ZooKeeper 還提供了 Watcher 機(jī)制,允許客戶端在指定的節(jié)點(diǎn)上注冊監(jiān)聽事件。當(dāng)這些事件觸發(fā)時,ZooKeeper 服務(wù)端會將事件通知到感興趣的客戶端,從而允許客戶端做出相應(yīng)的措施。這種機(jī)制使得 ZooKeeper 的分布式鎖可以實(shí)現(xiàn)阻塞鎖,即當(dāng)客戶端嘗試獲取已經(jīng)被其他客戶端持有的鎖時,它可以等待鎖被釋放。

基于以上特性,ZooKeeper 可以實(shí)現(xiàn)分布式鎖。具體實(shí)現(xiàn)流程如下:

  • 客戶端需要獲取鎖時,在 ZooKeeper 中創(chuàng)建一個臨時順序節(jié)點(diǎn)作為鎖標(biāo)識。
  • 客戶端判斷自己創(chuàng)建的節(jié)點(diǎn)是否是所有臨時順序節(jié)點(diǎn)中序號最小的。如果是,則客戶端獲得鎖;如果不是,則客戶端監(jiān)聽序號比它小的那個節(jié)點(diǎn)。
  • 當(dāng)被監(jiān)聽的節(jié)點(diǎn)被刪除時(即持有鎖的客戶端釋放鎖),監(jiān)聽者會收到通知,然后重新判斷自己是否獲得鎖。
  • 當(dāng)客戶端釋放鎖時,只需要將會話關(guān)閉,臨時節(jié)點(diǎn)就會被自動刪除,從而釋放了鎖。

因此,ZooKeeper 通過其臨時節(jié)點(diǎn)、順序節(jié)點(diǎn)和 Watcher 機(jī)制等特性,實(shí)現(xiàn)了分布式鎖的功能。

使用 Golang 實(shí)現(xiàn) Zookeeper 分布式鎖

下面我們通過一個簡單的例子來演示如何使用 Golang 實(shí)現(xiàn) ZooKeeper 分布式鎖。

創(chuàng)建 zookeeper 客戶端連接

import "github.com/go-zookeeper/zk"

func client() *zk.Conn {
    // 默認(rèn)端口 2181
 c, _, err := zk.Connect([]string{"192.168.2.168"}, time.Second)
 if err != nil {
  panic(err)
 }
 return c
}

創(chuàng)建父節(jié)點(diǎn) - /lock

我們可以在獲取鎖之前,先創(chuàng)建一個父節(jié)點(diǎn),用于存放鎖節(jié)點(diǎn)。

type Lock struct {
 c *zk.Conn
}

// 父節(jié)點(diǎn) /lock 不存在的時候進(jìn)行創(chuàng)建
func NewLock() *Lock {
 c := client()
 e, _, err := c.Exists("/lock")
 if err != nil {
  panic(err)
 }
 if !e {
  _, err := c.Create("/lock", []byte(""), 0, zk.WorldACL(zk.PermAll))
  if err != nil {
   panic(err)
  }
 }

 return &Lock{c: c}
}

獲取鎖

在 Zookeeper 分布式鎖實(shí)現(xiàn)中,獲取鎖的過程實(shí)際上就是創(chuàng)建一個臨時順序節(jié)點(diǎn),并判斷自己是否是所有臨時順序節(jié)點(diǎn)中序號最小的。

獲取鎖的關(guān)鍵是:

  • 創(chuàng)建的需要是臨時節(jié)點(diǎn)
  • 創(chuàng)建的需要是順序節(jié)點(diǎn)

具體創(chuàng)建代碼如下:

p, err := l.c.Create("/lock/lock", []byte(""), zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll))

其中 zk.FlagEphemeral 表示創(chuàng)建的是臨時節(jié)點(diǎn),zk.FlagSequence 表示創(chuàng)建的是順序節(jié)點(diǎn)。

判斷當(dāng)前創(chuàng)建的節(jié)點(diǎn)是否是最小節(jié)點(diǎn)

具體步驟如下:

  • 通過 l.c.Children("/lock") 獲取 /lock 下的所有子節(jié)點(diǎn)
  • 對所有子節(jié)點(diǎn)進(jìn)行排序
  • 判斷當(dāng)前創(chuàng)建的節(jié)點(diǎn)是否是最小節(jié)點(diǎn)
  • 如果是最小節(jié)點(diǎn),則獲取到鎖,函數(shù)調(diào)用返回;如果不是,則監(jiān)聽前一個節(jié)點(diǎn)(這會導(dǎo)致函數(shù)調(diào)用阻塞)
childs, _, err := l.c.Children("/lock")
if err != nil {
    return "", err
}

// childs 是無序的,所以需要排序,以便找到當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn),然后監(jiān)聽前一個節(jié)點(diǎn)
sort.Strings(childs)

// 成功獲取到鎖
p1 := strings.Replace(p, "/lock/", "", 1)
if childs[0] == p1 {
    return p, nil
}

不是最小節(jié)點(diǎn),監(jiān)聽前一個節(jié)點(diǎn)

具體步驟如下:

  • 通過 sort.SearchStrings 找到當(dāng)前節(jié)點(diǎn)在所有子節(jié)點(diǎn)中的位置
  • 調(diào)用 l.c.ExistsW 判斷前一個節(jié)點(diǎn)是否依然存在(鎖有可能在調(diào)用 ExistsW 之前已經(jīng)被釋放了),如果不存在則獲取到鎖
  • 如果前一個節(jié)點(diǎn)依然存在,則阻塞等待前一個節(jié)點(diǎn)被刪除
// 監(jiān)聽鎖,等待鎖釋放
// 也就是說,如果當(dāng)前節(jié)點(diǎn)不是最小的節(jié)點(diǎn),那么就監(jiān)聽前一個節(jié)點(diǎn)
// 一旦前一個節(jié)點(diǎn)被刪除,那么就可以獲取到鎖
index := sort.SearchStrings(childs, p1)
b, _, ev, err := l.c.ExistsW("/lock/" + childs[index-1])
if err != nil {
    return "", err
}

// 在調(diào)用 ExistsW 之后,前一個節(jié)點(diǎn)已經(jīng)被刪除
if !b {
    return p, nil
}

// 等待前一個節(jié)點(diǎn)被刪除
<-ev

return p, nil

在調(diào)用 ExistsW 的時候,如果前一個節(jié)點(diǎn)已經(jīng)被刪除,那么 ExistsW 會立即返回 false,否則我們可以通過 ExistsW 返回的第三個參數(shù) ev 來等待前一個節(jié)點(diǎn)被刪除。

在 <-ev 處,我們通過 <-ev 來等待前一個節(jié)點(diǎn)被刪除,一旦前一個節(jié)點(diǎn)被刪除,ev 會收到一個事件,這個時候我們就可以獲取到鎖了。

釋放鎖

如果調(diào)用 Lock 可以成功獲取到鎖,我們會返回當(dāng)前創(chuàng)建的節(jié)點(diǎn)的路徑,我們可以通過這個路徑來釋放鎖。

func (l *Lock) Unlock(p string) error {
 return l.c.Delete(p, -1)
}

完整代碼

package main

import (
 "github.com/go-zookeeper/zk"
 "sort"
 "strings"
 "time"
)

func client() *zk.Conn {
 c, _, err := zk.Connect([]string{"192.168.2.168"}, time.Second) //*10)
 if err != nil {
  panic(err)
 }
 return c
}

type Lock struct {
 c *zk.Conn
}

func NewLock() *Lock {
 c := client()
 e, _, err := c.Exists("/lock")
 if err != nil {
  panic(err)
 }
 if !e {
  _, err := c.Create("/lock", []byte(""), 0, zk.WorldACL(zk.PermAll))
  if err != nil {
   panic(err)
  }
 }

 return &Lock{c: c}
}

func (l *Lock) Lock() (string, error) {
 p, err := l.c.Create("/lock/lock", []byte(""), zk.FlagEphemeral|zk.FlagSequence, zk.WorldACL(zk.PermAll))
 if err != nil {
  return "", err
 }
 childs, _, err := l.c.Children("/lock")
 if err != nil {
  return "", err
 }

 // childs 是無序的,所以需要排序,以便找到當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn),然后監(jiān)聽前一個節(jié)點(diǎn)
 sort.Strings(childs)

 // 成功獲取到鎖
 p1 := strings.Replace(p, "/lock/", "", 1)
 if childs[0] == p1 {
  return p, nil
 }

 // 監(jiān)聽鎖,等待鎖釋放
 // 也就是說,如果當(dāng)前節(jié)點(diǎn)不是最小的節(jié)點(diǎn),那么就監(jiān)聽前一個節(jié)點(diǎn)
 // 一旦前一個節(jié)點(diǎn)被刪除,那么就可以獲取到鎖
 index := sort.SearchStrings(childs, p1)
 b, _, ev, err := l.c.ExistsW("/lock/" + childs[index-1])
 if err != nil {
  return "", err
 }

 // 在調(diào)用 ExistsW 之后,前一個節(jié)點(diǎn)已經(jīng)被刪除
 if !b {
  return p, nil
 }

 // 等待前一個節(jié)點(diǎn)被刪除
 <-ev

 return p, nil
}

func (l *Lock) Unlock(p string) error {
 return l.c.Delete(p, -1)
}

測試代碼

下面這個例子模擬了分布式的 counter 操作,我們通過 ZooKeeper 分布式鎖來保證 counter 的原子性。

當(dāng)然這個例子只是為了說明 ZooKeeper 分布式鎖的使用,實(shí)際上下面的功能通過 redis 自身提供的 incr 就可以實(shí)現(xiàn),不需要這么復(fù)雜。

package main

import (
 "context"
 "fmt"
 "github.com/redis/go-redis/v9"
 "sync"
)

func main() {
 var count = 1000
 var wg sync.WaitGroup
 wg.Add(count)

 l := NewLock()
    // 創(chuàng)建 redis 客戶端連接
 redisClient = redis.NewClient(&redis.Options{
  Addr:     "192.168.2.168:6379",
  Password: "", // no password set
  DB:       0,  // use default DB
 })

 for i := 0; i < count; i++ {
  go func(i1 int) {
   defer wg.Done()

    // 獲取 Zookeeper 分布式鎖
   p, err := l.Lock()
   if err != nil {
    return
   }
   // 成功獲取到了分布式鎖:
   // 1. 從 redis 獲取 zk_counter 的值
   // 2. 然后對 zk_counter 進(jìn)行 +1 操作
   // 3. 最后將 zk_counter 的值寫回 redis
   cmd := redisClient.Get(context.Background(), "zk_counter")
   i2, _ := cmd.Int()
   i2++
   redisClient.Set(context.Background(), "zk_counter", i2, 0)
   // 釋放分布式鎖
   err = l.Unlock(p)
   if err != nil {
    println(fmt.Errorf("unlock error: %v", err))
    return
   }
  }(i)
 }

 wg.Wait()

 l.c.Close()
}

我們需要將測試程序放到不同的機(jī)器上運(yùn)行,這樣才能模擬分布式環(huán)境。

總結(jié)

最后,再來回顧一下本文內(nèi)容:

  • sync.Mutex 這種鎖只能保證單進(jìn)程內(nèi)的并發(fā)安全,無法保證分布式環(huán)境下的并發(fā)安全。
  • 使用 Zookeeper 和 Redis 都能實(shí)現(xiàn)分布式鎖,但是 Zookeeper 可以保證順序性和公平性,而 Redis 可以保證高性能。
  • Zookeeper 通過其臨時節(jié)點(diǎn)、順序節(jié)點(diǎn)和 Watcher 機(jī)制等特性,實(shí)現(xiàn)了分布式鎖的功能。

以上就是Golang使用Zookeeper實(shí)現(xiàn)分布式鎖的詳細(xì)內(nèi)容,更多關(guān)于Go Zookeeper分布式鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Go語言中命令行參數(shù)解析工具pflag的使用指南

    Go語言中命令行參數(shù)解析工具pflag的使用指南

    在使用?Go?進(jìn)行開發(fā)的過程中,命令行參數(shù)解析是我們經(jīng)常遇到的需求,于是?Go?社區(qū)中出現(xiàn)了一個叫?pflag?的第三方包,功能更加全面且足夠強(qiáng)大,下面我們就來看看它的具體使用吧
    2024-11-11
  • 詳解Go語言RESTful JSON API創(chuàng)建

    詳解Go語言RESTful JSON API創(chuàng)建

    這篇文章主要介紹了詳解Go語言RESTful JSON API創(chuàng)建,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • Go語言實(shí)現(xiàn)AzDG可逆加密算法實(shí)例

    Go語言實(shí)現(xiàn)AzDG可逆加密算法實(shí)例

    這篇文章主要介紹了Go語言實(shí)現(xiàn)AzDG可逆加密算法,實(shí)例分析了AzDG可逆加密算法的實(shí)現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-02-02
  • golang?Gin上傳文件返回前端及中間件實(shí)現(xiàn)示例

    golang?Gin上傳文件返回前端及中間件實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了golang?Gin上傳文件返回前端及中間件實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • go語言實(shí)現(xiàn)mqtt協(xié)議的實(shí)踐

    go語言實(shí)現(xiàn)mqtt協(xié)議的實(shí)踐

    MQTT是一個基于客戶端-服務(wù)器的消息發(fā)布/訂閱傳輸協(xié)議。本文主要介紹了go語言實(shí)現(xiàn)mqtt協(xié)議的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • GoLang 中的隨機(jī)數(shù)的示例代碼

    GoLang 中的隨機(jī)數(shù)的示例代碼

    本篇文章主要介紹了GoLang 中的隨機(jī)數(shù)的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • golang實(shí)現(xiàn)簡易的分布式系統(tǒng)方法

    golang實(shí)現(xiàn)簡易的分布式系統(tǒng)方法

    這篇文章主要介紹了golang實(shí)現(xiàn)簡易的分布式系統(tǒng)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-10-10
  • golang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • 一文弄懂用Go實(shí)現(xiàn)MCP服務(wù)的示例代碼

    一文弄懂用Go實(shí)現(xiàn)MCP服務(wù)的示例代碼

    本文主要介紹了一文弄懂用Go實(shí)現(xiàn)MCP服務(wù)的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • Go語言并發(fā)控制之sync.WaitGroup使用詳解

    Go語言并發(fā)控制之sync.WaitGroup使用詳解

    這篇文章主要為大家詳細(xì)介紹了Go語言并發(fā)控制中sync.Map的原理與使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-02-02

最新評論