2 Star 2 Fork 1

fufuok / redis-lock

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
BSD-3-Clause

Go-RedisLock

基于 Redis 的分布式锁, 适用于多 Worker 或多服务器共用 Redis (单机/集群)服务的场景.

支持阻塞或非阻塞式获取锁.

特征

  • 可靠: 基于 Redis 原子操作.

  • 易用: 取个锁名, 给定锁过期时间就有了一个业务锁. 可以有无限个互不干扰的锁.

  • 获取锁:

    • 可选阻塞非阻塞式取锁, 均有以下几个方法.

    • Lock() 常用, 取一次锁.

    • SafeLock() 第一次未获得锁时, 每 1us 重试 1 次, 共重试 2 次.

      非主动释放锁的情景下(即依靠锁过期时间自动释放锁的场景)取锁时推荐使用.

    • TryLock() 指定重试次数, 指定重试时间间隔, 直到取锁成功或重试结束.

  • 保活锁: 重置当前锁的生命周期为取锁时设定的值.

  • 刷新锁: 更新当前锁的生命周期为指定值, 仅当次锁有效.

  • 释放锁: 锁释放后可立即被重新获取.

安装

go get github.com/fufuok/redislock

获取锁的几种方式

// 0.1 指定锁名 -> 初始化锁环境对象(RedisLocker) -> 指定锁生命周期 -> 获取锁(RedisLock)
locker1 := redislock.New(rdb, "lock1")
lock1, ok := locker1.Lock(1*time.Second)
lock1.Unlock()

// 0.2 获取过锁对象后, 可以直接使用锁对象获取锁
// 此时, 锁名和锁生命周期均为初始化时的值: lock1, 1*time.Second
ok = lock1.Lock()
lock1.Unlock()

// 0.3 也可以先初始化个空锁, 然后使用锁对象获取锁
lock2 := redislock.New(rdb, "lock2").New(1*time.Second)
ok = lock2.Lock()
lock2.Unlock()

// 0.4 锁环境对象(RedisLocker)和锁对象(RedisLock)都有 3 个获取锁的方法, 效果相同
// Lock() 获取一次
// SafeLock() 获取锁时重试 2 次, 每次间隔 1us, 常用于锁过期后获取锁的场景
// TryLock() 指定获取锁时重试次数和每次重试的时间间隔
locker1.Lock(1*time.Second)
locker1.SafeLock(1*time.Second)
locker1.TryLock(1*time.Second, 2, 3*time.Millisecond)

// 锁对象获取锁时默认使用锁环境初始化时的锁生命周期
lock1.Lock()
lock1.SafeLock()
lock1.TryLock(2, 3*time.Millisecond)

// 若要更换锁生命周期, 可以使用 Refresh() 方法 (立即生效)
lock1.Refresh(5*time.Second)

// 若要变更锁的默认生命周期 (下次取锁时生效)
lock1.SetTTL(8*time.Second).Lock()

阻塞模式取锁

// 0.5 阻塞模式取锁与上面方法相同, 只是初始化锁环境对象(RedisLocker)时使用方法不同, 如下:
blockingLocker := redislock.NewBlocking(rdb, "lockB", "lockBLPopList")
blockingLock, ok := blockingLocker.Lock(10 * time.Millisecond)
if ok {
    // 取到锁, 其他协程取锁将被阻塞住
    fmt.Printf("阻塞式取锁成功.\n  锁名: %s\n  锁生命周期: %s\n  锁对象: %+v\n",
               blockingLock.Key(), blockingLock.TTL(), blockingLock)
    // working...
    // 阻塞模式取锁推荐与主动释放锁搭配使用
    blockingLock.Unlock()
} else {
    // Blocking 超时, 即 Redis.BLPop() 超时
    // 锁生命周期小于 1 秒时, Blocking 的超时时间等于 1 秒, 否则等于锁生命周期
    // 当 Blocking 期间获取到锁, 则 ok == true
}

阻塞模式原理

阻塞模式利于 Redis::ListBLPop 实现阻塞, 解锁时 lpush 值, 从 BLPop 取得值的协程将获取锁.

以下时序图来源于(感谢): https://github.com/ionelmc/python-redis-lock

redis-lock-blocking

综合示例

// example/main.go
package main

import (
	"fmt"
	"time"

	"github.com/fufuok/redislock"
	"github.com/go-redis/redis/v8"
)

// Redis 连接 (可与项目共用, 确保连接正确)
rdb := redis.NewClient(&redis.Options{
    Network: "tcp",
    Addr:    "127.0.0.1:6379",
})

func main() {
	defer func() {
		_ = rdb.Close()
	}()

	// 1. 简单使用
	// 传入 Redis 连接和锁名 (锁名称是区分锁的唯一标识)
	// 获取锁时传入锁的过期时间 (生命周期)
	lock, ok := redislock.New(rdb, "simpleLock").Lock(10 * time.Millisecond)
	if ok {
		fmt.Printf("取锁成功.\n  锁名: %s\n  锁生命周期: %s\n  锁对象: %+v\n",
			lock.Key(), lock.TTL(), lock)
	} else {
		fmt.Printf("失败? 锁对象: %+v\n", lock)
	}

	// 刷新锁生命周期 (临时更新锁的生命周期, 当次锁有效)
	ok = lock.Refresh(1 * time.Hour)
	fmt.Printf("刷新锁: 成功 == %v, 锁生命周期 > 59m: %s\n", ok, lock.TTL())

	// 保活锁: 重置锁生命周期 (重置为获取锁时设定的生命周期)
	ok = lock.Keepalive()
	fmt.Printf("保活锁: 成功 == %v, 锁生命周期 >=8ms <=10ms: %s\n", ok, lock.TTL())

	// 锁占用期, 无法被再次获取到
	ok = lock.Lock()
	if ok {
		fmt.Printf("异常? 锁对象: %+v\n", lock)
	} else {
		fmt.Printf("锁被占用, 无法获取, 锁生命周期: %s\n", lock.TTL())
	}

	// 锁占用期, 新建锁对象(锁名相同时)也无法获取到 (其他协程或新建锁对象)
	newLock, ok := redislock.New(rdb, "simpleLock").Lock(10 * time.Millisecond)
	if ok {
		fmt.Printf("异常? 锁对象: %+v\n", newLock)
	} else {
		// 锁生命周期以锁名为标识, 即返回的是 Redis 键名 TTL
		fmt.Printf("锁被占用, 无法获取, 锁生命周期: %s\n", newLock.TTL())
	}

	// 锁过期后可重新获取到锁, 此时建议使用 SafeLock()
	time.Sleep(lock.TTL())
	ok = lock.SafeLock()
	if ok {
		fmt.Printf("锁过期, 重新取锁成功.\n  锁名: %s\n  锁生命周期: %s\n  锁对象: %+v\n",
			lock.Key(), lock.TTL(), lock)
	} else {
		fmt.Printf("失败? 锁对象: %+v\n", lock)
	}

	// 主动释放锁后可立即被重新获取
	ok = lock.Unlock()
	if ok {
		fmt.Printf("主动释放锁: 成功 == %v, 锁生命周期: %s, 锁对象: %+v\n",
			ok, lock.TTL(), lock)
		fmt.Printf("重新获取锁: 成功 == %v, 锁生命周期: %s, 锁对象: %+v\n",
			lock.Lock(), lock.TTL(), lock)
	} else {
		fmt.Printf("失败? 锁对象: %+v\n", lock)
	}

	// 按指定时间间隔重试取锁
	ok = lock.TryLock(3, 5*time.Millisecond)
	if ok {
		fmt.Printf("重试 3 次(每次间隔 5ms), > 锁生命周期 10ms, 取锁成功 == %v\n", ok)
	} else {
		fmt.Printf("失败? 锁生命周期: %s, 锁对象: %+v\n", lock.TTL(), lock)
	}

	// 设置非法锁生命周期 (禁止使用小于 1 毫秒的生命周期)
	errLock, ok := redislock.New(rdb, "testErrTTL").Lock(999 * time.Microsecond)
	if ok {
		fmt.Printf("异常? 锁生命周期: %s, 锁对象: %+v\n", errLock.TTL(), errLock)
	} else {
		fmt.Printf("非法生命周期值无法获得锁: %+v\n", errLock)
		ok = errLock.SetTTL(2 * time.Second).Lock()
		fmt.Printf("  SetTTL() 重设锁生命周期后取锁成功: %v, %+v\n", ok, errLock)
	}

	// 2. 通常使用场景
	// commonLock()

	// 3. 模拟定时任务场景
	// timeoutLock()

	// 4. 阻塞模式, 模拟必须任务执行完成才让出锁的场景
	// keepaliveBlockingLock()

	fmt.Println("the end...详见: example/main.go")
}

ff

BSD 3-Clause License Copyright (c) 2020, Fufu All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

简介

🔒️ 基于 Redis 的分布式锁, 适用于多 Worker 或多服务器共用 Redis (单机/集群)服务的场景. 展开 收起
Go
BSD-3-Clause
取消

贡献者

全部

近期动态

加载更多
不能加载更多了
Go
1
https://gitee.com/fufuok/redislock.git
git@gitee.com:fufuok/redislock.git
fufuok
redislock
redis-lock
master

搜索帮助