最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語言實現(xiàn)分布式鎖

 更新時間:2023年01月14日 09:05:18   作者:何憶清風(fēng)  
分布式鎖是控制分布式系統(tǒng)之間同步訪問共享資源的一種方式。如果不同的系統(tǒng)或是同一個系統(tǒng)的不同主機之間共享了一個或一組資源,那么訪問這些資源時,需要通過一些互斥手段來防止彼此之間的干擾以保證一致性,在這種情況下,就需要使用分布式鎖了

1. go實現(xiàn)分布式鎖

通過 golang 實現(xiàn)一個簡單的分布式鎖,包括鎖續(xù)約、重試機制、singleflght機制的使用

1.1 redis_lock.go

package redis_lock
import (
	"context"
	_ "embed"
	"errors"
	"github.com/go-redis/redis/v9"
	"github.com/google/uuid"
	"golang.org/x/sync/singleflight"
	"time"
)
// go:embed 可以直接解析出文件中的字符串
var (
	//go:embed lua_unlock.lua
	luaUnlock string
	//go:embed refresh.lua
	luaRefresh string
	//go:embed lock.lua
	luaLock string
	//定義好兩個異常信息
	ErrLockNotHold         = errors.New("未持有鎖")
	ErrFailedToPreemptLock = errors.New("加鎖失敗")
)
type Client struct {
	//采用公共的接口,后續(xù)實例通過傳入的方式
	client redis.Cmdable
	// singleflight 用于在一個實例的多個攜程中只需要競爭出一個攜程
	s singleflight.Group
}
func NewClient(c redis.Cmdable) *Client {
	return &Client{
		client: c,
	}
}
func (c *Client) SingleflightLock(ctx context.Context,
	key string,
	expire time.Duration,
	retry RetryStrategy,
	timeout time.Duration) (*Lock, error) {
	for {
		flag := false
		resCh := c.s.DoChan(key, func() (interface{}, error) {
			flag = true
			return c.Lock(ctx, key, expire, retry, timeout)
		})
		select {
		case res := <-resCh:
			if flag {
				if res.Err != nil {
					return nil, res.Err
				}
				//返回鎖對象
				return res.Val.(*Lock), nil
			}
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
}
//Lock 加鎖方法,根據(jù)重試機制進行重新獲取
func (c *Client) Lock(ctx context.Context,
	key string,
	expire time.Duration,
	retry RetryStrategy,
	timeout time.Duration) (*Lock, error) {
	var timer *time.Timer
	defer func() {
		if timer != nil {
			timer.Stop()
		}
	}()
	for {
		//設(shè)置超時
		lct, cancel := context.WithTimeout(ctx, timeout)
		//獲取到uuid
		value := uuid.New().String()
		//執(zhí)行l(wèi)ua腳本進行加鎖
		result, err := c.client.Eval(lct, luaLock, []string{key}, value, expire).Bool()
		//用于主動釋放資源
		cancel()
		if err != nil && !errors.Is(err, context.DeadlineExceeded) {
			return nil, err
		}
		if result {
			return newLock(c.client, key, value), nil
		}
		//可以不傳重試機制
		if retry != nil {
			//通過重試機制獲取重試的策略
			interval, ok := retry.Next()
			if !ok {
				//不用重試
				return nil, ErrFailedToPreemptLock
			}
			if timer == nil {
				timer = time.NewTimer(interval)
			}
			timer.Reset(interval)
			select {
			case <-timer.C: //睡眠時間超時了
				return nil, ctx.Err()
			case <-ctx.Done(): //整個調(diào)用的超時
				return nil, ctx.Err()
			}
		}
	}
}
// TryLock 嘗試加鎖
func (c *Client) TryLock(ctx context.Context, key string, expire time.Duration) (*Lock, error) {
	return c.Lock(ctx, key, expire, nil, 0)
}
// NewLock 創(chuàng)建一個鎖結(jié)構(gòu)體
func newLock(client redis.Cmdable, key string, value string) *Lock {
	return &Lock{
		client:     client,
		key:        key,
		value:      value,
		unLockChan: make(chan struct{}, 1), //設(shè)置1個緩存數(shù)據(jù),用于解鎖的信號量
	}
}
// Lock 結(jié)構(gòu)體對象
type Lock struct {
	client redis.Cmdable
	key    string
	value  string
	expire time.Duration
	//在解鎖成功之后發(fā)送信號來取消續(xù)約
	unLockChan chan struct{}
}
// AutoRefresh 自動續(xù)約
func (l *Lock) AutoRefresh(interval time.Duration, timeout time.Duration) error {
	//設(shè)計一個管道,如果失敗了,就發(fā)送數(shù)據(jù)到管道之中,通知進行重試
	retry := make(chan struct{}, 1)
	//方法返回時關(guān)閉close
	defer close(retry)
	ticker := time.NewTicker(interval)
	for {
		select {
		//接收到結(jié)束的信號時,直接return
		case <-l.unLockChan:
			return nil
		//監(jiān)聽重試的管道
		case <-retry:
			ctx, cancel := context.WithTimeout(context.Background(), timeout)
			err := l.Refresh(ctx)
			//主動調(diào)用釋放資源
			cancel()
			if err == context.DeadlineExceeded {
				// 執(zhí)行重試往管道中發(fā)送一個信號
				retry <- struct{}{}
				continue
			}
			if err != nil {
				return err
			}
		case <-ticker.C:
			ctx, cancel := context.WithTimeout(context.Background(), timeout)
			err := l.Refresh(ctx)
			//主動調(diào)用釋放資源
			cancel()
			if err == context.DeadlineExceeded {
				// 執(zhí)行重試往管道中發(fā)送一個信號
				retry <- struct{}{}
				continue
			}
			if err != nil {
				return err
			}
		}
	}
}
// Refresh 續(xù)約
func (l *Lock) Refresh(ctx context.Context) error {
	//執(zhí)行l(wèi)ua腳本,對鎖進行續(xù)約
	i, err := l.client.Eval(ctx, luaRefresh, []string{l.key}, l.value, l.expire.Milliseconds()).Int64()
	if err == redis.Nil {
		return ErrLockNotHold
	}
	if err != nil {
		return err
	}
	if i == 0 {
		return ErrLockNotHold
	}
	return nil
}
// Unlock 解鎖
func (l *Lock) Unlock(ctx context.Context) error {
	//解鎖時,退出方法需要發(fā)送一個信號讓自動續(xù)約的goroutine停止
	defer func() {
		l.unLockChan <- struct{}{}
		close(l.unLockChan)
	}()
	//判斷返回的結(jié)果
	result, err := l.client.Eval(ctx, luaUnlock, []string{l.key}, l.value).Int64()
	if err == redis.Nil {
		return ErrLockNotHold
	}
	if err != nil {
		return err
	}
	//lua腳本返回的結(jié)果如果為0,也是代表當前鎖不是自己的
	if result == 0 {
		return ErrLockNotHold
	}
	return nil
}

1.2 retry.go

package redis_lock
import "time"
// RetryStrategy 重試策略
type RetryStrategy interface {
	// Next 下一次重試的時間是多久,返回兩個參數(shù) time 時間,bool 是否直接重試
	Next() (time.Duration, bool)
}

1.3 lock.lua

lua腳本原子化加鎖

--[[ 獲取到對應(yīng)的value是否跟當前的一樣 ]]
if redis.call("get", KEYS[1]) == ARGV[1]
then
-- 如果一樣直接對其時間進行續(xù)約
	return redis.call("pexpire", KEYS[1], ARGV[2])
else
-- 如果不一樣調(diào)用setnx命令對其進行設(shè)置值
	return redis.call("set", KEYS[1], ARGV[1], "NX", "PX", ARGV[2])

1.4 lua_unlock.lua

lua腳本原子化解鎖

if redis.call("get", KEYS[1]) == ARGV[1] then
	-- 返回0,代表key不在
	return redis.call("del", KEYS[1])
else
	-- key在,但是值不對
	return 0
end

1.5 refresh.lua

lua腳本續(xù)約

if redis.call("get", KEYS[1]) == ARGV[1] then
	-- 返回0,代表key不在
	return redis.call("pexpire", KEYS[1], ARGV[2])
else
	-- key在,但是值不對
	return 0
end

1.6 單元測試

使用go-mock工具生成本地的單元測試,不需要再單獨的搭建一個 redis 的服務(wù)端

項目根目錄下安裝mockgen工具

go install github.com/golang/mock/mockgen@latest

添加依賴

go get github.com/golang/mock/mockgen/model

生成redis客戶端接口

mockgen -package=mocks -destination=mocks/redis_cmdable.mock.go github.com/go-redis/redis/v9 Cmdable

  • package:指定包
  • destination:生成路徑名稱
  • 剩下的是指定使用redis包下面的 Cmdable接口生成代碼

測試類

func TestClient_TryLock(t *testing.T) {
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()
	testCase := []struct {
		//測試的場景
		name string
		//輸入
		key        string
		expiration time.Duration
		//返回一個mock數(shù)據(jù)
		mock func() redis.Cmdable
		//期望的返回的錯誤值
		wantError error
		//期望返回的鎖
		wantLock *Lock
	}{
		{
			name:       "locked",
			key:        "locked-key",
			expiration: time.Minute,
			mock: func() redis.Cmdable {
				rdb := mocks.NewMockCmdable(ctrl)
				res := redis.NewBoolResult(true, nil)
				i := []interface{}{gomock.Any(), time.Minute}
				rdb.EXPECT().Eval(gomock.Any(), luaLock, []string{"locked-key"}, i...).Return(res)
				return rdb
			},
			wantLock: &Lock{
				key: "locked-key",
			},
		},
	}
	for _, tc := range testCase {
		t.Run(tc.name, func(t *testing.T) {
			var c = NewClient(tc.mock())
			l, err := c.TryLock(context.Background(), tc.key, tc.expiration)
			assert.Equal(t, tc.wantError, err)
			if err != nil {
				return
			}
			//判斷返回的key是否跟期望的一樣
			assert.Equal(t, tc.key, l.key)
			assert.Equal(t, tc.wantLock.key, l.key)
			assert.NotEmpty(t, l.value)
		})
	}
}

到此這篇關(guān)于Go語言實現(xiàn)分布式鎖的文章就介紹到這了,更多相關(guān)Go分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文詳解Golang內(nèi)存管理之棧空間管理

    一文詳解Golang內(nèi)存管理之??臻g管理

    這篇文章主要介紹了Golang內(nèi)存管理的棧空間管理,文章通過代碼示例介紹的非常詳細,對我們學(xué)習(xí)Golang內(nèi)存管理有一定的幫助,需要的朋友跟著小編一起來學(xué)習(xí)吧
    2023-06-06
  • Go中select行為規(guī)則和執(zhí)行順序的實現(xiàn)

    Go中select行為規(guī)則和執(zhí)行順序的實現(xiàn)

    Go語言中的select語句用于在多個通信操作中進行選擇,本文就來詳細的介紹一下Go中select行為規(guī)則和執(zhí)行順序的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2026-02-02
  • 8個Elasticsearch高頻面試題和答案整理

    8個Elasticsearch高頻面試題和答案整理

    這篇文章為大家精選了8道Elasticsearch高頻面試題和答案,并且給出了這些知識點的應(yīng)用場景、也給出了解決這些問題的思路,希望對大家有所幫助
    2023-06-06
  • 300行代碼實現(xiàn)go語言即時通訊聊天室

    300行代碼實現(xiàn)go語言即時通訊聊天室

    本文主要介紹了300行代碼實現(xiàn)go語言即時通訊聊天室,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • 解決Golang 中使用WaitGroup的那點坑

    解決Golang 中使用WaitGroup的那點坑

    這篇文章主要介紹了解決Golang 中使用WaitGroup的那點坑,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • Go常問的一些面試題匯總(附答案)

    Go常問的一些面試題匯總(附答案)

    通常我們?nèi)ッ嬖嚳隙〞行┎诲e的Golang的面試題目的,所以總結(jié)下,讓其他Golang開發(fā)者也可以查看到,同時也用來檢測自己的能力和提醒自己的不足之處,這篇文章主要給大家介紹了關(guān)于Go常問的一些面試題以及答案的相關(guān)資料,需要的朋友可以參考下
    2023-10-10
  • Go語言kube-scheduler深度剖析開發(fā)之scheduler初始化

    Go語言kube-scheduler深度剖析開發(fā)之scheduler初始化

    這篇文章主要介紹了Go語言kube-scheduler深度剖析開發(fā)之scheduler初始化實現(xiàn)過程示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • golang 進度條功能實現(xiàn)示例

    golang 進度條功能實現(xiàn)示例

    這篇文章主要介紹了golang 進度條功能實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 剖析Go編寫的Socket服務(wù)器模塊解耦及基礎(chǔ)模塊的設(shè)計

    剖析Go編寫的Socket服務(wù)器模塊解耦及基礎(chǔ)模塊的設(shè)計

    這篇文章主要介紹了Go的Socket服務(wù)器模塊解耦及日志和定時任務(wù)的模塊設(shè)計,舉了一些Go語言編寫的服務(wù)器模塊的例子,需要的朋友可以參考下
    2016-03-03
  • Go語言常見錯誤之將接口定義在實現(xiàn)方

    Go語言常見錯誤之將接口定義在實現(xiàn)方

    在Go中,接口起到一個十分關(guān)鍵的角色,它們提供了一種方式來定義對象的行為,而不需要知道對象的具體實現(xiàn),一個常見的錯誤是在實現(xiàn)方而不是使用方定義接口,本文將詳細探討為何這樣做是一個錯誤,以及如何避免它
    2024-01-01

最新評論

得荣县| 定陶县| 湘潭市| 曲松县| 扶余县| 宣恩县| 阿拉尔市| 台湾省| 四子王旗| 佛学| 银川市| 鄂伦春自治旗| 南充市| 漯河市| 铜川市| 和平县| 济阳县| 扬州市| 永靖县| 兴城市| 铜川市| 射阳县| 岑溪市| 盖州市| 天全县| 杨浦区| 汨罗市| 札达县| 玉田县| 当阳市| 阿拉善右旗| 平远县| 深泽县| 松原市| 玉林市| 万山特区| 饶平县| 延川县| 汽车| 克山县| 肥城市|