什么是 Redis 的数据分片?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,通常不仅仅是想知道一个简单的定义。其核心考察点在于:

  1. 对分布式系统核心思想的理解:考察你是否理解通过水平扩展来解决单点容量与性能瓶颈的基本架构思想。
  2. 对 Redis 高级特性与部署模式的掌握:区分你是仅会使用单机 Redis,还是了解其生产级分布式部署方案。
  3. 不同分片策略的对比与选型能力:你是否清楚不同分片策略(如客户端、代理、查询路由)的实现原理、优缺点及适用场景。
  4. 对数据一致性与系统扩展性复杂度的认知:考察你是否了解分片引入的新挑战,如数据迁移、扩容、热点键、跨分片事务等。

核心答案

Redis 数据分片是一种将整个数据集分布式地存储在多个 Redis 实例中的方法。其核心目的是突破单机 Redis 在内存容量、网络带宽和计算能力上的限制,实现水平扩展,从而获得更大的整体数据存储能力和更高的吞吐量。

主要的分片策略包括:

  1. 客户端分片:由应用端(或客户端库)使用一致性哈希等算法,直接决定数据应读写哪个 Redis 节点。

  2. 代理分片:在客户端与 Redis 节点之间引入代理层(如 Twemproxy, Codis),由代理负责计算数据路由,对客户端透明。

  3. 查询路由分片:由 Redis 集群自身实现。在 Redis Cluster 模式下,集群将整个键空间划分为 16384 个哈希槽,每个节点负责一部分槽。客户端可以连接到任意节点,若数据不在该节点,节点会返回重定向指令引导客户端访问正确的节点。

    Redis Cluster 模式Redis Cluster 模式

目前,Redis Cluster 是官方推荐且在生产环境中使用最广泛的去中心化分片方案。

深度解析

原理/机制

数据分片的本质是 “分散存储,集中访问”。通过一个确定性的规则(通常是基于 key 的哈希计算),将不同的 key 映射到不同的 Redis 实例上。理想情况下,数据和请求负载应均匀分布。

Redis Cluster 为例,其分片机制如下:

  1. 哈希槽:将所有可能的 key 映射到 0-16383 共 16384 个槽位中,计算公式为:CRC16(key) % 16384
  2. 槽分配:集群启动时,管理员通过命令将这些槽分配给各个主节点。例如,一个三主节点的集群,可以分配为:节点A (0-5500),节点B (5501-11000),节点C (11001-16383)。
  3. 路由查询:客户端与集群建立连接后,会获取一份“槽位-节点”映射表缓存。当执行命令时,客户端直接计算 key 的槽位,并发往对应的节点。如果缓存信息过时(发生了槽迁移),目标节点会返回一个 MOVED 错误并附带正确节点地址,客户端更新缓存并重试。

代码示例

以下是一个简化的客户端分片逻辑(伪代码)示例,用于说明分片的基本思想:

// 假设我们有三个 Redis 分片节点地址
List<JedisShardInfo> shards = Arrays.asList(
    new JedisShardInfo("redis-node1", 6379),
    new JedisShardInfo("redis-node2", 6379),
    new JedisShardInfo("redis-node3", 6379)
);

// 创建分片器 (这里简化了,实际使用如 Jedis 的 ShardedJedis)
ShardedJedis shardedJedis = new ShardedJedis(shards, Hashing.MURMUR_HASH);

// 存储数据:由分片器根据 key 的哈希值决定写入哪个具体节点
shardedJedis.set("user:1001:profile", "{...}");
shardedJedis.set("order:20230521:789", "{...}");

// 读取数据:同样的规则,可以定位到数据所在的节点进行读取
String profile = shardedJedis.get("user:1001:profile"); // 定位到 node1
String order = shardedJedis.get("order:20230521:789"); // 可能定位到 node2

对比分析与最佳实践

特性客户端分片 (如 ShardedJedis)代理分片 (如 Twemproxy)查询路由分片 (Redis Cluster)
架构复杂度低,无额外组件中,需部署维护代理中,集群自管理
客户端需支持分片逻辑无需修改,使用普通客户端需支持集群协议 (如 JedisCluster)
数据迁移/扩容非常困难,需停机或双写依赖代理实现,工具支持原生支持,可在线平滑扩容
高可用需配合 Sentinel 等代理自身可能成单点原生支持,主从复制与故障转移
性能直接通信,性能好多一次网络跳转,有性能损耗直接通信,MOVED 重定向有少量开销

最佳实践:

  1. 首选 Redis Cluster:对于新项目,除非有强一致性事务或复杂多键操作需求,否则应优先选择 Redis Cluster,因为它提供了开箱即用的分片、高可用和扩展能力。
  2. 键设计是关键:分片的基础是 key。确保 key 的哈希结果均匀分布。避免使用会导致数据倾斜的 key(如大量以相同前缀结尾的 key)。
  3. 规划容量与分片数:提前根据业务增长预估总数据量和吞吐量,规划初始分片数量,为未来扩容留有余地。

常见误区

  • 误区一:“分片可以无限扩展性能”:分片确实能提升整体吞吐,但会引入新的瓶颈,如客户端/代理的连接数、跨分片操作的复杂度、集群内部通信成本等。
  • 误区二:“所有命令在分片环境下都像单机一样工作”:涉及多个 key 的操作(如 MGETMSETSINTER)在分片环境下可能无法直接使用,除非这些 key 通过 hash tag 机制(如 {user1001}.order, {user1001}.profile)被强制分配到同一个分片。
  • 误区三:“分片等同于高可用”:分片解决的是容量和性能问题,高可用(故障自动转移)通常需要配合主从复制(Replication)来实现。Redis Cluster 集成了两者。

总结

Redis 数据分片是通过将数据分散到多个实例来突破单机限制的核心分布式技术,Redis Cluster 以其官方、去中心化和自管理的特性,成为当前最主流的分片解决方案;在实际应用中,合理的键设计和容量规划是发挥分片优势的关键。