Golang分布式鎖簡(jiǎn)單案例實(shí)現(xiàn)流程
其實(shí)鎖這種東西,都能能不加就不加,鎖會(huì)導(dǎo)致程序一定程度上退回到串行化,進(jìn)而降低效率。
首先,看一個(gè)案例,如果要實(shí)現(xiàn)一個(gè)計(jì)數(shù)器,并且是多個(gè)協(xié)程共同進(jìn)行的,就會(huì)出現(xiàn)以下的情況:
package main import ( "fmt" "sync" ) func main() { numberFlag := 0 wg := new(sync.WaitGroup) for i := 0; i < 200; i++ { wg.Add(1) go func() { defer wg.Done() numberFlag++ }() } fmt.Println(numberFlag) wg.Wait() }
每次執(zhí)行后的計(jì)數(shù)器結(jié)果都是不同的,這是因?yàn)橛?jì)數(shù)器本身是被不同的協(xié)程搶著+1,會(huì)產(chǎn)生多個(gè)協(xié)程同時(shí)拿到numberFlag=N的情況。為了避免這種資源競(jìng)爭(zhēng),要對(duì)資源進(jìn)行加鎖,使得同一時(shí)刻只有一個(gè)協(xié)程能對(duì)資源進(jìn)行操控。
package main import ( "fmt" "sync" ) func main() { numberFlag := 0 myLock := make(chan struct{}, 1) wg := new(sync.WaitGroup) for i := 0; i < 200; i++ { wg.Add(1) go func() { defer func() { // 釋放鎖 <-myLock }() defer wg.Done() // 搶鎖 myLock <- struct{}{} numberFlag++ }() } wg.Wait() fmt.Println(numberFlag) }
但是這種鎖只能用于你自己的本地服務(wù),一旦出現(xiàn)多服務(wù),比如分布式,微服務(wù),這樣的場(chǎng)景,這個(gè)鎖就沒啥用了,這就需要分布式鎖。
關(guān)于分布式鎖,一般的實(shí)現(xiàn)就是用redis或者zookeeper實(shí)現(xiàn)。redis比較方便的就是大部分的服務(wù)都會(huì)使用redis,無(wú)需額外安裝依賴,而zookeeper普通服務(wù)用的并不多,即使是kafka也在新版放棄了zookeeper。
zookeeper最大的好處就是可以通過心跳檢測(cè)客戶端的情況,進(jìn)而避免重復(fù)得鎖的問題。
但是同時(shí)也產(chǎn)生了一些問題,這個(gè)心跳檢測(cè)多久一次,在心跳檢測(cè)的間隔如果出現(xiàn)了鎖超時(shí)的問題怎么辦,等等。
所以一些服務(wù)還是傾向于使用redis來實(shí)現(xiàn)分布式鎖。
package main import ( "fmt" "github.com/gomodule/redigo/redis" "go-face-test/redisTest/redisOne/redisConn" "sync" "time" ) func main() { // 分布式鎖 var LockName = "lockLock" // 十秒過期時(shí)間 var ExpirationTime = 10 wg := new(sync.WaitGroup) wg.Add(2) // 起兩個(gè)協(xié)程來模擬分布式服務(wù)的搶占 go handleBusiness(LockName, ExpirationTime, "A", wg) go handleBusiness(LockName, ExpirationTime, "B", wg) wg.Wait() } func handleBusiness(lockName string, ExpTime int, nowGroName string, wg *sync.WaitGroup) { // One服務(wù)獲取鎖是否存在 c := redisConn.Get() defer c.Close() for { isKeyExists, err := redis.Bool(c.Do("EXISTS", lockName)) if err != nil { fmt.Println("err while checking keys:", err) } else { fmt.Println(isKeyExists) } if isKeyExists { // 存在這把鎖,開始自旋等待 fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 沒搶到鎖……") //休息1s time.Sleep(time.Second) } else { // 設(shè)置一把鎖 // 值為1,過期時(shí)間為10秒 reply, err := c.Do("SET", lockName, 2, "EX", ExpTime, "NX") // 搶占失敗 if reply == nil { fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 搶占鎖失敗") continue } // 開始業(yè)務(wù)處理 fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 啊啊啊啊。這是一個(gè)業(yè)務(wù)處理,預(yù)計(jì)處理時(shí)間為 3s 。處理開始........") fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 距離處理完成還有---3s" + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 距離處理完成還有---2s" + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 距離處理完成還有---1s" + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) //業(yè)務(wù)結(jié)束,釋放鎖 _, err = c.Do("DEL", lockName) if err != nil { fmt.Println("err while deleting:", err) } break } } wg.Done() }
但是這個(gè)鎖明顯有問題:
第一,當(dāng)A服務(wù)(本案例中其實(shí)是協(xié)程模擬的)拿到鎖之后,處理超時(shí)了,鎖還沒有釋放,就已經(jīng)過期,過期后B服務(wù)就搶到了鎖,此時(shí)AB均認(rèn)為自己拿到了鎖
第二,A服務(wù)按理說只能去掉自己的服務(wù)加上的鎖,如果不止是有AB兩個(gè)服務(wù),有更多的服務(wù),那么A如果出現(xiàn)處理較慢,鎖超時(shí)后,B服務(wù)搶到鎖,A又處理完成所有的事釋放了鎖,那其實(shí)是釋放掉了B的鎖。也就是說,釋放鎖的時(shí)候也必須判斷是否是自己的鎖
那么就得用redis的lua來保證原子性
package main import ( "fmt" "github.com/gomodule/redigo/redis" "go-face-test/redisTest/redisTwo/redisConn" "log" "math/rand" "strconv" "sync" "time" ) var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") var lockCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then redis.call("SET", KEYS[1], ARGV[1], "PX", ARGV[2] , "NX") return "OK" else return redis.call("SET", KEYS[1], ARGV[1], "NX", "PX", ARGV[2]) end` var delCommand = `if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) else return 0 end` func main() { // 分布式鎖 var LockName = "lockLock" // 十秒過期時(shí)間 var ExpirationTime = 3 wg := new(sync.WaitGroup) wg.Add(2) // 起兩個(gè)協(xié)程來模擬分布式服務(wù)的搶占 go handleBusiness(LockName, ExpirationTime, "A", wg) go handleBusiness(LockName, ExpirationTime, "B", wg) wg.Wait() } func init() { rand.Seed(time.Now().UnixNano()) } func handleBusiness(lockName string, ExpTime int, nowGroName string, wg *sync.WaitGroup) { // One服務(wù)獲取鎖是否存在 c := redisConn.Get() defer c.Close() for { isKeyExists, err := redis.Bool(c.Do("EXISTS", lockName)) if err != nil { fmt.Println("err while checking keys:", err) } else { fmt.Println(isKeyExists) } if isKeyExists { // 存在這把鎖,開始自旋等待 fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 沒搶到鎖……") //休息1s time.Sleep(time.Second) } else { // 設(shè)置一把鎖 // 鎖的值是根據(jù)當(dāng)前服務(wù)名稱和時(shí)間來的 lockFlag, lockValue, _ := getLock(lockName, nowGroName, ExpTime, c) // 搶占失敗 if !lockFlag { fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 搶占鎖失敗") continue } // 開始業(yè)務(wù)處理 fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 啊啊啊啊。這是一個(gè)業(yè)務(wù)處理,預(yù)計(jì)處理時(shí)間為 " + strconv.Itoa(ExpTime) + "s 。處理開始........") for i := ExpTime - 1; i > 0; i-- { fmt.Println("當(dāng)前協(xié)程為: " + nowGroName + " 距離處理完成還有---" + strconv.Itoa(i) + "s " + time.Now().Format("2006-01-02 15:04:05")) time.Sleep(time.Second) } //業(yè)務(wù)結(jié)束,釋放鎖 lockDelFlag, _ := delLock(lockName, lockValue, c) //獲取當(dāng)前鎖的值 if lockDelFlag { fmt.Println("釋放鎖成功") } else { fmt.Println("這個(gè)鎖不是你的,或者這個(gè)鎖已經(jīng)超時(shí)") } break } } wg.Done() } // 獲得唯一鎖的值 func getLockOnlyValue(nowGroName string) string { nano := strconv.FormatInt(time.Now().UnixNano(), 10) return nowGroName + "_" + nano + "_" + RandStringRunes(6) } // 獲得一個(gè)鎖 func getLock(LockName, nowGroName string, timeOut int, conn redis.Conn) (bool, string, error) { myLockValue := getLockOnlyValue(nowGroName) lua := redis.NewScript(1, lockCommand) resp, err := lua.Do(conn, LockName, myLockValue, strconv.Itoa(timeOut*1000)) if err != nil { log.Fatal(LockName, err) return false, "", err } else if resp == nil { return false, "", nil } s, ok := resp.(string) if !ok { return false, "", nil } if s != "OK" { return false, "", nil } return true, myLockValue, nil } // 刪除一個(gè)鎖 func delLock(LockName, LockeValue string, conn redis.Conn) (bool, error) { lua := redis.NewScript(1, delCommand) resp, err := lua.Do(conn, LockName, LockeValue) if err != nil { return false, err } reply, ok := resp.(int64) if !ok { return false, nil } return reply == 1, nil } func RandStringRunes(n int) string { b := make([]rune, n) for i := range b { b[i] = letterRunes[rand.Intn(len(letterRunes))] } return string(b) }
到此這篇關(guān)于Golang分布式鎖簡(jiǎn)單案例實(shí)現(xiàn)流程的文章就介紹到這了,更多相關(guān)Go分布式鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go調(diào)用opencv實(shí)現(xiàn)圖片矯正的代碼示例
這篇文章主要為大家詳細(xì)介紹了Go調(diào)用opencv實(shí)現(xiàn)圖片矯正的代碼示例,文中的示例代碼簡(jiǎn)潔易懂,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-09-09golang監(jiān)聽ip數(shù)據(jù)包的實(shí)現(xiàn)步驟(golang純享版)
這篇文章主要給大家介紹了golang監(jiān)聽ip數(shù)據(jù)包的實(shí)現(xiàn)步驟,本文以ip4 作為案例進(jìn)行包抓取示范,ip6抓取與ip4方式異曲同工,可自行舉一反三得出,文中通過圖文結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-02-02Go 微服務(wù)開發(fā)框架DMicro設(shè)計(jì)思路詳解
這篇文章主要為大家介紹了Go 微服務(wù)開發(fā)框架DMicro設(shè)計(jì)思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Go實(shí)現(xiàn)替換(覆蓋)文件某一行內(nèi)容的示例代碼
本文主要介紹了Go實(shí)現(xiàn)替換(覆蓋)文件某一行內(nèi)容的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07golang新手不注意可能會(huì)出現(xiàn)的一些小問題
最近在學(xué)習(xí)golang,發(fā)現(xiàn)了一些新手們需要注意的小問題,下面這篇文章主要給大家介紹了關(guān)于golang新手不注意可能會(huì)出現(xiàn)的一些小問題,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-12-12