redis 高可用集群

Redis的分布式

RedisCluster是Redis的分布式解决方案,在3.0版本后推出的方案,有效地解决了Redis分布式的需求;

当遇到单机内存、并发等瓶颈时,可使用此方案来解决这些问题

分布式数据库概念

分布式数据库把整个数据按分区规则映射到多个节点,即把数据划分到多个节点上,每个节点负责整体数据的一个子集。

比如我们库有900条用户数据,有3个redis节点,将900条分成3份,分别存入到3个redis节点

image-20221024141343948

分区规则

常见的分区规则哈希分区和顺序分区,redis集群使用了哈希分区,顺序分区暂用不到,不做具体说明;

RedisCluster采用了哈希分区的“虚拟槽分区”方式(哈希分区分节点取余、一致性哈希分区和虚拟槽分区)。

虚拟槽分区

槽:slot
RedisCluster采用此分区,所有的键根据哈希函数(CRC16[key]&16383)映射到0-16383槽内,共16384个槽位,每个节点维护部分槽及槽所映射的键值数据
哈希函数: Hash()=CRC16[key]&16383
image-20221024141554718

槽、键、数据关系

image-20221024141623153

RedisCluster的缺陷

a,键的批量操作支持有限,比如mset, mget,如果多个键映射在不同的槽,就不支持了   mset name james age 19
b,键事务支持有限,当多个键分布在不同节点时无法使用事务,同一节点是支持事务
c,键是数据分区的最小粒度,不能将一个很大的键值对映射到不同的节点
d,不支持多数据库,只有0,select 0
e,复制结构只支持单层结构,不支持树型结构。

redis 4.0 集群搭建

redis 4.0 之前版本搭建集群依靠 ruby 脚本。redis 5.0 之后集群搭建不需要了

# redis 集群的实现,使用了 ruby 脚本;所以先安装ruby及其ruby的安装工具
# 安装rvm
mkdir rvm && cd rvm
wget https://codeload.github.com/rvm/rvm/tar.gz/1.29.8
tar -zxvf 1.29.8.tar.gz
cd rvm-1.29.8/
./install --auto-dotfiles
# 激活rvm
source /etc/profile.d/rvm.sh
# 验证rvm
rvm -v
# 添加用户到组
usermod -aG rvm root

# 通过rvm升级ruby
# 安装 ruby。安装命令如下:
yum -y install ruby ruby-devel rubygems rpm-build

# 修改 rvm下载 ruby的源,到 Ruby China 的镜像
# 参考:https://blog.csdn.net/Gushiyuta/article/details/90770681
gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/

# 查看rvm库中已知的ruby版本
rvm list known

# 安装一个ruby版本
rvm install 2.3.3

# 使用一个ruby版本
rvm use 2.3.3

# 设置默认版本
rvm use 2.3.3 --default

# 卸载一个已知版本
rvm remove 2.0.0

# 查看ruby版本
ruby --version

# 安装redis的组件
gem install redis

# 创建集群环境
mkdir -p /opt/redis-cluster/{6379..6381}/{data,logs} /opt/redis-cluster/{6389..6391}/{data,logs}

# 拷贝配置文件
for i in $(ls /opt/redis-cluster/);do cp redis.conf /opt/redis-cluster/$i;done

# 修改配置文件,不同端口,port 不同。
bind 0.0.0.0
port ${port}
daemonize    yes                          #      //redis后台运行
dir /opt/redis-cluster/${port}/data/			# 指定dir
logfile "redis.log"						# log 路径
pidfile redis.pid
cluster-enabled  yes			# //开启集群  把注释#去掉
cluster-config-file  nodes_${port}.conf		# //集群的配置  配置文件首次启动自动生成
cluster-node-timeout  15000		# //请求超时  默认15秒,可自行设置
masterauth 12345678
requirepass 12345678
protected-mode no		# 关闭保护模式


# 启动六台redis
redis-server /opt/redis-cluster/6379/redis.conf
redis-server /opt/redis-cluster/6380/redis.conf
redis-server /opt/redis-cluster/6381/redis.conf
redis-server /opt/redis-cluster/6389/redis.conf
redis-server /opt/redis-cluster/6390/redis.conf
redis-server /opt/redis-cluster/6391/redis.conf

# 配置集群
redis-trib.rb create --replicas 1 172.16.40.141:6379 172.16.40.141:6380 172.16.40.141:6381 172.16.40.141:6389 172.16.40.141:6390 172.16.40.141:6391
# 输入 yes 

如果之前 redis 有数据存在,flushall 清空;(坑:不需要 cluster meet ..)

链接集群

# 添加 -c 参数可以链接 集群
redis-cli -h 172.16.40.141 -p 6379 -c

172.16.40.141:6379> set a 3
-> Redirected to slot [15495] located at 172.16.40.141:6381
OK
172.16.40.141:6381>

集群安全

在这里需要注意,因为 集群 建立需要接触 ruby脚本;所以在一开始创建的时候,不建议redis使用 pass,在建立 redis 集群之后再配置 pass;如果一开始就配置 reids pass,这里就需要修改 ruby 脚本,在脚本中配置 pass 字段;

# 集群正常启动后,每个 redis.conf 添加如下内容
masterauth 12345678
requirepass 12345678

# 当主节点下线时,从节点会变成主节点,用户和密码是很有必要的,设置成一致


# 也可通过如下方式写入密码
config set requirepass 12345678
config set masterauth 12345678

auth 12345678
config get requirepass

一主多从


redis 5.0 集群搭建

redis 编译安装

# 
wget http://download.redis.io/releases/redis-5.0.2.tar.gz
tar xf redis-5.0.2.tar.gz
cd redis-5.0.2/deps/
make -j2 hiredis lua jemalloc linenoise

cd ..
make
make install
mkdir -p /opt/redis-5.0.2/bin
cp redis.conf sentinel.conf /opt/redis-5.0.2/
cd src/
cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server redis-trib.rb /opt/redis-5.0.2/bin/

redis 集群搭建


# redis 主要配置如下
daemonize yes	# 
port 6379 # (分别对每个机器的端口号进行设置)
dir /opt/redis-cluster/6379/ # (指定数据文件存放位置,必须要指定不同的目录位置,不然会丢失数据)
cluster-enabled yes # (启动集群模式)
cluster-config-file nodes-8001.conf # (集群节点信息文件,这里800x最好和port对应上)
cluster-node-timeout 5000
bind IP地址 # (去掉bind绑定访问ip信息)
protected-mode no # (关闭保护模式)

# 如果要设置密码需要增加如下配置:
requirepass xxx # (设置redis访问密码)
masterauth xxx # (设置集群节点间访问密码,跟上面一致)


2. 把配置文件分发到每个节点,并且修改端口等配置
mkdir -p /opt/redis-cluster/{6379..6381}/ /opt/redis-cluster/{6389..6391}/
for i in $(ls /opt/redis-cluster/); do \cp redis.conf /opt/redis-cluster/$i/ ;done
for i in $(ls /opt/redis-cluster/); do sed -i "s#6379#$i#g" /opt/redis-cluster/$i/redis.conf ;done

3. 启动redis服务
redis-server /opt/redis-cluster/6379/redis.conf
redis-server /opt/redis-cluster/6380/redis.conf
redis-server /opt/redis-cluster/6381/redis.conf
redis-server /opt/redis-cluster/6389/redis.conf
redis-server /opt/redis-cluster/6390/redis.conf
redis-server /opt/redis-cluster/6391/redis.conf

4. 创建集群
# redis 5.0 之前创建集群借助 ruby 脚本。5.0 之后创建集群不需要了;
redis-cli -a 12345678 --cluster create --cluster-replicas 1 10.23.26.40:6379 10.23.26.40:6380 10.23.26.40:6381 10.23.26.40:6389 10.23.26.40:6390 10.23.26.40:6391
# 回车输入 yes 即可;

# ⚠️:如果我们一次性创建集群中包含主从节点。那么主从的顺序是随机分配的,直到集群创建完毕才能确定谁是谁的从

5. 登录集群验证
redis-cli -c -a 12345678 -h 10.23.26.40 -p 6379
注意:集群只有一个 0 库;

redis 集群 指定从节点

# 由于主从是随机分配的,我们有些场景中,需要进行主从的规划,这时候,就可以指定从节点;
1. 首先创建全是主节点的集群
redis-cli -a 12345678 --cluster create --cluster-replicas 0 10.23.26.40:6379 10.23.26.40:6380 10.23.26.40:6381

2. 集群中增加从节点
redis-cli -a 12345678 --cluster add-node 10.23.26.40:6389 10.23.26.40:6379 --cluster-slave --cluster-master-id d97b5d5cc75a06d534d3b260cd6ec45c2fb6656b
    - slave 表示要添加从节点
    - cluster-master-id 要添加到哪一个主节点,id是 d97b5d5cc75a06d534d3b260cd6ec45c2fb6656b
    - 10.23.26.40:6389 要添加的从节点
    - 10.23.26.40:6379 原集群中任意节点
    id 查看方法:cluster nodes
# 直接执行之后,就会添加node节点;

redis 集群操作

增加主节点

# 版本 redis 5.0.2
1. 增加主节点
redis-cli -a 12345678 --cluster add-node 10.23.26.40:6382 10.23.26.40:6379
  10.23.26.40:6382 要向集群添加新的节点
  10.23.26.40:6379 原集群中任意节点
  ⚠️:节点已经加入集群,但
    由于它还没有分配到 hash slots,所以它还没有数据
    由于它是还没有 hash slots 的主节点,所以它不会参与到从节点升级到主节点的选举中

# 执行 resharding 指令来为它分配 hash slots
redis-cli -a 12345678 --cluster reshard 10.23.26.40:6379

How many slots do you want to move (from 1 to 16384)? 1000
What is the receiving node ID? 39c45ecfac040a1be5e00c1e2b07cc9fc0f5fda2
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

第一个问题需要需要填写,如1000.
第二个问题可以通过命令查看:redis-cli -a 12345678 -p 6382 cluster nodes | grep myself
第三个问题:all,这样会从每个节点上移动一部分 hash slots 到新节点

# 然后执行如下命令查看集群是否正常:
redis-cli -a 12345678 --cluster check 10.23.26.40:6379 

增加从节点

# 版本 redis 5.0.2
# 指定主节点
redis-cli -a 12345678 --cluster add-node 10.23.26.40:6392 10.23.26.40:6379 --cluster-slave --cluster-master-id 39c45ecfac040a1be5e00c1e2b07cc9fc0f5fda2

    - 10.23.26.40:6392 需要添加的从节点
    - 10.23.26.40:6379 集群中的任意节点
    - -cluster-master-id 需要添加从节点的主节点id

# 不指定主节点
# 这会为该从节点随机分配一个主节点,优先从那些从节点数目最少的主节点中选取。
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 --cluster-slave

删除节点

# 版本 redis 5.0.2
# 只能删除从节点或者空的主节点,指令如下:
redis-cli --cluster del-node 127.0.0.1:7000 <node-id>
    - 127.0.0.1:7000为集群中任意节点
    - node-id为要删除的节点的id

⚠️:从节点删除不需要清空槽;

1. 清空槽
redis-cli -a 12345678 --cluster reshard 10.23.26.40:6379

How many slots do you want to move (from 1 to 16384)? 277
What is the receiving node ID? d97b5d5cc75a06d534d3b260cd6ec45c2fb6656b
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: 39c45ecfac040a1be5e00c1e2b07cc9fc0f5fda2
Source node #2: done

    - 输入需要移除的槽个数
    - 需要输入接受槽的 node id
    - 输入 槽的来源 node id;最后输入done 开始转移;

2. 删除主节点
redis-cli -a 12345678 --cluster del-node 10.23.26.40:6379 39c45ecfac040a1be5e00c1e2b07cc9fc0f5fda2

    - 10.23.26.40:6379 集群任意节点的端口
    - 39c45ecfac040a1be5e00c1e2b07cc9fc0f5fda2 需要删除的主节点的 id

集群节点之前的通讯

1,节点之间采用 Gossip 协议进行通信,Gossip 协议就是指节点彼此之间不断

通信交换信息

![image-20221027180410937](/Users/liulei/Library/Application Support/typora-user-images/image-20221027180410937.png)

当主从角色变化或新增节点,彼此通过 ping/pong 进行通信知道全部节点的最新状 态并达到集群同步

2,Gossip 协议

  • Gossip 协议的主要职责就是信息交换,信息交换的载体就是节点之间彼此发送的 Gossip 消息,常用的 Gossip 消息有 ping 消息、pong 消息、meet 消息、fail 消息

  • meet 消息:用于通知新节点加入,消息发送者通知接收者加入到当前集群,meet 消息通信完后,接收节点会加入到集群中,并进行周期性 ping pong 交换

  • ping 消息:集群内交换最频繁的消息,集群内每个节点每秒向其它节点发 ping 消 息,用于检测节点是在在线和状态信息,ping 消息发送封装自身节点和其他节点的状态 数据;

  • pong 消息,当接收到 ping meet 消息时,作为响应消息返回给发送方,用来确认正 常通信,pong 消息也封闭了自身状态数据;

  • fail 消息:当节点判定集群内的另一节点下线时,会向集群内广播一个 fail 消息, 后面会讲到。......

3,消息解析流程

所有消息格式为:消息头、消息体,消息头包含发送节点自身状态数据(比如节点 ID、槽映射、节点角色、是否下线等),接收节点根据消息头可以获取到发送节点的相 关数据。

消息解析流程:

image-20221027180550687

4,选择节点并发送 ping 消息:

Gossip 协议信息的交换机制具有天然的分布式特性,但 ping pong 发送的频率很

高,可以实时得到其它节点的状态数据,但频率高会加重带宽和计算能力,因此每次都 会有目的性地选择一些节点; 但是节点选择过少又会影响故障判断的速度,redis 集群 的 Gossip 协议兼顾了这两者的优缺点,看下图:

image-20221027180654782

不难看出:节点选择的流程可以看出消息交换成本主要体现在发送消息的节点数量 和每个消息携带的数据量

流程说明:

  • A,选择发送消息的节点数量:集群内每个节点维护定时任务默认为每秒执行 10 次,每秒会随机选取 5 个节点,找出最久没有通信的节点发送 ping 消息,用来 保证信息交换的随机性,每 100 毫秒都会扫描本地节点列表,如果发现节点最近 一次接受 pong 消息的时间大于 cluster-node-timeout/2 则立刻发送 ping 消息,这 样做目的是防止该节点信息太长时间没更新,当我们宽带资源紧张时,在可 redis.conf 将 cluster-node-timeout 15000 改成 30 秒,但不能过度加大
  • B,消息数据:节点自身信息和其他节点信息

集群扩容(老版本)

这也是分布式存储最常见的需求,当我们存储不够用时,要考虑扩容 扩容步骤如下:

A,准备好新节点

image-20221028101739663

B,加入集群,迁移槽和数据

image-20221028102300234

具体操作

1),同目录下新增 6382、6392 两 启动两个新 redis 节点
redis-server /opt/redis-cluster/6382/redis.conf (新主节点) 
redis-server /opt/redis-cluster/6392/redis.conf (新从节点)


2),新增主节点
redis-trib.rb add-node 172.16.40.141:6382 172.16.40.141:6379
6379 是原存在的主节点,6382 是新的主节点

3),添加从节点
redis-trib.rb add-node --slave --master-id 03ccad2ba5dd1e062464bc7590400441fafb63f2 192.168.42.111:6392 192.168.42.111:6379
--slave,表示添加的是从节点
--master-id 03ccad2ba5dd1e062464bc7590400441fafb63f2 表示主节点 6382 的 master_id
192.168.42.111:6392,新从节点 192.168.42.111:6379 集群原存在的旧节点
4),redis-trib.rb reshard 192.168.42.111:6382 //为新主节点重新分配 solt How many slots do you want to move (from 1 to 16384)? 1000 //设置 slot 数 1000 What is the receiving node ID? 464bc7590400441fafb63f2 //新节点 node id Source node #1:all //表示全部节点重新洗牌
新增完毕!

请求路由重定向

我们知道,在 redis 集群模式下,redis 接收的任何键相关命令首先是计算这个键 CRC值,通过 CRC 找到对应的槽位,再根据槽找到所对应的 redis 节点,如果该节点是 本身,则直接处理键命令;如果不是,则回复键重定向到其它节点,这个过程叫做 MOVED 重定向

image-20221028163621753

故障转移:

redis 集群实现了高可用,当集群内少量节点出现故障时,通过故障转移可以保证集群正常对外提供服务。

当集群里某个节点出现了问题,redis 集群内的节点通过 ping pong 消息发现节点是否健康,是否有故障,其实主要环节也包括了 主观下线和客观下线;

主观下线:指某个节点认为另一个节点不可用,即下线状态,当然这个状态不是最终的 故障判定,只能代表这个节点自身的意见,也有可能存在误判;

image-20221028163710475

下线流程:

A,节点 a 发送 ping 消息给节点 b ,如果通信正常将接收到 pong 消息,节点 a 更新最近一次与节点 b 的通信时间;

B,如果节点 a 与节点 b 通信出现问题则断开连接,下次会进行重连,如果一直通信失败,则它们的最后通信时间将无法更新;

C,节点 a 内的定时任务检测到与节点 b 最后通信时间超过 cluster-note-timeout 时, 更新本地对节点 b 的状态为主观下线(pfail)

客观下线

指真正的下线,集群内多个节点都认为该节点不可用,达成共识,将它下线, 如果下线的节点为主节点,还要对它进行故障转移

假如节点 a 标记节点 b 为主观下线,一段时间后节点 a 通过消息把节点 b 的状态发 到其它节点,当节点 c 接受到消息并解析出消息体时,会发现节点 b 的 pfail 状态时, 会触发客观下线流程;

当下线为主节点时,此时 redis 集群为统计持有槽的主节点投票数是否达到一半, 当下线报告统计数大于一半时,被标记为客观下线状态。

image-20221028163949110

故障恢复

故障主节点下线后,如果下线节点的是主节点,则需要在它的从节点中选一个替换 它,保证集群的高可用;

转移过程如下:

  1. 资格检查:检查该从节点是否有资格替换故障主节点,如果此从节点与主节点断开过通信,那么当前从节点不具体故障转移;
  2. 准备选举时间:当从节点符合故障转移资格后,更新触发故障选举时间,只有到达该时间后才能执行后续流程;
  3. 发起选举:当到达故障选举时间时,进行选举;
  4. 选举投票:只有持有槽的主节点才有票,会处理故障选举消息,投票过程其实是一个领导者选举(选举从节点为领导者)的过程,每个主节点只能投一张票给从节点, 当从节点收集到足够的选票(大于 N/2+1)后,触发替换主节点操作,撤销原故障主节点的 槽,委派给自己,并广播自己的委派消息,通知集群内所有节点。