万隆的笔记 万隆的笔记
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
博文索引
笔试面试
  • 在线学站

    • 菜鸟教程 (opens new window)
    • 入门教程 (opens new window)
    • Coursera (opens new window)
  • 在线文档

    • w3school (opens new window)
    • Bootstrap (opens new window)
    • Vue (opens new window)
    • 阿里开发者藏经阁 (opens new window)
  • 在线工具

    • tool 工具集 (opens new window)
    • bejson 工具集 (opens new window)
    • 文档转换 (opens new window)
  • 更多在线资源
  • Changlog
  • Aboutme
GitHub (opens new window)
  • Redis

  • 集群架构

    • Redis主从模式
    • Redis Sentinel 集群部署
    • Redis集群分区
      • 分区的意义
      • 分区的方式
      • 分区方案
      • RedisCluster分区
      • 附录:一致性Hash
    • 缓存架构设计
    • 分布式缓存问题
    • Redis分布式锁
  • Redis
  • 集群架构
2022-04-02
目录

Redis集群分区

# Redis集群分区

同很多DBMS一样,Redis在应对高可用的时候,可以进行分区。

所谓分区是指将将数据分散在多个Redis实例上,以至于每个实例只包含一部分数据,而所有的Redis实例构成的集群构成了数据的完整性。

# 分区的意义

分区主要能带来两个能力:性能提升以及横向扩展

  • 性能的提升:单机Redis的网络I/O能力和计算资源是有限的,将请求分散到多台机器,充分利用多台机器的计算能力,有助于提高Redis总体的服务能力。
  • 横向扩展:指存储横向扩展。即使Redis的服务能力能够满足应用需求,但是随着存储数据的增加,单台机器受限于机器本身的存储容量,这个时候则需要将数据分散到多台机器上存储,使得Redis服务可以横向扩展。

# 分区的方式

分区主要围绕着分区键(ID,或根据具体业务情况而定)展开,方式大致可以分为两类:范围分区、Hash分区

  • 范围分区:根据分区键的数字范围进行分区,例如 1~10000分到DB0、100001~20000分到DB1......,即划分范围并分到不同的Redis实例。实现简单、方便迁移拓展,但是又可能会造成热点分布不均,性能损失,此外这种范围分区仅支持数字型的分区键。
  • Hash分区:又可以分为简单hash和一致性hash。简单hash指利用hahs算法对分区键进行分区,Redis实例=hash(key)%N(key:要进行分区的键,N:Redis实例个数),这种分区方法支持任何类型的key,热点分布较均匀,性能较好,但是迁移复杂,需要重新计算,扩展较差。这个时候,一致性hash可以解决这个问题,它采用了hash环的方式,让拓展变得简单。详细说明见附录。

# 分区方案

同大多数DBMS大同小异,Redis的分区方案也是有两种:Client分区以及Proxy代理。

  • Client分区:Cluster(官方)
  • Proxy代理:Codis(豌豆荚)、TwemProxy(Twitter)

# Client分区

Client分区是指由客户端确定分片策略。即对于一个给定的key,客户端自己选择正确的节点来进行读写。

许多Redis客户端都实现了客户端分区(JedisPool),也可以自行编程实现。通常都是采用hash算法分片。

sharding1

缺点:

  • 复杂度高。客户端还需要自己处理数据路由、高可用、故障转移等问题。数据的处理会变得复杂,不得不对付多个redis数据库和AOF文件,不得在多个实例和主机之间持久化你的数据。
  • 不易扩展:一旦节点的增或者删操作,都会导致key无法在redis中命中,必须重新根据节点计算,并手动迁移全部或部分数据。

# Proxy代理

proxy代理指在客户端和服务器端引入一个代理或代理集群,客户端将命令发送到代理上,由代理根据算法,将命令路由到相应的服务器上。常见的代理有Codis(豌豆荚)和TwemProxy(Twitter)。

Codis (opens new window)由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。Codis部署架构如下:

codis

简单了解一下Codis的分片原理:Codis 将所有的 key 默认划分为 1024 个槽位(slot),它首先对客户端传过来的 key 进行 crc32 运算计算哈希值,再将 hash 后的整数值对 1024 这个整数进行取模得到一个余数,这个余数就是对应 key 的槽位。Codis的槽位和分组的映射关系就保存在codis proxy当中。

codis2

Codis 目前不怎么维护了,版本定格在codis 3.x,如果实际开发有需要的话看官方文档。

目前大多数采用的分区方案是采用官方的Client分区-Cluster。

# RedisCluster分区

Redis 3.0之后,Redis官方提供了完整的集群解决方案。

方案采用去中心化的方式,包括:sharding(分区)、replication(复制)、failover(故障转移),称为RedisCluster。

Redis 5.0 前采用redis-trib进行集群的创建和管理,需要ruby支持。

Redis 5.0 可以直接使用Redis-cli进行集群的创建和管理。

# Cluster部署架构

cluster

# 去中心化

RedisCluster由多个Redis节点组构成,是一个P2P无中心节点的集群架构,依靠Gossip协议传播的集群。

# Gossip协议

Gossip协议是一个通信协议,一种传播消息的方式。(起源于:病毒传播)

Gossip协议基本思想就是:

  • 一个节点周期性(每秒)随机选择一些节点,并把信息传递给这些节点。

  • 这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点。

  • 信息会周期性的传递给N个目标节点。这个N被称为fanout(扇出)

  • Gossip协议包含多种消息,包括meet、ping、pong、fail、publish等等。

    消息 说明
    meet sender向receiver发出,请求receiver加入sender的集群
    ping 节点检测其他节点是否在线
    pong receiver收到meet或ping后的回复信息;在failover后,新的Master也会广播pong
    fail 节点A判断节点B下线后,A节点广播B的fail信息,其他收到节点会将B节点标记为下线
    publish 节点A收到publish命令,节点A执行该命令,并向集群广播publish命令,收到publish

通过Gossip协议,cluster可以提供集群间状态同步更新、选举自助failover等重要的集群功能。

# Slot

RedisCluster把所有的物理节点映射到[0-16383]个Slot上,基本上采用平均分配和连续分配的方式。

假如有5个Redis主节点,这样在RedisCluster创建时,slot槽可按下表分配:

节点名称 slot范围
Redis1 0-3270
Redis2 3271-6542
Redis3 6543-9814
Redis4 9815-13087
Redis5 13088-16383

Cluster 负责维护节点和slot槽的对应关系。当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

注意,Slot槽必须在节点上连续分配,如果出现不连续的情况,则RedisCluster不能工作。

# Cluster优点

  • 高性能:RedisCluster 的性能与单节点部署是同级别的。(多主节点、负载均衡、读写分离)
  • 高可用:RedisCluster 支持标准的主从复制配置来保障高可用和高可靠。
  • 容灾:RedisCluster 也实现了一个类似 Raft 的共识方式,来保障整个集群的可用性。
  • 易扩展:向 RedisCluster 中添加新节点,或者移除节点,都是透明的,不需要停机。水平、垂直方向都非常容易扩展。
  • 原生:部署 RedisCluster 不需要其他的代理或者工具,而且 RedisCluster 和单机 Redis 几乎完全兼容。

# Cluster集群搭建

RedisCluster集群搭建最少需要三台主服务器,三台从服务器。

Redis 集群教程 (opens new window):入门级的Redis集群使用指南。

集合 | Cluster (opens new window)

注意,这里是使用虚拟机搭建的伪集群。新建六个Redis示例,端口号从7001~7006。

# 配置

创建实例参考这里——Linux安装Redis的正确方式,不再赘述。主要区别是开启cluster配置:

# bind 127.0.0.1
protected-mode no # 关闭保护模式,允许远端访问
cluster-enable yes # 开启cluster配置
daemonize yes

注意,主从是由cluster管理的,我们不用配置。

[root@localhost utils]# ps aux | grep redis
root      4398  0.0  0.3  55880  6972 ?        Ssl  20:09   0:00 /usr/local/redis5/bin/redis-server *:7001 [cluster]
root      4422  0.0  0.1  52808  2912 ?        Ssl  20:09   0:00 /usr/local/redis5/bin/redis-server *:7002 [cluster]
root      4463  0.0  0.1  52808  2924 ?        Ssl  20:09   0:00 /usr/local/redis5/bin/redis-server *:7003 [cluster]
root      4716  0.0  0.1  52808  2924 ?        Ssl  20:10   0:00 /usr/local/redis5/bin/redis-server *:7004 [cluster]
root      4762  0.0  0.1  52808  2924 ?        Ssl  20:10   0:00 /usr/local/redis5/bin/redis-server *:7005 [cluster]
root      4911  0.0  0.1  52808  2916 ?        Ssl  20:11   0:00 /usr/local/redis5/bin/redis-server *:7006 [cluster]

启动所有实例后,进行集群配置(创建时Redis不要有数据),

# --cluster-replicas 1 主节点数/从节点数的比例(1表示我们希望为集群中的每个主节点创建一个从节点)
[root@localhost wenwl]# redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
Adding replica 127.0.0.1:7006 to 127.0.0.1:7002
Adding replica 127.0.0.1:7004 to 127.0.0.1:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001
   slots:[0-5460] (5461 slots) master
M: 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002
   slots:[5461-10922] (5462 slots) master
M: f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003
   slots:[10923-16383] (5461 slots) master
S: 105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004
   replicates 61fffe775b57692c18590827fd8b7b28fe20224a
S: 7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005
   replicates 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8
S: a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006
   replicates f67551b4c1d640da69abc9e72f5f0dafdae72ce6
# rediscluster会打印出一份预想中的配置给你看,确认没问题就往下执行
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
.....
>>> Performing Cluster Check (using node 127.0.0.1:7001)
M: 61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
M: 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
S: 7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8
S: a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006
   slots: (0 slots) slave
   replicates f67551b4c1d640da69abc9e72f5f0dafdae72ce6
M: f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
S: 105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004
   slots: (0 slots) slave
   replicates 61fffe775b57692c18590827fd8b7b28fe20224a
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

# 连接集群

# -c 表示是以redis集群方式进行连接
[root@localhost wenwl]# redis-cli -h 127.0.0.1 -p 7001 -c
127.0.0.1:7001> set k1 testcluster
-> Redirected to slot [12706] located at 127.0.0.1:7003 # 这里的key被保存到7003这个示例
OK
127.0.0.1:7003> get k1 # 在获取k1的时候,客户端重定向去从7003获取
"testcluster"

# 查看集群状态

127.0.0.1:7003> cluster nodes
61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001@17001 master - 0 1648732119334 1 connected 0-5460
18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002@17002 master - 0 1648732118000 2 connected 5461-10922
7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005@17005 slave 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 0 1648732121374 5 connected
f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003@17003 myself,master - 0 1648732119000 3 connected 10923-16383
105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004@17004 slave 61fffe775b57692c18590827fd8b7b28fe20224a 0 1648732118316 4 connected
a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006@17006 slave f67551b4c1d640da69abc9e72f5f0dafdae72ce6 0 1648732120351 6 connected

更多命令看help @cluster。

# Cluster分片路由

Redis Cluster中不同节点分组服务于相互无交集的分片,由于Redis Cluster不存在单独的proxy或配置服务器,所以需要将客户端路由到目标的分片。

RedisCluster的客户端相比单机Redis 需要具备路由语义的识别能力,且具备一定的路由缓存能力(客户端路由)。

# moved重定向

  • 每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系
  • 客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16384取余,计算自己的槽和对应节点
  • 如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
  • 如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常
  • 客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息,客户端向目标节点发送命令,获取命令执行结果

例如前面连接集群的例子,就是moved重定向啦。

cluster2

# ask重定向

在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移。当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息,如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制。(moved到OldTarget之后又ask到newTarget)

  • 客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端
  • 客户端向新的节点发送Asking命令给新的节点,然后再次向新节点发送命令
  • 新节点执行命令,把命令执行结果返回给客户端

cluster3

# moved和ask的区别

  • moved:槽已确认转移
  • ask:槽还在转移过程中

# Smart智能客户端

JedisCluster是Jedis根据RedisCluster的特性提供的集群智能客户端。JedisCluster会为每个节点创建连接池,并跟节点建立映射关系缓存(Cluster Slots),此外,还会将每个主节点负责的槽位一一与主节点连接池建立映射缓存。

JedisCluster启动时,已经知道key,slot和node之间的关系,那么可以很快找到目标节点。JedisCluster对目标节点发送命令,目标节点直接响应给JedisCluster。如果JedisCluster与目标节点连接出错,则JedisCluster会知道连接的节点是一个错误的节点,此时节点返回moved异常给JedisCluster,JedisCluster会重新初始化slot与node节点的缓存关系,然后向新的目标节点发送命令,目标命令执行 命令并向JedisCluster响应。如果命令发送次数超过5次,则抛出异常"Too many cluster redirection!"

cluster4

简单来说,JedisCluster通过建立slot、node的映射缓存,可以快速定位,此外,如果出现moved异常,则刷新这些映射缓存,同时还有个5次错误兜底。

JedisPoolConfig config = new JedisPoolConfig();
Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>(); 
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7001)); 
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7002)); 
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7003)); 
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7004)); 
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7005)); 
jedisClusterNode.add(new HostAndPort("127.0.0.1", 7006)); 
JedisCluster jcd = new JedisCluster(jedisClusterNode, config); 
jcd.set("k1","testJedisCluster");
String value = jcd.get("k1");

# Cluster迁移

在RedisCluster中每个Slot 对应的节点在初始化后就是确定的。但在某些情况下,节点和分片需要变更:

  • 新的节点作为master加入;
  • 某个节点分组需要下线;
  • 负载不均衡需要调整Slot 分布。

此时需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。包含下面 2 种:

  • 节点迁移状态设置:迁移前标记源/目标节点。

  • key迁移的原子化命令:例如A迁移到B, 迁移的具体步骤:

    1. 向节点B发送状态变更命令,将B的对应slot 状态置为importing。
    2. 向节点A发送状态变更命令,将A对应的slot 状态置为migrating。
    3. 向A 发送migrate 命令,告知A 将要迁移的slot对应的key 迁移到B。
    4. 当所有key 迁移完成后,cluster setslot 重新设置槽位。

    cluster5

# Cluster扩容缩容

# 添加主节点

  1. 先创建7007节点(无数据),并且修改配置,启动(参考上面集群配置,不再赘述)。

  2. 添加7007结点作为新节点:

    # 添加7007结点作为新节点
    [root@localhost utils]# redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.0:7001
    >>> Adding node 127.0.0.1:7007 to cluster 127.0.0.0:7001
    Could not connect to Redis at 127.0.0.0:7001: Network is unreachable
    [root@localhost utils]# redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
    >>> Adding node 127.0.0.1:7007 to cluster 127.0.0.1:7001
    >>> Performing Cluster Check (using node 127.0.0.1:7001)
    M: 61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    M: 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    S: 7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005
       slots: (0 slots) slave
       replicates 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8
    S: a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006
       slots: (0 slots) slave
       replicates f67551b4c1d640da69abc9e72f5f0dafdae72ce6
    M: f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
    S: 105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004
       slots: (0 slots) slave
       replicates 61fffe775b57692c18590827fd8b7b28fe20224a
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    >>> Send CLUSTER MEET to node 127.0.0.1:7007 to make it join the cluster.
    
  3. hash槽重新分配(数据迁移):添加完主节点后需要对主节点进行hash槽分配,这样该主节才可以存储数据

    # 连接集群(任意一个可用结点都行)
    [root@localhost utils]# redis-cli --cluster reshard 127.0.0.1:7007
    >>> Performing Cluster Check (using node 127.0.0.1:7007)
    M: 562bd8a95bd1d8dbd1cc3b0c11e0d0f94a90542a 127.0.0.1:7007
       slots: (0 slots) master
    M: f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
    M: 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    S: a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006
       slots: (0 slots) slave
       replicates f67551b4c1d640da69abc9e72f5f0dafdae72ce6
    S: 105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004
       slots: (0 slots) slave
       replicates 61fffe775b57692c18590827fd8b7b28fe20224a
    S: 7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005
       slots: (0 slots) slave
       replicates 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8
    M: 61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    # 上面是node的信息,同时会问你要分配的槽数量,这里我输入5000
    How many slots do you want to move (from 1 to 16384)? 5000
    # 问你要分配的目标nodeID,看上面就可以,输入7007 nodeID
    What is the receiving node ID? 562bd8a95bd1d8dbd1cc3b0c11e0d0f94a90542a
    # 问你输入的源nodeID,我输入all
    Please enter all the source node IDs.
      Type 'all' to use all the nodes as source nodes for the hash slots.
      Type 'done' once you entered all the source nodes IDs.
    Source node #1:all
    ..... 
    # 他会问你是否执行分配计划,输入yes往下跑就行了
    
  4. 查看结果

    127.0.0.1:7007> cluster nodes
    f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003@17003 master - 0 1648735217000 3 connected 12589-16383
    562bd8a95bd1d8dbd1cc3b0c11e0d0f94a90542a 127.0.0.1:7007@17007 myself,master - 0 1648735215000 7 connected 0-1665 5461-7127 10923-12588 # 分配了5000个slot
    18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002@17002 master - 0 1648735216367 2 connected 7128-10922
    a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006@17006 slave f67551b4c1d640da69abc9e72f5f0dafdae72ce6 0 1648735215354 3 connected
    105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004@17004 slave 61fffe775b57692c18590827fd8b7b28fe20224a 0 1648735212000 1 connected
    7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005@17005 slave 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 0 1648735217384 2 connected
    61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001@17001 master - 0 1648735216000 1 connected 1666-5460
    

# 添加从节点

  1. 创建7008实例,将7008作为7007的从结点。

  2. 添加从节点命令:

    # redis-cli --cluster add-node 新节点的ip 端口 主节点ip 端口 --cluster-slave -- cluster-master-id 主节点id
    [root@localhost utils]# redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7007 --cluster-slave --cluster-master-id 562bd8a95bd1d8dbd1cc3b0c11e0d0f94a90542a
    >>> Adding node 127.0.0.1:7008 to cluster 127.0.0.1:7007
    >>> Performing Cluster Check (using node 127.0.0.1:7007)
    M: 562bd8a95bd1d8dbd1cc3b0c11e0d0f94a90542a 127.0.0.1:7007
       slots:[0-1665],[5461-7127],[10923-12588] (4999 slots) master
    M: f67551b4c1d640da69abc9e72f5f0dafdae72ce6 127.0.0.1:7003
       slots:[12589-16383] (3795 slots) master
       1 additional replica(s)
    M: 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8 127.0.0.1:7002
       slots:[7128-10922] (3795 slots) master
       1 additional replica(s)
    S: a224a372905800dba85273bf3b78768823da744d 127.0.0.1:7006
       slots: (0 slots) slave
       replicates f67551b4c1d640da69abc9e72f5f0dafdae72ce6
    S: 105bba88ed7fb19344a6acf6ade881eba4c1ded4 127.0.0.1:7004
       slots: (0 slots) slave
       replicates 61fffe775b57692c18590827fd8b7b28fe20224a
    S: 7cc7bb82eb674f5312cc82a810526a4aaa5f59cb 127.0.0.1:7005
       slots: (0 slots) slave
       replicates 18f0f2d51c88391e2dcde1f54fcca6f7247a14c8
    M: 61fffe775b57692c18590827fd8b7b28fe20224a 127.0.0.1:7001
       slots:[1666-5460] (3795 slots) master
       1 additional replica(s)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    >>> Send CLUSTER MEET to node 127.0.0.1:7008 to make it join the cluster.
    Waiting for the cluster to join
    
    >>> Configure node as replica of 127.0.0.1:7007.
    
  3. 通过cluster nodes可以检查结果。

# 删除节点

删除节点命令:

redis-cli --cluster del-node <ip>:<port> <nodeId>

注意,如果删除的目标节点已经占有Slot槽会失败,需要将节点占用的槽分配出去,步骤可以参考添加主节点分配槽。

# Cluster容灾原理

# 故障检测

集群中的每个节点都会定期地(每秒)向集群中的其他节点发送PING消息。如果在一定时间内(cluster-node-timeout),发送ping的节点A没有收到某节点B的pong回应,则A将B标识为pfail。 A在后续发送ping时,会带上B的pfail信息, 通知给其他节点(传声筒,像主从的主观下线)。如果B被标记为pfail的个数大于集群主节点个数的一半(N/2 + 1)时,B会被标记为fail,A向整个集群广播,该节点已经下线(像主从的客观下线)。其他节点收到广播,标记B为fail。

# 从节点选举

Raft协议的实现。每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。

slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,master 收到后回复FAILOVER_AUTH_ACK 消息告知是否同意。 slave 发送FAILOVER_AUTH_REQUEST前会将currentEpoch 自增,并将最新的Epoch 带入到FAILOVER_AUTH_REQUEST 消息中,如果自己未投过票,则回复同意,否则回复拒绝。

所有的master开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 +1)都投票给了某个从节点,那么选举通过,则那个从节点可以切换成master。

RedisCluster失效的判定:

  1. 集群中半数以上的主节点都宕机(无法投票)
  2. 宕机的主节点的从节点也宕机了(slot槽分配不连续)

# 变更通知

当slave 收到过半的master 同意时,会成为新的master。此时会以最新的 Epoch 通过pong消息广播自己成为master,让Cluster 的其他节点尽快的更新拓扑结构(node.conf)。

# 主从切换

主从切换分为,自动切换以及手动切换。自动切换就是上面讲的从节点选举。

手动切换:如字面意思。人工故障切换是预期的操作,而非发生了真正的故障,目的是以一种安全的方式(数据无丢失)将当前master节点和其中一个slave节点(执行cluster-failover的节点)交换角色,步骤参考如下:

  1. 向从节点发送cluster failover命令(slaveof no one)
  2. 从节点告知其主节点要进行手动切换(CLUSTERMSG_TYPE_MFSTART)
  3. 主节点会阻塞所有客户端命令的执行(10s)
  4. 从节点从主节点的ping包中获得主节点的复制偏移量
  5. 从节点复制达到偏移量后发起选举、统计选票、赢得选举、升级为主节点并更新配置
  6. 切换完成后,原主节点向所有客户端发送moved指令重定向到新的主节点

以上是在主节点在线情况下。如果主节点下线了,则采用cluster failover force或cluster failover takeover 进行强制切换。

# 副本漂移

我们知道在一主一从的情况下,如果主从同时挂了,那整个集群就挂了。为了避免这种情况我们可以做一主多从,但这样成本就增加了。Redis提供了一种方法叫副本漂移,这种方法既能提高集群的可靠性又不用增加太多的从机。如图:

cluster6

Master1宕机,则Slaver11提升为新的Master1。Cluster集群检测到新的Master1是单点的,就会从拥有最多的从机的节点组(Master3)中,选择节点名称字母顺序最小的从机(Slaver31)漂移到单点的主从节点组(Master1)。具体流程如下:

  1. 将Slaver31的从机记录从Master3中删除
  2. 将Slaver31的的主机改为Master1
  3. 在Master1中添加Slaver31为从节点
  4. 将Slaver31的复制源改为Master1
  5. 通过ping包将信息同步到集群的其他节点

# 附录:一致性Hash

关于普通hash算法(如CRC32、CRC16等)分片的问题不再赘述,这里主要是了解一致性hash是怎么解决普通hash的缺陷的。

普通hash是对主机数量取模,而一致性hash是对 2^32(4 294 967 296)取模。我们把2^32想象成一个圆,就像钟表一样,钟表的圆可以理解成由60个点组成的圆,而此处我们把这个圆想象成由2^32个点组成的圆,示意图如下:

hashring1

圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1,也就是说0点左侧的第一个点代表2^32-1 。我们把这个由2的32次方个点组成的圆环称为hash环。

假设我们有3台缓存服务器,服务器A、服务器B、服务器C,那么,在生产环境中,这三台服务器肯定有自己的IP地址,我们使用它们各自的IP地址进行哈希计算,使用哈希后的结果对2^32取模,即如下公式:

hash(服务器的IP地址) % 2^32

通过上述公式算出的结果一定是一个0到2^32-1之间的一个整数,我们就用算出的这个整数,代表服务器A、服务器B、服务器C,既然这个整数肯定处于0到2^32-1之间,那么,上图中的hash环上必定有一个点与这个整数对应,也就是服务器A、服务器B、服务C就可以映射到这个环上,如下图:

hashring2

假设,我们需要使用Redis缓存数据,那么我们使用如下公式可以将数据映射到上图中的hash环上:

hash(key) % 2^32

hashring3

现在服务器与数据都被映射到了hash环上,上图中的数据将会被缓存到服务器A上,因为从数据的位置开始,沿顺时针方向遇到的第一个服务器就是A服务器,所以,上图中的数据将会被缓存到服务器A上。如图:

hashring4

将缓存服务器与被缓存对象都映射到hash环上以后,从被缓存对象的位置出发,沿顺时针方向遇到的第一个服务器,就是当前对象将要缓存于的服务器,由于被缓存对象与服务器hash后的值是固定的,所以,在服务器不变的情况下,数据必定会被缓存到固定的服务器上,那么,当下次想要访问这个数据时,只要再次使用相同的算法进行计算,即可算出这个数据被缓存在哪个服务器上,直接去对应的服务器查找对应的数据即可。多条数据存储如下:

hashring5

优点:添加或移除节点时,数据只需要做部分的迁移,比如上图中把C服务器移除,则数据4迁移到服务器A中,而其他的数据保持不变,添加效果是一样的。 hash环偏移:在介绍一致性哈希的概念时,我们理想化的将3台服务器均匀的映射到了hash环上。也就是说数据的范围是2^32/N。但实际情况往往不是这样的。有可能某个服务器的数据会很多,某个服务器的数据会很少,造成服务器性能不平均。这种现象称为hash环偏移,如下图

hashring6

理论上我们可以通过增加服务器的方式来减少偏移,但这样成本较高,所以我们可以采用虚拟节点的方 式,也就是虚拟服务器,如图:

hashring7

"虚拟节点"是"实际节点"(实际的物理服务器)在hash环上的复制品,一个实际节点可以对应多个虚拟节点。从上图可以看出,A、B、C三台服务器分别虚拟出了一个虚拟节点,当然,如果你需要,也可以虚拟出更多的虚拟节点。引入虚拟节点的概念后,缓存的分布就均衡多了,上图中,1号、3号数据被缓存在服务器A中,5号、4号数据被缓存在服务器B中,6号、2号数据被缓存在服务器C中,如果你还不放心,可以虚拟出更多的虚拟节点,以便减小hash环偏斜所带来的影响,虚拟节点越多,hash环上的节点就越多,缓存被均匀分布的概率就越大。

上次更新: 5/30/2023, 11:42:20 PM
缓存架构设计

缓存架构设计→

最近更新
01
2025
01-15
02
Elasticsearch面试题
07-17
03
Elasticsearch进阶
07-16
更多文章>
Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式