2023.1.27 Redis 基于 Doker 模拟搭建集群环境

目录

Doker 模拟搭建 Redis 集群

集群的使用

使用集群存储数据

集群故障转移

具体流程

集群宕机

集群扩容


阅读下文之前建议点击下方链接了解相关基础知识

Redis 集群哈希槽算法


Doker 模拟搭建 Redis 集群

  • 此处我们模拟上图进行部署

  • 当前阶段,因为我只有一个云服务器,所以搞分布式系统就会比较麻烦
  • 此处我们使用 docker 解决

注意点一:

  • 实际工作中,一般通过多个主机的方式,来搭建集群的
  • 当前这么做只是条件有限,我仅买了一个云服务器!

注意点二:

  • 此处我们先创建出 11个 Redis 节点,其中前 9个用来演示集群的搭建
  • 后 2个用来演示集群扩容

1、安装 docker 和 docker-compose(点击下方链接即可跳转安装)

CentOS7 安装 docker 和 docker-compose


 2、停止之前的 Redis 容器!


3、使用 docker 获取 Redis 镜像

  • 此处我们通过下方命令,直接从 docker hub 上拉取 redis 5.0.9 的镜像
docker pull redis:5.0.9
  • 使用 docker images 命令查看是否成功拉取镜像


注意点一:

  • docker 中的 镜像 和 容器
  • 类似于 可执行程序 和 进程 的关系

注意点二:

  • 镜像可以自己构建,也可以直接拿别人已经构建好的
  • docker hub 中包含了很多其他大佬们构建好的镜像,其中便有 Redis 官方提供的镜像

注意点三:

  • 拉取到的镜像里面包含一个精简的 Linux 操作系统,并且上面会安装 Redis
  • 只要直接基于这个镜像创建一个 容器 跑起来,此时 Redis 服务器便搭建好了!

4、创建目录和文件


 5、编写 generate.sh 内容

注意点一:

  • 在 linux 上,以 .sh 后缀结尾的文件被称为 shell 脚本
  • 使用 linux 时,都是通过命令来进行操作
  • 此时我们便可以将命令写到一个文件中,用来批量化执行
  • 同时还能加入 条件、循环、函数 等机制
  • 从而便可基于这些来完成更复杂的工作

注意点二:

  • 此处我们需要创建 11个 Redis 节点
  • 因为这些 Redis 的配置文件内容大同小异,所以我们便可采用脚本来批量生成
  • 当然也可以不使用脚本,自己手动一个一个改

for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
 
# 注意 cluster-announce-ip 的值有变化,和上面分开写也是因为这个原因
 
for port in $(seq 10 11); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done

预期效果:

  • 得到 11 个目录,每个目录里均有一个配置文件,且配置文件中 ip 地址各不相同

注意:

  • 这些配置项不需要刻意地去记,要用到时,直接查询即可


6、执行 shell 脚本


7、编写 docker-compose.yml 文件,创建出 Redis 容器

version: '3.3'
networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24

services:
  redis1:
    image: 'redis:5.0.9'
    container_name: redis1
    restart: always
    volumes:
      - ./redis1/:/etc/redis/
    ports:
      - 6371:6379
      - 16371:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.101
 
  redis2:
    image: 'redis:5.0.9'
    container_name: redis2
    restart: always
    volumes:
      - ./redis2/:/etc/redis/
    ports:
      - 6372:6379
      - 16372:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.102
 
  redis3:
    image: 'redis:5.0.9'
    container_name: redis3
    restart: always
    volumes:
      - ./redis3/:/etc/redis/
    ports:
      - 6373:6379
      - 16373:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.103
 
  redis4:
    image: 'redis:5.0.9'
    container_name: redis4
    restart: always
    volumes:
      - ./redis4/:/etc/redis/
    ports:
      - 6374:6379
      - 16374:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.104
 
  redis5:
    image: 'redis:5.0.9'
    container_name: redis5
    restart: always
    volumes:
      - ./redis5/:/etc/redis/
    ports:
      - 6375:6379
      - 16375:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.105
 
  redis6:
    image: 'redis:5.0.9'
    container_name: redis6
    restart: always
    volumes:
      - ./redis6/:/etc/redis/
    ports:
      - 6376:6379
      - 16376:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.106
 
  redis7:
    image: 'redis:5.0.9'
    container_name: redis7
    restart: always
    volumes:
      - ./redis7/:/etc/redis/
    ports:
      - 6377:6379
      - 16377:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.107
 
  redis8:
    image: 'redis:5.0.9'
    container_name: redis8
    restart: always
    volumes:
      - ./redis8/:/etc/redis/
    ports:
      - 6378:6379
      - 16378:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.108
 
  redis9:
    image: 'redis:5.0.9'
    container_name: redis9
    restart: always
    volumes:
      - ./redis9/:/etc/redis/
    ports:
      - 6379:6379
      - 16379:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.109
 
  redis10:
    image: 'redis:5.0.9'
    container_name: redis10
    restart: always
    volumes:
      - ./redis10/:/etc/redis/
    ports:
      - 6380:6379
      - 16380:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.110
 
  redis11:
    image: 'redis:5.0.9'
    container_name: redis11
    restart: always
    volumes:
      - ./redis11/:/etc/redis/
    ports:
      - 6381:6379
      - 16381:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.111
 

8、启动 Redis 容器

 再次强调:

  • 启动之前,一定要把之前已经运行的 Redis 进程全都给干掉!
  • 否则就可能因为端口冲突等原因,导致现在的启动失败!


9、配置集群关系,将上述 Redis 节点构建成集群

redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379  --cluster-replicas 2

注意点一:

  • Redis 在构建集群时,谁是主节点、谁是从节点、谁和谁是一个分片,都是不固定的!
  • 本身从集群的角度来看,提供的这些节点之间本身就应该是等价的 

注意点二:

  • 上述命令需直接在命令行中执行,而不是在 Redis 客户端中执行

集群的使用

  • 101 -109 这九个节点,现在均为一个整体
  • 所以使用客户端连上其中的任意一个节点,本质上都是等价的
  • 此处我们选择连接 主机号为 101 的 Redis 节点

  • 此时我们可以通过 cluster nodes 命令来查看当前集群的信息


使用集群存储数据

  • 前面学过的所有 Redis 相关的命令,仅一部分能够正常使用

  • 此处的错误代表无法在 101 分片上设置 k1 键值对,需到 103 分片上设置才行

解决方案:

  • 可以在启动 redis-cli 的时候,加上 -c 选项
  • 此时客户端发现当前 key 的操作不在当前分片上,就能自动重定向到对应的分片主机上


补充:

  • 有些指令是能操作多个 key 的
  • 假如这 key 均分散在不同分片上,此时便可能会出现问题了

  • 当然其实也是有特殊的方式来解决上述问题的,如 hash tag

集群故障转移

  • 如果 从节点 挂了,没啥太大影响
  • 如果 主节点 挂了,此时将无法处理写操作,因为 从节点 无法处理写操作

实例理解


  • 此处我们直接模拟主节点挂掉了的情况

1、通过 docker stop redis1  命令手动将 redis1 主节点给干掉

  • 此处集群所做的工作 与之前哨兵做的相类似
  • 会自动地挑选一个该主节点麾下的从节点,并将其提拔成主节点

2、我们重新启动 redis1 ,并继续观察

  • 由此可见 集群机制 也能处理故障转移
  • 此处故障转移的 具体处理流程 和 哨兵模式 的处理流程还不太一样

具体流程

1、故障判定

  • 通过心跳包机制识别出某个节点是否挂了

具体理解

  1. 节点A 给节点B 发送 ping 包,B 便会给 A 返回一个 pong 包。ping 和 pong 除了 message type 属性之外,其他部分都是一样的,其中包含了集群的配置信息(该节点的 id,该节点从属于哪个分片,是主节点还是 从节点,从属于谁,持有哪些 槽位号 的位图)
  2. 每个节点,每秒钟,都会给一些随机的节点发起 ping 包,而不是全发一遍。这样的设定是为了避免在节点很多的时候,心跳包也非常多(比如有 9个节点,如果全发,就是 9 * 8 有 72 组心跳了,而且这是按照 N^2 这样的级别增长的)
  3. 当节点A 给节点B 发起 ping 包,B 不能如期回应时,此时 A 就会尝试重置和 B 的 tcp 连接,看能是否连接成功,如果仍连接失败,A 就会把 B 设为 PFAIL 状态(相当于主观下线)
  4. A 判定 B 为 PFAL 后,会通过 Redis 内置的 Gossip 协议,和其他节点进行沟通,向其他节点确认 B 的状态(每个节点都会维护一个自己的 "下线列表",由于视角不同,每个节点的下线列表也不一定相同)
  5. 此时 A 发现其他很多节点,也认为 B 为 PFAIL,并且数目超过总集群个数的一半,那么 A 就会把 B 标记成 FAIL (相当于客观下线),并且把这个消息同步给其他节点(其他节点收到之后,也会把 B 标记成 FAIL)
  6. 至此节点B 是否下线,就已经十分明确了

2、故障迁移

  • 如果 B 是从节点,则无需进行故障迁移
  • 如果 B 是主节点,那么就会由 B 的从节点(比如 C 和 D)触发故障迁移了

具体理解

  1. 从节点判断自己是否具有参选资格,如果从节点和主节点已经太久没有通信了(此时认为从节点的数据和主节点差异太大了),即时间超过阈值,失去竞选资格
  2. 具有资格的节点(比如 C 和 D),便会先休眠一定时间,休眠时间 = 500ms 基础时间 + [0,500ms] 随机时间 + 排名 * 1000ms ,offset 的值越大,则排名越靠前(休眠时间越短)
  3. 假如 C 的休眠时间到了,C 就会对其他所有集群中的节点,进行拉票操作,但是只有主节点才有投票资格
  4. 主节点便会将自己的票投给 C (每个主节点仅有 1 票),当 C 收到的票数超过主节点数目的一半,C 便会晋升成主节点(C 自己负责 slaveof no one,并且让 D 执行 slaveof C)
  5. 同时,C 还会将自己成为主节点的消息,同步给其他集群的节点,大家也都会更新自己保存的集群结构信息

注意点一:

  • 哨兵是先竞选出 leader,由 leader 负责找一个从节点升级成主节点
  • 而此处则是直接投票选出新的主节点

注意点二:

  • 谁休眠时间短,谁大概率就是新的主节点

注意点三: 

  • 上述选举的过程,称为 Raft 算法,是一种在分布式系统中广泛使用的算法

注意点四:

  • 更多的时候,仅仅只是为了选一个节点出来,至于选谁,没那么重要!

集群宕机

  • 以下三种情况会出现集群宕机
  1. 某个分片,所有的主节点和从节点都挂了(该分片就无法提供数据服务了)
  2. 某个分片,主节点挂了,但是没有从节点(该分片一样无法继续提供数据服务了)
  3. 超过半数的 master 节点都挂了(此时 master 挂了,但后面还有 slave 做补充)

注意:

  • 如果突然一系列的 master 都挂了,此时说明集群遇到了非常严重的情况!
  • 此时就得赶紧停下来,检查检查是不是有啥问题!
  • 如果集群中有个节点挂了,无论什么节点,程序员都应尽快处理好(最晚第二天上班前得处理好)

集群扩容

  • 上文已将 101-109 这 9 个主机构成了 3 主 6 从 结构的集群了
  • 此时我们想将 110 和 111 这 2 个主机也加入到该集群中
  • 以 110 为 master,111 为 slave ,将数据分片从 3片 增加至 4片

注意:

  • 集群扩容操作,往往都是一件风险较高、成本较大的操作

1、将新的主节点(110)加入到集群中

redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379


2、重新分配 槽位号

  • 将之前三组 master 上面的槽位号给拎出来,再重新分配给新的主节点
redis-cli --cluster reshard 172.30.0.101:6379

注意:

  • 当搬运正式开始时,不仅仅是 槽位号 重新划分,同时也会将槽位号上对应的数据,也给搬运到新的主机上
  • 而搬运数据到新的主机,这就属于一个比较重量的操作!

问题:

  • 当正在搬运 槽位号 和 槽位号上对应的数据 时,此时客户端能否访问该 Redis 集群呢?

回答:

  • 说是搬运 key,但其中大部分的 key 是无需搬运的
  • 针对这些未搬运的 key,此时可以正常访问
  • 而针对这些正在搬运中的 key ,是有可能会出现访问错误的情况

具体理解:

  • 假设客户端访问 k1,集群通过分片算法,得到 k1 是第一个分片的数据,便会重定向到第一个分片的节点,此时重定向过去后,可能 k1 正好被搬走了,自然也就无法访问了

建议:

  • 如果想针对生成环境进行扩容操作,还是得悠着点
  • 比如找个夜深人静,没啥客户端访问集群的时候,进行扩容,此时就可将损失降到最低
  • 如果想追求更高的可用性,让扩容对于用户影响更小
  • 此时就需搞一组新机器,重新搭建集群,并将数据导过来,使用新集群代替旧集群,即需付出更高的成本

3、将从节点(111)也添加到集群中,并将其作为 110 的从节点

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.0.110 节点的 nodeId]


  • 与集群扩容相对应的,还有集群缩容,即将一些节点给拿掉,减少分片的数量
  • 但一般都是进行扩容,很少有进行缩容的!
  • 22
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茂大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值