Golang 基于单节点 Redis 实现的分布式锁

分布式锁的基本思路

原子性加锁

加锁问题,为避免死锁,需要加锁的时候为锁设置一个存活时间,过了这个时间,锁自动被释放掉

安全释放锁

1.安全释放锁需要达到一个目的,A客户端加锁,必须由A客户端释放锁或者锁超时自动释放
2.锁重入问题,A线程由于业务处理时间过长,比存活时间还要长,锁到期自动释放,就会导致A进程去释放掉其它进程产生的锁
3.为了解决锁重入问题,我们需要在A客户端业务逻辑处理过程中,延长这个锁的存活时间,防止业务未处理完成导致锁自动释放.(租约,自动续期)

代码

package dislock

import (
    "context"
    "errors"
    "github.com/gomodule/redigo/redis"
    "log"
    "time"
)

//基于redis的分布式锁
type DisLockRedis struct {
    key       string             //锁名称
    ttl       int64              //锁超时时间
    isLocked  bool               //上锁成功标识
    cancelFun context.CancelFunc //用于取消自动续租携程

    redis *redis.Pool
    debug bool
}

func NewDisLockRedis(key string, redis *redis.Pool) *DisLockRedis {
    this := &DisLockRedis{
        key:   key,
        ttl:   30,
        redis: redis,
    }

    return this
}

//上锁
func (this *DisLockRedis) TryLock() (err error) {
    if err = this.grant(); err != nil {
        return
    }

    ctx, cancelFun := context.WithCancel(context.TODO())

    this.cancelFun = cancelFun
    //自动续期
    this.renew(ctx)

    this.isLocked = true

    return nil
}

//释放锁
func (this *DisLockRedis) Unlock() (err error) {
    var res int
    if this.isLocked {
        if res, err = redis.Int(this.redisConn().Do("DEL", this.key)); err != nil {

            if this.debug {
                log.Println(err.Error())
            }
            return errors.New("释放锁失败")
        }

        if res == 1 {
            //释放成功,取消自动续租
            this.cancelFun()
            return
        }
    }

    return errors.New("释放锁失败")

}

//自动续期
func (this *DisLockRedis) renew(ctx context.Context) {

    go func() {

        for {
            select {
            case <-ctx.Done():
                return
            default:
                res, err := redis.Int(this.redisConn().Do("EXPIRE", this.key, this.ttl))
                if this.debug {
                    if err != nil {
                        log.Println("锁自动续期失败:", err)
                    }

                    if res != 1 {
                        log.Println("锁自动续期失败")
                    }
                }
            }

            time.Sleep(time.Duration(this.ttl/3) * time.Second)
        }
    }()

}

//创建租约
func (this *DisLockRedis) grant() (err error) {

    if res, err := redis.String(this.redisConn().Do("SET", this.key, "xxx", "NX", "EX", this.ttl)); err != nil {
        if this.debug {
            log.Println(err)
        }

    } else {
        if res == "OK" {
            return nil
        }
    }

    return errors.New("上锁失败")
}

func (this *DisLockRedis) redisConn() redis.Conn {
    return this.redis.Get()
}

func (this *DisLockRedis) Debug() *DisLockRedis {
    this.debug = true
    return this
}

注意

每次使用锁,都必须调用NewDisLockRedis新建对象
如有问题欢迎指正

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!