創(chuàng)業(yè)如何進行網(wǎng)站建設(shè)路由優(yōu)化大師
Redis分布式鎖
Redis 分布式鎖是一種使用 Redis 數(shù)據(jù)庫實現(xiàn)分布式鎖的方式,可以保證在分布式環(huán)境中同一時間只有一個實例可以訪問共享資源。
實現(xiàn)機制
以下是實現(xiàn)其加鎖步驟:
獲取鎖
在 Redis 中,一個相同的key
代表一把鎖。是否擁有這把鎖,需要判斷key
和value
是否是自己設(shè)置的,同時還要判斷鎖是否已經(jīng)過期。
- 首先通過get命令去獲取鎖,如果獲取不到說明還沒有加鎖
- 如果還沒有加鎖我們就可以去通過set命令去加鎖,并且需要設(shè)置一個expire過期時間防止成為一個長生不老鎖,那如果業(yè)務(wù)還沒有執(zhí)行完鎖就釋放了怎么辦呢?這個后面會提到續(xù)鎖
- 如果獲取到了key說明已經(jīng)被其他實例搶到了鎖,加鎖失敗
- 加鎖失敗還需要根據(jù)一些操作例如超時時間內(nèi)去重試加鎖,直到加鎖成功或者超時
這些操作都需要原子性操作,需要用lua腳本進行封裝
lock.lua
val = redis.call('get', KEYS[1])
if val == false thenreturn redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2])
elseif val == ARGV[1] thenredis.call('expire', KEYS[1], ARGV[2])return 'OK'
elsereturn ''
end
釋放鎖
釋放鎖的時候就是把key刪除,不過刪除的時候需要判斷是不是自己加的鎖
unlock.lua
if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
elsereturn 0
end
Go 實現(xiàn)分布式鎖
結(jié)構(gòu)體字段配置
// redis客戶端連接
type Client struct {client redis.CmdablevarFunc func() stringg singleflight.Group
}// 鎖的結(jié)構(gòu)體
type Lock struct {client redis.Cmdablekey stringvalue stringexpiration time.Durationunlock chan struct{}unlockOne sync.Once
}// NewClient creates a *Client
func NewClient(client redis.Cmdable) *Client {return &Client{client: client,varFunc: func() string {return uuid.New().String()},}
}// 重試策略
type RetryStrategy interface {// Next determines the time interval for Lock// and whether Lock to retryNext() (time.Duration, bool)
}// 周期性重試
type FixedIntervalRetry struct {Interval time.DurationMax intcnt int
}
lua 腳本,使用go的embed映射到luaLock string
var (ErrFailedToPreemptLock = errors.New("redis-lock: failed to lock")ErrLockNotHold = errors.New("redis-lock: lock not hold")ErrLockTimeout = errors.New("redis-lock: lock timeout")//go:embed lua/unlock.lualuaUnlock string//go:embed lua/refresh.lualuaRefresh string//go:embed lua/lock.lualuaLock string
)
加鎖Lock
加鎖時有兩種方案,一種是比較簡單的( TryLock )嘗試加鎖,只需要傳個過期時間,另一種是比較完善的( Lock )加鎖,會有超時策略等
func newLock(client redis.Cmdable, key string, value string, expiration time.Duration) *Lock {return &Lock{client: client,key: key,value: value,expiration: expiration,unlock: make(chan struct{}, 1),}
}// TryLock tries to acquire a lock
func (c *Client) TryLock(ctx context.Context,key string,expiration time.Duration) (*Lock, error) {val := c.varFunc()ok, err := c.client.SetNX(ctx, key, val, expiration).Result()if err != nil {return nil, err}if !ok {return nil, ErrFailedToPreemptLock}return newLock(c.client, key, val, expiration), nil
}// Lock tries to acquire a lock with timeout and retry strategy
func (c *Client) Lock(ctx context.Context,key string,expiration time.Duration,timeout time.Duration, retry RetryStrategy) (*Lock, error) {var timer *time.Timerval := c.varFunc()for {lCtx, cancel := context.WithTimeout(ctx, timeout)res, err := c.client.Eval(lCtx, luaLock, []string{key}, val, expiration.Seconds()).Result()cancel()if err != nil && !errors.Is(err, context.DeadlineExceeded) {return nil, err}if res == "OK" {return newLock(c.client, key, val, expiration), nil}interval, ok := retry.Next()if !ok {return nil, ErrLockTimeout}if timer == nil {timer = time.NewTimer(interval)} else {timer.Reset(interval)}select {case <-timer.C:case <-ctx.Done():return nil, ctx.Err()}}
}
解鎖unLock
// Unlock releases the lock
func (l *Lock) Unlock(ctx context.Context) error {res, err := l.client.Eval(ctx, luaUnlock, []string{l.key}, l.value).Int64()defer func() {l.unlockOne.Do(func() {l.unlock <- struct{}{}close(l.unlock)})}()if errors.Is(err, redis.Nil) {return ErrLockNotHold}if err != nil {return err}if res != 1 {return ErrLockNotHold}return nil
}
小結(jié)
- 使用分布式鎖本身會有各種各樣的問題,需要自己去處理異常情況例如超時等
- 對鎖的操作一定要判斷是不是自己加的那把鎖,否則會誤刪會導(dǎo)致業(yè)務(wù)錯誤
- 對鎖的續(xù)約部分我們下一篇再講
本文go的代碼是完整的,可以直接copy使用,有興趣的小伙伴可以去使用一下