Redis Golang 分布式锁
English README
为什么要应用分布式锁?
- 多个过程或服务竞争资源,而这些过程或服务又部署在多台机器,此时须要应用分布式锁,确保资源被正确地应用,否则可能呈现副作用。
- 防止反复执行某一个动作,多台机器部署的同一个服务上都有雷同定时工作,而定时工作只需执行一次,此时须要应用分布式锁,确保效率。
具体业务:电商业务防止反复领取,微服务上多正本服务的夜间定时统计。
分布式锁反对:
- 单机模式的 Redis。
- 哨兵模式的 Redis。阐明:该模式也是属于单机,Redis 是一主(Master)多从(Slave),只不过有哨兵机制,主挂掉后哨兵发现后能够将从晋升到主。
单机和哨兵模式的 Redis 无奈做到锁的高可用,比方 Redis 挂掉了可能导致服务不可用(单机),锁凌乱(哨兵),但对可靠性要求没那么高的,咱们还是能够应用单机 Redis,毕竟大多数状况都比较稳定。
对可靠性要求较高,要求高可用,官网给出了 Redlock
算法,利用多台主(Redis Master
)来逐个加锁,半数加锁胜利就认为胜利,因为要保护多套 Redis
,略显繁琐。真真须要高可用,我倡议应用 etcd
官网实现的分布式锁。
如何应用
很简略,执行:
go get -v github.com/hunterhug/gorlock
本库反对无感知锁续命,你只有开启 KeepAlive
,能够解决业务解决工夫过长,导致锁超时,而被其他人抢占重入的问题:
// LockFactory is main interface
type LockFactory interface {SetRetryCount(c int64) LockFactory
GetRetryCount() int64
SetUnLockRetryCount(c int64) LockFactory
GetUnLockRetryCount() int64
SetKeepAlive(isKeepAlive bool) LockFactory
IsKeepAlive() bool
SetRetryMillSecondDelay(c int64) LockFactory
GetRetryMillSecondDelay() int64
SetUnLockRetryMillSecondDelay(c int64) LockFactory
GetUnLockRetryMillSecondDelay() int64
LockFactoryCore
}
// LockFactoryCore is core interface
type LockFactoryCore interface {
// Lock lock resource default keepAlive depend on LockFactory
Lock(ctx context.Context, resourceName string, lockMillSecond int) (lock *Lock, err error)
// LockForceKeepAlive lock resource force keepAlive
LockForceKeepAlive(ctx context.Context, resourceName string, lockMillSecond int) (lock *Lock, err error)
// LockForceNotKeepAlive lock resource force not keepAlive
LockForceNotKeepAlive(ctx context.Context, resourceName string, lockMillSecond int) (lock *Lock, err error)
// UnLock unlock resource
UnLock(ctx context.Context, lock *Lock) (isUnLock bool, err error)
// Done asynchronous see lock whether is release
Done(lock *Lock) chan struct{}}
外围操作,就是建一个锁工厂,而后应用接口契约中的办法来生成锁,并操作这把锁。
例子
package main
import (
"context"
"fmt"
"github.com/hunterhug/gorlock"
"time"
)
func main() {gorlock.SetDebug()
// 1. 配置 Redis
redisHost := "127.0.0.1:6379"
redisDb := 0
redisPass := "hunterhug" // may redis has password
config := gorlock.NewRedisSingleModeConfig(redisHost, redisDb, redisPass)
pool, err := gorlock.NewRedisPool(config)
if err != nil {fmt.Println("redis init err:", err.Error())
return
}
// 2. 新建一个锁工厂
lockFactory := gorlock.New(pool)
// 设置锁重试次数和尝试等待时间,示意加锁不胜利等 200 毫秒再尝试,总共尝试 3 次,设置正数示意始终尝试,会堵住
lockFactory.SetRetryCount(3).SetRetryMillSecondDelay(200)
// 主动续命锁
lockFactory.SetKeepAlive(true)
// 3. 锁住资源,资源名为 myLock
resourceName := "myLock"
// 过期工夫 10 秒
expireMillSecond := 10 * 1000
lock, err := lockFactory.Lock(context.Background(), resourceName, expireMillSecond)
if err != nil {fmt.Println("lock err:", err.Error())
return
}
// 锁为空,示意加锁失败
if lock == nil {fmt.Println("lock err:", "can not lock")
return
}
// 加锁胜利
fmt.Printf("add lock success:%#v\n", lock)
// 能够期待锁被开释
//<-lockFactory.Done(lock)
time.Sleep(3 * time.Second)
// 4. 解锁
isUnlock, err := lockFactory.UnLock(context.Background(), lock)
if err != nil {fmt.Println("lock err:", err.Error())
return
}
fmt.Printf("lock unlock: %v, %#v\n", isUnlock, lock)
// 下面曾经解锁了,能够屡次解锁
isUnlock, err = lockFactory.UnLock(context.Background(), lock)
if err != nil {fmt.Println("lock err:", err.Error())
return
}
fmt.Println("lock unlock:", isUnlock)
}
后果:
add lock success:&gorlock.Lock{CreateMillSecondTime:1627543007573, LiveMillSecondTime:10001, LastKeepAliveMillSecondTime:0, UnlockMillSecondTime:0, ResourceName:"myLock", RandomKey:"a6f438bd9b1f4d759e818dbc78e890f4", IsUnlock:false, isUnlockChan:(chan struct {})(0xc00007a1e0), IsClose:false, OpenKeepAlive:true, closeChan:(chan struct {})(0xc00007a240), keepAliveDealCloseChan:(chan bool)(0xc00007a2a0), mutex:sync.Mutex{state:0, sema:0x0}}
2021-07-29 15:16:48.083 github.com/hunterhug/gorlock:(*redisLockFactory).keepAlive [DEBUG]: start in 2021-07-29 15:16:48.079402 +0800 CST m=+0.518137615 keepAlive myLock with random key:a6f438bd9b1f4d759e818dbc78e890f4 doing
2021-07-29 15:16:48.586 github.com/hunterhug/gorlock:(*redisLockFactory).keepAlive [DEBUG]: start in 2021-07-29 15:16:48.583449 +0800 CST m=+1.022178426 keepAlive myLock with random key:a6f438bd9b1f4d759e818dbc78e890f4 doing
2021-07-29 15:16:49.090 github.com/hunterhug/gorlock:(*redisLockFactory).keepAlive [DEBUG]: start in 2021-07-29 15:16:49.087154 +0800 CST m=+1.525877218 keepAlive myLock with random key:a6f438bd9b1f4d759e818dbc78e890f4 doing
2021-07-29 15:16:49.594 github.com/hunterhug/gorlock:(*redisLockFactory).keepAlive [DEBUG]: start in 2021-07-29 15:16:49.590883 +0800 CST m=+2.029599435 keepAlive myLock with random key:a6f438bd9b1f4d759e818dbc78e890f4 doing
2021-07-29 15:16:50.097 github.com/hunterhug/gorlock:(*redisLockFactory).keepAlive [DEBUG]: start in 2021-07-29 15:16:50.094998 +0800 CST m=+2.533708261 keepAlive myLock with random key:a6f438bd9b1f4d759e818dbc78e890f4 doing
2021-07-29 15:16:50.575 github.com/hunterhug/gorlock:(*redisLockFactory).UnLock [DEBUG]: UnLock send signal to keepAlive ask game over
2021-07-29 15:16:50.575 github.com/hunterhug/gorlock:(*redisLockFactory).keepAlive [DEBUG]: start in 2021-07-29 15:16:50.575115 +0800 CST m=+3.013818937 keepAlive myLock with random key:a6f438bd9b1f4d759e818dbc78e890f4 close
2021-07-29 15:16:50.575 github.com/hunterhug/gorlock:(*redisLockFactory).UnLock [DEBUG]: UnLock receive signal to keepAlive response keepAliveHasBeenClose=false
lock unlock: true, &gorlock.Lock{CreateMillSecondTime:1627543007573, LiveMillSecondTime:10001, LastKeepAliveMillSecondTime:1627543010097, UnlockMillSecondTime:1627543010575, ResourceName:"myLock", RandomKey:"a6f438bd9b1f4d759e818dbc78e890f4", IsUnlock:true, isUnlockChan:(chan struct {})(0xc00007a1e0), IsClose:true, OpenKeepAlive:true, closeChan:(chan struct {})(0xc00007a240), keepAliveDealCloseChan:(chan bool)(0xc00007a2a0), mutex:sync.Mutex{state:0, sema:0x0}}
lock unlock: true
License
Copyright [2019-2021] [github.com/hunterhug]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.