【8.0】Redis主从复制和哨兵集群

发布时间 2023-09-10 13:45:16作者: Chimengmeng

【一】主从复制

【1】引入

  • 原因 : redis 单实例 机器故障;容量瓶颈;QPS瓶颈

  • 主从复制:一主一从,一主多从

    • 做读写分离

    • 做数据副本

    • 扩展数据性能

  • 一个master可以有多个slave

  • 一个slave只能有一个master

  • 数据流向是单向的,从master到slave

【2】主从原理

image-20230416224827965

  • 主从复制的原理

    • 副本库通过执行命令slaveof ip port连接到主库,并发送SYNC命令给主库。
    • 主库收到SYNC命令后,立即触发BGSAVE命令进行后台RDB持久化操作,并将生成的RDB文件发送给副本库。
    • 副本库接收到RDB文件后,对其进行快照恢复,应用RDB快照中的数据。
    • 之后,主库会将产生的新操作依次保存并发送给副本库。
    • 这样,主从复制集就正常工作了,在这之后,主库发生的任何新操作都会自动传播给副本库。
    • 所有与复制相关的信息都可以通过INFO命令查看,并且即使重启任何节点,他们的主从关系仍然存在。
    • 如果主从关系断开,并在之后重新连接,副本库会发送PSYNC命令给主库,主库只会将缺失部分的数据同步给副本库,以快速恢复主从关系。
  • 在部署主从复制之前,需要注意以下事项:

    • 主库一般要开启持久化功能,以防止主库重启操作导致数据丢失。

    • 如果不开启持久化,在主库重启时会导致所有主从数据丢失。

  • 副本库通过slaveof 127.0.0.1 6379命令,连接主库,并发送SYNC给主库

  • 主库收到SYNC,会立即触发BGSAVE,后台保存RDB,发送给副本库

  • 副本库接收后会应用RDB快照

  • 主库会陆续将中间产生的新的操作,保存并发送给副本库

  • 到此,我们主复制集就正常工作了

  • 再此以后,主库只要发生新的操作,都会以命令传播的形式自动发送给副本库.

  • 所有复制相关信息,从info信息中都可以查到.即使重启任何节点,他的主从关系依然都在.

  • 如果发生主从关系断开时,从库数据没有任何损坏,在下次重连之后,从库发送PSYNC给主库

  • 主库只会将从库缺失部分的数据同步给从库应用,达到快速恢复主从的目的

    • 主库是否要开启持久化(一般情况要开启)

    • 如果不开有可能,主库重启操作,造成所有主从数据丢失!

【3】主从搭建步骤

(1)前置条件

  • 准备至少两台机器(可以在一台机器上运行两个Redis实例)
  • 前置条件1 :至少需要两台机器--》在一台机器运行两个redis实例
  • 前置条件2:辅助配置(主从数据一致性配置)

(2)配置辅助参数

  • 配置辅助参数以保证主从数据一致性:
    • min-slaves-to-write和min-slaves-max-lag。
  • 例如,设置min-slaves-to-write为1,min-slaves-max-lag为3。
  • 这样,在从服务器数量少于1个或三个从服务器延迟超过或等于3秒时,主服务器将拒绝执行写命令。
min-slaves-to-write 1
min-slaves-max-lag 3
  • 那么在从服务器的数量少于1个,或者三个从服务器的延迟(lag)值都大于或等于3秒时,主服务器将拒绝执行写命令

(3)搭建主从关系

方式一:通过命令行搭建主从关系:

  • 1 6380是从,6379是主
  • 2 启动器两台实例
  • 3 搭建主从关系
    • 在从库上:slaveof ip port
      • slaveof 127.0.0.1 6379
  • 4 断开主从关系
    • 在从库上:slaveof no one
  • 在从库上执行命令:slaveof ip port,其中ip和port是主库的IP地址和端口号。
  • 例如,slaveof 127.0.0.1 6379

方式二:通过配置文件搭建主从关系:

  • 在从库的Redis配置文件中,添加如下配置:

    slaveof 127.0.0.1 6379
    slave-read-only yes
    

(4)查看主从关系

  • 可以使用INFO命令查看主从关系
info

【二】哨兵

  • 哨兵(Sentinel)是Redis提供的一种高可用性解决方案,可以监控主从复制中的主节点,并在主节点出现故障时自动进行故障转移。

【1】主从复制存在的问题

  • 主从复制,主节点发生故障,需要做故障转移

    • 可以手动转移:让其中一个slave变成master
    • 哨兵做高可用
  • 主从复制,只能主写数据,所以写能力和存储能力有限

    • 使用集群解决
  • 高可用:服务对外高度可用

    • 高可用 四个9 99.99%
  • 主从复制存在一些问题
    • 例如主节点故障时需要手动进行故障转移以及主节点的写能力和存储能力有限。
  • 为了解决这些问题,可以使用哨兵来提供高可用性,并通过使用集群来增加写能力和存储能力。
    • 高可用性:
      • 指系统能够长时间稳定地运行
      • 最常见的衡量指标是“四个9”,即系统的可用时间达到99.99%。

【2】哨兵(sentinel)原理

  • 1 多个sentinel发现并确认master有问题
  • 2 选举触一个sentinel作为领导(raft算法)
  • 3 选取一个slave作为新的master
  • 4 通知其余slave成为新的master的slave
  • 5 通知客户端主从变化
  • 6 等待老的master复活成为新master的slave
  • 哨兵:是一个进程---》启动使用redis-sentinel启动,有自己的配置文件,启动后,可以客户端连接(redis-cli),查看哨兵状态
  • 多个哨兵节点共同工作,发现并确认主节点出现故障。
  • 通过选举算法(如Raft算法),选举出一个哨兵节点作为领导者。
  • 领导者选取一个从节点作为新的主节点。
  • 领导者通知其他从节点成为新的主节点的从节点。
  • 领导者通知客户端主从变化。
  • 等待原来的主节点复活后,将其作为新的主节点的从节点。
  • 哨兵本身是一个进程,可以使用redis-sentinel命令启动,并具有自己的配置文件。
  • 启动后可以通过客户端连接(如redis-cli)查看哨兵的状态。

【3】配置哨兵

(0)前置工作

  • 1 配置开启主从节点

  • 2 配置开启sentinel监控主节点(sentinel是特殊的redis)

  • 3 应该是多台机器

  • 配置开启sentinel监控主节点

mkdir -p redis4/conf redis4/data redis5/conf redis5/data redis6/data redis6/conf
  • (一主两从架构---》运行多个哨兵--》一般奇数个(3个))

(1)配置哨兵配置文件

vi sentinel.conf
  • 配置文件内容
port 26379
daemonize yes
dir data
protected-mode no
bind 0.0.0.0
logfile "redis_sentinel.log"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
  • 配置会重写,自动发现slave
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
  • 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
sentinel monitor <master-name> <ip> <redis-port> <quorum>
  • 设置连接master和slave时的密码,注意的是sentinel不能分别为master和slave设置不同的密码,因此master和slave的密码应该设置相同。
sentinel auth-pass <master-name> <password>
  • 这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒
sentinel down-after-milliseconds <master-name> <milliseconds> 

这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。

sentinel parallel-syncs <master-name> <numslaves> 
  • failover-timeout 可以用在以下这些方面:
    • 同一个sentinel对同一个master两次failover之间的间隔时间。

    • 当一个slave从一个错误的master那里同步数据开始计算时间。

      • 直到slave被纠正为向正确的master那里同步数据时。
    • 当想要取消一个正在进行的failover所需要的时间。

    • 当进行failover时,配置所有slaves指向新的master所需的最大时间。

      • 不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了。
sentinel failover-timeout <master-name> <milliseconds>
  • 【1】搭一个一主两从

    • 创建三个配置文件
    • 第一个是主配置文件
    daemonize yes
    pidfile /var/run/redis.pid
    port 6379
    dir "/opt/soft/redis/data"
    logfile “6379.log”
    
    • 第二个是从配置文件
    daemonize yes
    pidfile /var/run/redis2.pid
    port 6378
    dir "/opt/soft/redis/data2"
    logfile “6378.log”
    slaveof 127.0.0.1 6379
    slave-read-only yes
    
    • 第三个是从配置文件
    daemonize yes
    pidfile /var/run/redis3.pid
    port 6377
    dir "/opt/soft/redis/data3"
    logfile “6377.log”
    slaveof 127.0.0.1 6379
    slave-read-only yes
    
  • 【2】搭建哨兵

    • sentinel.conf这个文件

    • 把哨兵也当成一个redis服务器

    • 创建三个配置文件分别叫

      • sentinel_26379.conf
      • sentinel_26378.conf
      • sentinel_26377.conf
    • 当前路径下创建 data1 data2 data3 个文件夹

      • 内容如下(需要修改端口,文件地址日志文件名字)

        port 26379
        daemonize yes
        dir ./data3
        protected-mode no
        bind 0.0.0.0
        logfile "redis_sentinel3.log"
        sentinel monitor mymaster 127.0.0.1 6379 2
        sentinel down-after-milliseconds mymaster 30000
        sentinel parallel-syncs mymaster 1
        sentinel failover-timeout mymaster 180000
        
    • 启动三个哨兵

      ./src/redis-sentinel sentinel_26379.conf
      ./src/redis-sentinel sentinel_26378.conf
      ./src/redis-sentinel sentinel_26377.conf
      
    • 登陆哨兵

      ./src/redis-cli -p 26377
      
    • 输入 info

    • 查看哨兵的配置文件被修改了,自动生成的

    • 主动停掉主redis 6379,哨兵会自动选择一个从库作为主库

      redis-cli -p 6379
      shutdown
      
    • 等待原来的主库启动,该主库会变成从库

(2)配置3个配置文件

  • 配置3个配置文件,监听端口不一样(正常应该放在3台机器上)
docker run -p 26379:26379 --name redis_26379 -v /home/redis4/conf/sentinel.conf:/etc/redis/sentinel.conf -v /home/redis4/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf

docker run -p 26378:26379 --name redis_26378 -v /home/redis5/conf/sentinel.conf:/etc/redis/sentinel.conf -v /home/redis5/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf

docker run -p 26377:26379 --name redis_26377 -v /home/redis6/conf/sentinel.conf:/etc/redis/sentinel.conf -v /home/redis6/data:/data -d redis redis-sentinel /etc/redis/sentinel.conf

(3)启动哨兵

  • 启动三个哨兵
./src/redis-sentinel sentinel_26379.conf
./src/redis-sentinel sentinel_26380.conf
./src/redis-sentinel sentinel_26381.conf

(4)演示故障

  • 把主库停止

  • 查看自动故障切换

  • 启动起原来主库,变成了从

  • 停止主节点。
  • 查看自动故障转移的过程。
  • 启动原来的主节点,它会成为新的从节点。

【4】Python 操作哨兵

import redis
from redis.sentinel import Sentinel

# 连接哨兵服务器(主机名也可以用域名)
# 10.0.0.101:26379
sentinel = Sentinel([('8.130.125.9', 26379),
                     ('8.130.125.9', 26380),
                     ('8.130.125.9', 26381)
         ],
                    socket_timeout=5)

print(sentinel)
# 获取主服务器地址
master = sentinel.discover_master('mymaster')
print(master)

# 获取从服务器地址
slave = sentinel.discover_slaves('mymaster')
print(slave)



##### 读写分离
# 获取主服务器进行写入
master = sentinel.master_for('mymaster', socket_timeout=0.5)
w_ret = master.set('foo', 'bar')

# 获取信息进行查看
# slave = sentinel.slave_for('mymaster', socket_timeout=0.5)
# r_ret = slave.get('foo')
# print(r_ret)
  • 以上示例代码演示了如何使用Python连接到哨兵,并进行主从切换和读写分离操作。
  • 首先连接到哨兵获取到主服务器地址和从服务器地址
  • 然后可以使用master_for方法获取主服务器实例进行写操作
  • 使用slave_for方法获取从服务器实例进行读操作。

【补充】Raft算法

  • Raft是一种分布式共识算法,用于解决分布式系统中的一致性问题。
    • 它的设计目标是使得理解和实现更加简单,并且可以在广泛的应用场景中使用。
  • 首先,我们来了解一下Raft算法的基本原理。
    • Raft通过选举一个领导者来进行操作的协调,这个领导者负责接收和处理客户端请求,并将结果复制到其他副本(称为跟随者)上。
    • 由于Raft算法的核心思想是复制状态机,它在一系列日志条目上传递给状态机之后,确保所有的副本最终达到相同的状态。
  • Raft算法包含三个关键的角色:
    • 领导者、跟随者和候选者。
    • 初始状态下,所有节点都是跟随者。当选举时间超时时,跟随者会转变为候选者进行竞选,然后发送选举请求给其他节点,如果获得大多数节点的投票,则成为领导者。
    • 领导者负责接收客户端的请求,并发起一系列的日志条目的追加和复制。
    • 跟随者则监听来自领导者的心跳信息,以便及时检测到领导者的失效并进行选举。
  • 下面是一个简单的Raft算法的示例代码(以伪代码形式展示):
// 定义节点状态
enum State { follower, candidate, leader }

// 定义日志条目
struct LogEntry {
    int term;
    // 附加的命令或数据
    // ...
}

// 定义节点
struct Node {
    int currentTerm;
    int voteFor;
    State state;
    // 状态机等其他字段
    // ...

    // 处理接受到的来自 Leader 的日志追加 RPC
    function appendEntries(AppendEntriesArgs args) {
        // 处理追加日志的逻辑
        // ...
        // 如果追加成功,则返回结果给 Leader
    }

    // 处理接受到的来自 Candidate 的投票请求 RPC
    function requestVote(RequestVoteArgs args) {
        // 处理投票逻辑
        // ...
        // 如果投票成功,则返回结果给 Candidate
    }
}

// 定义领导者选举过程
function electionProcess() {
    // 如果收到来自领导者心跳消息的跟随者没有回复,则领导者宣布该跟随者失效

    // 如果领导者超时,并且当前节点是跟随者,则转变为候选者
    // 并向其他节点发送投票请求

    // 如果获得大多数节点的投票,则成为新的领导者
}
  • 这只是一个简单的Raft算法示例,实际的实现需要考虑更多的细节和场景需求。
    • 例如,处理网络不稳定、节点故障和日志复制等异常情况时的容错机制,以及如何保证领导者和跟随者之间的数据一致性。
  • 总结起来,Raft算法通过选举领导者、追加日志、复制状态机等机制来实现分布式系统的一致性。
    • 它简化了分布式系统中的共识问题,并提供了可靠的数据一致性保证。

【三】集群(Cluser)

【1】引入

(1)问题

  • 1 并发量:单机redis qps为10w/s,但是我们可能需要百万级别的并发量
  • 2 数据量:机器内存16g--256g,如果存500g数据呢?

(2)解决办法

  • 解决:加机器,分布式

  • redis cluster 在2015年的 3.0 版本加入了,满足分布式的需求

【2】数据分布(分布式数据库)

(1)存在问题

  • 假设全量的数据非常大,500g,单机已经无法满足,我们需要进行分区,分到若干个子集中

(2)分区方式

分布方式 特点 产品
哈希分布 数据分散度高,建值分布于业务无关,无法顺序访问,支持批量操作 一致性哈希memcache,redis cluster,其他缓存产品
顺序分布 数据分散度易倾斜,建值业务相关,可顺序访问,支持批量操作 BigTable,HBase

[1] 顺序分区

  • 原理:
    • 100个数据分到3个节点上 1--33第一个节点;
    • 34--66第二个节点;
    • 67--100第三个节点(很多关系型数据库使用此种方式)

[2]哈希分区

  • 原理:
    • hash分区: 节点取余 ,假设3台机器, hash(key)%3,落到不同节点上
1节点取余分区

[image-20230416224340301](http://photo.liuqingzheng.top/2023 04 16 22 59 08 /image-20230416224340301.png)

  • 缺陷:节点扩容,添加一个节点,存在问题,很多数据需要偏移,总偏移量要大于80%

[image-20230416224428340](http://photo.liuqingzheng.top/2023 04 16 22 59 15 /image-20230416224428340.png)

  • 解决办法:推荐翻倍扩容,由3变成6,数据量迁移为50%,比80%降低

[image-20230416224441812](http://photo.liuqingzheng.top/2023 04 16 22 59 23 /image-20230416224441812.png)

  • 总结:
    • 客户端分片,通过hash+取余
    • 节点伸缩,数据节点关系发生变化,导致影响数据迁移过大
    • 迁移数量和添加节点数量有关:建议翻倍扩容
2一致性哈希分区
  • 每个节点负责一部分数据,对key进行hash,得到结果在node1和node2之间,就放到node2中,顺时针查找

[image-20230416224454832](http://photo.liuqingzheng.top/2023 04 16 22 59 30 /image-20230416224454832.png)

  • 假设添加一个新节点node5,现在只需要迁移一小部分数据,不会影响node3和node4的数据,只会迁移node1和node2的数据

[image-20230416224525371](http://photo.liuqingzheng.top/2023 04 16 22 59 36 /image-20230416224525371.png)

  • 节点比较多的话合适,假设有1000个节点,加一个只要迁移千分之一的数据
  • 总结:
    • 客户端分片:哈希+顺时针(优化取余)
    • 节点伸缩:只影响临近节点,但是还有数据迁移的情况
    • 伸缩:保证最小迁移数据和无法保证负载均衡(这样总共5个节点,数据就不均匀了),翻倍扩容可以实现负载均衡
3虚拟槽分区
  • 预设虚拟槽:每个槽映射一个数据子集,一般比节点数大
  • 良好的哈希函数:如CRC16
  • 服务端管理节点、槽、数据:如redis cluster(槽的范围0–16383)
  • 5个节点,把16384个槽平均分配到每个节点
    • 客户端会把数据发送给任意一个节点
    • 通过CRC16对key进行哈希对16383进行取余,算出当前key属于哪部分槽,属于哪个节点,
    • 每个节点都会记录是不是负责这部分槽
      • 如果是负责的,进行保存
      • 如果槽不在自己范围内,redis cluster是共享消息的模式,它知道哪个节点负责哪些槽,返回结果,让客户端找对应的节点去存
  • 服务端管理节点,槽,关系

[image-20230416224541292](http://photo.liuqingzheng.top/2023 04 16 22 59 44 /image-20230416224541292.png)

【3】集群搭建

  • 搭建redis集群
    • 6台机器(一主一从:3个节点)
    • 8台机器,4个节点
    • 恢复成6台机器3个节点

(1)单机架构

[image-20230416224555061](http://photo.liuqingzheng.top/2023 04 16 22 59 52 /image-20230416224555061.png)

(2)分布式架构

  • 每个节点之间相互通信,都负责读写,客户端去存,如果不是当前节点,会返回应该存到哪个节点

[image-20230416224622949](http://photo.liuqingzheng.top/2023 04 16 22 59 59 /image-20230416224622949.png)

(3)Redis Cluster架构

  • 节点,meet,指派槽,复制,高可用

[1]meet解释

  • A meet一下C,C回复一下,A meet一下B ,B回复一下,这样B和C也能相互感知,A,B,C之间就可以相关交互数据,所有节点共享消息

[image-20230416224639370](http://photo.liuqingzheng.top/2023 04 16 23 00 05 /image-20230416224639370.png)

[2]指派槽

  • 总共有16384个槽,平均分配到每个节点上

[image-20230416224702100](http://photo.liuqingzheng.top/2023 04 16 23 00 13 /image-20230416224702100.png)

(4)原生安装

1 配置开启节点

  • 步骤简解
# 【1】创建配置文件
vim redis-${port}.conf

# 【2】写入配置
port ${port}
daemonize yes
dir "/opt/redis/redis/data/"
logfile "${port}.log"

#masterauth  集群搭建时,主的密码
cluster-enabled yes  # 开启cluster
cluster-node-timeout 15000 # 故障转移,超时时间 15s
cluster-config-file nodes-${port}.conf  # 给cluster节点增加一个自己的配置文件
cluster-require-full-coverage yes  #只要集群中有一个故障了,整个就不对外提供服务了,这个实际不合理,假设有50个节点,一个节点故障了,所有不提供服务了;,需要设置成no

# 【3】创建六个节点
sed 's/7000/7001/g' redis-7000.conf > redis-7001.conf
sed 's/7000/7002/g' redis-7000.conf > redis-7002.conf
sed 's/7000/7003/g' redis-7000.conf > redis-7003.conf
sed 's/7000/7004/g' redis-7000.conf > redis-7004.conf
sed 's/7000/7005/g' redis-7000.conf > redis-7005.conf

# 【4】开启六个节点
redis-server redis-7000.conf
redis-server redis-7001.conf
redis-server redis-7002.conf
redis-server redis-7003.conf
redis-server redis-7004.conf
redis-server redis-7005.conf
  • 演示
# 【1】创建配置文件
vim redis-7000.conf

# 【2】写入配置
port 7000
daemonize yes
dir "/root/lqz/redis/data/"
logfile "7000.log"
dbfilename "dump-7000.rdb"

# 【3】创建节点
sed 's/7000/7001/g' redis-7000.conf > redis-7001.conf
sed 's/7000/7002/g' redis-7000.conf > redis-7002.conf
sed 's/7000/7003/g' redis-7000.conf > redis-7003.conf
sed 's/7000/7004/g' redis-7000.conf > redis-7004.conf
sed 's/7000/7005/g' redis-7000.conf > redis-7005.conf

# 【4】开启节点
./src/redis-server ./redis-7000.conf
./src/redis-server ./redis-7001.conf
./src/redis-server ./redis-7002.conf
./src/redis-server ./redis-7003.conf
./src/redis-server ./redis-7004.conf
./src/redis-server ./redis-7005.conf

# 查看集群
ps -ef |grep redis

# 【5】查看集群命令(暂时没配好)
cluster nodes
cluster info

# 【6】集群不能运行,运行如下命令
./src/redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 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



# 【7】演示 ,集群模式连接

./src/redis-cli -c  -p 7005  # 自动找到节点,去操作

# 【8】故障转移
把主库,7000停止---》7005从库---》升级为主库

image-20230821121609441

image-20230821121712991

2 meet(相互通信)

redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7001
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7002
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7003
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7004
redis-cli -h 127.0.0.1 -p 7000 cluster meet 127.0.0.1 7005

3 指派槽

redis-cli -h 127.0.0.1 -p 7000 cluster addslots {0...5461}
redis-cli -h 127.0.0.1 -p 7000 cluster addslots {5462...10922}
redis-cli -h 127.0.0.1 -p 7000 cluster addslots {10923...16383}

4 主从

# cluster replicate node-id
# 让7003复制7000
redis-cli -h 127.0.0.1 -p 7003 cluster replicate ${node-id-7000}
redis-cli -h 127.0.0.1 -p 7004 cluster replicate ${node-id-7001}
redis-cli -h 127.0.0.1 -p 7005 cluster replicate ${node-id-7002}
# 实操
cd /opt/soft/redis/config
vim redis-7000.conf

# 写入
port 7000
daemonize yes
dir "/opt/soft/redis/data/"
logfile "7000.log"
dbfilename "dump-7000.rdb"
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-require-full-coverage yes 

# 快速生成其他配置
sed 's/7000/7001/g' redis-7000.conf > redis-7001.conf
sed 's/7000/7002/g' redis-7000.conf > redis-7002.conf
sed 's/7000/7003/g' redis-7000.conf > redis-7003.conf
sed 's/7000/7004/g' redis-7000.conf > redis-7004.conf
sed 's/7000/7005/g' redis-7000.conf > redis-7005.conf

# 启动
./src/redis-server ./config/redis-7000.conf
ps -ef |grep redis
./src/redis-server ./config/redis-7001.conf
./src/redis-server ./config/redis-7002.conf
./src/redis-server ./config/redis-7003.conf
./src/redis-server ./config/redis-7004.conf
./src/redis-server ./config/redis-7005.conf

# 连接其中一个,set数据(失败,因为没有分配槽)
redis-cli -p 7000
set hello world #报错

# config文件夹下出现了:nodes-7000.conf,查看一下可以看到节点的id

# 也可以:
redis-cli -p 7000 cluster nodes

# 也可以:查看更详细信息
redis-cli -p 7000 cluster info
#### 节点握手(meet操作)
# 7000和7001 握手
redis-cli -p 7000 cluster meet 127.0.0.1 7001
# 查看握手情况
redis-cli -p 7000 cluster nodes # 可以看到已经达成了握手
redis-cli -p 7002 cluster nodes # 没有握手,还是孤立
# 继续握手
redis-cli -p 7000 cluster meet 127.0.0.1 7002
redis-cli -p 7000 cluster meet 127.0.0.1 7003
redis-cli -p 7000 cluster meet 127.0.0.1 7004
redis-cli -p 7000 cluster meet 127.0.0.1 7005
# 查看最后结果
redis-cli -p 7000 cluster info  # 可以看到6个节点握手成功了
#### 当前还是不可以读写,还没分配槽
redis-cli -p 7000 cluster addslots 0 # 给7000分配第0个槽
# 这样一个个设置太麻烦,咱们写个脚本执行
mkdir script
cd script
vim addslots.sh
# 写入
start=$1
end=$2
port=$3
for slot in `seq ${start} ${end}`
do
 echo "slot:${slot}"
done
# 保存退出,测试
sh addslots.sh 0 4096 7000
# 写具体命令
start=$1
end=$2
port=$3
for slot in `seq ${start} ${end}`
do
 echo "slot:${slot}"
  redis-cli -p ${port} cluster addslots ${slot}
done

# 写入
sh addslots.sh 0 5641 7000
# 查看
redis-cli -p 7000
cluster info
cluster nodes
# 继续分槽
sh addslots.sh 5641 10922 7001
sh addslots.sh 10923  16383 7002
# 查看集群状态
redis-cli -p 7000 cluster info
##  配置主从
# 7003是7000的从
# 7004是7001的从
# 7005是7002的从
redis-cli -p 7003 cluster replicate 7000id
redis-cli -p 7004 cluster replicate 7001id
redis-cli -p 7005 cluster replicate 7002id
## 查看
redis-cli -p 7000 cluster info
redis-cli -p 7000 cluster nodes
redis-cli -p 7000 cluster slots # 查看槽的信息
# 存放数据
redis-cli -c -p 7000
set hello world # 可以成功
  • 生产环境建议 三台机器,主从不放在同一台机器上

(5)官方工具安装(Ruby脚本)

[1]准备环境-下载编译安装ruby

wget https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.8.tar.gz
tar -zxvf ruby-2.5.8.tar.gz
cd ruby
./configure -prefix=/usr/local/ruby
make && make install
cd /usr/local/ruby
cp bin/ruby /usr/local/bin  # ruby类似于python3
cp bin/gem /usr/local/bin   # gem类似于pip
ruby -v # 检查版本

[2]安装rubygem redis

### 更换gem源
gem sources -l
# 移除https://rubygems.org源
gem sources --remove https://rubygems.org/
# 增加https://gems.ruby-china.com/源
gem sources -a https://gems.ruby-china.com/
# 查看
gem sources -l
## 安装gem redis
gem install redis -v 3.3.3
# 查看
gem list check redis gem

[3]安装redis-trib.rb

cd /opt/soft/redis/src
./redis-trib.rb 弃用了,需要使用

# 1 表示给每个主节点配置一个从节点
redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7000 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
yes

【4】集群伸缩

  • 6台机器,3个节点集群
  • 8台机器,4个节点集群

(1)伸缩原理

# 加入节点,删除节点:槽和数据在节点之间的移动

[image-20230416224722563](http://photo.liuqingzheng.top/2023 04 16 23 00 25 /image-20230416224722563.png)

(2)集群扩容

  • 作用:为它迁移槽和数据实现扩容 作为从节点负责故障转移

[1]准备新节点

  • 集群模式
  • 配置和其他节点统一
  • 启动后是孤儿节点
# 【1】准备两台机器
sed 's/7000/7006/g' redis-7000.conf > redis-7006.conf
sed 's/7000/7007/g' redis-7000.conf > redis-7007.conf

# 【2】启动两台机器
redis-server conf/redis-7006.conf
redis-server conf/redis-7007.conf
  • 查看集群状态
cluster nodes
cluster info
  • 孤立状态re
redis-cli -p 7006 cluster nodes

[2]加入集群

  • 方式一
在7000上执行
redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7000 cluster meet 127.0.0.1 7007
  • 方式二
redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000
redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000
  • 查看配置
cluster nodes
  • 把7007做为7006的从
redis-cli -p 7007 cluster replicate 7006的id

[3]迁移槽和数据

  • 槽迁移计划
    • 迁移数据
    • 添加从节点
  • 我们不操作原生,直接使用redis-trip
redis-cli --cluster reshard 127.0.0.1:7000 
  • 打印当前集群状态
    • 希望迁移多少个槽:4096
    • 希望哪个id是接收的:7006的id
    • 传入source id :all
    • yes

[4]查看

redis-cli -p 7000 cluster nodes
redis-cli -p 7000 cluster slots

[5]其他

  • 如果想给7000再加一个从节点怎么弄?
    • 启动起7006,meet一下,让7006复制7000
redis-cli -p 7000 cluster meet 127.0.0.1 7006
redis-cli -p 7006 cluster replicate 
e03fb9a259cd314e9a23e17573d07c477d3242f7

(3)集群缩容

[1]下线迁槽(把7006的1366个槽迁移到7000上)

redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7000的id --cluster-slots 1366 127.0.0.1:7000
yes
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7001的id --cluster-slots 1366 127.0.0.1:7001
yes
redis-cli --cluster reshard --cluster-from 7006的id --cluster-to 7002的id --cluster-slots 1365 127.0.0.1:7002
yes

[2]忘记节点,关闭节点

redis-cli --cluster del-node 127.0.0.1:7000 要下线的7007id 
  • 先下从,再下主,因为先下主会触发故障转移
redis-cli --cluster del-node 127.0.0.1:7000 要下线的7006id 
  • 关掉其中一个主,另一个从立马变成主顶上, 重启停止的主,发现变成了从

【5】Python客户端连接

redis-cli -c -p 7000  
# -c表示集群模式


set hello world  
# ok


cluster keyslot php
# 9244
set php sb 
# 不命中,会返回7001,自动跳转到7001上  不加-c,只会返回错误,不会去执行7001上保存
  • 安装rediscluster
pip3 install redis-py-cluster
  • 使用
from rediscluster import RedisCluster
startup_nodes = [{"host":"127.0.0.1", "port": "7000"},{"host":"127.0.0.1", "port": "7001"},{"host":"127.0.0.1", "port": "7002"}]

# rc = RedisCluster(startup_nodes=startup_nodes,decode_responses=True)
rc = RedisCluster(startup_nodes=startup_nodes)
rc.set("foo", "bar")
print(rc.get("foo"))

【6】集群原理

(1)move重定向

[image-20230416224738043](http://photo.liuqingzheng.top/2023 04 16 23 00 33 /image-20230416224738043.png)

(2)槽命中

  • cluster keyslot hello 可以计算出槽的值

[image-20230416224750513](http://photo.liuqingzheng.top/2023 04 16 23 00 40 /image-20230416224750513.png)

(3)槽不命中:moved异常

[image-20230416224804007](http://photo.liuqingzheng.top/2023 04 16 23 00 47 /image-20230416224804007.png)

【7】补充

(1)5.0以后集群搭建

  • Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境。
  • 直接使用redis-clit的参数–cluster 来取代
redis-cli --cluster help
Cluster Manager Commands:
  create         host1:port1 ... hostN:portN   #创建集群
                 --cluster-replicas <arg>      #从节点个数
  check          host:port                     #检查集群
                 --cluster-search-multiple-owners #检查是否有槽同时被分配给了多个节点
  info           host:port                     #查看集群状态
  fix            host:port                     #修复集群
                 --cluster-search-multiple-owners #修复槽的重复分配问题
  reshard        host:port                     #指定集群的任意一节点进行迁移slot,重新分slots
                 --cluster-from <arg>          #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
                 --cluster-to <arg>            #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入
                 --cluster-slots <arg>         #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
                 --cluster-yes                 #指定迁移时的确认输入
                 --cluster-timeout <arg>       #设置migrate命令的超时时间
                 --cluster-pipeline <arg>      #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
                 --cluster-replace             #是否直接replace到目标节点
  rebalance      host:port                                      #指定集群的任意一节点进行平衡集群节点slot数量 
                 --cluster-weight <node1=w1...nodeN=wN>         #指定集群节点的权重
                 --cluster-use-empty-masters                    #设置可以让没有分配slot的主节点参与,默认不允许
                 --cluster-timeout <arg>                        #设置migrate命令的超时时间
                 --cluster-simulate                             #模拟rebalance操作,不会真正执行迁移操作
                 --cluster-pipeline <arg>                       #定义cluster getkeysinslot命令一次取出的key数量,默认值为10
                 --cluster-threshold <arg>                      #迁移的slot阈值超过threshold,执行rebalance操作
                 --cluster-replace                              #是否直接replace到目标节点
  add-node       new_host:new_port existing_host:existing_port  #添加节点,把新节点加入到指定的集群,默认添加主节点
                 --cluster-slave                                #新节点作为从节点,默认随机一个主节点
                 --cluster-master-id <arg>                      #给新节点指定主节点
  del-node       host:port node_id                              #删除给定的一个节点,成功后关闭该节点服务
  call           host:port command arg arg .. arg               #在集群的所有节点执行相关命令
  set-timeout    host:port milliseconds                         #设置cluster-node-timeout
  import         host:port                                      #将外部redis数据导入集群
                 --cluster-from <arg>                           #将指定实例的数据导入到集群
                 --cluster-copy                                 #migrate时指定copy
                 --cluster-replace                              #migrate时指定replace
  help           

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

① 创建集群主节点

redis-cli --cluster create 192.168.163.132:6379 192.168.163.132:6380 192.168.163.132:6381

② 创建集群主从节点

/redis-cli --cluster create 192.168.163.132:6379 192.168.163.132:6380 192.168.163.132:6381 192.168.163.132:6382 192.168.163.132:6383 192.168.163.132:6384 --cluster-replicas 1

说明:–cluster-replicas 参数为数字,1表示每个主节点需要1个从节点。

通过该方式创建的带有从节点的机器不能够自己手动指定主节点,所以如果需要指定的话,需要自己手动指定,先使用①或③创建好主节点后,再通过④来处理。

③ 添加集群主节点

redis-cli --cluster add-node 192.168.163.132:6382 192.168.163.132:6379

说明:为一个指定集群添加节点,需要先连到该集群的任意一个节点IP(192.168.163.132:6379),再把新节点加入。该2个参数的顺序有要求:新加入的节点放前

④ 添加集群从节点

redis-cli --cluster add-node 192.168.163.132:6382 192.168.163.132:6379 --cluster-slave --cluster-master-id 117457eab5071954faab5e81c3170600d5192270

说明:把6382节点加入到6379节点的集群中,并且当做node_id为 117457eab5071954faab5e81c3170600d5192270 的从节点。如果不指定 –cluster-master-id 会随机分配到任意一个主节点。

⑤ 删除节点

redis-cli --cluster del-node 192.168.163.132:6384 f6a6957421b80409106cb36be3c7ba41f3b603ff

说明:指定IP、端口和node_id 来删除一个节点,从节点可以直接删除,主节点不能直接删除,删除之后,该节点会被shutdown。

注意:当被删除掉的节点重新起来之后不能自动加入集群,但其和主的复制还是正常的,也可以通过该节点看到集群信息(通过其他正常节点已经看不到该被del-node节点的信息)。

如果想要再次加入集群,则需要先在该节点执行cluster reset,再用add-node进行添加,进行增量同步复制。

到此,目前整个集群的状态如下:

192.168.163.132:6379> cluster nodes
815da8448f5d5a304df0353ca10d8f9b77016b28 192.168.163.132:6380@16380 master - 0 1569748297177 2 connected 5461-10922
0c21b6cee354594a23f4d5abf0d01b48bdc96d55 192.168.163.132:6383@16383 slave 56005b9413cbf225783906307a2631109e753f8f 0 1569748295000 4 connected
3a1d04983ab6c4ae853f9602dd922d4ebadc4dbf 192.168.163.132:6382@16382 slave 815da8448f5d5a304df0353ca10d8f9b77016b28 0 1569748295000 5 connected
117457eab5071954faab5e81c3170600d5192270 192.168.163.132:6379@16379 myself,master - 0 1569748297000 1 connected 0-5460
56005b9413cbf225783906307a2631109e753f8f 192.168.163.132:6381@16381 master - 0 1569748295000 3 connected 10923-16383
f6a6957421b80409106cb36be3c7ba41f3b603ff 192.168.163.132:6384@16384 slave 117457eab5071954faab5e81c3170600d5192270 0 1569748298185 6 connected

⑥ 检查集群

redis-cli --cluster check 192.168.163.132:6384 --cluster-search-multiple-owners

说明:任意连接一个集群节点,进行集群状态检查

⑦ 集群信息查看

redis-cli --cluster info 192.168.163.132:6384

说明:检查key、slots、从节点个数的分配情况

/redis-cli --cluster info 192.168.163.132:6384
192.168.163.132:6380 (815da844...) -> 0 keys | 5462 slots | 1 slaves.
192.168.163.132:6381 (56005b94...) -> 0 keys | 5461 slots | 1 slaves.
192.168.163.132:6379 (117457ea...) -> 2 keys | 5461 slots | 1 slaves.
[OK] 2 keys in 3 masters.
0.00 keys per slot on average.

⑧ 修复集群

redis-cli --cluster fix 192.168.163.132:6384 --cluster-search-multiple-owners

说明:修复集群和槽的重复分配问题

⑨ 设置集群的超时时间

redis-cli --cluster set-timeout 192.168.163.132:6382 10000

说明:连接到集群的任意一节点来设置集群的超时时间参数cluster-node-timeout

redis-cli --cluster set-timeout 192.168.163.132:6382 10000
>>> Reconfiguring node timeout in every cluster node...
*** New timeout set for 192.168.163.132:6382
*** New timeout set for 192.168.163.132:6384
*** New timeout set for 192.168.163.132:6383
*** New timeout set for 192.168.163.132:6379
*** New timeout set for 192.168.163.132:6381
*** New timeout set for 192.168.163.132:6380
>>> New node timeout set. 6 OK, 0 ERR.

⑩ 集群中执行相关命令

redis-cli --cluster call 192.168.163.132:6381 config set requirepass cc
redis-cli -a cc --cluster call 192.168.163.132:6381 config set masterauth cc
redis-cli -a cc --cluster call 192.168.163.132:6381 config rewrite

说明:连接到集群的任意一节点来对整个集群的所有节点进行设置。

redis-cli --cluster call 192.168.163.132:6381 config set cluster-node-timeout 12000
>>> Calling config set cluster-node-timeout 12000
192.168.163.132:6381: OK
192.168.163.132:6383: OK
192.168.163.132:6379: OK
192.168.163.132:6384: OK
192.168.163.132:6382: OK
192.168.163.132:6380: OK
...
...

到此,相关集群的基本操作已经介绍完,现在说明集群迁移的相关操作。

(2)迁移相关

在线迁移slot :在线把集群的一些slot从集群原来slot节点迁移到新的节点,即可以完成集群的在线横向扩容和缩容。有2种方式进行迁移

一是根据提示来进行操作:

直接连接到集群的任意一节点
redis-cli -a cc --cluster reshard 192.168.163.132:6379

信息如下:

二是根据参数进行操作:

redis-cli -a cc --cluster reshard 192.168.163.132:6379 --cluster-from 117457eab5071954faab5e81c3170600d5192270 --cluster-to 815da8448f5d5a304df0353ca10d8f9b77016b28 --cluster-slots 10 --cluster-yes --cluster-timeout 5000 --cluster-pipeline 10 --cluster-replace

说明:连接到集群的任意一节点来对指定节点指定数量的slot进行迁移到指定的节点。

② 平衡(rebalance)slot

1)平衡集群中各个节点的slot数量

redis-cli -a cc --cluster rebalance 192.168.163.132:6379

2)根据集群中各个节点设置的权重等平衡slot数量(不执行,只模拟)

redis-cli -a cc --cluster rebalance --cluster-weight 117457eab5071954faab5e81c3170600d5192270=5 815da8448f5d5a304df0353ca10d8f9b77016b28=4 56005b9413cbf225783906307a2631109e753f8f=3 --cluster-simulate 192.168.163.132:6379

③ 导入集群

redis-cli --cluster import 192.168.163.132:6379 --cluster-from 192.168.163.132:9021 --cluster-replace

说明:外部Redis实例(9021)导入到集群中的任意一节点。

注意:测试下来发现参数–cluster-replace没有用,如果集群中已经包含了某个key,在导入的时候会失败,不会覆盖,只有清空集群key才能导入。

*** Importing 97847 keys from DB 0
Migrating 9223372011174675807 to 192.168.163.132:6381: Source 192.168.163.132:9021 replied with error:
ERR Target instance replied with error: BUSYKEY Target key name already exists

并且发现如果集群设置了密码,也会导入失败,需要设置集群密码为空才能进行导入(call)。通过monitor(9021)的时候发现,在migrate的时候需要密码进行auth认证。