rlock

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2023 License: MIT Imports: 13 Imported by: 0

README

RLock

A distributed lock base on Redis achieved by Golang.


Status

Usage

go get -u github.com/pjimming/rlock

Achieve

  • Mutual exclusion: Redis distributed lock can ensure that only one client can acquire the lock at the same time, realizing mutual exclusion between threads.
  • Security: Redis distributed locks use atomic operations, which can ensure the security of locks under concurrent conditions and avoid problems such as data competition and deadlocks.
  • Lock timeout: In order to avoid deadlock caused by a failure of a certain client after acquiring the lock, the Redis distributed lock can set the lock timeout period, and the lock will be released automatically when the timeout is exceeded.
  • Reentrancy: Redis distributed locks can support the same client to acquire the same lock multiple times, avoiding deadlocks in nested calls.
  • High performance: Redis is an in-memory database with high read and write performance, enabling fast locking and unlocking operations.
  • Atomicity: The locking and unlocking operations of Redis distributed locks use atomic commands, which can ensure the atomicity of operations and avoid competition problems under concurrency.
  • RedLock: In the implementation of RedLock, the contradiction between Consistency C and Availability A in CAP will be eased based on the majority principle, ensuring that when more than half of all Redis nodes under RedLock are available, the entire RedLock can be provided normally Serve.

Quick Start

RLock
package test

import (
	"testing"
	"time"

	"github.com/pjimming/rlock"
	"github.com/pjimming/rlock/utils"

	"github.com/stretchr/testify/assert"
)

var op = rlock.RedisClientOptions{
	Addr:     "127.0.0.1:6379",
	Password: "",
}

func TestLock(t *testing.T) {
	ast := assert.New(t)

	l := rlock.NewRLock(op, "")
	ast.NotNil(l)

	ttl := l.Lock()
	ast.Equal(int64(0), ttl)

	l.UnLock()
}

func TestTryLock(t *testing.T) {
	ast := assert.New(t)

	l := rlock.NewRLock(op, "")
	ast.NotNil(l)

	ttl := l.TryLock()
	t.Log("ttl:", ttl)
	ast.Equal(int64(0), ttl)

	l.UnLock()
}

func TestDelayExpire(t *testing.T) {
	ast := assert.New(t)
	key := utils.GenerateRandomString(8)

	l1 := rlock.NewRLock(op, key).
		SetToken(key + "111").
		SetExpireSeconds(5).
		SetWatchdogSwitch(true)

	l2 := rlock.NewRLock(op, key).
		SetToken(key + "222").
		SetBlockWaitingSecond(20)

	t.Log("l1:", l1.Key(), l1.Token())
	t.Log("l2:", l2.Key(), l2.Token())

	l1.Lock()

	start := time.Now()
	l2.TryLock()
	t.Log("l2 TryLock cost:", time.Now().Sub(start).String())

	t.Log("l1 start sleep...", time.Now().Sub(start).String())
	time.Sleep(time.Second * 10)
	ast.Equal(int64(1), l1.UnLock())
	t.Log("l1 unlock", time.Now().Sub(start).String())

	l2.Lock()
	t.Log("l2 Lock cost:", time.Now().Sub(start).String())

	ast.Equal(int64(1), l2.UnLock())
}
RedLock
package test

import (
	"testing"
	"time"

	"github.com/pjimming/rlock"

	"github.com/stretchr/testify/assert"
)

func TestRedLock(t *testing.T) {
	redLock, err := rlock.NewRedLock([]rlock.RedisClientOptions{
		{Addr: "127.0.0.1:7001", Password: ""},
		{Addr: "127.0.0.1:7002", Password: ""},
		{Addr: "127.0.0.1:7003", Password: ""},
		{Addr: "127.0.0.1:7004", Password: ""},
		{Addr: "127.0.0.1:7005", Password: ""},
	}, "1234567_key", 30*time.Second)

	if err != nil {
		t.Log(err)
		return
	}

	t.Log(redLock.TryLock())
	redLock.UnLock()
}

Lua Scripts

Hint: Your redis should support lua script.

LockLua
if (redis.call('EXISTS', KEYS[1]) == 0) then
    -- don't have lock
    redis.call('HINCRBY', KEYS[1], ARGV[1], 1)
    redis.call('PEXPIRE', KEYS[1], tonumber(ARGV[2]))
    return 0
end
if (redis.call('HEXISTS', KEYS[1], ARGV[1]) == 1) then
    -- reentry
    redis.call('HINCRBY', KEYS[1], ARGV[1], 1)
    redis.call('PEXPIRE', KEYS[1], tonumber(ARGV[2]))
    return 0
end
return redis.call('PTTL', KEYS[1])
UnLockLua
if (redis.call('HEXISTS', KEYS[1], ARGV[1]) == 0) then
    -- not hold lock
    return -1
end
local counter = redis.call('HINCRBY', KEYS[1], ARGV[1], -1)
if (counter > 0) then
    -- update expire
    redis.call('PEXPIRE', KEYS[1], tonumber(ARGV[2]))
    return 0
else
    -- release lock
    redis.call('DEL', KEYS[1])
    return 1
end
return -1
DelayExpireLua
if (redis.call('HEXISTS', KEYS[1], ARGV[1]) == 1) then
    -- hold lock
    redis.call('PEXPIRE', KEYS[1], tonumber(ARGV[2]))
    return 1
end
return 0

Reference

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type LockOption added in v1.0.1

type LockOption func(*LockOptions)

func WithBlockWaitingTime added in v1.0.1

func WithBlockWaitingTime(blockWaitingTime time.Duration) LockOption

func WithExpireTime added in v1.0.1

func WithExpireTime(expireTime time.Duration) LockOption

func WithWatchDogSwitch added in v1.0.1

func WithWatchDogSwitch(watchdogSwitch bool) LockOption

type LockOptions added in v1.0.1

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

type RLock

type RLock struct {
	LockOptions
	// contains filtered or unexported fields
}

func NewRLock

func NewRLock(op RedisClientOptions, key string, opts ...LockOption) (rLock *RLock)

NewRLock new a redis lock. You can set params with set function.

Default lockOptions:

blockWaitingTime : 60 * time.Second
expireTime       : 30 * time.Second
watchdogSwitch   : false

func (*RLock) BlockWaitingSecond

func (l *RLock) BlockWaitingSecond() time.Duration

func (*RLock) ExpireTime

func (l *RLock) ExpireTime() time.Duration

func (*RLock) Key

func (l *RLock) Key() string

func (*RLock) Lock

func (l *RLock) Lock() int64

Lock try to acquire lock, until acquire lock or blocking timeout. Returns:

1. ttl < 0 error;

2. ttl = 0 success;

3. ttl > 0 acquire by others

func (*RLock) SetBlockWaitingSecond

func (l *RLock) SetBlockWaitingSecond(blockWaitingTime time.Duration) *RLock

func (*RLock) SetExpireTime

func (l *RLock) SetExpireTime(expireTime time.Duration) *RLock

func (*RLock) SetKey

func (l *RLock) SetKey(key string) *RLock

func (*RLock) SetToken

func (l *RLock) SetToken(token string) *RLock

func (*RLock) SetWatchdogSwitch

func (l *RLock) SetWatchdogSwitch(watchdogSwitch bool) *RLock

func (*RLock) Token

func (l *RLock) Token() string

func (*RLock) TryLock

func (l *RLock) TryLock() int64

TryLock try to acquire lock in once, support reentrant. Returns:

1. ttl < 0 error;

2. ttl = 0 success;

3. ttl > 0 acquire by others.

func (*RLock) UnLock

func (l *RLock) UnLock() int64

UnLock try to release lock, support reentrant. Returns:

1. res < 0 error or release other's lock;

2. res = 0 release lock success, but still hold lock because of reentry;

3. res = 1 release lock success absolutely.

func (*RLock) WatchdogSwitch

func (l *RLock) WatchdogSwitch() bool

type RedLock

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

func NewRedLock

func NewRedLock(rcs []RedisClientOptions, key string, expireTime time.Duration) (redLock *RedLock, err error)

NewRedLock new a RedLock from multi redis servers.

It is required that the cumulative timeout threshold of all nodes is less than one-tenth of the distributed lock expiration time.

func (*RedLock) TryLock

func (l *RedLock) TryLock() bool

TryLock try to acquire lock.

If RedLock gets lock count greater than half of locks, it means acquire lock successfully.

func (*RedLock) UnLock

func (l *RedLock) UnLock()

UnLock release lock.

type RedisClientOptions

type RedisClientOptions struct {
	Addr     string // redis address
	Password string // redis password
}

Jump to

Keyboard shortcuts

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