Redis单实例分布式锁SET NX实现
分布式锁是微服务架构中保证数据一致性的关键组件。在多实例部署环境下,本地互斥锁无法跨进程协调,需要借助Redis等外部存储实现跨实例的锁机制。Redis分布式锁因其高性能、低延迟的特性,在高并发设计和分布式事务场景中被广泛采用。
最基础的Redis分布式锁通过SET命令的NX(Not eXists)选项实现原子性加锁:
package distributedlock
import (
"context"
"time"
"github.com/redis/go-redis/v9"
)
type RedisLock struct {
client *redis.Client
key string
value string
ttl time.Duration
}
func NewRedisLock(client *redis.Client, key, value string, ttl time.Duration) *RedisLock {
return &RedisLock{
client: client,
key: key,
value: value,
ttl: ttl,
}
}
func (l *RedisLock) TryLock(ctx context.Context) (bool, error) {
ok, err := l.client.SetNX(ctx, l.key, l.value, l.ttl).Result()
if err != nil {
return false, err
}
return ok, nil
}
func (l *RedisLock) Unlock(ctx context.Context) error {
script := redis.NewScript(`
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`)
_, err := script.Run(ctx, l.client, []string{l.key}, l.value).Result()
return err
}
加锁时使用SET key value NX PX ttl一条命令完成设置键值、条件判断和过期时间三个操作,保证原子性。解锁时必须使用Lua脚本先比较value再删除,不能直接DEL。原因在于锁可能已经过期被其他实例获取,直接DEL会误删别人的锁。
value必须是全局唯一标识(如UUID),用于解锁时验证锁的持有者。如果所有实例使用相同value,就无法区分锁属于谁,导致误删。
锁超时与续期机制设计
设置TTL是防止持有锁的实例崩溃导致死锁的必要措施。但业务执行时间超过TTL时,锁会自动释放,其他实例获取锁后可能导致数据不一致。续期机制通过后台goroutine定期延长锁的TTL解决此问题:
func (l *RedisLock) LockWithRenewal(ctx context.Context) error {
ok, err := l.TryLock(ctx)
if err != nil {
return err
}
if !ok {
return ErrLockConflict
}
renewalCtx, cancel := context.WithCancel(ctx)
go l.renewLoop(renewalCtx)
l.cancelFunc = cancel
return nil
}
func (l *RedisLock) renewLoop(ctx context.Context) {
ticker := time.NewTicker(l.ttl / 3)
defer ticker.Stop()
script := redis.NewScript(`
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("pexpire", KEYS[1], ARGV[2])
else
return 0
end
`)
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
_, err := script.Run(ctx, l.client,
[]string{l.key},
l.value,
int64(l.ttl.Milliseconds()),
).Result()
if err != nil {
return
}
}
}
}
续期间隔设为TTL的1/3,在锁过期前有两次续期机会。续期脚本同样是原子操作,先验证value再延长过期时间。Unlock时先取消续期goroutine再释放锁,避免goroutine泄漏。这种续期机制类似于Redisson的WatchDog机制。
Redlock算法原理与多节点容错
单实例Redis分布式锁存在单点故障风险。当Redis主节点宕机时,若从节点尚未同步锁数据就提升为主,会导致锁丢失。Redlock算法由Redis作者Antirez提出,通过在多个独立Redis实例上加锁解决单点问题。
Redlock的核心流程:客户端获取当前时间T1,依次向N个(通常5个)独立Redis实例发送SET NX加锁请求,统计成功加锁的实例数。如果超过半数(N/2+1)成功且总耗时小于锁的TTL,则认为加锁成功。实际锁的有效时间等于TTL减去获取锁的耗时。
type RedLock struct {
clients []*redis.Client
quorum int
retryCount int
retryDelay time.Duration
}
func (rl *RedLock) Lock(ctx context.Context, key, value string, ttl time.Duration) (bool, error) {
for attempt := 0; attempt < rl.retryCount; attempt++ {
success := 0
start := time.Now()
for _, client := range rl.clients {
ok, err := client.SetNX(ctx, key, value, ttl).Result()
if err == nil && ok {
success++
}
}
elapsed := time.Since(start)
drift := ttl / 10
if success >= rl.quorum && elapsed < (ttl - drift) {
return true, nil
}
rl.unlockAll(ctx, key, value)
if attempt < rl.retryCount-1 {
time.Sleep(rl.retryDelay)
}
}
return false, nil
}
Redlock的争议点在于对时钟同步的依赖。如果系统发生长时间的GC暂停或时钟跳变,客户端可能持有已过期的锁。实际工程中,Redlock适用于对正确性要求较高但可容忍极小概率错误的场景,金融级场景建议使用基于共识算法的锁服务(如etcd、ZooKeeper)。
分布式锁的羊群效应与可重入设计
当大量客户端同时竞争同一把锁时,锁释放瞬间所有等待者同时发起SET NX请求,造成Redis负载突增。引入随机退避时间缓解羊群效应:加锁失败后等待一个随机时间再重试,避免所有客户端同步竞争。
可重入锁允许同一持有者多次获取锁,适用于递归调用场景。实现方式是在Redis中维护一个Hash结构,key存储锁名,field存储持有者标识,value存储重入计数:
var reentrantLockScript = redis.NewScript(`
local key = KEYS[1]
local holder = ARGV[1]
local ttl = tonumber(ARGV[2])
if redis.call("exists", key) == 0 then
redis.call("hset", key, holder, 1)
redis.call("pexpire", key, ttl)
return 1
end
if redis.call("hexists", key, holder) == 1 then
redis.call("hincrby", key, holder, 1)
redis.call("pexpire", key, ttl)
return 1
end
return 0
`)
var reentrantUnlockScript = redis.NewScript(`
local key = KEYS[1]
local holder = ARGV[1]
if redis.call("hexists", key, holder) == 0 then
return -1
end
local count = redis.call("hincrby", key, holder, -1)
if count <= 0 then
redis.call("del", key)
return 0
end
return count
`)
可重入锁的解锁操作递减计数,当计数归零时删除锁。可重入锁增加了实现复杂度,在服务治理中需要根据业务场景权衡是否需要可重入特性。大多数场景下,非重入锁配合合理的锁粒度设计已能满足需求。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/redis-fen-bu-shi-suo-shi-xian-yuan-li-yu-redlock-suan-fa/