1、分布式锁的应用场景:
多个进程同时操作共享资源
实际用到的场景:
定时任务,在多台机器上对共享资源进行操作,比如:
1)每天的某一时刻,要生成对前一天数据的统计,多个定时任务只需要执行其中一个就可以了
2)定时告警任务,不加分布式锁去取数据库的数据,会重复通知
3)分表存储数据,每天会定时检查索引,多个进程去执行,虽然不会重复建索引,但会产生错误日志
2、分布式锁的实现方案
2.1 基于redis
//初始化redis连接池
type RedisPool struct {
redis.Pool
}
type RedisMap map[string]*RedisPool
var RDS = RedisMap{}
func (rm *RedisMap) GetDefaultRedisPool() *RedisPool {
return RDS["default"]
}
func init() {
rd := redis.Pool{
TestOnBorrow: nil,
MaxIdle: 10,
MaxActive: 10,
IdleTimeout: 5 * time.Second,
Wait: false,
Dial: func() (conn redis.Conn, err error) {
conn, err = redis.Dial("tcp", "127.0.0.1:6379")
if err != nil {
}
return
},
}
RDS["default"] = &RedisPool{
rd,
}
}
//加锁
func TryLock(key, requestId string, expireSeconds int) (bool, error) {
conn := RDS.GetDefaultRedisPool().Get()
defer conn.Close()
msg, err := redis.String(conn.Do("SET", key, requestId, "EX", expireSeconds, "NX"))
if err == redis.ErrNil {
//锁已经存在
return false, err
}
if err != nil {
return false, err
}
//已获取到锁
if msg == "OK" {
return true, nil
}
return false, errors.New("")
}
func GetLock(key string) string {
conn := RDS.GetDefaultRedisPool().Get()
defer conn.Close()
msg, err := redis.String(conn.Do("GET", key))
if err != nil {
return ""
}
return msg
}
//解锁
func UnLock(key, requestId string) bool {
if GetLock(key) == requestId {
conn := RDS.GetDefaultRedisPool().Get()
defer conn.Close()
msg, _ := redis.Int64(conn.Do("DEL", key))
//自动过期时再删除返回结果未0
if msg == 1 || msg == 0 {
fmt.Println("unlock successed...")
return true
}
fmt.Println("unlock failed...")
return false
}
fmt.Println("unlock failed...")
return false
}
func AddTimeout(key, requestId string, expireSeconds int64) (bool, error) {
conn := RDS.GetDefaultRedisPool().Get()
defer conn.Close()
ttlTime, err := redis.Int64(conn.Do("TTL", key))
if err != nil {
return false, err
}
fmt.Println("ttlTime:", ttlTime)
if ttlTime > 0 {
_, err := conn.Do("SETEX", key, int(ttlTime + expireSeconds), requestId)
if err != nil {
fmt.Println("22")
return false, err
}
return true, nil
}
fmt.Println("33")
return false, nil
}
func main() {
key := "key1"
localIp := "localIp"
expireSeconds := 20
fmt.Println("start...")
ok, err := TryLock(key, localIp, expireSeconds)
if err != nil || !ok {
fmt.Println("the lock exists...", err, ok)
return
}
defer UnLock(key, localIp)
fmt.Println("lock successed...")
ok, err = AddTimeout(key, localIp, 30)
if err != nil || !ok {
fmt.Println("add timeout failed...", err, ok)
return
}
fmt.Println("add timeout successed...")
time.Sleep(time.Second * 30)
fmt.Println("end...")
}
总结:
通过setex设置超时时间,达到时间之后数据会自动删除,但是失效时间设置的太短,方法没执行完锁就自动释放了,会产生并发问题,如果设置的时间太长,其他获取锁的线程就要多等一段时间
解决:
redis锁的过期时间能够自动续期,每隔一段时间(10s)查看这个锁,如果还持有锁,就对过期时间进行续期
2.2 基于zookeeper
2.3 基于数据表