Redis面试复盘:从连接到扩容与数据定位的极致详解(含Java RedisTemplate交互)

Redis面试复盘:从连接到扩容与数据定位的极致详解(含RedisTemplate交互及底层剖析)

最近参加了一场Redis技术面试,面试官围绕Redis的核心机制提问,从请求连接到扩容策略,再到主从模式和数据定位,问题深入且全面。之前的复盘已涵盖大部分内容,但面试官特别要求对数据定位(问题6)和扩容后读数据(问题7)的底层实现进行剖析。这次,我将进一步完善内容,聚焦底层原理,加入RedisTemplate的Java交互实现,并确保每个环节都细致入微。以下是博客风格的超详细复盘,供准备Redis面试的你参考。


前置知识:什么是RESP协议?

在讲解连接过程前,先介绍RESP(Redis Serialization Protocol),它是Redis通信的核心协议。

  • 定义 :RESP是Redis的文本序列化协议,用于客户端与服务器交互,以\r\n分隔。
  • 类型
    • 简单字符串:+OK\r\n
    • 错误:-ERR message\r\n
    • 整数::100\r\n
    • 批量字符串:$5\r\nhello\r\n
    • 数组:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
  • 作用 :客户端编码命令,服务器解析并返回结果,Java客户端(如RedisTemplate)自动处理。

1. Redis从一次请求到服务器返回的连接过程(含RedisTemplate交互)

  • 客户端连接

    • RedisTemplate通过JedisConnectionFactory建立TCP连接。

    • 代码

      java 复制代码
      import org.springframework.data.redis.core.RedisTemplate;
      import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
      
      RedisTemplate<String, String> template = new StringRedisTemplate(new JedisConnectionFactory());
      template.afterPropertiesSet();
      System.out.println(template.getConnectionFactory().getConnection().ping()); // +PONG\r\n
  • 发送命令template.opsForValue().set("key", "value")转为*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

  • 服务器解析与执行:单线程解析RESP,内存操作。

  • 响应返回+OK\r\ntemplate解码。


2. Redis怎么做横向和纵向扩容,什么情况下做(含RedisTemplate适配)

  • 纵向 :升级单节点,template更新host。

  • 横向 :加节点,用Redis Cluster。

    • 代码

      java 复制代码
      import org.springframework.data.redis.connection.RedisClusterConfiguration;
      
      RedisClusterConfiguration clusterConfig = new RedisClusterConfiguration();
      clusterConfig.clusterNode("node1", 6379);
      RedisTemplate<String, String> template = new StringRedisTemplate(new JedisConnectionFactory(clusterConfig));
  • 场景:内存满、QPS不足。


3. 两个Redis节点20G已满,横向还是纵向扩?子节点如何加入集群?

  • 横向

    • 加2节点,用redis-cli --cluster add-node new_ip:6379 existing_ip:6379

    • 子节点通过现有节点IP获取集群元数据(Gossip协议)。

    • 代码

      java 复制代码
      clusterConfig.clusterNode("new_ip", 6379);
      template.opsForValue().set("key", "value");
  • redis-cli环境:任意可达节点的机器。


4. 主从模式两个主一定有两个从吗?

  • 不固定,灵活配置。

  • 代码

    java 复制代码
    RedisTemplate<String, String> master = new StringRedisTemplate(new JedisConnectionFactory("master", 6379));
    RedisTemplate<String, String> slave = new StringRedisTemplate(new JedisConnectionFactory("slave", 6379));
    master.opsForValue().set("key", "value");

5. 主节点能读吗?什么情况下读?

  • 可读,适用于从故障或强一致性。

  • 代码

    java 复制代码
    String value = master.opsForValue().get("key");

6. 数据怎么定位到哪个节点?(底层剖析)

Redis的数据定位取决于部署模式,这里剖析Redis Cluster的底层实现,并结合RedisTemplate

  • 表面交互(RedisTemplate)

    java 复制代码
    template.opsForValue().set("key", "value"); // 自动定位
    String value = template.opsForValue().get("key");
  • 底层原理

    • 分片机制
      • Redis Cluster将数据分为16384个哈希槽(Hash Slots)。
      • 每个槽分配给一个主节点,存储在集群的槽映射表中。
    • 键到槽的计算
      • 对键应用CRC16算法,取模16384。
      • 公式:slot = CRC16(key) % 16384
      • 示例:CRC16("key")可能是12345,12345 % 16384 = 12345(若超出则取模)。
      • 哈希标签{tag}key只对tag计算,确保相关键在同一槽。
    • 节点定位
      • 每个节点维护槽映射表(如0-5460在node1,5461-10922在node2)。
      • 客户端发送命令时,底层(Jedis或Lettuce)查询本地缓存的映射。
    • 交互流程
      1. RedisTemplate调用opsForValue().set("key", "value")
      2. JedisConnectionFactoryRedisClusterConnection计算CRC16("key") % 16384
      3. 检查槽映射,找到目标节点(如node1:6379)。
      4. 发送*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
    • 重定向
      • 若槽不在当前节点,服务器返回-MOVED slot ip:port
      • 示例:-MOVED 12345 192.168.1.2:6379
      • RedisTemplate更新映射,重新发送。
    • 底层数据结构
      • 节点用clusterState结构体维护槽和节点关系(C语言实现)。
      • 客户端缓存ClusterNode映射,定期通过CLUSTER SLOTS同步。
  • 代码验证

    java 复制代码
    import org.springframework.data.redis.connection.RedisClusterConnection;
    
    RedisClusterConnection connection = template.getConnectionFactory().getClusterConnection();
    System.out.println(connection.clusterGetNodes()); // 查看节点
  • 手动分片

    • 客户端用一致性哈希:

      java 复制代码
      int slot = Math.abs("key".hashCode() % 2);
      RedisTemplate<String, String> node = (slot == 0) ? node1Template : node2Template;
      node.opsForValue().set("key", "value");
  • 关键点

    • RedisTemplate屏蔽了底层复杂度,依赖Lettuce/Jedis实现槽定位。
    • 网络延迟或映射过期可能触发重定向。

7. 扩主节点后怎么知道数据从哪个节点读?(底层剖析)

扩容后数据分布变化,这里剖析底层如何更新并定位。

  • 表面交互(RedisTemplate)

    java 复制代码
    clusterConfig.clusterNode("new_master", 6379);
    String value = template.opsForValue().get("key"); // 自动适应
  • 底层原理

    • 扩容过程

      1. 新节点加入:redis-cli --cluster add-node new_ip:6379 existing_ip:6379
      2. 槽迁移:redis-cli --cluster reshard,手动指定槽(如0-1000到新节点)。
      3. 数据迁移:源节点将键值对发送到目标节点(MIGRATE命令)。
      • 示例:MIGRATE new_ip 6379 "" 0 5000 KEYS key1 key2
    • 槽映射更新

      • 集群通过Gossip协议广播新映射。
      • 每个节点更新clusterState.slots(槽到节点的映射)。
    • 客户端感知

      • RedisTemplateJedisClusterConnection定期执行CLUSTER SLOTS

      • 返回格式:

        bash 复制代码
        *3
        *3
        :0
        :5460
        *2
        $9
        192.168.1.1
        :6379
        *3
        :5461
        :10922
        *2
        $9
        192.168.1.2
        :6379
      • 更新本地缓存(如JedisClusterInfoCache)。

    • 读数据流程

      1. template.opsForValue().get("key")
      2. 计算CRC16("key") % 16384(如12345)。
      3. 查询缓存,若槽12345在新节点(new_ip:6379),发送*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n
      4. 若缓存过期,收到-MOVED,跳转新节点。
    • 从节点读

      • 新主配从后,template连接从节点:
      java 复制代码
      JedisConnectionFactory slaveFactory = new JedisConnectionFactory(new RedisStandaloneConfiguration("slave_ip", 6379));
      RedisTemplate<String, String> slaveTemplate = new StringRedisTemplate(slaveFactory);
      slaveTemplate.getConnectionFactory().getConnection().readOnly();
      String value = slaveTemplate.opsForValue().get("key");
      • 底层同步:从节点通过PSYNC从主拉取数据。
  • 关键点

    • 底层依赖Gossip和CLUSTER SLOTS确保一致性。
    • RedisTemplate动态适应,无需手动干预。
相关推荐
Asthenia04129 分钟前
Netty优势/应用场景/高性能体现/BIO,NIO,AIO/Netty序列化
后端
Y第五个季节1 小时前
Spring AOP
java·后端·spring
xjz18421 小时前
基于SpingBoot3技术栈的微服务系统构建实践
后端
省长1 小时前
Sa-Token v1.41.0 发布 🚀,来看看有没有令你心动的功能!
java·后端·开源
全栈智擎1 小时前
Java高效开发实战:10个让代码质量飙升的黄金法则
后端·程序员
风象南1 小时前
Spring Boot 项目 90% 存在这 15 个致命漏洞!你的代码在裸奔吗?
java·spring boot·后端
坐望云起1 小时前
ASP.NET Web的 Razor Pages应用,配置热重载,解决.NET Core MVC 页面在更改后不刷新
前端·后端·asp.net·mvc·.net core·razor pages
静海_JH1 小时前
针对 SQLAlchemy 异步会话工厂 async_session 的优化方案
后端
未完结小说1 小时前
雪崩问题及解决方案
后端
aircrushin1 小时前
如何在1分钟内编写Cursorrules
前端·人工智能·后端