Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis分片集群

Redis分片集群的实现方法

作者:码熔burning

Redis Cluster是Redis官方提供的分布式解决方案,它不是像哨兵那样只负责高可用切换,而是同时解决了数据分片和高可用两个问题,感兴趣的可以了解一下

一、 为什么需要 Redis 分片集群? 🤔

想象一下,你开了一家生意爆火的小卖部 (单个 Redis 实例) 🏪。

分片集群就是为了解决这些问题! 🎉

它的思路是:既然一个小卖部不够用,那就开连锁超市!🏢🏢🏢

所以,Redis 分片集群的核心思想就是:

  1. 分片 (Sharding) 🔪:把数据“切”成很多片,分散存储在多个 Redis 实例(主节点)上。
  2. 复制 (Replication) 👯:给每个存储数据的实例(主节点)配备一个或多个备份实例(从节点),保证高可用。

二、 Redis 分片集群是什么? 💡

Redis Cluster 是 Redis 官方提供的分布式解决方案。它不是像哨兵(Sentinel)那样只负责高可用切换,而是同时解决了数据分片高可用两个问题。✨

它是一个去中心化的架构,这意味着没有一个“中央调度员”或者“代理服务器”来指挥所有请求。集群中的每个节点都知道其他节点的存在,也知道哪些数据(通过后面会讲的 Slot)应该由哪个节点负责。客户端可以直接连接到集群中的任意一个节点发起请求,如果这个节点恰好负责处理这个请求的数据,就直接处理;如果不是,它会告诉客户端“喂,你应该去找 XXX 节点 👉”,然后客户端再去连接正确的节点。

三、 散列插槽 (Slot) 是什么? (数据怎么分?) 📮

前面说到要把数据“切片”分散到不同的店(主节点)。那具体怎么切?怎么知道哪个数据该去哪个店呢? 这就是散列插槽 (Hash Slot) 的作用。

为什么是 16384? Redis 作者认为这个数字:

  1. 足够分散数据到最多 1000 个主节点(实践中很少有这么大规模的集群 💪)。
  2. 节点间传输 Slot 配置信息时,用 16384 个 Slot(即 16384 bit = 2KB)的位图 (bitmap) 来表示,这个大小比较合适,不会太大导致网络开销过高。👌

如何查看某个 Key 属于哪个 Slot?
可以使用命令:CLUSTER KEYSLOT {key},例如 CLUSTER KEYSLOT mykey

四、 如何搭建三主六从的 Redis 分片集群? 🐳🔧

这里我们用 Docker Compose 来模拟 9 个 Redis 实例,组成一个 3 主 6 从(每个主节点带 2 个从节点)的集群。

前提条件:

步骤:

创建工作目录: 📂

mkdir redis-cluster-demo
cd redis-cluster-demo

创建 docker-compose.yml 文件: 📝

services:
  # 定义 9 个 Redis 节点服务 (这里只展示 node-1 和 node-4 作为例子, 其他类似)
  redis-node-1: # Master 1 Candidate
    image: redis:latest # 或者更新的版本
    container_name: redis-node-1
    # 注意:推荐将 nodes.conf 放在持久化数据目录中
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7001:6379"     # 客户端端口映射
      - "17001:16379"   # 集群总线端口映射
    volumes:
      - ./node-1/data:/data # 只映射数据目录
    networks:
      - redis-cluster-net

  redis-node-2: # Master 2 Candidate
    image: redis:latest 
    container_name: redis-node-2
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7002:6379"
      - "17002:16379"
    volumes:
      - ./node-2/data:/data
    networks:
      - redis-cluster-net

  redis-node-3: # Master 3 Candidate
    image: redis:latest 
    container_name: redis-node-3
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7003:6379"
      - "17003:16379"
    volumes:
      - ./node-3/data:/data
    networks:
      - redis-cluster-net

  redis-node-4: # Slave Candidate 1
    image: redis:latest 
    container_name: redis-node-4
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7004:6379"
      - "17004:16379"
    volumes:
      - ./node-4/data:/data
    networks:
      - redis-cluster-net

  # ... (省略 redis-node-5 到 redis-node-9 的定义,与 node-4 类似,只需修改名称、端口映射和卷映射目录即可) ...
  # 例如 redis-node-5: ports: ["7005:6379", "17005:16379"], volumes: ["./node-5/data:/data"]
  # 例如 redis-node-9: ports: ["7009:6379", "17009:16379"], volumes: ["./node-9/data:/data"]

  redis-node-5:
    image: redis:latest 
    container_name: redis-node-5
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7005:6379"
      - "17005:16379"
    volumes:
      - ./node-5/data:/data
    networks:
      - redis-cluster-net

  redis-node-6:
    image: redis:latest 
    container_name: redis-node-6
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7006:6379"
      - "17006:16379"
    volumes:
      - ./node-6/data:/data
    networks:
      - redis-cluster-net

  redis-node-7:
    image: redis:latest 
    container_name: redis-node-7
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7007:6379"
      - "17007:16379"
    volumes:
      - ./node-7/data:/data
    networks:
      - redis-cluster-net

  redis-node-8:
    image: redis:latest 
    container_name: redis-node-8
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7008:6379"
      - "17008:16379"
    volumes:
      - ./node-8/data:/data
    networks:
      - redis-cluster-net

  redis-node-9:
    image: redis:latest 
    container_name: redis-node-9
    command: redis-server --port 6379 --cluster-enabled yes --cluster-config-file /data/nodes.conf --cluster-node-timeout 5000 --appendonly yes --bind 0.0.0.0
    ports:
      - "7009:6379"
      - "17009:16379"
    volumes:
      - ./node-9/data:/data
    networks:
      - redis-cluster-net


networks:
  redis-cluster-net:
    driver: bridge # 或者其他网络驱动

说明:

创建必要的目录: 🛠️

for i in {1..9}; do mkdir -p ./node-$i/data; done

启动所有 Redis 容器: ▶️

docker compose up -d

等待所有容器启动完成。可以用 docker ps 查看状态。

创建集群:关键一步
这一步需要使用 redis-cli 工具来告诉这些独立的 Redis 节点:“嘿,你们现在组成一个大家庭了!”

找到你的宿主机 IP 地址: 不能用 127.0.0.1localhost。用 ip addrifconfig 查找。假设你的 IP 是 172.29.0.10 (请替换成你自己的!)。可以使用 docker inspect redis-node-1 | grep '"IPAddress":' 命令查看:

执行集群创建命令:

# 获取任一节点的 redis-cli (用 node-1 举例)
docker exec -it redis-node-1 redis-cli --cluster create \
172.28.120.43:7001 172.28.120.43:7002 172.28.120.43:7003 \
172.28.120.43:7004 172.28.120.43:7005 172.28.120.43:7006 \
172.28.120.43:7007 172.28.120.43:7008 172.28.120.43:7009 \
--cluster-replicas 2

解释:

验证集群状态:
连接到任意一个节点(使用映射的端口),查看集群信息。

# 连接到 7001 节点 (在容器内)
docker exec -it redis-node-1 redis-cli -p 6379 cluster info
# 或者从宿主机连接 (需要安装 redis-cli, -c 表示启用集群模式)
# redis-cli -c -h 172.29.0.10 -p 7001 cluster info

你应该看到 cluster_state:ok

查看节点信息:

docker exec -it redis-node-1 redis-cli -p 6379 cluster nodes
# 或者从宿主机连接
# redis-cli -c -h 172.29.0.10 -p 7001 cluster nodes

你会看到 9 个节点的信息,包括角色 (master/slave)、负责的 Slot 等。你会发现 3 个 master 和 6 个 slave,关系明确。

🎉 恭喜!你的 3 主 6 从 Redis 集群现在跑起来了!

五、 分片集群的集群伸缩是什么? ➕➖

集群搭好了,但业务发展太快 📈,3 家分店又不够用了,或者某个区域顾客少了 📉,想关掉一家店。这就是集群伸缩

伸缩操作都需要时间让集群状态同步和数据迁移。耐心点哦!⏳

六、 如何使用 RedisTemplate 访问分片集群?💻

在 Java 应用中(特别是 Spring Boot / Spring Data Redis),访问 Redis Cluster 很方便。配置好后,用 RedisTemplate 就行,跟用单实例差不多。

  1. 添加依赖: (pom.xml / build.gradle)
    确保有 spring-boot-starter-data-redis。它通常默认使用 Lettuce 客户端,Lettuce 对 Cluster 支持很好。

    <!-- Maven 示例 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- 一般不需要额外添加 Lettuce 或 Jedis,除非你想强制用 Jedis -->
    
  2. 配置 application.propertiesapplication.yml:
    告诉 Spring Data Redis 你的集群在哪儿。不需要写全所有节点,写几个就行,客户端会自动发现其他的。👍
    application.properties 示例:

    # Redis Cluster 节点列表 (逗号分隔 host:port)
    spring.redis.cluster.nodes=172.29.0.10:7001,172.29.0.10:7002,172.29.0.10:7003
    # 如果有密码
    # spring.redis.password=yourpassword
    # 连接池配置 (可选, Lettuce)
    spring.redis.lettuce.pool.max-active=8
    spring.redis.lettuce.pool.max-idle=8
    

    application.yml 示例:

    spring:
      redis:
        cluster:
          nodes:
            - 172.29.0.10:7001
            - 172.29.0.10:7002
            - 172.29.0.10:7003
        # password: yourpassword # 可选
        lettuce: # 可选连接池
          pool:
            max-active: 8
            max-idle: 8
    
  3. 在代码中使用 RedisTemplate:
    注入 RedisTemplateStringRedisTemplate,然后就像操作普通 Redis 一样用。

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Service;
    
    @Service
    public class MyRedisService {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate; // 或者 RedisTemplate<String, Object>
    
        public void setValue(String key, String value) {
            // 直接用!它会自动找到正确的节点去执行 set 命令
            stringRedisTemplate.opsForValue().set(key, value);
            System.out.println("设置 Key: " + key + " 到集群! 😎");
        }
    
        public String getValue(String key) {
            // 同样,自动路由到持有这个 key 的节点去 get
            String value = stringRedisTemplate.opsForValue().get(key);
            System.out.println("从集群获取 Key: " + key + ", Value: " + value + " 😊");
            return value;
        }
    
        // 其他操作 hash, list, set, zset 都类似!
    }
    

关键点:

七、 分片集群的原理是什么? ⚙️🤝

Redis Cluster 的魔法主要靠这几招:

  1. 节点间八卦 (Gossip 协议):

    深入了解Gossip协议请看:Gossip协议:分布式系统中的“八卦”传播艺术

    • 每个节点心里都有本账(集群状态:谁活着,谁管哪些 Slot,主从关系等)。
    • 节点之间通过 集群总线端口 (客户端端口+10000那个) 定期互相 PING/PONG,顺便交换自己知道的“八卦消息”(集群状态信息)。🗣️👥
    • 这种方式没有中心老大,大家互相通气,最终信息会传遍整个集群,达成一致(虽然可能有点延迟,叫最终一致性)。
  2. Slot 地盘划分与同步:

    • 每个主节点“认领”自己负责的 Slot。
    • 认领信息通过 Gossip 协议告诉所有人。
    • 客户端连上任何一个节点,都能拿到最新的“地盘划分图”(Slot -> 节点映射)。🗺️
  3. 心跳检测与“怀疑人生”(PFAIL & FAIL):

    • 节点间互相 PING,如果 A 在 cluster-node-timeout 内没收到 B 的 PONG 回复,A 就开始“怀疑” B 是不是挂了,标记为 PFAIL (Possible Fail - 可能挂了)。这只是 A 的个人看法。🤔
    • A 会把这个“怀疑”通过 Gossip 告诉其他主节点。
    • 如果超过一半的主节点都觉得“嗯,我也联系不上 B 了”,大家就达成共识:B 确实挂了!状态升级为 FAIL。这个“噩耗”会广播给所有人。💀
  4. 小弟上位记 (自动故障转移 Failover):

    • 一旦某个主节点被确认 FAIL 了,它的从节点们就开始骚动了:“大哥倒了,机会来了!” 💪
    • 谁能上位?得看:确认大哥真挂了,并且自己数据跟大哥差别不大(同步延迟小)。
    • 选举流程:
      • 想上位的从节点,先给自己“资历”加一分 (currentEpoch++), 然后广发英雄帖给所有主节点:“选我!选我!” 🗳️
      • 还活着的主节点收到帖子,如果在这个“任期”(epoch) 内还没投过票,就给第一个发帖的从节点投一票。
      • 哪个从节点最先拿到超过半数主节点的投票,就成功当选新大哥!👑
    • 走马上任:新大哥(原从节点)立马:
      • 宣布自己是主节点了。
      • 接管原来大哥的所有 Slot 地盘。
      • 通过 Gossip 昭告天下:“从今天起,这些 Slot 归我管了!”📢
    • 服务恢复:整个过程是自动的,客户端在短暂的切换后就能找到新大哥继续工作。注意⚠️:如果某个主节点和它的所有从节点同时挂掉,那它负责的 Slot 就真的服务不了了,需要人工介入。另外,如果超过半数的主节点都挂了,为了防止数据错乱,整个集群会停止服务!🛑
  5. 客户端导航 (MOVED & ASK):

    • MOVED (搬家了,去那边!) 👉:客户端可能拿着旧地图(过时的 Slot 映射)找错了节点。这个节点会说:“兄弟,这个 Slot 不归我管了,已经搬到 ip:port 那家去了,你去那边吧!” 并返回 MOVED <slot> <correct_ip>:<correct_port>。客户端收到后,会更新地图,然后乖乖去新地址重新请求。
    • ASK (稍等,问问那边) ➡️:在 Slot 迁移过程中,比如 Slot X 正从 A 往 B 搬。客户端还是找旧主 A 问 Slot X 里的某个 key:
      • 如果 key 还在 A 这,A 就处理了。
      • 如果 key 已经搬到 B 那了,A 会说:“这个 key 可能在 B 那了,你去问问 B 试试?” 并返回 ASK <slot> <destination_ip>:<destination_port>
      • 客户端收到 ASK不会更新地图(因为搬家还没彻底完成)。它会先去 B 那边敲门说 ASKING(表明我是被 A 指过来的),然后再发真正的命令。B 收到 ASKING 就知道怎么回事了,即使 Slot X 还没完全归它管,也会临时处理这条命令。ASK 只管用一次。

八、 分片集群的优缺点是什么?

优点:

  1. 能屈能伸 (Scalability) ✅:加机器就能加容量、加性能,理论上可以搞很大规模。就像开连锁店,想开多少开多少。
  2. 打不死的小强 (High Availability) ✅:主从热备 + 自动切换,挂掉一两个节点服务基本不受影响(只要不是主从一起挂,或挂掉太多主)。
  3. 跑得快 (Performance) ✅:请求分散到多个节点,大家分担压力,整体响应快。
  4. 亲儿子待遇 (Official Solution) ✅:Redis 官方出品,稳定性和兼容性有保障,社区活跃。
  5. 群龙无首也行 (Decentralized) ✅:没有中央控制节点,避免了单点瓶颈和故障。

缺点:

  1. 多 Key 操作有点瘸 (Multi-key Operations Limitation) ⚠️:像 MSET、事务等,默认要求 Key 在同一个 Slot,否则报错。需要开发者注意,或者用 Hash Tags 绕一下。跨 Slot 的复杂操作比较麻烦。
  2. 有点复杂 (Complexity) ⚠️:配置、管理、排错比单机或哨兵模式复杂,需要懂 Slot、Gossip 这些概念。运维成本高点。
  3. 话痨费网 (Network Overhead) ⚠️:节点间需要不停地 Gossip 来同步状态,会消耗一些网络带宽和 CPU。
  4. 客户端要配套 (Client Compatibility) ⚠️:必须用支持 Redis Cluster 的客户端库(新版的 Lettuce、Jedis 都支持)。
  5. 可能偏心 (Data Skew) ⚠️:如果 Key 设计不好(比如 Hash Tag 用得太集中,或有超级热点 Key),可能导致数据和流量集中在少数节点,失去均衡效果。🤷‍♀️
  6. 批量活儿效率可能不高 (Batch Efficiency) ⚠️:如果要操作一大堆分布在不同 Slot 的 Key,客户端得跟多个节点打交道,可能不如单实例高效。
  7. 团结就是力量(但散了就…) (Cluster Integrity Requirement) ⚠️:集群的正常运行和故障转移依赖于“少数服从多数”原则。如果挂掉的主节点太多(超过半数),整个集群就“瘫痪”了,拒绝服务以保护数据。

到此这篇关于Redis分片集群的实现方法的文章就介绍到这了,更多相关Redis分片集群内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

您可能感兴趣的文章:
阅读全文