面试官:分布式锁最终解决方案是RedLock吗?为什么?

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 面试官:分布式锁最终解决方案是RedLock吗?为什么?

RedLock 是 Redis 分布式锁的一种实现方案,由 Redis 的作者 Salvatore Sanfilippo 提出。

RedLock 算法旨在解决单个 Redis 实例作为分布式锁时可能出现的单点故障问题,通过在多个独立运行的 Redis 实例上同时获取锁的方式来提高锁服务的可用性和安全性。

1.实现思路

RedLock 是对集群的每个节点进行加锁,如果大多数节点(N/2+1)加锁成功,则才会认为加锁成功。
这样即使集群中有某个节点挂掉了,因为大部分集群节点都加锁成功了,所以分布式锁还是可以继续使用的。

2.实现代码

在 Java 开发中,可以使用 Redisson 框架很方便的实现 RedLock,具体操作代码如下:

import org.redisson.Redisson;
import org.redisson.api.RedisClient;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.redisson.RedissonRedLock;

public class RedLockDemo {
   
   

    public static void main(String[] args) {
   
   
        // 创建 Redisson 客户端配置
        Config config = new Config();
        config.useClusterServers()
        .addNodeAddress("redis://127.0.0.1:6379",
                        "redis://127.0.0.1:6380",
                        "redis://127.0.0.1:6381"); // 假设有三个 Redis 节点
        // 创建 Redisson 客户端实例
        RedissonClient redissonClient = Redisson.create(config);
        // 创建 RedLock 对象
        RedissonRedLock redLock = redissonClient.getRedLock("resource");
        try {
   
   
            // 尝试获取分布式锁,最多尝试 5 秒获取锁,并且锁的有效期为 5000 毫秒
            boolean lockAcquired = redLock.tryLock(5, 5000, TimeUnit.MILLISECONDS); 
            if (lockAcquired) {
   
   
                // 加锁成功,执行业务代码...
            } else {
   
   
                System.out.println("Failed to acquire the lock!");
            }
        } catch (InterruptedException e) {
   
   
            Thread.currentThread().interrupt();
            System.err.println("Interrupted while acquiring the lock");
        } finally {
   
   
            // 无论是否成功获取到锁,在业务逻辑结束后都要释放锁
            if (redLock.isLocked()) {
   
   
                redLock.unlock();
            }
            // 关闭 Redisson 客户端连接
            redissonClient.shutdown();
        }
    }
}

3.实现原理

Redisson 中的 RedLock 是基于 RedissonMultiLock(联锁)实现的。

RedissonMultiLock 是 Redisson 提供的一种分布式锁类型,它可以同时操作多个锁,以达到对多个锁进行统一管理的目的。联锁的操作是原子性的,即要么全部锁住,要么全部解锁。这样可以保证多个锁的一致性。

RedissonMultiLock 使用示例如下:

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.multi.MultiLock;

public class RedissonMultiLockDemo {
   
   

    public static void main(String[] args) throws InterruptedException {
   
   
        // 创建 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);

        // 创建多个分布式锁实例
        RLock lock1 = redisson.getLock("lock1");
        RLock lock2 = redisson.getLock("lock2");
        RLock lock3 = redisson.getLock("lock3");

        // 创建 RedissonMultiLock 对象
        MultiLock multiLock = new MultiLock(lock1, lock2, lock3);

        // 加锁
        multiLock.lock();
        try {
   
   
            // 执行任务
            System.out.println("Lock acquired. Task started.");
            Thread.sleep(3000);
            System.out.println("Task finished. Releasing the lock.");
        } finally {
   
   
            // 释放锁
            multiLock.unlock();
        }
        // 关闭客户端连接
        redisson.shutdown();
    }
}

在示例中,我们首先创建了一个 Redisson 客户端并连接到 Redis 服务器。然后,我们使用 redisson.getLock 方法创建了多个分布式锁实例。接下来,我们通过传入这些锁实例来创建了 RedissonMultiLock 对象。

说回正题,RedissonRedLock 是基于 RedissonMultiLock 实现的这点,可以从继承关系看出。

RedissonRedLock 继承自 RedissonMultiLock,核心实现源码如下:

public class RedissonRedLock extends RedissonMultiLock {
   
   
    public RedissonRedLock(RLock... locks) {
   
   
        super(locks);
    }

    /**
     * 锁可以失败的次数,锁的数量-锁成功客户端最小的数量
     */
    @Override
    protected int failedLocksLimit() {
   
   
        return locks.size() - minLocksAmount(locks);
    }

    /**
     * 锁的数量 / 2 + 1,例如有3个客户端加锁,那么最少需要2个客户端加锁成功
     */
    protected int minLocksAmount(final List<RLock> locks) {
   
   
        return locks.size()/2 + 1;
    }

    /** 
     * 计算多个客户端一起加锁的超时时间,每个客户端的等待时间
     */
    @Override
    protected long calcLockWaitTime(long remainTime) {
   
   
        return Math.max(remainTime / locks.size(), 1);
    }

    @Override
    public void unlock() {
   
   
        unlockInner(locks);
    }
}

从上述源码可以看出,Redisson 中的 RedLock 是基于 RedissonMultiLock(联锁)实现的,当 RedLock 是对集群的每个节点进行加锁,如果大多数节点,也就是 N/2+1 个节点加锁成功,则认为 RedLock 加锁成功。

4.存在问题

RedLock 主要存在以下两个问题:

  1. 性能问题:RedLock 要等待大多数节点返回之后,才能加锁成功,而这个过程中可能会因为网络问题,或节点超时的问题,影响加锁的性能。
  2. 并发安全性问题:当客户端加锁时,如果遇到 GC 可能会导致加锁失效,但 GC 后误认为加锁成功的安全事故,例如以下流程:
    1. 客户端 A 请求 3 个节点进行加锁。
    2. 在节点回复处理之前,客户端 A 进入 GC 阶段(存在 STW,全局停顿)。
    3. 之后因为加锁时间的原因,锁已经失效了。
    4. 客户端 B 请求加锁(和客户端 A 是同一把锁),加锁成功。
    5. 客户端 A GC 完成,继续处理前面节点的消息,误以为加锁成功。
    6. 此时客户端 B 和客户端 A 同时加锁成功,出现并发安全性问题。

      5.已废弃 RedLock

      因为 RedLock 存在的问题争议较大,且没有完美的解决方案,所以 Redisson 中已经废弃了 RedLock,这一点在 Redisson 官方文档中能找到,如下图所示:

      6.废弃 RedLock 后的解决方案

      虽然 Redisson 中已经废弃了 RedLock,但是你可以直接使用 Redisson 中的普通的加锁即可,因为它的普通锁会基于 wait 机制,等待锁将信息同步到从节点,从而保证数据一致性的,虽然不能完全避免数据一致性问题,但也能最大限度的保证数据的一致性。

      课后思考

      既然普通的分布式锁存在单点问题?而 RedLock 又不是最完美的解决方案,那么在分布式锁领域,谁才是最终的解决方案呢?请在评论区留下您的解决方案,以及对应的原因?

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5月前
|
存储 SQL 微服务
常用的分布式事务解决方案(三)
常用的分布式事务解决方案(三)
|
5月前
|
关系型数据库 MySQL
常见分布式事务的解决方案(一)
常见分布式事务的解决方案(一)
|
1月前
|
存储 监控 Java
招行面试: 分布式调度 设计,要考虑 哪些问题?
45岁资深架构师尼恩在读者交流群中分享了关于设计分布式调度框架时需考虑的关键问题。近期有小伙伴在面试招商银行时遇到了相关难题,因准备不足而失利。为此,尼恩系统化地梳理了以下几点核心内容,帮助大家在面试中脱颖而出,实现“offer直提”。
|
2月前
|
消息中间件 NoSQL Java
面试官必问的分布式锁面试题,你答得上来吗?
本文介绍了分布式锁的概念、实现方式及其在项目中的应用。首先通过黄金圈法则分析了分布式锁的“为什么”、“怎么做”和“做什么”。接着详细讲解了使用 Redisson 和 SpringBoot + Lettuce 实现分布式锁的具体方法,包括代码示例和锁续期机制。最后解释了 Lua 脚本的作用及其在 Redis 中的应用,强调了 Lua 保证操作原子性的重要性。文中还提及了 Redis 命令组合执行时的非原子性问题,并提供了 Lua 脚本实现分布式锁的示例。 如果你对分布式锁感兴趣或有相关需求,欢迎关注+点赞,必回关!
61 2
|
3月前
|
缓存 NoSQL PHP
Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出
本文深入探讨了Redis作为PHP缓存解决方案的优势、实现方式及注意事项。Redis凭借其高性能、丰富的数据结构、数据持久化和分布式支持等特点,在提升应用响应速度和处理能力方面表现突出。文章还介绍了Redis在页面缓存、数据缓存和会话缓存等应用场景中的使用,并强调了缓存数据一致性、过期时间设置、容量控制和安全问题的重要性。
68 5
|
4月前
|
消息中间件 架构师 Java
阿里面试:秒杀的分布式事务, 是如何设计的?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试阿里、滴滴、极兔等一线互联网企业时,遇到了许多关于分布式事务的重要面试题。为了帮助大家更好地应对这些面试题,尼恩进行了系统化的梳理,详细介绍了Seata和RocketMQ事务消息的结合,以及如何实现强弱结合型事务。文章还提供了分布式事务的标准面试答案,并推荐了《尼恩Java面试宝典PDF》等资源,帮助大家在面试中脱颖而出。
|
4月前
|
NoSQL Java Redis
Redlock分布式锁高并发下有什么问题
Redlock分布式锁在高并发场景下可能面临的问题主要包括:网络延迟、时钟偏移、单点故障、宕机重启问题、脑裂问题以及效率低等。接下来,我将使用Java代码示例来说明其中一些问题。
149 12
|
5月前
|
消息中间件 中间件 关系型数据库
常用的分布式事务解决方案(四)
常用的分布式事务解决方案(四)
|
5月前
常用的分布式事务解决方案(二)
常用的分布式事务解决方案(二)
|
4月前
|
SQL NoSQL 安全
分布式环境的分布式锁 - Redlock方案
【10月更文挑战第2天】Redlock方案是一种分布式锁实现,通过在多个独立的Redis实例上加锁来提高容错性和可靠性。客户端需从大多数节点成功加锁且总耗时小于锁的过期时间,才能视为加锁成功。然而,该方案受到分布式专家Martin的质疑,指出其在特定异常情况下(如网络延迟、进程暂停、时钟偏移)可能导致锁失效,影响系统的正确性。Martin建议采用fencing token方案,以确保分布式锁的正确性和安全性。
78 0