如何用Redis实现分布式锁?RedLock算法的核心思想?Redisson的看门狗机制原理?

一、Redis分布式锁基础实现

public class RedisDistributedLock {
    private JedisPool jedisPool;
    private String lockKey;
    private String clientId;
    private int expireTime = 30; // 默认30秒

    public boolean tryLock() {
        try (Jedis jedis = jedisPool.getResource()) {
            // NX表示不存在时设置,PX设置过期时间(毫秒)
            String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().px(expireTime * 1000));
            return "OK".equals(result);
        }
    }

    public void unlock() {
        try (Jedis jedis = jedisPool.getResource()) {
            // 使用Lua脚本保证原子性
            String script = 
                "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";
            jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));
        }
    }
}

关键点:

  1. 使用SET NX PX命令保证原子性
  • 单命令原子性:Redis服务器单线程顺序执行命令
  • 仅包含SET操作:仅完成键值设置和过期时间配置
  • 无逻辑判断:仅判断键是否存在(NX特性)
  1. 客户端唯一标识(clientId)防止误删
  2. Lua脚本保证解锁操作的原子性
  • 多命令原子性:组合GET、DEL等命令的复合操作
  • 包含业务逻辑:实现"比较后删除"的CAS(Compare And Set)操作
  • 支持复杂流程:可包含条件判断、循环等逻辑

缺陷:
如果锁存在A redis节点,然后B是A的从库,服务先获取A节点的redis key锁,如果A网络波动的时候的时候,主从切换,B节点升级为主节点,这个时候另一个服务获取B节点的相同的redis key,这种情况就发生脑裂了。

二、RedLock算法核心思想

RedLock算法由Redis作者提出,主要解决单点故障问题:

  1. 多节点部署:使用5个(奇数)独立的Redis节点
  2. 顺序获取:客户端依次向所有节点申请锁
  3. 成功条件:获得超过半数的锁(3个)
  4. 耗时计算:总耗时应小于锁的TTL时间
  5. 失败释放:失败时需要释放所有已获得的锁

三、Redisson看门狗机制原理

public class RedissonWatchdogExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        
        RedissonClient redisson = Redisson.create(config);
        RLock lock = redisson.getLock("myLock");

        try {
            // 默认30秒过期,看门狗自动续期
            lock.lock();
            // 业务逻辑执行时间可能超过30秒
            Thread.sleep(40000); 
        } finally {
            lock.unlock();
        }
    }
}

集群防止脑裂

Redisson 实现分布式锁的核心机制和集群脑裂防护原理如下:

1. 基础锁实现原理
-- Redis 原子操作脚本
if redis.call('exists', KEYS[1]) == 0 then
    redis.call('hset', KEYS[1], ARGV[2], 1)
    redis.call('pexpire', KEYS[1], ARGV[1])
    return nil
end
2. 集群模式防护机制
// RedissonMultiLock 集群锁实现
List<RLock> locks = new ArrayList<>();
locks.add(redissonClient1.getLock("lock1"));
locks.add(redissonClient2.getLock("lock2"));
RLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[0]));

关键实现要点:

1. 多节点提交机制
  • 需要 N/2+1 个节点成功获取锁才算有效
  • 使用异步线程维持锁心跳(Watchdog 机制)
// Watchdog 线程实现(伪代码)
private void scheduleExpirationRenewal() {
    if (expirationRenewalMap.putIfAbsent(lockName, newTimeout) == null) {
        // 每 10 秒续期一次
        internalLockLeaseTime / 3 周期执行
    }
}
2. 脑裂防护策略
  • 同步延迟控制:主从同步超时时间 > 锁过期时间
  • 多数派原则:成功获取锁的节点数 > 集群半数节点
  • 故障转移阻断:当节点失联时自动启动锁失效倒计时
3. 异常处理机制
// 锁释放时的集群同步
public void unlock() {
    for (RLock lock : locks) {
        if (lock.isHeldByCurrentThread()) {
            lock.unlockAsync();
        }
    }
}

集群模式下重要参数配置建议:

# redisson.yaml
clusterServersConfig:
  nodeAddresses:
    - "redis://127.0.0.1:7000"
    - "redis://127.0.0.1:7001"
  scanInterval: 1000
  retryAttempts: 3
  retryInterval: 500
  slaveConnectionMinimumIdleSize: 8
  failedSlaveReconnectionInterval: 30000

该实现通过以下方式保证脑裂场景下的数据一致性:

  1. 使用 Raft 式多数派提交协议
  2. 网络分区时自动降级为只读模式
  3. 主节点切换后新主节点会等待旧主节点锁超时
  4. 客户端自动检测集群拓扑变化并重建连接
    看门狗机制关键点:
  5. 后台线程:每10秒检查锁状态
  6. 自动续期:当业务未完成时,将过期时间重置为30秒
  7. 客户端存活判断:只有客户端保持活跃才会续期
  8. 默认配置:lockWatchdogTimeout=30秒

四、完整方案对比

方案优点缺点
基础Redis锁实现简单单点故障风险
RedLock算法高可用性实现复杂、性能损耗
Redisson实现自动续期、可重入锁、多种锁类型需要维护客户端连接

实际建议:

  1. 单节点场景使用Redisson基础锁
  2. 高可用场景使用Redisson+Redis Cluster
  3. 极端可靠性需求使用RedLock算法

生产环境注意事项:

  1. 合理设置超时时间(业务平均耗时 * 2)
  2. 监控锁等待时间和获取次数
  3. 为不同业务使用不同的锁前缀
  4. 做好锁等待超时的异常处理

竞品分析

分布式锁实现方案对比及优劣势分析

一、ZooKeeper 实现方案
// 使用Curator框架示例
public class ZkDistributedLock {
    private CuratorFramework client;
    private InterProcessMutex lock;
    
    public boolean tryLock(String lockPath) throws Exception {
        lock = new InterProcessMutex(client, lockPath);
        return lock.acquire(3, TimeUnit.SECONDS); // 3秒获取超时
    }
    
    public void unlock() throws Exception {
        if (lock != null) {
            lock.release();
        }
    }
}

核心原理
通过创建临时顺序节点实现,获取锁的客户端会生成有序节点,只有序号最小的节点持有锁

优势

  1. 自动释放(会话失效时自动删除节点)
  2. 公平锁机制(顺序节点)
  3. 强一致性保证

劣势

  1. 写操作性能低于Redis
  2. 需要维护ZooKeeper集群
  3. 客户端实现相对复杂

二、Etcd 实现方案
// 使用jetcd客户端示例
public class EtcdDistributedLock {
    private Client client;
    private Lease lease;
    
    public boolean tryLock(String lockKey) throws Exception {
        lease = client.getLeaseClient().grant(30).get(); // 30秒租约
        Txn txn = client.getKVClient().txn();
        txn.If(new Cmp(lockKey, Cmp.Op.EQUAL, CmpTarget.version(0)))
           .Then(Op.put(lockKey, "locked", PutOption.newBuilder().withLeaseId(lease.getID()).build()))
           .Else(Op.get(lockKey));
        return txn.commit().get().isSucceeded();
    }
}

核心原理
基于租约(Lease)机制,利用事务操作实现原子性锁获取

优势

  1. 强一致性(Raft协议)
  2. 自动续期机制
  3. 支持公平锁/非公平锁

劣势

  1. 运维复杂度较高
  2. 客户端生态不如Redis完善
  3. 性能低于Redis

三、数据库实现方案
// 基于MySQL的乐观锁实现
public class DbDistributedLock {
    @Transactional
    public boolean tryLock(String lockName) {
        // 使用唯一索引约束
        int result = jdbcTemplate.update(
            "INSERT INTO distributed_lock(lock_name,owner) VALUES (?,?) ON DUPLICATE KEY UPDATE owner=IF(expire_time < NOW(), VALUES(owner), owner)",
            lockName, UUID.randomUUID().toString());
        return result > 0;
    }
}

核心原理
基于数据库唯一约束或排他锁(SELECT FOR UPDATE)

优势

  1. 无需额外中间件
  2. 实现简单快速

劣势

  1. 性能差(高并发场景容易成为瓶颈)
  2. 无自动释放机制
  3. 死锁风险较高

方案对比总结表

方案一致性性能自动释放实现复杂度适用场景
Redis最终一致支持简单高并发、允许短暂不一致
ZooKeeper强一致支持复杂强一致性要求、公平锁场景
Etcd强一致支持较复杂强一致性且需要自动续期
数据库强一致不支持简单低频访问、无中间件环境的应急方案

选型建议

  1. 追求性能 ➜ Redis(Redisson实现)
  2. 强一致性要求 ➜ ZooKeeper/Etcd
  3. 无中间件环境 ➜ 数据库方案(需谨慎处理超时)
  4. 混合使用场景 ➜ 可组合使用(如Redis做主锁,数据库做备用锁)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值