生產(chǎn)環(huán)境go-redsync使用示例
一、問(wèn)題和意義
go-redsync是go語(yǔ)言實(shí)現(xiàn)分布式鎖的常用工具,但官方文檔是的入門示例并不是一個(gè)可以直接用于生產(chǎn)環(huán)境的版本。很多人將官方文檔中的入門示例使用到實(shí)際項(xiàng)目中導(dǎo)致了生產(chǎn)事故。故文本提供一個(gè)可以用于生產(chǎn)環(huán)境的使用示例。
二、官方入門示例存在的問(wèn)題
官方示例代碼為:
package main
import (
goredislib "github.com/redis/go-redis/v9"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/goredis/v9"
)
func main() {
// Create a pool with go-redis (or redigo) which is the pool redisync will
// use while communicating with Redis. This can also be any pool that
// implements the `redis.Pool` interface.
client := goredislib.NewClient(&goredislib.Options{
Addr: "localhost:6379",
})
pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
// Create an instance of redisync to be used to obtain a mutual exclusion
// lock.
rs := redsync.New(pool)
// Obtain a new mutex by using the same name for all instances wanting the
// same lock.
mutexname := "my-global-mutex"
mutex := rs.NewMutex(mutexname)
// Obtain a lock for our given mutex. After this is successful, no one else
// can obtain the same lock (the same mutex name) until we unlock it.
if err := mutex.Lock(); err != nil {
panic(err)
}
// Do your work that requires the lock.
// Release the lock so other processes or threads can obtain a lock.
if ok, err := mutex.Unlock(); !ok || err != nil {
panic("unlock failed")
}
}
接下來(lái),我們開兩個(gè)協(xié)程實(shí)測(cè)一下。
// 這里省去創(chuàng)建redis連接的操作
mutexname := "my-global-mutex"
var wg sync.WaitGroup // 用于實(shí)現(xiàn)主函數(shù)等待所有子協(xié)程執(zhí)行完畢之后再退出
wg.Add(2)
go func() {
defer wg.Done()
mutex := rs.NewMutex(mutexname)
// 開始嘗試獲得分布式鎖
if err := mutex.Lock(); err != nil {
log.Errorf("failed to acquire lock in task1: %v", err)
return
}
// 執(zhí)行一些任務(wù)
log.Info("task1 start at ", time.Now().Format("15:04:05.000"))
time.Sleep(time.Second * 10) // 模擬一個(gè)耗時(shí)的任務(wù)
log.Info("task1 end at ", time.Now().Format("15:04:05.000"))
// 執(zhí)行完任務(wù),釋放鎖
if _, err := mutex.Unlock(); err != nil {
log.Errorf("failed to release lock in task1: %v", err)
}
}()
go func() {
defer wg.Done()
mutex := rs.NewMutex(mutexname)
// 開始嘗試獲得分布式鎖
if err := mutex.Lock(); err != nil {
log.Errorf("failed to acquire lock in task2: %v", err)
return
}
// 執(zhí)行一些任務(wù)
log.Info("task2 start at ", time.Now().Format("15:04:05.000"))
time.Sleep(time.Second * 10) // 模擬一個(gè)耗時(shí)的任務(wù)
log.Info("task2 end at ", time.Now().Format("15:04:05.000"))
// 執(zhí)行完任務(wù),釋放鎖
if _, err := mutex.Unlock(); err != nil {
log.Errorf("failed to release lock in task2: %v", err)
}
}()
wg.Wait()
程序執(zhí)行結(jié)果如下:
INFO msg=task2 start at 02:22:00.330
INFO msg=task1 start at 02:22:08.508
INFO msg=task2 end at 02:22:10.330
ERROR msg=failed to release lock in task2: lock already taken, locked nodes: [0]
INFO msg=task1 end at 02:22:18.508
ERROR msg=failed to release lock in task1: lock already taken, locked nodes: [0]
可以看出,分布式鎖并沒(méi)有起作用,任務(wù)2還沒(méi)執(zhí)行完,任務(wù)1就已經(jīng)獲得鎖并開始。原因是go-redsync默認(rèn)的加鎖時(shí)間只有8秒鐘,如果一個(gè)任務(wù)執(zhí)行時(shí)間超過(guò)8秒,則分布式鎖會(huì)在任務(wù)執(zhí)行結(jié)束前釋放。
三、生產(chǎn)環(huán)境可用的版本
生產(chǎn)環(huán)境中,任務(wù)沒(méi)結(jié)束時(shí)需要調(diào)用mutex.Extend()方法延長(zhǎng)鎖的時(shí)間
mutexname := "my-global-mutex"
var wg sync.WaitGroup // 用于實(shí)現(xiàn)主函數(shù)等待所有子協(xié)程執(zhí)行完畢之后再退出
wg.Add(2)
go func() {
defer wg.Done()
mutex := client.NewMutex(mutexname)
// 開始嘗試獲得分布式鎖
if err := mutex.Lock(); err != nil {
log.Errorf("failed to acquire lock in task1: %v", err)
return
}
var lockReleased atomic.Bool
lockReleased.Store(false)
go func() { // 只要當(dāng)前任務(wù)還在執(zhí)行,每過(guò)1秒就延長(zhǎng)鎖的過(guò)期時(shí)間
for {
time.Sleep(time.Second)
if lockReleased.Load() {
return
}
_, err := mutex.Extend()
if err != nil {
log.Errorf("extend lock in task1 fail: %v", err)
}
}
}()
log.Info("task1 start at ", time.Now().Format("15:04:05.000"))
time.Sleep(time.Second * 10) // 模擬一個(gè)耗時(shí)的任務(wù)
log.Info("task1 end at ", time.Now().Format("15:04:05.000"))
// 執(zhí)行完任務(wù),釋放鎖
if _, err := mutex.Unlock(); err != nil {
log.Errorf("failed to release lock in task1: %v", err)
}
lockReleased.Store(true)
}()
go func() {
defer wg.Done()
mutex := client.NewMutex(mutexname)
// 開始嘗試獲得分布式鎖
if err := mutex.Lock(); err != nil {
log.Errorf("failed to acquire lock in task2: %v", err)
return
}
var lockReleased atomic.Bool
lockReleased.Store(false)
go func() { // 只要當(dāng)前任務(wù)還在執(zhí)行,每過(guò)1秒就延長(zhǎng)鎖的過(guò)期時(shí)間
for {
time.Sleep(time.Second)
if lockReleased.Load() {
return
}
_, err := mutex.Extend()
if err != nil {
log.Errorf("extend lock in task2 fail: %v", err)
}
}
}()
log.Info("task2 start at ", time.Now().Format("15:04:05.000"))
time.Sleep(time.Second * 10) // 模擬一個(gè)耗時(shí)的任務(wù)
log.Info("task2 end at ", time.Now().Format("15:04:05.000"))
// 執(zhí)行完任務(wù),釋放鎖
if _, err := mutex.Unlock(); err != nil {
log.Errorf("failed to release lock in task2: %v", err)
}
lockReleased.Store(true)
}()
wg.Wait()
執(zhí)行結(jié)果如下:
INFO msg=task2 start at 02:31:06.973
INFO msg=task2 end at 02:31:16.974
INFO msg=task1 start at 02:31:17.471
INFO msg=task1 end at 02:31:27.471
這一執(zhí)行結(jié)果符合預(yù)期,任務(wù)1會(huì)在任務(wù)2執(zhí)行完之后才能獲得鎖。
四、 對(duì)go-redsync做封裝
前面的代碼示例可用于生產(chǎn),但代碼過(guò)于冗長(zhǎng),每次使用分布式鎖時(shí)都寫那么多代碼也太麻煩。我們可以將其封裝為了個(gè)TryLock方法:
type LockHolder interface {
ReleaseLock()
}
type lockHolder struct {
mutex *redsync.Mutex
lockReleased atomic.Bool
}
func (h *lockHolder) ReleaseLock() {
_, err := h.mutex.Unlock()
if err != nil {
log.Errorf("failed to release lock: %v", err)
}
h.lockReleased.Store(true)
}
func TryLock(mutexName string) (LockHolder, error) {
rs := getRedsync()
mutex := rs.NewMutex(mutexName)
if err := mutex.Lock(); err != nil {
log.Errorf("failed to acquire lock: %v", err)
return nil, err
}
holder := &lockHolder{
mutex: mutex,
lockReleased: atomic.Bool{},
}
holder.lockReleased.Store(false)
go func() {
for {
time.Sleep(time.Second)
if holder.lockReleased.Load() {
return
}
_, err := mutex.Extend()
if err != nil {
log.Errorf("extend lock fail: %v", err)
}
}
}()
return holder, nil
}
接下來(lái)使用分布式鎖的代碼可以簡(jiǎn)化為:
mutexname := "my-global-mutex"
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
lock, err := TryLock(mutexname)
if err != nil {
log.Errorf("failed to acquire lock in task1: %v", err)
return
}
defer lock.ReleaseLock()
log.Info("task1 start at ", time.Now().Format("15:04:05.000"))
time.Sleep(time.Second * 10)
log.Info("task1 end at ", time.Now().Format("15:04:05.000"))
}()
go func() {
defer wg.Done()
lock, err := TryLock(mutexname)
if err != nil {
log.Errorf("failed to acquire lock in task2: %v", err)
return
}
defer lock.ReleaseLock()
log.Info("task2 start at ", time.Now().Format("15:04:05.000"))
time.Sleep(time.Second * 10)
log.Info("task2 end at ", time.Now().Format("15:04:05.000"))
}()
wg.Wait()
到此這篇關(guān)于生產(chǎn)環(huán)境go-redsync使用示例的文章就介紹到這了,更多相關(guān)生產(chǎn)環(huán)境go-redsync使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Golang實(shí)現(xiàn)Redis分布式鎖(Lua腳本+可重入+自動(dòng)續(xù)期)
- Go語(yǔ)言如何使用分布式鎖解決并發(fā)問(wèn)題
- 在Go語(yǔ)言開發(fā)中實(shí)現(xiàn)高性能的分布式日志收集的方法
- Golang使用etcd構(gòu)建分布式鎖的示例分享
- Go使用Redis實(shí)現(xiàn)分布式鎖的常見方法
- Go分布式鏈路追蹤實(shí)戰(zhàn)探索
- 分布式架構(gòu)在Go語(yǔ)言網(wǎng)站的應(yīng)用
- Go語(yǔ)言使用Etcd實(shí)現(xiàn)分布式鎖
- Golang分布式注冊(cè)中心實(shí)現(xiàn)流程講解
相關(guān)文章
Golang利用casbin實(shí)現(xiàn)權(quán)限驗(yàn)證詳解
Casbin是一個(gè)強(qiáng)大的、高效的開源訪問(wèn)控制框架,其權(quán)限管理機(jī)制支持多種訪問(wèn)控制模型,Casbin只負(fù)責(zé)訪問(wèn)控制。本文將利用casbin實(shí)現(xiàn)權(quán)限驗(yàn)證功能,需要的可以參考一下2023-02-02
解決goland 導(dǎo)入項(xiàng)目后import里的包報(bào)紅問(wèn)題
這篇文章主要介紹了解決goland 導(dǎo)入項(xiàng)目后import里的包報(bào)紅問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05
golang基礎(chǔ)之waitgroup用法以及使用要點(diǎn)
WaitGroup是Golang并發(fā)的兩種方式之一,一個(gè)是Channel,另一個(gè)是WaitGroup,下面這篇文章主要給大家介紹了關(guān)于golang基礎(chǔ)之waitgroup用法以及使用要點(diǎn)的相關(guān)資料,需要的朋友可以參考下2023-01-01
Go語(yǔ)言實(shí)現(xiàn)的排列組合問(wèn)題實(shí)例(n個(gè)數(shù)中取m個(gè))
這篇文章主要介紹了Go語(yǔ)言實(shí)現(xiàn)的排列組合問(wèn)題,結(jié)合實(shí)例形式分析了Go語(yǔ)言實(shí)現(xiàn)排列組合數(shù)學(xué)運(yùn)算的原理與具體操作技巧,需要的朋友可以參考下2017-02-02
一個(gè)簡(jiǎn)單的Golang實(shí)現(xiàn)的HTTP Proxy方法
今天小編就為大家分享一篇一個(gè)簡(jiǎn)單的Golang實(shí)現(xiàn)的HTTP Proxy方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-08-08
基于Golang實(shí)現(xiàn)延遲隊(duì)列(DelayQueue)
延遲隊(duì)列是一種特殊的隊(duì)列,元素入隊(duì)時(shí)需要指定到期時(shí)間(或延遲時(shí)間),從隊(duì)頭出隊(duì)的元素必須是已經(jīng)到期的。本文將用Golang實(shí)現(xiàn)延遲隊(duì)列,感興趣的可以了解下2022-09-09
使用go net實(shí)現(xiàn)簡(jiǎn)單的redis通信協(xié)議
本文主要介紹了go net實(shí)現(xiàn)簡(jiǎn)單的redis通信協(xié)議,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
Windows下CMD執(zhí)行Go出現(xiàn)中文亂碼的解決方法
本文主要介紹了Windows下CMD執(zhí)行Go出現(xiàn)中文亂碼的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
Golang 的defer執(zhí)行規(guī)則說(shuō)明
這篇文章主要介紹了Golang 的defer執(zhí)行規(guī)則說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04

