关于redis:工具库系列之Redis-Golang分布式锁

4次阅读

共计 5820 个字符,预计需要花费 15 分钟才能阅读完成。

Redis Golang 分布式锁





English README

为什么要应用分布式锁?

  1. 多个过程或服务竞争资源,而这些过程或服务又部署在多台机器,此时须要应用分布式锁,确保资源被正确地应用,否则可能呈现副作用。
  2. 防止反复执行某一个动作,多台机器部署的同一个服务上都有雷同定时工作,而定时工作只需执行一次,此时须要应用分布式锁,确保效率。

具体业务:电商业务防止反复领取,微服务上多正本服务的夜间定时统计。

分布式锁反对:

  1. 单机模式的 Redis。
  2. 哨兵模式的 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.
正文完
 0