介绍一下Redis的高可用方案

介绍一下Redis的高可用方案

主从复制(基础高可用)✅

这是所有高可用方案的基石,核心解决数据备份读写分离问题。

核心架构

工作原理

  1. 从节点启动后,向主节点发送PSYNC命令
  2. 首次连接:主节点执行全量复制(生成 RDB 文件发送给从节点)
  3. 后续连接:主节点执行增量复制(只发送偏移量之后的命令)

优缺点

  • ✅ 优点:实现简单,读能力线性提升,数据有备份
  • ❌ 缺点:不能自动故障转移,主节点挂了需要手动切换;单主写能力瓶颈

适用场景

  • 测试环境、小流量业务(QPS < 1000)
  • 只需要读写分离和数据备份的场景

哨兵模式(Sentinel)🛡️

在主从复制基础上,解决自动故障转移问题,是中小流量生产环境最常用的方案。

三大核心功能

  1. 监控:持续检测主从节点是否正常运行
  2. 选主:主节点故障时,自动从从节点中选举新主
  3. 通知:将故障转移结果通知客户端和其他节点

核心架构

故障转移流程

  1. 单个哨兵发现主节点不可达 → 主观下线
  2. 超过半数哨兵确认主节点不可达 → 客观下线
  3. 哨兵集群选举出一个领导者哨兵
  4. 领导者哨兵从从节点中选举新主节点
  5. 更新配置,通知客户端,旧主节点变为从节点

优缺点

  • ✅ 优点:自动故障转移,高可用,运维简单
  • ❌ 缺点:不能水平扩展,单主写能力瓶颈;哨兵集群本身也需要高可用

适用场景

  • 中小流量生产环境(QPS < 10000)
  • 写压力不大,读压力较大的业务

Redis Cluster 集群模式(分片高可用)⚡

解决单节点写能力瓶颈水平扩展问题,是大流量生产环境的标准方案。

核心概念:16384 个哈希槽

  • Redis Cluster 将所有数据映射到 16384 个哈希槽中
  • 每个主节点负责一部分哈希槽
  • 客户端通过CRC16(key) % 16384计算 key 所在的槽,直接连接对应节点

核心架构

故障转移机制

  • 每个节点都监控其他节点的状态
  • 当某个主节点故障时,它的从节点会自动晋升为新主节点
  • 集群自动更新哈希槽映射,客户端通过MOVED重定向到新节点

优缺点

  • ✅ 优点:线性水平扩展,写能力和读能力都能线性提升;自动故障转移
  • ❌ 缺点:运维复杂;不支持跨槽的多键操作(如MGET、MSET);数据迁移有短暂影响

适用场景

  • 大流量高并发生产环境(QPS > 10000)
  • 数据量超过单节点内存容量的业务

三大核心方案对比表 📊

方案 最小节点数 故障转移 水平扩展 写能力 读能力 运维复杂度 适用场景
主从复制 2 ❌ 手动 ❌ 不支持 单主瓶颈 线性提升 测试环境、小流量
哨兵模式 5(3 哨兵 + 2 主从) ✅ 自动 ❌ 不支持 单主瓶颈 线性提升 中小流量生产
Redis Cluster 6(3 主 3 从) ✅ 自动 ✅ 线性扩展 线性提升 线性提升 大流量高并发

企业级进阶优化与云原生方案 💡

  1. 读写分离优化:使用客户端分片或代理(如 Twemproxy、Codis)实现更灵活的读写分离
  2. 持久化策略:主节点关闭 RDB,从节点开启 RDB+AOF 混合持久化,平衡性能和数据安全
  3. 慢查询监控:定期清理慢查询日志,设置合理的超时时间
  4. 云原生方案:在 K8s 环境中使用 Redis Operator(如 Redis Cluster Operator),实现自动化部署、扩缩容和故障转移
  5. 云厂商托管:对于核心业务,建议使用阿里云 Redis、腾讯云 Redis 等托管服务,省心又稳定

面试加分总结 ✨

在实际项目中,我一般会根据业务阶段和流量规模来选择合适的方案:

  1. 项目初期:主从复制 + 哨兵,运维简单,成本低
  2. 业务增长期:当单主写能力达到瓶颈时,迁移到 Redis Cluster
  3. 云原生环境:优先使用 Redis Operator,自动化运维效率更高
  4. 核心业务:使用云厂商托管 Redis,提供 99.99% 以上的可用性

核心代码与技术亮点实现 💻

1 Jedis 连接哨兵集群(生产级配置)

java 复制代码
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisSentinelPool;
import java.util.HashSet;
import java.util.Set;

public class RedisSentinelConfig {
    // 技术亮点1:单例模式+双重检查锁,保证连接池唯一
    private static volatile JedisSentinelPool jedisSentinelPool;

    public static JedisSentinelPool getJedisSentinelPool() {
        if (jedisSentinelPool == null) {
            synchronized (RedisSentinelConfig.class) {
                if (jedisSentinelPool == null) {
                    // 哨兵节点地址集合
                    Set<String> sentinels = new HashSet<>();
                    sentinels.add("192.168.1.10:26379");
                    sentinels.add("192.168.1.11:26379");
                    sentinels.add("192.168.1.12:26379");

                    // 技术亮点2:精细化连接池配置,避免连接泄漏
                    jedisSentinelPool = new JedisSentinelPool(
                            "mymaster", // 主节点名称
                            sentinels,
                            "password123", // Redis密码
                            2000, // 连接超时
                            100, // 最大连接数
                            10, // 最小空闲连接数
                            30000 // 连接最大空闲时间
                    );
                }
            }
        }
        return jedisSentinelPool;
    }

    // 使用示例
    public static void main(String[] args) {
        try (Jedis jedis = getJedisSentinelPool().getResource()) {
            jedis.set("key", "value");
            System.out.println(jedis.get("key"));
        }
    }
}

2 Spring Boot 集成 Redis Cluster(Lettuce 客户端)

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisClusterConfig {
    @Bean
    public LettuceConnectionFactory redisConnectionFactory() {
        RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
        // 集群节点地址
        clusterConfig.clusterNode("192.168.1.20", 6379);
        clusterConfig.clusterNode("192.168.1.21", 6379);
        clusterConfig.clusterNode("192.168.1.22", 6379);
        clusterConfig.setPassword("password123");
        // 技术亮点3:开启集群重定向自动跟随
        clusterConfig.setMaxRedirects(3);

        return new LettuceConnectionFactory(clusterConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // 技术亮点4:使用String序列化器,避免默认JDK序列化的乱码问题
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

3 技术亮点:自定义 Redis Cluster 哈希标签路由

解决 Redis Cluster 不支持跨槽多键操作的核心方案

java 复制代码
/**
 * 技术亮点:自定义哈希标签工具类
 * 将相同业务的key强制路由到同一个哈希槽,支持MGET/MSET等多键操作
 */
public class RedisHashTagUtil {
    // 哈希标签分隔符
    private static final String HASH_TAG_START = "{";
    private static final String HASH_TAG_END = "}";

    /**
     * 生成带哈希标签的key
     * @param businessId 业务ID(如用户ID、订单ID)
     * @param keySuffix key后缀
     * @return 带哈希标签的key
     */
    public static String generateKey(String businessId, String keySuffix) {
        return HASH_TAG_START + businessId + HASH_TAG_END + ":" + keySuffix;
    }

    // 使用示例
    public static void main(String[] args) {
        // 同一个用户的所有key都会路由到同一个哈希槽
        String userInfoKey = generateKey("user123", "info");
        String userOrderKey = generateKey("user123", "orders");
        String userCartKey = generateKey("user123", "cart");

        // 现在可以安全地执行MGET操作
        // redisTemplate.opsForValue().multiGet(Arrays.asList(userInfoKey, userOrderKey, userCartKey));
    }
}

4 技术亮点:主从切换时的客户端重试机制

java 复制代码
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;
import redis.clients.jedis.exceptions.JedisConnectionException;

@Component
public class RedisRetryService {
    /**
     * 技术亮点:使用Spring Retry实现主从切换时的自动重试
     * 当发生连接异常时,最多重试3次,每次间隔1秒
     */
    @Retryable(
            value = JedisConnectionException.class,
            maxAttempts = 3,
            backoff = @Backoff(delay = 1000, multiplier = 2)
    )
    public String getWithRetry(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}

技术难点与解决方案汇总 🛠️

技术难点 问题描述 根本原因 解决方案
主从复制数据不一致 从节点数据比主节点旧,读取到脏数据 主从同步有延迟;网络抖动导致增量复制中断 1. 监控主从延迟(info replication中的lag字段)2. 关键业务强制读主节点3. 开启repl-disable-tcp-nodelay减少延迟
哨兵模式脑裂问题 网络分区导致出现两个主节点,数据丢失 哨兵集群无法达成共识;旧主节点恢复后继续接收写请求 1. 配置min-replicas-to-write 1(至少 1 个从节点确认才允许写)2. 配置min-replicas-max-lag 10(从节点延迟超过 10 秒拒绝写)3. 哨兵节点数量必须为奇数,且至少 3 个
Redis Cluster 跨槽操作 执行MGET/MSET/ 事务时抛出CrossSlot异常 不同 key 映射到不同哈希槽,集群不支持跨槽操作 1. 使用哈希标签将相关 key 路由到同一个槽(推荐)2. 客户端拆分多键操作,分别执行后合并结果3. 使用代理层(如 Codis)自动处理跨槽操作
大集群数据迁移 扩容 / 缩容时数据迁移慢,影响业务 全量复制 RDB 文件大;迁移过程中阻塞节点 1. 采用增量迁移方式,先迁移全量数据,再同步增量命令2. 低峰期执行迁移操作3. 限制迁移速度(cluster-migration-barrier)
主从切换请求丢失 主节点故障时,未同步到从节点的写请求丢失 Redis 异步复制机制,主节点写成功后立即返回客户端 1. 开启WAIT命令,等待至少 N 个从节点确认后再返回2. 业务层实现幂等性,允许重复请求3. 核心业务使用强一致性方案(如 Redis 7.0 的 WAITAOF)
缓存雪崩 大量缓存同时过期,数据库压力骤增 缓存过期时间设置相同;Redis 集群整体宕机 1. 给缓存过期时间添加随机值(±5 分钟)2. 搭建 Redis 集群保证高可用3. 服务降级和熔断机制

面试加分总结 ✨

以上就是我对 Redis 高可用方案的完整理解。在实际项目中,我不仅掌握了这四种核心方案的原理和部署,还解决了很多生产环境中的实际问题,比如脑裂问题、跨槽操作问题主从切换数据丢失问题

我认为 Redis 高可用的核心不是简单地搭建集群,而是根据业务特点选择合适的方案,并做好监控和应急预案 。比如对于金融支付业务,我会优先保证数据一致性,开启WAIT命令和强一致复制;对于电商秒杀业务,我会优先保证性能,使用 Redis Cluster 分片集群,并做好缓存降级。

🎤 面试现场实录

🙋‍♂️ 面试官 :你好,今天我们聊一道场景设计题:介绍一下你对 Redis 高可用方案的理解,以及你们项目中是怎么用的? 不用紧张,想到哪说到哪,我来顺着问。

🧱 主从复制 ------ 地基

👨‍💻 候选人:

好的,我从最基础的开始讲。如果只部署单个 Redis,它一旦挂了业务就全停,所以第一步我们一定会做主从复制

架构很简单:一个 Master 负责处理写请求,挂一或多个 Slave,Slave 主要负责读请求,同时异步从 Master 同步数据。这样至少数据有了多副本,不至于单点丢失。

🙋‍♂️ 面试官:

嗯,那这算高可用吗?

👨‍💻 候选人:

严格来说不算,它只是数据冗余 。因为 Master 挂了之后,Slave 没法自动接管,需要手动敲 SLAVEOF NO ONE 命令把一台从库提成主库。这个过程运维介入慢,业务中断时间长。所以主从复制只是地基,高可用得在这个基础上再加东西。

🙋‍♂️ 面试官:

明白,那你们怎么解决自动切换的问题?

🛡️ 哨兵 Sentinel ------ 自动容灾

👨‍💻 候选人:

我们在主从之上部署哨兵集群。哨兵本质上也是一个 Redis 进程,只是它不存数据,专门盯着主节点和从节点的健康状态。

整个机制我分几步说:

  1. 监控:每个哨兵定期 PING 主从节点。
  2. 主观下线:如果一个哨兵发现主节点 PING 不通了,会认为它"主观下线"。
  3. 客观下线 :为了避免网络抖动误判,必须达到配置的 quorum 数量(比如3个哨兵里至少2个)都认为它挂了,才会真正判定"客观下线",触发故障转移。
  4. 选举 Leader:哨兵集群内部用类似 Raft 的机制选出一个哨兵领导者来执行切换。
  5. 切换:Leader 从健康的从库中选一个提升为新主,改配置,然后通知其他从库去复制新主。最关键的是,它还会通过 Pub/Sub 机制通知客户端新主地址。

这样,整个切换过程几秒到十几秒,运维完全不用干预。我们很多中小型项目,用哨兵方案就足够了。😊

🙋‍♂️ 面试官:

不错,那你遇到过脑裂的问题吗?怎么解决的?

👨‍💻 候选人:

遇到过,这是个经典坑。比如网络分区,旧主其实没死,只是和哨兵失联了,哨兵选出了新主,这时就存在两个 Master 同时接收写入。等网络恢复,旧主的数据会被覆盖丢掉。

我们的解决办法是在 Master 上配置两个参数:

  • min-replicas-to-write 1:至少要有1个从库连接。
  • min-replicas-max-lag 10:从库复制延迟不能超过10秒。

如果达不到这个条件,旧主就拒绝写入,这样它自己就发现"我可能被孤立了",主动放弃写入,避免脑裂数据冲突。虽然会牺牲一部分可用性,但保证了数据一致。🛡️

🙋‍♂️ 面试官:

那对于异步复制导致的数据丢失,你们怎么看?

👨‍💻 候选人:

确实,主挂时还没来得及传给从库的数据就丢了。我们一般这样取舍:如果对一致性要求极高(比如库存扣减),就用 Redis 的 WAIT 命令强制同步复制一定数量的从库再返回,但这会增加延迟。大部分缓存场景我们接受少量丢失,靠持久化兜底,业务上做好最终一致性的补偿逻辑。

🌐 Redis Cluster ------ 海量数据与流量

🙋‍♂️ 面试官:

哨兵方案听起来很成熟了,那为什么后面还要用 Redis Cluster?

👨‍💻 候选人:因为哨兵只能解决高可用 ,但解决不了数据量大****和写流量高 的问题。所有写请求都还压在一个 Master 上,存储也受单机内存限制。当数据量上了百 G、TPS 上万,哨兵方案就吃力了,这时就需要 Redis Cluster,它既高可用,又能水平扩展

核心设计我讲三点:

  • 分片 :把整个数据空间划分成 16384 个哈希槽(slot),每个主节点负责一部分槽。Key 通过 CRC16(key) % 16384 确定属于哪个槽,然后去对应节点处理。
  • 去中心化 :所有节点之间用 Gossip 协议互相交换状态,没有中心代理,每个节点都知道整个集群的拓扑。
  • 内建故障转移:每个主节点可以挂至少一个从节点。当某个主挂了,它的从节点会发起选举(由集群中其他主节点投票),晋升为新主,这个过程不依赖任何外部哨兵,集群自己搞定。

🙋‍♂️ 面试官:

那客户端怎么知道去哪个节点找数据?

👨‍💻 候选人:

这就要用智能客户端 ,像 Java 里的 LettuceJedisCluster 。它们会缓存槽与节点的映射关系,计算好槽后直接连目标节点。如果因为扩缩容槽迁移了,节点会返回 MOVED 错误码和新节点地址,客户端会自动更新映射并重试,对业务几乎透明。👍

🙋‍♂️ 面试官:

Cluster 有什么限制?

👨‍💻 候选人:

最主要的是多 Key 操作限制,比如 mget、事务,要求所有 Key 必须在同一个槽。我们可以用 {} 强制让部分 Key 落到同一槽,但不能滥用。另外跨节点的事务不支持,所以我们会在设计 Key 时尽量规避这种需求。

📊 对比与选型

🙋‍♂️ 面试官:

最后你帮我总结一下,什么时候选什么方案?

👨‍💻 候选人:

我画个表吧,一目了然:

方案 自动故障转移 写扩展 运维复杂度 适合场景
主从 ❌ 手动 测试、对可用性无要求
哨兵 Sentinel ✅ 自动 ❌ (只有读扩展) ⭐⭐ 中小型项目首选,数据量不大但必须自动容灾
Cluster ✅ 自动 ✅ (读写都可扩展) ⭐⭐⭐ 大流量/海量数据,互联网核心链路

我们一般的路径是:项目初期用哨兵 ,等数据量或写入压力上来了,再平滑迁移到 Cluster。迁移前会在备集群先建好 Cluster,用双写或同步工具切流。

☕ Java 研发配合点

🙋‍♂️ 面试官:

很好,最后说下你们 Java 客户端怎么配合这些方案?

👨‍💻 候选人:

  • 哨兵模式 :我们用 LettuceRedisSentinelClientJedisSentinelPool,只需配置哨兵地址,框架自动发现并连接主库,主从切换时客户端能收到通知并重连。
  • Cluster 模式 :直接用 Spring Boot 默认的 Lettuce,开启 spring.redis.cluster.nodes 配置,它会自动处理 MOVEDASK 重定向,底层长连接会跟 Gossip 拓扑变化动态刷新。
  • 另外,不管哪种模式,我们都会开启 RDB + AOF 混合持久化,保证节点重启后能快速恢复数据,避免空节点加入集群导致数据迁移的灾难。🔥

💻 核心代码 & 技术亮点

🙋‍♂️ 面试官:

刚才你提到用 Lettuce 连接哨兵和 Cluster,能挑几个关键代码段展示一下吗?主要看看你们的连接配置和故障转移时的处理。

👨‍💻 候选人:

没问题,我先贴一段生产环境简化版,用 Spring Boot + Lettuce 连接 哨兵模式 的配置和自愈处理。

1️⃣ 哨兵模式:自动发现主库、切换重连

java 复制代码
@Configuration
public class RedisSentinelConfig {

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {
        RedisSentinelConfiguration sentinelConfig = 
            new RedisSentinelConfiguration()
                .master("mymaster")                     // 哨兵监控的主库名
                .sentinel("192.168.1.10", 26379)
                .sentinel("192.168.1.11", 26379)
                .sentinel("192.168.1.12", 26379);
        sentinelConfig.setPassword(RedisPassword.of("sentinel_pass"));
        
        LettuceClientConfiguration clientConfig = 
            LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED)   // 优先从副本读,分担压力
                .commandTimeout(Duration.ofSeconds(2))
                .build();
                
        return new LettuceConnectionFactory(sentinelConfig, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            LettuceConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return template;
    }
}

🔍 技术亮点:

  • ReadFrom.REPLICA_PREFERRED 让读请求优先落到从库,仅当从库不可用时才读主库,实现读写分离且不丢可用性。
  • 只需配置哨兵节点,客户端在背后会自动订阅哨兵频道,主从切换后 1~3 秒内就能刷新拓扑,完全无感。
  • commandTimeout 设得短一点,防止在故障切换时请求长时间卡死。

2️⃣ Cluster 模式:智能重定向 & 管道优化

java 复制代码
@Configuration
public class RedisClusterConfig {

    @Bean
    public LettuceConnectionFactory clusterConnectionFactory() {
        RedisClusterConfiguration clusterConfig = 
            new RedisClusterConfiguration(Arrays.asList(
                "192.168.2.10:6379",
                "192.168.2.11:6379",
                "192.168.2.12:6379"
            ));
        clusterConfig.setPassword("cluster_pass");
        // 开启自适应刷新,集群拓扑变化时自动感知
        ClusterTopologyRefreshOptions refreshOptions = 
            ClusterTopologyRefreshOptions.builder()
                .enablePeriodicRefresh(Duration.ofSeconds(30))  // 每30秒检查一次
                .enableAllAdaptiveRefreshTriggers()             // 检测到MOVED/ASK等自动立即刷新
                .build();
        
        LettuceClientConfiguration clientConfig = 
            LettuceClientConfiguration.builder()
                .clientOptions(ClusterClientOptions.builder()
                    .topologyRefreshOptions(refreshOptions)
                    .build())
                .build();
                
        return new LettuceConnectionFactory(clusterConfig, clientConfig);
    }
}

🔍 技术亮点:

  • ClusterTopologyRefreshOptions 是生产必备,避免槽迁移时客户端长时间拿着过期映射,导致大量 MOVED 重定向增加延迟。
  • enableAllAdaptiveRefreshTriggers() 会在收到重定向错误或节点角色变更时立即刷新拓扑,配合 30 秒定期刷新,双保险。
  • 连接池天然支持每个分片内部的主从自动切换,代码零改动。

3️⃣ 业务层防御性编码:故障时降级

java 复制代码
@Service
public class CacheService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public Optional<User> getUserById(String id) {
        try {
            User user = (User) redisTemplate.opsForValue().get("user:" + id);
            return Optional.ofNullable(user);
        } catch (RedisConnectionFailureException e) {
            log.warn("Redis 不可用,降级查 DB", e);
            // 降级到数据库查询
            return Optional.ofNullable(userDao.findById(id));
        } catch (Exception e) {
            log.error("Redis 异常", e);
            return Optional.empty();
        }
    }
}

🔍 技术亮点:

  • 捕获 RedisConnectionFailureException,这是 Lettuce 在连接断开或集群不可达时抛出的,防止缓存雪崩
  • 降级查 DB 并打印监控日志,保证核心链路可用。
  • 结合 Hystrix/Sentinel 熔断,能在 Redis 大面积故障时快速失败,保护整体系统。😌

🧨 技术难点与解决方案

🙋‍♂️ 面试官:

代码挺扎实,那你觉得实现 Redis 高可用最大的技术难点有哪些?你们是怎么一个一个啃下来的?

👨‍💻 候选人:

我梳理了四个生产环境下最头疼的点,以及对应的解法。

难点 详细描述 我们的解决方案
🧠 脑裂(Split-Brain) 哨兵模式网络分区,旧主被孤立却继续接写入,恢复后数据被新主覆盖 ① 主节点设置 min-replicas-to-writemin-replicas-max-lag,当没有足够从库同步时,旧主直接拒绝写入。② 客户端收到异常后触发告警并降级,人工确认修复。
📉 异步复制数据丢失 主宕机前瞬间写入的数据,还没来得及复制到从库就丢失了 ① 核心业务(如金融订单)使用 WAIT 命令,强制同步到指定数量的从库后才返回成功,牺牲部分延迟换取一致性。② 结合 AOF 持久化 appendfsync everysec,重启后能从磁盘恢复绝大部分数据。
🔄 故障切换期间的"短暂不可用" 哨兵选举、提升新主期间(2~10秒),所有写请求都会失败 ① 客户端侧开启重试机制 ,Lettuce 默认会重试失效节点,配合指数退避。② 业务层加本地缓存(Caffeine) 兜底,读请求可容忍几秒旧数据。③ 监控切换耗时,及时优化哨兵参数(sentinel down-after-milliseconds 不宜过大)。
⚡ 数据倾斜与热点 Key Cluster 中某些槽的数据量或访问量远超其他节点,导致单分片压力过高 ① 对热点 Key 做 多副本打散 ,比如 hot:key:{hash} 分散到多个 Key,本地随机取一个。② 业务层先通过监控发现后,主动调整 Key 设计,使用 {} 将互相访问频繁的 Key 规范到同一槽,但避免大 Key 聚集。③ 必要时升级分片规格或垂直扩容该分片。
🌀 集群扩容槽迁移影响延迟 迁移过程中,客户端会遇到 ASK 重定向,频繁的重定向导致毛刺 ① 打开客户端自适应拓扑刷新,减少重复的 ASK 请求。② 分批次、低峰期迁移,控制 migrate 的 pipeline 速度。③ 对延迟敏感业务,迁移时手动临时切换读取从节点,避免主节点阻塞。

🙋‍♂️ 面试官:

最后一个问题,你觉得这些难点里,哪一个是最容易被开发者忽略,但又最致命的

👨‍💻 候选人:

绝对是脑裂 。因为很多团队配置哨兵或者 Cluster 后,看到能自动切换就以为万事大吉了。脑裂一旦发生,在没有 min-replicas-to-write 保护的情况下,丢失的是切换期间所有 写入数据,而且是静默丢失,业务侧完全感知不到,直到对账才发现。所以我们规定,所有线上 Redis 主节点必须配上这些反脑裂参数,否则上线审批不通过。🚫

🙋‍♂️ 面试官:

思路很严谨,这些坑都是生产里实打实的,看得出来你是真用过。这一块我没其他问题了。

公众号"Rain的Java大神之路"

个人博客"www.javadashen.com"

相关推荐
Rain的Java大神实战圈1 天前
如何保证Mysql和Redis双写一致性
场景设计题
Rain的Java大神实战圈2 天前
介绍一下Redis的缓存穿透击穿雪崩
场景设计题
Rain的Java大神实战圈3 天前
对千万级单表进行分页查询
场景设计题
Rain的Java大神实战圈4 天前
MySQL建表时需要注意什么
场景设计题
Rain的Java大神实战圈7 天前
千万级大表如何新增字段
场景设计题
Rain的Java大神实战圈9 天前
日增量500万的表结构如何设计
场景设计题
Rain的Java大神实战圈11 天前
数据库主从有延迟怎么处理
架构设计·场景设计题
Rain的Java大神实战圈15 天前
分库分表会带来哪些问题如何解决
架构设计·场景设计题
Rain的Java大神实战圈16 天前
介绍一下分库分表
架构设计·场景设计题