当前位置: 首页 > news >正文

Redis学习--集群 数据分片、哈希槽、集群配置、主从容错迁移、扩缩容

目录

一、前言

二、Redis集群概念

三、作用

四、集群算法

1、数据分片

2、哈希取余分区

优点

缺点

3、一致性哈希算法分区

算法构建一致性哈希环

Redis服务器IP节点映射

key 落到服务器的落键规则

优点

缺点

4、哈希槽分区

优点

缺点

5、经典面试题

五、实例演示

集群配置(三主三从)

启动6台redis实例

通过redis-cli命令为6台机器构建集群关系

任意连接一个作为切入点(集群只需要连一个),并检验集群状态

查看节点状态

测试集群读写

主从容错切换迁移

主从扩容

主从缩容

六、常用操作命令

通配符{}

集群提供服务选项

其他命令


一、前言

上篇文章中我们学习了Redis的哨兵架构,它主要解决的是主从复制的模式下,意外宕机后主节点无法重新设置的问题,利用哨兵监视主节点,在主节点宕机之后,从其他的从节点中选举出一个节点作为主节点,哨兵模式虽然解决了这个问题,但是仍存在其他缺陷:

  1. 哨兵模式本质仍是“一主多从”架构,数据集过大时,会存在单主节点写入压力过大。
  2. 单主节点的存储容量受限于单台服务器的硬件配置。
  3. 在选举新主节点的空档期,写入操作不可用。
  4. 主节点故障前未同步到从节点容易数据丢失。

基于哨兵模式的不足,本文来学习一种新的模式--Redis集群

二、Redis集群概念

集群,顾名思义,由于单节点Redis的存储容量和写入性能瓶颈,所以需要对多个Redis节点进行集群,形成水平扩展,每个复制集只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点间共享数据的程序集。

它通过 数据分片(Sharding)去中心化架构 ,支持大规模数据存储和高并发访问。

三、作用

  • Redis集群是一个提供在多个Redis节点间共享数据的程序集

    • Redis集群可以支持多个Master
  • Redis集群支持多个Master,每个Master又可以挂载多个Slave
    • 读写分离
    • 支持海量数据的高可用
    • 支持海量数据的读写存储操作
  • 由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能
  • 集群支持主从复制和主节点的自动故障转换(与哨兵类似);当任一节点发生故障时,集群仍然可以对外提供服务。
  • 高可用:客户端和Redis的节点连接,不再需要连接集群中所有节点,只需连接集群中的任意一个可用节点即可
  • 槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系(后面讲到)

四、集群算法

1、数据分片

数据分区(或称数据分片)是集群最核心的功能。
集群将数据分散到多个节点,一方面突破了Redis单机内存大小的限制,存储容量大大增加;另一方面每个主节点度可以对外提供读服务和写服务,大大提高了集群的响应能力。
Redis单机内存大小受限问题,在介绍持久化和主从复制时都有体积;例如,如果单机内存太大,bgsave和bgrewriteaof的fork操作可能导致主进程阻塞,主从环境下主机切换时可能导致从节点长时间无法提供服务,全量复制阶段主节点的复制缓冲区可能溢出。

  • Redis集群引入了哈希槽的概念
  • Redis集群有16384个哈希槽(编号0-16383)
  • 集群的每个节点负责一部分哈希槽

Redis集群分布式存储中实现 数据分片(Sharding) 的三大核心方案

  1. 哈希取余分区
  2. 一致性哈希算法分区
  3. 哈希槽分区

2、哈希取余分区

在数据量比较大的情况下,假设有3台机器构成一个集群,用户每次读写操作都是根据公式,hash(key) % N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。

    
    

    优点

    • 简单粗暴,直接有效,只需要预估好数据规划节点例如3台、8台、10台,就能保证一段时间的数据支撑。使用Hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。

    缺点

    • 直接规划好节点,进行扩容或者缩容会很麻烦,不管扩还是缩,每次数据变动会导致节点有变动,映射关系都要重新计算,在服务器个数固定不变时没有问题。
    • 如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化,Hash(key)/3会变成Hash(key) /?。此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变得不可控。
    • 某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。

    3、一致性哈希算法分区

    一致性哈希算法在1997年由麻省理工学院中提出的,设计目标是为了解决 分布式缓存数据变动映射问题。我们知道如果某个机器宕机了,分母数量改变了,自然取余之后就会改变。

    所以提出一致性Hash解决方案。目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系。

    算法构建一致性哈希环

    • 一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],这个是一个线性空间,但是在算法中,我们通过适当的逻辑控制将它首尾相连(0 = 2^32),这样让它逻辑上形成了一个环形空间。
    •    它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模。而一致性Hash算法是对2^32取模,简单来说,一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图:整个空间按顺时针方向组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1, 0和2^32-1在零点中方向重合,我们把这个由2^32个点组成的圆环称为Hash环。

    Redis服务器IP节点映射

    • 将集群中各个IP节点映射到环上的某一个位置。
    • 将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。假如4个节点NodeA、B、C、D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:

    key 落到服务器的落键规则

    • 当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上。
    • 如我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间上的位置如下:根据一致性Hash算法,数据A会被定为到Node A上,B被定为到Node B上,C被定为到Node C上,D被定为到Node D上。

      优点

      一致性哈希算法的容错性

      假设Node C宕机,可以看到此时对象A、B、D不会受到影响,只有C对象被重定位到Node D。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它不会受到影响。简单说,就是C挂了,受到影响的只是B、C之间的数据,并且这些数据会转移到D进行存储。

      一致性哈希算法的扩展性

      数据量增加了,需要增加一台节点NodeX,X的位置在A和B之间,那收到影响的也就是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。

      缺点

      一致性哈希算法的数据倾斜问题

      Hash环的数据倾斜问题
      一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题,
      例如系统中只有两台服务器:

      4、哈希槽分区

      针对上面的一致性哈希算法的倾斜问题,哈希槽对它做了改进。

      在数据和节点之间加入“哈希槽”作为中间层,将“数据→节点”的直接映射改为“数据→槽→节点”的间接映射,现在就相当于节点上放的是槽,槽里放的是数据。

      举个例子类比了解一下:

      • 一致性哈希:相当于“学生(数据)直接按学号(哈希值)分到不同班级(节点)”,班级人数可能不均。
      • 哈希槽:相当于“先将学生按学号分到 16384 个固定小组(槽),再将小组分配给班级(节点)”,通过调整小组数量实现各班人数均匀。

    • 槽解决的是粒度问题,相当于把粒度变大了,这样便于数据移动。哈希解决的是映射问题,使用key的哈希值来计算所在的槽,便于数据分配
    • 一个集群只能有16384个槽,编号0-16383(0-2^14-1)。这些槽会分配给集群中的所有主节点,分配策略没有要求。
    • 集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求哈希值,然后对16384取模,余数是几key就落入对应的槽里。HASH_SLOT = CRC16(key) mod 16384。以槽为单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动问题就解决了。

    Redis 集群中内置了 16384 个哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在 Redis 集群中放置一个 key-value时,redis先对key使用crc16算法算出一个结果然后用结果对16384求余数[ CRC16(key) % 16384],这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,也就是映射到某个节点上。如下代码,A 、B在Node2, C落在Node3上

     

    优点

    方便扩缩容和数据分派查找

    这种结构很容易添加或者删除节点。比如如果我想新添加个节点D, 我需要从节点 A、B、C中得部分槽到D上。如果我想移除节点A, 需要将A中的槽移到B和C节点上, 然后将没有任何槽的A节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务, 所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。

    缺点

    Redis集群不保证 强一致性,这意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令。

    一旦一个写操作成功返回给客户端,所有后续的读操作都应该能读到这个最新的值。而 Redis 集群不保证这一点,它提供的是最终一致性(Eventual Consistency)。

    为什么 Redis 集群不保证强一致性?

    核心原因在于:Redis 使用异步复制

    写操作流程:

    1. 客户端向主节点 A 发起写请求。
    2. 主节点 A 执行写操作,立即返回成功给客户端。
    3. 主节点 A 在后台异步地将写操作同步给它的从节点(replica)。

    这意味着:写操作在主节点本地完成就返回成功,不等待从节点确认

    也就是说,假设在主节点收到写入命令后,写入成功,且返回给客户端表示写入成功,但是还没来得及跟从节点同步的时候突然宕机,导致客户端明明收到了“写入成功”,但数据却丢了的情况。

    那为什么Redis不采用同步复制?

    如果采用同步复制(等待多数从节点确认),可以避免上述问题,但会带来严重缺点:

    • 延迟高:每次写操作都要等待网络同步,性能下降。
    • 可用性降低:只要一个从节点慢或宕机,主节点就无法写入。

    Redis 的设计哲学是 高性能优先,因此选择了异步复制,牺牲了强一致性来换取低延迟和高吞吐。

    5、经典面试题

    Redis集群并没有使用一致性hash而是引入了哈希槽的概念。Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽。但为什么哈希槽的数量是16384(2^14)个呢?

    即CRC16算法产生的hash值有16bit,该算法可以产生2^16=65536个值。换句话说值是分布在0~65535之间,有更大的65536不用为什么只用16384就够?作者在做mod运算的时候,为什么不mod65536,而选择mod16384?

    Redis 集群使用 16384 个哈希槽,是为了在 性能、内存开销、集群规模 之间找到一个最佳平衡点

    1. 首先Redis 集群中,节点之间通过 心跳包(ping/pong) 来保持联系,告诉对方:“我还活着,我管哪些槽”。这个心跳包里要包含:节点状态和它负责的槽信息(用一个 位图 bitmap 表示)
      1. 举个例子:如果有 16384 个槽,就需要 16384 个“小格子”来标记每个槽归谁管。每个格子用 1 bit 表示(0 或 1),总共需要:16384 / 8 = 2048 字节 = 2KB也就是说,每个心跳包只需要 约 2KB 的空间来记录槽的归属。如果槽是 65536 个,则需要 65536 / 8 = 8192 字节 = 8KB ,如果集群有 1000 个节点,每个节点频繁发心跳,网络流量会暴增,影响性能。
    2. redis的集群主节点数量基本不可能超过1000个。
      1. 集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
    3. 槽位越小,节点少的情况下,压缩比高,容易传输
      1. Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。

    五、实例演示

    集群配置(三主三从)

    1.准备三台虚拟机,各自新建文件目放配置文件

    mkdir -p /myredis/cluster
    

    2.新建6个独立的redis实例服务 (一台虚拟机两个)因为多个太过于占内存了,也可以用6个服务器

    第一台虚拟机IP: 192.168.238.111 + 端口 6381 / 6382

    • 端口6381 (记着将密码改成自己平常用的)

    vim /myredis/cluster/redisCluster6381.conf
    
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6381
    logfile "/myredis/cluster/cluster6381.log"
    pidfile /myredis/cluster6381.pid
    dir /myredis/cluster
    dbfilename dump6381.rdb
    appendonly yes
    appendfilename "appendonly6381.aof"
    requirepass 123456
    masterauth 123456cluster-enabled yes
    cluster-config-file nodes-6381.conf
    cluster-node-timeout 5000
    

    端口 6382

    vim /myredis/cluster/redisCluster6382.conf
    
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6382
    logfile "/myredis/cluster/cluster6382.log"
    pidfile /myredis/cluster6382.pid
    dir /myredis/cluster
    dbfilename dump6382.rdb
    appendonly yes
    appendfilename "appendonly6382.aof"
    requirepass 123456
    masterauth 123456cluster-enabled yes
    cluster-config-file nodes-6382.conf
    cluster-node-timeout 5000
    

    第二台虚拟机IP: 192.168.238.112 + 端口 6383 / 6384

    • 端口 6383

    vim /myredis/cluster/redisCluster6383.conf
    
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6383
    logfile "/myredis/cluster/cluster6383.log"
    pidfile /myredis/cluster6383.pid
    dir /myredis/cluster
    dbfilename dump6383.rdb
    appendonly yes
    appendfilename "appendonly6383.aof"
    requirepass 123456
    masterauth 123456cluster-enabled yes
    cluster-config-file nodes-6383.conf
    cluster-node-timeout 5000
    

    端口 6384

    vim /myredis/cluster/redisCluster6384.conf
    
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6384
    logfile "/myredis/cluster/cluster6384.log"
    pidfile /myredis/cluster6384.pid
    dir /myredis/cluster
    dbfilename dump6384.rdb
    appendonly yes
    appendfilename "appendonly6384.aof"
    requirepass 123456
    masterauth 123456cluster-enabled yes
    cluster-config-file nodes-6384.conf
    cluster-node-timeout 5000
    

    第三台虚拟机IP: 192.168.238.113 + 端口 6385 / 6386

    • 端口 6385

    vim /myredis/cluster/redisCluster6385.conf
    
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6385
    logfile "/myredis/cluster/cluster6385.log"
    pidfile /myredis/cluster6385.pid
    dir /myredis/cluster
    dbfilename dump6385.rdb
    appendonly yes
    appendfilename "appendonly6385.aof"
    requirepass 111111
    masterauth 111111cluster-enabled yes
    cluster-config-file nodes-6385.conf
    cluster-node-timeout 5000
    

    端口 6386

    vim /myredis/cluster/redisCluster6386.conf
    
    bind 0.0.0.0
    daemonize yes
    protected-mode no
    port 6386
    logfile "/myredis/cluster/cluster6386.log"
    pidfile /myredis/cluster6386.pid
    dir /myredis/cluster
    dbfilename dump6386.rdb
    appendonly yes
    appendfilename "appendonly6386.aof"
    requirepass 111111
    masterauth 111111cluster-enabled yes
    cluster-config-file nodes-6386.conf
    cluster-node-timeout 5000
    

    启动6台redis实例

    redis-server /myredis/cluster/redisCluter6381.conf 
    ..........
    redis-server /myredis/cluster/redisCluter6386.conf 
    

    通过redis-cli命令为6台机器构建集群关系

    构建主从关系 注意用自己的IP

    redis-cli -a 123456 --cluster create --cluster-replicas 1 192.168.238.111:6381 192.168.238.111:6382 192.168.238.112:6383 192.168.238.112:6384 192.168.238.113:6385 192.168.238.113:6386
    
    • -cluster-replicas 1 表示为每个master创建一个slave节点

    任意连接一个作为切入点(集群只需要连一个),并检验集群状态

    这里连6381端口

    redis-cli -a 123456 -p 6381 -c     // -c表示集群 不加的话不是按照集群启动的,对于在别的机器上的key,会报错
    
    • 加 -c 启动,优化路由

    查看节点状态

    测试集群读写

    对 6381 新增两个key,看看集群是否成功

    查看key的槽位值

    主从容错切换迁移

    先声明一下,本次6381为主下面挂从6384每次案例下面挂的从机以实际情况为准,具体是几号机器就是几号。

    当主节点6381发生故障时,Redis集群会自动执行故障检测和选举过程,选择一个从节点6384来接管原主节点的工作,成为新的主节点。这个过程是自动化的,并且尽量保证服务的连续性。

    • 停止主机6381:当主节点6381被停止后,集群会识别到这一情况,并开始尝试将其中一个从节点提升为新的主节点。
    • 从节点上位:一旦6381停机,它的从节点6384会被选中并晋升为主节点,继续提供服务。
    • 恢复前后的状态变化:即使原来的主节点6381恢复正常并重新加入集群,它也不会立即重新成为主节点,而是以从节点的身份回归集群。
      • 恢复前
      • 恢复后
    • Redis集群不保证强一致性,意味着在特定的条件下,Redis集群可能会丢掉一些被系统收到的写入请求命令

      • 因为本质还是发送心跳包,需要一些时间判断是否down机,如果down机,对应的slave直接成为master

    上面一换后6381、6384主从对调了,和原始设计图不一样了,该如何?

    • 重新登陆6381机器
      • 常用命令 CLUSTER FAILOVER,该命令通常用于手动将一个从节点提升为主节点,尤其是在需要进行维护或测试时。当在一个从节点上执行该命令时,该从节点会尝试接管其所属主节点的工作,成为新的主节点。

    主从扩容

    新建6387、6388 两个服务实例配置文件+启动 (又加了个虚拟机 或者 直接在三个虚拟机里选一个)

    IP:192.168.111.174+端口6387/端口6388

    redisCluster6387.conf

    redisCluster6388.conf

    启动87/88两个新的节点实例,此时他们自己都是master

    将新增的6387节点(空槽号)作为master节点加入原集群

    检查集群情况第1次

    重新分派槽号 (reshard)

    检查集群情况第2次 槽号分派说明

    为什么6387是3个新的区间,以前的还是连续?

    重新分配成本太高,所以前3家各自匀出来一部分,从6381/6383/6385三个旧节点分别匀出1364个坑位给新节点6387

    接着为主节点6387分配从节点6388

    检查集群情况第3次

    主从缩容

    6387和6388下线

    检查集群情况第一次,先获得从节点6388的节点ID

    从集群中将4号从节点6388删除

    将6387的槽号清空,重新分配,本例将清出来的槽号都给6381

    检查集群情况第二次

    4096个槽位都指给6381,它变成了8192个槽位,相当于全部都给6381了,不然要输入3次,不如直接一锅端

    将6387删除

    检查集群情况第三次,6387/6388被彻底祛除

    六、常用操作命令

    通配符{}

    不在同一个slot槽位下的键值无法使用mset、mget等多键操作

    可以通过{}来定义同一个组的概念,使key中{}内相同内容的键值对放到一个slot槽位去,对照下图类似k1 k2 k3都映射为x,自然槽位一样

    集群提供服务选项

    集群是否完整才能提供服务呢?

    在配置文件中有这么一个选项

    默认YES,现在集群架构是3主3从的redis cluster由3个master平分16384个slot,每个master的小集群负责1/3的slot,对应一部分数据。

    cluster-require-full-coverage:默认值 yes,即需要集群完整性,方可对外提供服务 通常情况,如果这3个小集群中,任何一个(1主1从)挂了,你这个集群对外可提供的数据只有2/3了,整个集群是不完整的,redis 默认在这种情况下,是不会对外提供服务的。

    如果你的诉求是,集群不完整的话也需要对外提供服务,需要将该参数设置为no,这样的话你挂了的那个小集群是不行了,但是其他的小集群仍然可以对外提供服务。

    其他命令

    1. CLUSTER COUNTKEYSINSLOT 槽位数字编号:这个命令用于统计指定哈希槽中存储的键的数量。
    2. CLUSTER KEYSLOT 键名称:这个命令用于计算给定键应该被存储在哪个哈希槽上。

    感谢阅读!

    http://www.dtcms.com/a/336070.html

    相关文章:

  • live555 rtsp server
  • 通达信【二板爆量涨停】副图/选股指标,首板次日继续强势封板,整合MACD和KDJ指标确保趋势向上,专注二板机会
  • 【计算机网络面试】TCP/IP网络模型有哪几层
  • Python中f - 字符串(f-string)
  • 软考 系统架构设计师系列知识点之杂项集萃(127)
  • 第2章 高并发IO的底层原理
  • 数据结构:二叉搜索树(Binary Search Tree)
  • 【Android】Activity创建、显式和隐式跳转、清单文件声明
  • Pytorch模型复现笔记-VGG讲解+架构搭建(可直接copy运行)+冒烟测试
  • MLArena:一款不错的AutoML工具介绍
  • 【股票数据API接口33】如何获取股票所属指数数据之Python、Java等多种主流语言实例代码演示通过股票数据接口获取数据
  • PCA 实现多向量压缩:首个主成分的深层意义
  • JZ57 和为S的两个数字
  • Traefik网关DNS解析超时问题优化
  • Agent开发进阶路线:从基础响应到自主决策的架构演进
  • C++类型转换详解:从C风格到C++风格
  • 如何理解事件循环和JS的异步?
  • LintCode第137-克隆图
  • PostgreSQL导入mimic4
  • SQL详细语法教程(四)约束和多表查询
  • C语言相关简单数据结构:双向链表
  • Rust Async 异步编程(五):执行器和系统 I/O
  • Effective C++ 条款47: 使用traits classes表现类型信息
  • 基于强化学习的柔性机器人控制研究
  • 【大模型微调系列-07】Qwen3全参数微调实战
  • 关于虾的智能养殖系统的开发与实现(LW+源码+讲解+部署)
  • 【LeetCode题解】LeetCode 33. 搜索旋转排序数组
  • 详解flink java基础(一)
  • 嵌入式软件--->任务间通信
  • 【C++知识杂记1】智能指针及其分类