使用gin封装一个web脚手架(十):分布式锁(下)

680 阅读1分钟

上一篇已经实现了获取锁和解锁的功能,但是获取锁是非阻塞的,也就是说获取锁失败后就立马返回失败结果了。在一些场景下,我们需要的是保持某一任务只有一个请求在执行,其他的请求在后面排队。就像公司只有一个厕所,每次只能有一个人在里面,其他人在外面排队,只是这个排队是抢占式的,谁速度快,谁就先。

接下来我们实现一个阻塞式的获取锁,代码如下

// Block 阻塞获取锁
func (lk *lock) Block(expiration time.Duration) bool {

	t := time.Now()

	for {

		cxt, cancel := context.WithTimeout(context.Background(), 3*time.Second)

		ok, err := redis.Client().SetNX(cxt, lk.key, lk.requestId, lk.expiration).Result()

		cancel()

		if err != nil {

			return false
		}

		if ok {

			return true
		}

		time.Sleep(200 * time.Millisecond)

		if time.Now().Sub(t) > expiration {

			return false
		}

	}

}

原理比较简单,就是在非阻塞获取锁的基础上加上一个循环,反复的获取锁,在这个期间要是超过了等待时间就返回false。

再实现一个强行释放锁的方法,某些场景下可能会用上

// ForceRelease 强制释放锁,忽略请求id
func (lk *lock) ForceRelease() error {

	cxt, cancel := context.WithTimeout(context.Background(), 3*time.Second)

	defer cancel()

	_, err := lk.connect.Del(cxt, lk.key).Result()

	return err

}

调用实例

非阻塞

l := lock.NewLock("test", 10*time.Second)

defer l.Release()

if l.Get() {

    return response.Resp().String("拿锁成功")
}

return response.Resp().String("拿锁失败")

阻塞

l := lock.NewLock("test", 10*time.Second)

defer l.Release()

if l.Block(5 * time.Second) {

    return response.Resp().String("拿锁成功")

}

return response.Resp().String("拿锁失败")

源代码:github.com/PeterYangs/…