redislock

package module
v0.0.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 6, 2021 License: BSD-3-Clause Imports: 4 Imported by: 1

README

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type RedisLock

type RedisLock struct {
	// contains filtered or unexported fields
}

锁标识: 锁环境, 锁随机值, 锁生命周期

func (*RedisLock) Keepalive

func (l *RedisLock) Keepalive() bool

保活锁

func (*RedisLock) Key

func (l *RedisLock) Key() string

锁键名

func (*RedisLock) Lock

func (l *RedisLock) Lock() bool

重新获取锁

func (*RedisLock) Refresh

func (l *RedisLock) Refresh(ttl time.Duration) bool

重置锁当前生命周期 (禁止使用负数)

func (*RedisLock) SafeLock

func (l *RedisLock) SafeLock() bool

重新获取锁 (安全模式, 重试 2 次)

func (*RedisLock) SetTTL

func (l *RedisLock) SetTTL(ttl time.Duration) *RedisLock

设置锁默认生命周期 (下次取锁成功或保活时生效)

func (*RedisLock) TTL

func (l *RedisLock) TTL() time.Duration

获取锁(名)当前生命周期(毫秒)

func (*RedisLock) TryLock

func (l *RedisLock) TryLock(retry int, retryDelay time.Duration) bool

重新多次尝试获取锁 (指定重试时间间隔)

func (*RedisLock) Unlock

func (l *RedisLock) Unlock() bool

释放锁

func (*RedisLock) Value

func (l *RedisLock) Value() string

锁键值

type RedisLocker

type RedisLocker struct {
	// contains filtered or unexported fields
}

锁公共项: Redis 连接, context, 锁名, 阻塞键名, 阻塞超时时间

func CTXNew

func CTXNew(rdb *redis.Client, ctx context.Context, key string, keyB string) *RedisLocker

新建锁环境

func New

func New(rdb *redis.Client, key string) *RedisLocker

新建锁环境, 默认非阻塞, 使用 context.Background()

func NewBlocking added in v0.0.2

func NewBlocking(rdb *redis.Client, key, keyB string) *RedisLocker

新建锁环境, 默认阻塞, 使用 context.Background()

func (*RedisLocker) Lock

func (c *RedisLocker) Lock(ttl time.Duration) (*RedisLock, bool)

获取锁

func (*RedisLocker) New

func (c *RedisLocker) New(ttl time.Duration) *RedisLock

新建空锁

func (*RedisLocker) SafeLock

func (c *RedisLocker) SafeLock(ttl time.Duration) (*RedisLock, bool)

获取锁 (安全模式, 重试 2 次)

func (*RedisLocker) TTL

func (c *RedisLocker) TTL() time.Duration

获取锁(名)当前生命周期(毫秒)

func (*RedisLocker) TryLock

func (c *RedisLocker) TryLock(ttl time.Duration, retry int, retryDelay time.Duration) (*RedisLock, bool)

多次尝试获取锁

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL