GoLang 基于 Redis 实现分布式锁

828 阅读2分钟

「这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战

分布式系统中,共享资源常常需要安全被访问和处理。这个时候,就需要分布式锁,保障有序访问共享资源。

分布式锁的原则:

  • 互斥性。 在任何时刻,保证只有一个客户端持有锁。
  • 不能出现死锁。 如果在一个客户端持有锁的期间,这个客户端崩溃了,也要保证后续的其他客户端可以上锁。
  • 保证上锁和解锁都是同一个客户端

基于 redis 实现分布式锁

  • 当 key 存在时, 设置失败,可以保证互斥
  • 设置了超时时间,避免死锁
  • 当前实例程序加锁不并发冲突

基于以上,可以写出如下 Redis 分布式锁代码:

import (
	"context"
	"sync"
	"time"
)

var mutex sync.Mutex
var rdb *redis.Client

func NewRedis() {
    rdb = redis.NewClient(&redis.Options{
        Addr:     setting.Redis.Ip + ":" + setting.Redis.Port,
        Password: "", // no password set
        DB:       0,  // use default DB
    })
}
func Lock(ctx context.Context, key string) bool {
	mutex.Lock()
	defer mutex.Unlock()
	if rdb == nil {
		logs.CtxError(ctx, "redis client nil")
		return false
	}
	bool, err := rdb.SetNX(key, 1, 30*time.Minute).Result()
	if err != nil {
		logs.CtxError(ctx, " redis setnx err:%v", err.Error())
		return false
	}
	return bool
}
func UnLock(ctx context.Context, key string) int64 {
	nums, err := rdb.Del(key).Result()
	if err != nil {
		logs.CtxError(ctx, "err %v", err)
		return 0
	}
	return nums
}

上面的代码有没有问题呢, 分布式锁的还有第二个写法。

func AcquireLock(lock_key string, lock_value string, timeout uint32) bool {
     // SET lock_key lock_value NX PX ttl_with_seconds 
     // return is_success
}

func ReleaseLock(lock_key string, lock_value string) bool {
     // 在释放锁的时候加入乐观锁校验, 并通过lua脚本保证原子性
     // return_val = eval (
     //           if redis.call("get",lock_key) == lock_value then
     //                return redis.call("del",lock_key)
     //           else
     //                return 0
     //           end
     // )
     return return_val!=0
}

func Process(lock_key string, lock_value string, timeout uint32) {
    if  AcquireLock(lock_key, lock_value, timeout) {
        // 无论业务逻辑执行是否成功, 一定释放锁
        defer func() {
            release_success := ReleaseLock(lock_key)
            if !release_success {
                // 如果锁释放失败, 说明锁超时, 其他人已经获取了锁, 需要根据业务决定是否rollback刚刚的操作
                // maybe rollback?? 
            }
        }()

        // do something
        // maybe process over lock TTL
    }
}

参考资料