• redis(分布式篇)


    分布式理论

    Redis Cluster

    Redis官方在3.0版本推出了自己的分布式方案,这就是Redis Cluster。
    Redis Cluster 提供了一种 去中心化 的分布式方案,该方案可以实现水平拆分,故障转移等需求。
    拓扑结构 通过一个简单的额例子说明:

    一个Redis Cluster由多个Redis节点组成,这里的节点指的是Redis实例,一台服务器上可能有多个实例。
    假设 有 1、2、3、4、5、6、号redis实例,其中 1和2 是一个 节点组, 且 1号是 master 2号是 slave;
    同理 3 和 4 是一个 节点组, 且 3号是 master 4号是 slave; 一次类推 一共有 3个 节点组。
    也就是全量数据被划分为3份,分别标号1/2/3。

    主从复制

    是Redis分布式的基础。Redis中包含两种节点:master节点与slave节点。
    同一份数据存放在master与多个slave节点上,master对外提供读写,slave不对外提供写操作。
    当master宕机时,slave节点还能继续提供服务。主从复制中最重要的就是 master 和 slave 之间数据如何同步。
    主从同步分为 全量同步 和 增量同步

    全量同步

    主节点上进将当前内存的数据全部快照写到磁盘文件中,然后将文件同步给从节点。
    从节点清空当前内存全部数据后全量加载该文件,这样达到与主节点数据同步的目的。
    但是在从节点加载快照文件的过程中,主节点还在 对外提供写服务。
    所以当从节点加载完快照后,依旧可能与主节点数据不一致,这时就需要增量同步上场了。

    增量同步

    增量同步的不是数据,而是指令流, 这个 指令流 类似于 mysql 主从复制 的 binlog。
    主节点会将对当前数据状态产生修改的指令记录在内存的一个buffer中,
    然后异步地将buffer同步到从节点,从节点通过执行buffer中的指令,达到与主节点数据一致的目的。

    Redis的buffer是一个定长的环形结构,当指令流满的时候, 一定会覆盖最前面的内容。
    所以当从节点上次增量同步由于各种原因,导致花费时间较长时,
    再次同步指令流时,就有可能前面没有同步的指令被覆盖掉了。
    这种情况就需要进行全量同步了。

    哨兵模式

    当master宕机时,需要 人工将slave节点切换成master,这 显然是无法接受的。
    所以需要有一个机制,当master宕机时能自动进行主从切换,应用程序无感知,继续提供服务。
    于是Redis官方提供了一种方案: Redis Sentinel(哨兵模式)。

    简单的说,哨兵模式就是在主从基础上增加了哨兵节点,哨兵节点不存储业务数据,它负责监控主从节点的健康,当主节点宕机时,它能及时发现,并自动选择一个从节点,将其切换成主节点。为了避免哨兵本身成为单点,哨兵一般也由多个节点组成

    分片

    所谓分片(Sharding),就是将 数据集 按照一定规则,分散存储在 各个节点上。这里涉及两个问题:

    1. 分片规则是什么?
    2. 如何存储在各个节点上?

    Redis将所有数据分为16384个hash slot(槽),每条数据(key-value)根据key值通过算法映射到其中一个slot上,
    这条数据就存储在该slot中。映射算法是:slotId=crc16(key)%16384
    Redis的每个key都会基于该分片规则,落到特定的slot上。而在集群部署完成时,slot的分布就已经确定了。

    对于一个稳定的集群,slot的分布也是固定的。但在一些情况下,slot的分布需要发生改变:

    • 新的master加入
    • 节点分组退出集群
    • slot分布不均匀

    这些情况下就需要进行slot的迁移。slot迁移的触发与过程控制都是由外部系统完成,Redis只提供能力,但不自动进行slot迁移。

    Move 和 Asking

    • Move (永久重定向)

    当客户端向某个节点发出指令,该节点发现指令的key对应的slot不在当前节点上,
    这时Redis会向客户端发送一个MOVED指令,告诉它正确的节点,
    然后客户端去连这个正确的节点并进行再次操作。如下,15495为key a所在的slot id。

    172.16.190.78:7001> get a
    (error) MOVED 15495 172.16.190.77:7000
    
    • 1
    • 2
    • Asking(临时重定向)

    在slot迁移过程中 的一种错误返回。当某个slot在迁移过程中,
    客户端发了一个位于该slot的某个key的操作请求,请求被路由到旧的节点。
    此时该key如果在旧节点上存在,则正常操作;如果在旧的节点上找不到,
    那么可能该key已被迁移到新的节点上,也可能就没有该key,
    此时会返回ASKING,让客户端跳转到新的节点上去执行。

    • 区别:

    MOVED 是永久重定向,下次对同样的key 进行操作,客户端就将请求发送到正确的节点
    ASKING 是临时重定向,它只对这次操作起作用,不会更新客户端的槽位关系表

    故障恢复

    Redis Cluster没有专门用于维护节点状态的节点,而是所有节点通过 Gossip协议相互通信
    广播自己的状态以及自己对整个集群认知的改变。

    如果一个节点宕掉了,其他节点和它进行通信时,会发现改节点失联。当某个节点发现其他节点失联时,
    会将这个失联节点状态变成PFail(possible fail),并广播给其他节点。
    当一个节点收到某个节点PFail的数量达到了主节点的大多数,就标记该节点为Fail,并进行广播,通过这种方式确认节点故障。

    当slave发现其master状态为Fail后,它会发起选举,如果其他master节点都同意,则该slave进行从主切换,变成master节点。
    同时会将自己的状态广播给其他节点,达到大家信息一致性。

    分布式部署

    这里采用docker的形式 部署 redis cluster。

    准备工作

    # 获取镜像
    docker pull redis:5.0.9-alpine3.11
    
    # 自定义一个网络
    docker network create redis --subnet 172.28.0.0/16
    
    • 1
    • 2
    • 3
    • 4
    • 5

    shell脚本1:创建 各个 redis 的 分布式 配置文件

    通过 脚本文件 generate_redis_config.sh 创建6个 redis 配置

    for port in $(seq 1 6); \
    do \
    mkdir -p ./redis/node-${port}/conf
    touch ./redis/node-${port}/conf/redis.conf
    cat << EOF > ./redis/node-${port}/conf/redis.conf
    port 6379
    bind 0.0.0.0
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    cluster-announce-ip 172.28.0.1${port}
    cluster-announce-port 6379
    cluster-announce-bus-port 16379
    appendonly yes
    EOF
    done
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    脚本文件输出:

    redis/
    ├── node-1
    │   └── conf
    │       └── redis.conf
    ├── node-2
    │   └── conf
    │       └── redis.conf
    ├── node-3
    │   └── conf
    │       └── redis.conf
    ├── node-4
    │   └── conf
    │       └── redis.conf
    ├── node-5
    │   └── conf
    │       └── redis.conf
    └── node-6
        └── conf
            └── redis.conf
    
    12 directories, 6 files
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    shell脚本2:启动6个容器

    通过脚本文件 generate_redis_container.sh 生成6个redis容器

    # 容器1
    docker run -p 6371:6379 -p 16371:16379 --name redis-1 \
    -v ${PWD}/redis/node-1/data:/data \
    --mount type=bind,src=${PWD}/redis/node-1/conf/redis.conf,dst=/etc/redis/redis.conf \
    -d --net redis --ip 172.28.0.11 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
    
    # 容器2
    docker run -p 6372:6379 -p 16372:16379 --name redis-2 \
    -v ${PWD}/redis/node-2/data:/data \
    --mount type=bind,src=${PWD}/redis/node-2/conf/redis.conf,dst=/etc/redis/redis.conf \
    -d --net redis --ip 172.28.0.12 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
    
    # 容器3
    docker run -p 6373:6379 -p 16373:16379 --name redis-3 \
    -v ${PWD}/redis/node-3/data:/data \
    --mount type=bind,src=${PWD}/redis/node-3/conf/redis.conf,dst=/etc/redis/redis.conf \
    -d --net redis --ip 172.28.0.13 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
    
    # 容器4
    docker run -p 6374:6379 -p 16374:16379 --name redis-4 \
    -v ${PWD}/redis/node-4/data:/data \
    --mount type=bind,src=${PWD}/redis/node-4/conf/redis.conf,dst=/etc/redis/redis.conf \
    -d --net redis --ip 172.28.0.14 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
    
    # 容器5
    docker run -p 6375:6379 -p 16375:16379 --name redis-5 \
    -v ${PWD}/redis/node-5/data:/data \
    --mount type=bind,src=${PWD}/redis/node-5/conf/redis.conf,dst=/etc/redis/redis.conf \
    -d --net redis --ip 172.28.0.15 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
    
    # 容器6
    docker run -p 6376:6379 -p 16376:16379 --name redis-6 \
    -v ${PWD}/redis/node-6/data:/data \
    --mount type=bind,src=${PWD}/redis/node-6/conf/redis.conf,dst=/etc/redis/redis.conf \
    -d --net redis --ip 172.28.0.16 redis:5.0.9-alpine3.11 redis-server /etc/redis/redis.conf
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    创建集群

    • 进入容器
    docker exec -it redis-1 /bin/sh
    
    • 1
    • 进入容器后,在容器中创建集群
    redis-cli --cluster create 172.28.0.11:6379 172.28.0.12:6379 172.28.0.13:6379 172.28.0.14:6379 172.28.0.15:6379 172.28.0.16:6379 --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 172.28.0.15:6379 to 172.28.0.11:6379
    Adding replica 172.28.0.16:6379 to 172.28.0.12:6379
    Adding replica 172.28.0.14:6379 to 172.28.0.13:6379
    M: cfb09d104f563d5dea870437cf73fac266b69a30 172.28.0.11:6379
       slots:[0-5460] (5461 slots) master
    M: 47d28c2cc92ce7fafbe09b14d521a35e56a2c02c 172.28.0.12:6379
       slots:[5461-10922] (5462 slots) master
    M: be4034133574260765165beac5fd1edacb63a2bd 172.28.0.13:6379
       slots:[10923-16383] (5461 slots) master
    S: 74685c90c5aebdc8a035d288c962e22f98e994e8 172.28.0.14:6379
       replicates be4034133574260765165beac5fd1edacb63a2bd
    S: 96b4185cc40b532b144b2ac3e2fe7213e7c13a50 172.28.0.15:6379
       replicates cfb09d104f563d5dea870437cf73fac266b69a30
    S: edd2e12510d921be2193df5b461033fc0f465144 172.28.0.16:6379
       replicates 47d28c2cc92ce7fafbe09b14d521a35e56a2c02c
    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 172.28.0.11:6379)
    M: cfb09d104f563d5dea870437cf73fac266b69a30 172.28.0.11:6379
       slots:[0-5460] (5461 slots) master
       1 additional replica(s)
    S: 96b4185cc40b532b144b2ac3e2fe7213e7c13a50 172.28.0.15:6379
       slots: (0 slots) slave
       replicates cfb09d104f563d5dea870437cf73fac266b69a30
    S: edd2e12510d921be2193df5b461033fc0f465144 172.28.0.16:6379
       slots: (0 slots) slave
       replicates 47d28c2cc92ce7fafbe09b14d521a35e56a2c02c
    M: be4034133574260765165beac5fd1edacb63a2bd 172.28.0.13:6379
       slots:[10923-16383] (5461 slots) master
       1 additional replica(s)
    M: 47d28c2cc92ce7fafbe09b14d521a35e56a2c02c 172.28.0.12:6379
       slots:[5461-10922] (5462 slots) master
       1 additional replica(s)
    S: 74685c90c5aebdc8a035d288c962e22f98e994e8 172.28.0.14:6379
       slots: (0 slots) slave
       replicates be4034133574260765165beac5fd1edacb63a2bd
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 启动redis集群客户端(-c表示集群
    redis-cli -c
    
    • 1
    • 查看集群信息
    cluster info
    
    • 1
    • 查看节点信息

    集群创建好之后,11,12,13为主节点,其余为从节点

    cluster nodes
    
    # 输出
    ec2d5b58fb0572e06ba8eeac62f87c595fa73913 172.28.0.14:6379@16379 slave ad075641de017884f05e9b17365a956994912f77 0 1656504631515 4 connected
    3d7137bd841fd90e37880e3fa2dc0eeb6586ea18 172.28.0.12:6379@16379 slave 8dcfe2052c7a278d2de90a1ee9b48d287f11c2a7 0 1656504632018 7 connected
    8dcfe2052c7a278d2de90a1ee9b48d287f11c2a7 172.28.0.16:6379@16379 master - 0 1656504630000 7 connected 5461-10922
    ad075641de017884f05e9b17365a956994912f77 172.28.0.13:6379@16379 master - 0 1656504630000 3 connected 10923-16383
    0e0dcd720fee1821826fb9808ad0d3446afc3fe4 172.28.0.15:6379@16379 slave 172c41d7253e68cb3564f115b21450575f43e5d3 0 1656504630513 5 connected
    172c41d7253e68cb3564f115b21450575f43e5d3 172.28.0.11:6379@16379 myself,master - 0 1656504631000 1 connected 0-5460
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    运行测试

    • 加入一个数据
      会返回所添加的对应的节点,如这里返回的是12的节点
    set name Stephen
    
    -> Redirected to slot [5798] located at 172.28.0.12:6379
    OK
    
    • 1
    • 2
    • 3
    • 4
    • 停止redis-2容器
      停止刚添加缓存的对应的redis节点
    docker stop redis-2
    
    • 1
    • 获取数据
      当主节点断开之后,会自动将从节点切换为主节点,比如这里将16切换为了主节点,
      如果能取到值,表示集群搭建成功,如下,从16这个节点中取到了值
    get name
    
    -> Redirected to slot [5798] located at 172.28.0.16:6379
    "stephen"
    
    • 1
    • 2
    • 3
    • 4

    redis cluster 常用命令

    • 集群(cluster)
    cluster info       打印集群的信息
    cluster nodes      列出集群当前已知的所有节点(node),以及这些节点的相关信息  
    
    • 1
    • 2
    • 节点(node)
    cluster meet <ip> <port>        将ip和port所指定的节点添加到集群当中,让它成为集群的一份子  
    cluster forget <node_id>        从集群中移除node_id指定的节点
    cluster replicate <node_id>     将当前节点设置为node_id指定的节点的从节点
    cluster saveconfig              将节点的配置文件保存到硬盘里面
    cluster slaves <node_id>        列出该slave节点的master节点
    cluster set-config-epoch        强制设置configEpoch 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 槽(slot)
    cluster addslots <slot> [slot ...]               将一个或多个槽(slot)指派(assign)给当前节点
    cluster delslots <slot> [slot ...]               移除一个或多个槽对当前节点的指派 
    cluster flushslots                               移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点 
    cluster setslot <slot> node <node_id>            将槽slot指派给node_id指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽,然后再进行指派 
    cluster setslot <slot> migrating <node_id>       将本节点的槽slot迁移到node_id指定的节点中  
    cluster setslot <slot> importing <node_id>       从node_id 指定的节点中导入槽slot到本节点 
    cluster setslot <slot> stable                    取消对槽slot的导入(import)或者迁移(migrate) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 键(key)
    cluster keyslot <key>                             计算键key应该被放置在哪个槽上  
    cluster countkeysinslot <slot>                    返回槽slot目前包含的键值对数量 
    cluster getkeysinslot <slot> <count>              返回count个slot槽中的键
    
    • 1
    • 2
    • 3
    • 其它
    cluster myid       返回节点的ID
    cluster slots      返回节点负责的slot
    cluster reset      重置集群,慎用
    
    • 1
    • 2
    • 3

    集群 配置项

    
    
    • 1
  • 相关阅读:
    论文复现--VideoTo3dPoseAndBvh(视频转BVH和3D关键点开源项目)
    h0173. 01背包问题
    数据结构与算法(Java)-前后缀分解题单
    基于STM32+华为云IOT设计的火灾感知系统
    算法与设计分析--分治算法的设计与分析
    vue+nodejs商城实战项目【登录 + 购物车 + 支付】
    哪些不得不记下的汇编指令
    前端常见问题(vue-题库一)
    Ubuntu22.04桌面版的 mkdir , mkdir -p 命令笔记2221107
    深度学习系列60: 大模型文本理解和生成概述
  • 原文地址:https://blog.csdn.net/weixin_45541665/article/details/125528816