# Go Redis Lua 分布式锁
分布式锁在分布式系统中非常重要,它可以帮助我们避免多个进程或线程同时访问共享资源,从而避免数据一致性问题。在Go语言中,我们可以使用Redis来实现分布式锁。
# 获取锁
在Redis中,我们可以使用SETNX命令来获取锁。SETNX命令的作用是,如果指定的key不存在,则设置key的值为指定的value,并返回1;如果指定的key已经存在,则不设置key的值,并返回0。
success, err := d.client.SetNX(ctx, key, value, expiration).Result()
# 释放锁
为了保证分布式锁的拥有者才能释放锁,我们需要在释放锁时判断锁的拥有者是否是当前进程。在Redis中,我们可以使用Lua脚本来实现这个功能。Lua脚本可以在Redis服务器端执行,这样可以保证在执行Lua脚本时,其他客户端无法执行其他命令,从而保证了Lua脚本的原子性。
Lua脚本如下:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这个Lua脚本的作用是,如果指定的key的值等于指定的value,则删除key,并返回1;否则,不删除key,并返回0。
在Go语言中,我们可以使用redis.Eval方法来执行Lua脚本:
success, err := d.client.Eval(ctx, luaScript, []string{key}, value).Result()
# RedLock
单个redis实例可能会出现故障,为了解决这个问题,我们可以使用多个redis实例来实现分布式锁。RedLock是一种分布式锁的实现方式,它可以在多个Redis实例上实现分布式锁。RedLock的实现方式如下:
- 获取锁时,尝试在多个Redis实例上获取锁,如果获取锁的实例数量大于等于N/2+1,则认为获取锁成功;
- 释放锁时,尝试在所有Redis实例上释放锁。
# 代码
package fsdktype
import (
"context"
"errors"
"time"
"gitea.office.51fanli.com/goweb/fsdk-go/fsdknodelog"
"gitea.office.51fanli.com/goweb/nodelog"
"github.com/go-redis/redis/v8"
)
// 分布式锁
type DistributedLock struct {
client *redis.Client
}
// 实例化分布式锁
func NewDistributedLock(client *redis.Client) *DistributedLock {
return &DistributedLock{
client: client,
}
}
// 尝试获取分布式锁
func (d *DistributedLock) AcquireLock(ctx context.Context, key string, value string, expiration time.Duration) bool {
success, err := d.client.SetNX(ctx, key, value, expiration).Result()
if err != nil {
// 记录日志
fsdknodelog.Record(nodelog.LogEntry{
SerialId: 902606,
Data1: "acquire-lock-error",
Data2: err.Error(),
})
return false
}
return success
}
// 释放分布式锁
func (d *DistributedLock) ReleaseLock(ctx context.Context, key string, value string) bool {
// lua脚本 删除key 并判断value是否一致
luaScript := `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`
success, err := d.client.Eval(ctx, luaScript, []string{key}, value).Result()
if err != nil {
// 记录日志
fsdknodelog.Record(nodelog.LogEntry{
SerialId: 902606,
Data1: "release-lock-error",
Data2: err.Error(),
})
return false
}
return success == int64(1)
}
// RedLock
type RedLock struct {
clients []*redis.Client
}
func NewRedLock(clients []*redis.Client) (*RedLock, error) {
if len(clients) < 3 {
return nil, errors.New("至少需要3个Redis实例")
}
return &RedLock{
clients: clients,
}, nil
}
// 尝试获取分布式锁
func (r *RedLock) AcquireLock(ctx context.Context, key string, value string, expiration time.Duration) bool {
var acquireCount int
quorum := len(r.clients)/2 + 1
for _, client := range r.clients {
dl := NewDistributedLock(client)
if dl.AcquireLock(ctx, key, value, expiration) {
acquireCount++
}
if acquireCount >= quorum {
return true
}
}
return false
}
// 释放分布式锁
func (r *RedLock) ReleaseLock(ctx context.Context, key string, value string) bool {
var releaseCount int
quorum := len(r.clients)/2 + 1
for _, client := range r.clients {
dl := NewDistributedLock(client)
if dl.ReleaseLock(ctx, key, value) {
releaseCount++
}
if releaseCount >= quorum {
return true
}
}
return false
}
DistributedLock
结构体表示一个分布式锁,它包含一个 Redis 客户端。NewDistributedLock
函数用于创建一个新的分布式锁实例。AcquireLock
方法尝试获取分布式锁,如果成功则返回 true,否则返回 false。ReleaseLock
方法释放分布式锁,如果成功则返回 true,否则返回 false。
RedLock
结构体表示一个 RedLock,它包含多个 Redis 客户端。NewRedLock
函数用于创建一个新的 RedLock 实例,需要至少3个 Redis 实例。AcquireLock
方法尝试获取 RedLock,如果成功则返回 true,否则返回 false。ReleaseLock
方法释放 RedLock,如果成功则返回 true,否则返回 false。