亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

go語(yǔ)言分布式id生成器及分布式鎖介紹

 更新時(shí)間:2023年04月06日 17:16:50   作者:路由器沒(méi)有路  
這篇文章主要為大家介紹了go語(yǔ)言分布式id生成器及分布式鎖介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

分布式 id 生成器

在分布式場(chǎng)景中,唯一 id 的生成算比較重要。

而通常在高并發(fā)場(chǎng)景中,需要類似 MySQL 自增 id 一樣不斷增長(zhǎng)且又不會(huì)重復(fù)的 id,即 MySql 的主鍵 id。

比如,在電商 618 或者雙 11 搞活動(dòng)的時(shí)候,一般在 0 點(diǎn) 開始,會(huì)有千萬(wàn)到億級(jí)的訂單量寫入,每秒大概需要處理 10 萬(wàn)加的訂單。

而在將訂單插入數(shù)據(jù)庫(kù)之前,我們?cè)跇I(yè)務(wù)上需要給訂單一個(gè)唯一的 id,即利用 idMaker 生存唯一的訂單號(hào),再插入數(shù)據(jù)庫(kù)內(nèi)。如果生成的 id 是隨機(jī)且沒(méi)有含義的純數(shù)字的話,在大訂單量的情況下,對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪改查時(shí)就不能起到提高效率的作用。所以 此 id 應(yīng)該應(yīng)該包含一些時(shí)間信息,機(jī)器信息等,這樣即使后端的系統(tǒng)對(duì)消息進(jìn)行了分庫(kù)分表,也能夠以時(shí)間順序?qū)@些消息進(jìn)行排序了。

比較典型的就是推特的【雪花算法】了,在以上場(chǎng)景下可以算是最優(yōu)解,原理如圖:

首先確定的是,id 數(shù)值長(zhǎng)度是 64 位,int64 類型,除去開頭的符號(hào)位 unused ,其它可以分為四個(gè)部分:

  • 41 位來(lái)表示收到請(qǐng)求時(shí)的時(shí)間戳,單位為毫秒
  • 5 位表示數(shù)據(jù)中心的 id
  • 5 位表求機(jī)器的實(shí)例 id
  • 12 位為循環(huán)自增 id,到達(dá) 1111,1111,1111 后歸就會(huì) 0

以上機(jī)制原理生成的 id,可以支持一臺(tái)機(jī)器在一毫秒內(nèi)能夠產(chǎn)生 4096 條消息。也就是一秒共 409.6w 條消息。單單從值域上來(lái)講是完全夠用。

數(shù)據(jù)中心 id 加上實(shí)例 id 共有 10 位,每個(gè)數(shù)據(jù)中心可以部署 32 臺(tái)實(shí)例,搭建 32 個(gè)數(shù)據(jù)中心,所以可以一共部署 1024 臺(tái)實(shí)例。

而 41 位的時(shí)間戳(毫秒為單位)能夠使用 69 年。

worker_id 如何分配

timestamp(時(shí)間戳),datacenter_id(數(shù)據(jù)中心),worker_id(機(jī)器 ID) 和 sequence_id(序號(hào)) 這四個(gè)字段中,timestamp 和 sequence_id 是由程序在運(yùn)行期生成的。但 datacenter_id 和 worker_id 需要在部署階段就要能夠獲取得到,并且一旦程序啟動(dòng)之后,就是不可更改的了,因?yàn)槿绻梢噪S意更改,可能會(huì)造成最終生成的 id 有沖突。

不過(guò)一般不同數(shù)據(jù)中心的機(jī)器,會(huì)提供對(duì)應(yīng)的獲取數(shù)據(jù)中心 id 的 API,因此 datacenter_id 我們可以在部署階段輕松地獲取到。而 worker_id 是我們邏輯上給機(jī)器分配的一個(gè) id,比較簡(jiǎn)單的做法就是由能夠提供這種自增 id 功能的工具來(lái)支持,比如 MySql:

mysql> insert into a (ip) values("10.115.4.66");
Query OK, 1 row affected (0.00 sec)
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)

從 MySql 中獲取到 worker_id 之后,就把這個(gè) worker_id 直接持久化到本地,以避免每次上線時(shí)都需要獲取新的 worker_id。讓單實(shí)例的 worker_id 可以始終保持不變。

但是,使用 MySQL 的話,相當(dāng)于給 id 生成服務(wù)增加了一個(gè)外部依賴。當(dāng)然依賴越多,服務(wù)的運(yùn)維成本就會(huì)增加。

考慮到集群中即使有單個(gè) id 生成服務(wù)的實(shí)例掛了,也就是損失一段時(shí)間的一部分 id,所以我們也可以更簡(jiǎn)單暴力一些,把 worker_id 直接寫在 worker 的配置中,上線時(shí),由部署腳本完成 worker_id 字段替換即可。

開源示例:標(biāo)準(zhǔn)雪花算法

github.com/bwmarrin/snowflake 是一個(gè)相對(duì)輕量級(jí)的 snowflake 的 Go 實(shí)現(xiàn)。其文檔對(duì)各位使用的定義如下圖所示:

此庫(kù)和標(biāo)準(zhǔn)的 snowflake 實(shí)現(xiàn)方式全完一致,使用也比較簡(jiǎn)單,直接上示例代碼:

package main
import (
  "fmt"
 "github.com/bwmarrin/snowflake"
)
func main() {
 node, err := snowflake.NewNode(1)
 if err != nil {
  println(err.Error())
  os.Exit(1)
 }
 for i := 0; i < 20; i++ {
  id := node.Generate()
  fmt.Printf("Int64  ID: %d\n", id)
  fmt.Printf("String ID: %s\n", id)
  fmt.Printf("ID Time  : %d\n", id.Time())
  fmt.Printf("ID Node  : %d\n", id.Node())
  fmt.Printf("ID Step  : %d\n", id.Step())
  fmt.Println("--------- end ----------")
 }
}

分布式鎖

單機(jī)程序并發(fā)或并行修改全局共享變量時(shí),需要對(duì)修改行為加鎖。因?yàn)槿绻患渔i,多個(gè)協(xié)程序就會(huì)對(duì)該變量競(jìng)爭(zhēng),然后得到的結(jié)果就會(huì)不準(zhǔn)確,或者說(shuō)得到的結(jié)果不是我們所預(yù)期的,比如下面的例子:

package main
func main() {
 var wg sync.WaitGroup
 var count = 0
 for i := 1; i < 1000; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   count++
  }()
 }
 wg.Wait()
 fmt.Println(count)
}

多次運(yùn)行結(jié)果不同:

?  go run main.go
884
?  go run main.go
957
?  go run main.go
923

預(yù)期的結(jié)果是:999

進(jìn)程內(nèi)加鎖

而如果想要得到正確(預(yù)期)的結(jié)果,要把計(jì)數(shù)器的操作代碼部分加上鎖:

package main
import (
 "fmt"
 "sync"
)
func main() {
 var wg sync.WaitGroup
 var lock sync.Mutex
 var count = 0
 for i := 1; i < 1000; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   lock.Lock()  // 加鎖
   count++
   lock.Unlock()  // 釋放鎖
  }()
 }
 wg.Wait()
 fmt.Println(count)
}

這樣能夠得到正確結(jié)果:

?  go run main.go
999

嘗試加鎖 tryLock

在某些場(chǎng)景,我們往往只希望一個(gè)任務(wù)有單一的執(zhí)行者,而不像計(jì)數(shù)器一樣,所有的 Goroutine 都成功執(zhí)行。后續(xù)的 Goroutine 在搶鎖失敗后,需要放棄執(zhí)行,這時(shí)候就需要用到嘗試加鎖,即實(shí)現(xiàn) trylock。

嘗試加鎖,在加鎖成功后執(zhí)行后續(xù)流程,失敗時(shí)不可以阻塞,而是直接返回加鎖的結(jié)果。

在 Go 語(yǔ)言中可以用大小為 1 的 Channel 來(lái)模擬 trylock:

package main
import (
  "fmt"
  "sync"
)
type MyLock struct {
 lockCh chan struct{}
}
func NewLock() MyLock {
 var myLock MyLock
 myLock = MyLock{
  lockCh:make(chan struct{}, 1),
 }
 myLock.lockCh <- struct{}{}
 return myLock
}
func (l *MyLock) Lock() bool {
 result := false
 select {
 case <-l.lockCh:
  result = true
 default:  // 這里去掉就會(huì)阻塞,直到獲取到鎖
 }
 return result
}
func (l *MyLock) Unlock() {
 l.lockCh <- struct{}{}
}
func main() {
 var wg sync.WaitGroup
 var count int
 l := NewLock()
 for i := 0; i < 10; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   if !l.Lock() {
    fmt.Println("get lock failed")
    return
   }
   count++
   fmt.Println("count=", count)
   l.Unlock()
  }()
 }
 wg.Wait()
}

每個(gè) Goruntine 只有獲取到鎖(成功執(zhí)行了 Lock)才會(huì)繼續(xù)執(zhí)行后續(xù)代碼,然后在 Unlock()時(shí)可以保證 Lock 結(jié)構(gòu)體里的 Channel 一定是空的,所以不會(huì)阻塞也不會(huì)失敗。

在單機(jī)系統(tǒng)中,tryLock 并不是一個(gè)好選擇,因?yàn)榇罅康?Goruntine 搶鎖會(huì)無(wú)意義地占用 cpu 資源,這就是活鎖,所有不建議使用這種鎖。

基于 Redis 的 setnx 分布式鎖

在分布式場(chǎng)景中,也需要“搶占”的邏輯,可以用 Redis 的 setnx 實(shí)現(xiàn):

package main
import (
 "github.com/go-redis/redis"
 "sync"
 "time"
)
func setnx() {
 client := redis.NewClient(&redis.Options{})
 var lockKey = "counter_lock"
 var counterKey = "counter"
 // lock
 resp := client.SetNX(lockKey, 1, time.Second*6)
 lockStatus, err := resp.Result()
 if err != nil || !lockStatus {
  println("lock failed")
  return
 }
 // counter++
 getResp := client.Get(counterKey)
 cntValue, err := getResp.Int64()
 if err == nil || err == redis.Nil {
  cntValue++
  resp := client.Set(counterKey, cntValue, 0)
  _, err := resp.Result()
  if err != nil {
   println(err)
  }
 }
 println("current counter is ", cntValue)
 // unlock
 delResp := client.Del(lockKey)
 unlockStatus, err := delResp.Result()
 if err == nil && unlockStatus > 0 {
  println("unlock success")
 } else {
  println("unlock failed", err)
 }
}
func main() {
 var wg sync.WaitGroup
 for i := 0; i < 10; i++ {
  wg.Add(1)
  go func() {
   defer wg.Done()
   setnx()
  }()
 }
 wg.Wait()
}

運(yùn)行結(jié)果:

?  go run main.go
lock failed
lock failed
lock failed
lock failed
lock failed
current counter is  34
lock failed
unlock success

通過(guò)上面的代碼和執(zhí)行結(jié)果可以看到,遠(yuǎn)程調(diào)用 setnx 運(yùn)行流程上和單機(jī)的 troLock 非常相似,如果獲取鎖失敗,那么相關(guān)的任務(wù)邏輯就不會(huì)繼續(xù)向后執(zhí)行。

setnx 很適合高并發(fā)場(chǎng)景下用來(lái)爭(zhēng)搶一些“唯一”的資源。比如,商城秒殺的商品,在某個(gè)時(shí)間點(diǎn),多個(gè)買家會(huì)對(duì)其進(jìn)行下單并發(fā)爭(zhēng)搶。這種場(chǎng)景我們沒(méi)有辦法依賴具體的時(shí)間來(lái)判斷先后,因?yàn)椴煌O(shè)備的時(shí)間不能保證使用的是統(tǒng)一的時(shí)間,也就不能保證時(shí)序。

所以,我們需要依賴于這些請(qǐng)求到達(dá) redis 節(jié)點(diǎn)的順序來(lái)做正確的搶鎖操作。

如果用戶的網(wǎng)絡(luò)環(huán)境比較差,是有可能搶不到的。

基于 ZooKeeper 分布式鎖

基于 ZooKeeper 的鎖與基于 Redis 的鎖有點(diǎn)類似,不同之處在于 Lock 成功之前會(huì)一直阻塞,這與單機(jī)場(chǎng)景中的 mutex.Lock 很相似。

package main
import (
 "github.com/go-zookeeper/zk"
 "time"
)
func main() {
 c, _, err := zk.Connect([]string{"127.0.0.1"}, time.Second)
 if err != nil {
  panic(err)
 }
 l := zk.NewLock(c, "/lock", zk.WorldACL(zk.PermAll))
 err = l.Lock()
 if err != nil {
  panic(err)
 }
 println("lock success, do your business logic")
 time.Sleep(time.Second * 10) // 模擬業(yè)務(wù)處理
 l.Unlock()
 println("unlock success, finish business logic")
}

其原理也是基于臨時(shí) Sequence 節(jié)點(diǎn)和 watch API,例如我們這里使用的是 /lock 節(jié)點(diǎn)。

Lock 會(huì)在該節(jié)點(diǎn)下的節(jié)點(diǎn)列表中插入自己的值,只要節(jié)點(diǎn)下的子節(jié)點(diǎn)發(fā)生變化,就會(huì)通知所有 watch 該節(jié)點(diǎn)的程序。這時(shí)候程序會(huì)檢查當(dāng)前節(jié)點(diǎn)下最小的子節(jié)點(diǎn)的 id 是否與自己的一致。如果一致,說(shuō)明加鎖成功了。

這種分布式的阻塞鎖比較適合分布式任務(wù)調(diào)度場(chǎng)景,但不適合高頻次持鎖時(shí)間短的搶鎖場(chǎng)景。

一般基于強(qiáng)一致協(xié)議的鎖適用于粗粒度的加鎖操作。這里的粗粒度指鎖占用時(shí)間較長(zhǎng)。我們?cè)谑褂脮r(shí)也應(yīng)思考在自己的業(yè)務(wù)場(chǎng)景中使用是否合適。

總結(jié)

本期主要介紹了分布式 id 的使用場(chǎng)景、分布式 id 如何生成的,以及分布式鎖和使用。

  • 雪花算法介紹和實(shí)現(xiàn)
  • 分布式鎖介紹和相關(guān)實(shí)現(xiàn)

以上就是go語(yǔ)言分布式id生成器及分布式鎖介紹的詳細(xì)內(nèi)容,更多關(guān)于go 分布式id生成器 鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang接口的定義與空接口及斷言的使用示例

    Golang接口的定義與空接口及斷言的使用示例

    在?Golang?中,接口是一種類型,它是由一組方法簽名組成的抽象集合。這篇文章主要為大家介紹了Golang接口的具體使用,希望對(duì)大家有所幫助,空接口是特殊形式的接口類型,普通的接口都有方法,而空接口沒(méi)有定義任何方法口,也因此,我們可以說(shuō)所有類型都至少實(shí)現(xiàn)了空接口
    2023-04-04
  • 一文詳解golang延時(shí)任務(wù)的實(shí)現(xiàn)

    一文詳解golang延時(shí)任務(wù)的實(shí)現(xiàn)

    這篇文章主要為大家介紹了golang延時(shí)任務(wù)的實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 使用gopkg.in/yaml.v3?解析YAML數(shù)據(jù)詳解

    使用gopkg.in/yaml.v3?解析YAML數(shù)據(jù)詳解

    這篇文章主要為大家介紹了使用gopkg.in/yaml.v3?解析YAML數(shù)據(jù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • Golang中的變量學(xué)習(xí)小結(jié)

    Golang中的變量學(xué)習(xí)小結(jié)

    本文主要帶大家學(xué)習(xí)了Golang里面的四大類型的變量,十分的詳細(xì),有需要的小伙伴可以參考下
    2018-10-10
  • golang類型轉(zhuǎn)換組件Cast的使用詳解

    golang類型轉(zhuǎn)換組件Cast的使用詳解

    這篇文章主要介紹了golang類型轉(zhuǎn)換組件Cast的使用詳解,幫助大家更好的理解和學(xué)習(xí)使用golang,感興趣的朋友可以了解下
    2021-02-02
  • Go語(yǔ)言實(shí)現(xiàn)基于websocket瀏覽器通知功能

    Go語(yǔ)言實(shí)現(xiàn)基于websocket瀏覽器通知功能

    這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)基于websocket瀏覽器通知功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 基于Go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)易api網(wǎng)關(guān)的示例代碼

    基于Go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)易api網(wǎng)關(guān)的示例代碼

    本文主要介紹了基于Go語(yǔ)言實(shí)現(xiàn)的簡(jiǎn)易api網(wǎng)關(guān),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Golang 獲取系統(tǒng)信息的實(shí)現(xiàn)

    Golang 獲取系統(tǒng)信息的實(shí)現(xiàn)

    本文主要介紹了Golang 獲取系統(tǒng)信息的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • web項(xiàng)目中g(shù)olang性能監(jiān)控解析

    web項(xiàng)目中g(shù)olang性能監(jiān)控解析

    這篇文章主要為大家介紹了web項(xiàng)目中g(shù)olang性能監(jiān)控詳細(xì)的解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-04-04
  • Golang實(shí)現(xiàn)四種負(fù)載均衡的算法(隨機(jī),輪詢等)

    Golang實(shí)現(xiàn)四種負(fù)載均衡的算法(隨機(jī),輪詢等)

    本文介紹了示例介紹了Golang 負(fù)載均衡的四種實(shí)現(xiàn),主要包括了隨機(jī),輪詢,加權(quán)輪詢負(fù)載,一致性hash,感興趣的小伙伴們可以參考一下
    2021-06-06

最新評(píng)論