• REDIS CLUSTER 搭建,扩容缩容基本原理


    摘要

    在redis4.0.14版本,是通过ruby的工具redis-trib.rb工具进行扩容缩容以及集群搭建的工作,然后到redis5.0后取消了这个工具的功能并合并到redis-cli中,这里就让我们了解一下redis-trib.rb工具在搭建集群和扩容缩容中到底做了什么把

    源码在github 上搜索redis,第一个就是了,这里就不贴代码了

    1. Redis4.0 不使用redis-trib.rb工具搭建cluster

    1.1 redis redis-trib.rb create方法分析

    步骤 作用
    node ClusterNode.new(n) 初始化,n是node,对节点信息初始化
    node.connect(:abort => true) 连接节点
    node.assert_cluster 判断节点信息,看cluster_enabled是否开启
    node.load_info 如果节点已经有slot,则对该节点执行addslot命令,设置节点的cluster info信息
    node.assert_empty

    验证该节点的cluster_known_nodes是否为1 和验证 该节点的db0是否为空

    如果cluster_known_nodes不为1则认为已经加入其他集群,如果db0不为空则认为该节点不为空不能加入该集群

    add_node(node) @nodes << node 把该node节点加入@nodes数组
    把所有节点按以上流程走一遍,再开始执行下面
    check_create_parameters

    masters @nodes.length/(@replicas+1) 计算master 节点,@replicas代表每个主有多少个从

    如果是一主二从的集群,master的结点数=用节点数/2+1

    所以一主二从的集群起码要9个节点,才能搭建cluster

    alloc_slots

    先维护一个字典,每个node的ip为键,所有的nodes的信息为值(一个字典),把nodes加入字典中

    ips[n.info[:host]= [if !ips[n.info[:host]]

    ips[n.info[:host]] << n

    选择命令的前3个作为master节点

    计算每个每个节点分配多少槽(ClusterHashSlots.to_f / masters_count)

    然后调用addslots方法,其实就是把要分配的slots 遍历加入节点的对象(一个字典)中的key[slots]中,每个master节点都执行一个分配槽的操作

    show_nodes 输出槽的分配结果

    1.2 搭建过程

    ## step 1

    ## 以第一点的配置启动6个节点

    ## redis-server /usr/conf/cluster/7000.conf

    ## redis-server /usr/conf/cluster/7001.conf

    ## .....................................................................

    ## redis-server /usr/conf/cluster/7005.conf

    ## step 2

    ## redis-cli -p 7001 -c   (随便进入一个集群)

    ## 执行cluster meet / redis-trib.rb add nodes 把其他节点加入集群

     

    ## 查看集群的节点信息

    ## cluster nodes

    ## step 4

    ## 挑选前3个节点作为master

    ##  16384 / 3 = 5461.333 

    ## 先给第一个master几点分配 5461个节点

    ##  redis-cli -p 7001 -c cluster addslots {0..5460}

    ## 第二个节点分配的位置的初始位置为5461,结束为5461+5461 =10922

    ## redis-cli -p 7003 -c cluster addslots {5461..10922}

    ## 第三个节点分配的位置的初始位置为10923,last = 16384-1

    ## redis-cli -p 7000 -c cluster addslots {10923..16838}

     

    ## step 5

    ## 给master 节点设置slave节点

    ## 分别进入3个没有槽的节点 执行cluster replicate

    ## redis-cli -p 7002 -c cluster replicate 9222d226718a707c4517e634dfe38e81c026986c

    ## redis-cli -p 7005 -c cluster replicate 403b709820b1deeea8eab0744c76f928b53b7e86

    ## redis-cli -p 7004 -c cluster replicate 7bca1a86e03c7c176a8298c34ff363b7100d21d8

    ## step 6 

    ## 查看每个节点的集群状态

    ## redis-cli -p 7000 -c cluster info

    ## redis-cli -p 7001 -c cluster info

    ## redis-cli -p 7002 -c cluster info

     ## 确认每个节点的集群状态,因为加入A节点的槽部分不可用,A的cluster_state的状态会为fail,其他节点的clutser_state会显示ok

    2.Redis4.0 不使用redis-trib.rb工具对集群进行扩容缩容

    2.1 redis-trib.rb reshard 方法

    ALL的分配方式顺序
    作用

    遍历所有节点,过滤掉slave的节点,和接收槽的节点

    把剩余的节点加入sources数组中,作为槽的来源节点

     

    多个槽来源的计算方式:

    1.先对来源槽进行排序,

    2 计算每个节点要移出多少个槽 (numslots.to_f/source_tot_slots*s.slots.length)

    这样的处理方式会带来最终分配的slot与请求迁移的slot数量不一致

    3. 计算出每个节点需要移出的槽数

    4. 遍历每个节点的所需要移出的槽,加入move数组中

    move = []

    move << {:source=>s,slot=>slot}

    要移动的槽全部放进数组中

    numslots.to_f 总共要移动的槽数

    source_tot_slots 所有来源节点的槽数

    s.slots.length 单个来源节点拥有的槽数

    没记错是入栈出栈的计算方式

    先计算numslots.to_f/source_tot_slots

    再算 *s.slots.length

    target.r.cluster("setslot",slot,"importing",source.info[:name])

    source.r.cluster("setslot",slot,"migrating",target.info[:name])

    migrate ...迁移键

    n.r.cluster("setslot",slot,"node",target.info[:name])

    每个槽使用importing导出,然后再移动到接收槽上

    源节点和目标节点都执行cluster setslot ... node ...

    2.2 扩容介绍

    ## step 1

    ## redis-server 启动两个节点,一个主一个从

    ## redis-server /usr/conf/cluster/7006.conf

    ## redis-server /usr/conf/cluster/7007.conf

    ## netstat -lnp | grep redis

    ## 新启动了两个节点

    ## step 2

    ## 进入集群中的任意带槽的节点节点

    ## redis-cli -p 7003 -c

    ## 把新启动的节点加入集群

    ## cluster meet 127.0.0.1 7006

    ## cluster meet 127.0.0.1 7007

    ## cluster nodes

    ## 成功把两个节点加入集群

    ## step 3 扩容

    ## 扩容,给新的一个master 节点分配槽

    ## 多个来源节点的情况

    ## 遍历每个节点,计算每个节点应该移出多少个槽 (numslots.to_f/source_tot_slots*s.slots.length)

    ## numslots.to_f 总共要移动的槽数

    ## source_tot_slots 所有来源节点的槽数

    ## s.slots.length 单个来源节点拥有的槽数

    ## 循环每个slot:

    ## 接收节点执行 cluster setslot <slot> importing <node_id>   (node_id为源节点id)

    ## 来源节点执行 cluster setslot <slot> migrating <node_id>    (node_id为接收节点id)

    ## 源节点执行 cluster countkeysinslot <slot> 查看要移动的槽 有没有键

    ## 如果有 则 继续执行 cluster getkeysinslot <slot> <count>  查看slot节点的多少个key值, 如果上一步槽的键数量为空跳过此步骤

    ## 然后执行 MIGRATE 源节点ip 源节点port "" 0 5000 KEYS key1 key2 key3  把键先迁移,执行这条命令后该键不可读不可写

    ## 分别在源节点和接收节点执行 cluster setslot <slot> node <node_id>   

    ## 现在看 主要在接收节点上执行 cluster setslot ...node ...命令,只有在接收节点上面执行后会跟新接收节点的epoch 版本号,然后发消息跟其他节点说该slot已经归我管了,如果epoch比接收信息的节点高,接收信息的节点会跟新该slot的状态

    ## 而源节点上面执行cluster setslot .... node ...命令只移除mirating flag 并不会更新版本号

    ## 那么问题来了,那importing 和migrating 步骤不就可以省略吗,那源节点也没必要执行cluster setslot ... node ...命令了(其实并不是这样,详见测试)

    2.3 扩容实例

    把7373 节点从 7004 移动到7009

    ## step 1

    ## redis-cli -p 7009 -c cluster setslot 7373 importing cd8cd114215e4c2869d6d31ae7fe5f5cff922ed3

    ## 从其他节点观察槽信息

    ## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 从7009 节点上看到设置了一个importing flag (-<-)

    ## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 在7004 上面没有变化

    ## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 在7003 上面没有变化

    ##往槽7373 插入数据

     ## 说明槽还在 7004 上面

    ## step 2

    ## redis-cli -p 7004 -c cluster setslot 7373 migrating 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## 从其他节点观察槽信息

    ## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

    ## 7009 上面仍然只看到自己身上只有一个importing flag,7373槽仍然在7004 上面

    ## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2 

    ## 7004 上面看到自己多出一个mirating flag,7373槽仍然在7004 上面

    ## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 7003 仍然没变化

    ## redis-cli -p 7003 -c get gw 

     ## 7373槽仍在在7004上面

    ## step 3

    ## redis-cli -p 7004 -c cluster countkeysinslot 7373

    ## 查看槽7373 有多少个key,必须在源节点上面执行

    ## 7373 上面有一个key

    ## step 4

    ## redis-cli -p 7004 -c cluster getkeysinslot 7373 1

    ## 7373槽上面只有一个key gw,必须在源节点上面执行

    ## step 5

    ## redis-cli -p 7004 -c MIGRATE 127.0.0.1 7009 "" 0 5000 KEYS gw

    ## 把槽7373的键从7004 移动到 7009

    ## redis-cli -p 7004 -c get gw

    ## redis-cli -p 7004 -c set gw qwes

    ## 执行完migrate 后键也不可访问

    ## 因为执行 setslot migrating 和setslot importing之后,旧的键可以在源节点上访问,但是新的键设置只能在新节点上面执行asking后执行设置新键命令才能成功

    ## 这样保证了 setslot migrating 和setslot importing之后不会在源节点上设置新键值对,避免setslot ,,,node,,, 后新键值丢失

    ## 由于mirate命令命令把旧键移动到接收节点上去,所以现在源节点上已经没有旧键了,这个时候,所有键的访问都必须在接收节点上执行asking后执行操作

    ## 例如:

    ## redis-cli -p 7009 -c

    ## asking

    ## set  sand aksnd

    ## 才会成功

    ## step 6

    ## redis-cli -p 7004 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2

    ## 从7004节点上面看,原本7004 上面的migrating flag 没有了,7373槽已经移动到 7009 上面去

    ## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

    ## 从 7009 上面看 importing flag 还没被消除, 7373slot 仍然在7004上面

    ## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 从7003 上面看 7373 仍然在 7004 上面

    ## get gw

    ## set gw asd

    ## 在 7003和7004 上面反复横跳

    ##注意,这里先在源节点上面执行setslot.... node....命令

    ## 形成原因

     ## 无论从哪个节点上面访问都会导致这种情况,这是由于每个节点信息不同步 或者说数据不一致导致的

    ## step 7

    ## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## 从其他节点查看slot7373的信息

    ## redis-cli -p 7009 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 从7009节点上面看,原本7009上面的importing flag 没有了,7373槽已经移动到 7009 上面去,迁移成功

    ## redis-cli -p 7004 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 从7004 节点上面看,7373slot 已经迁移成功

    ## redis-cli -p 7003 -c cluster nodes | grep master | sort -t ' ' -k2

     ## 从7003上面看,7373slot已经迁移成功

    ## get gw 

    ## set ge asds

     ## 数据已经可读可写

     ## 反复横跳现象已经消失,只要先在接收节点上面执行setslot ... node  就不会出现反复横跳现象

    ## step 8

    ## 观察日志 发现 只有接收slot 的节点 执行setslot ... node 命令 才会更新epoch 版本号,其他节点不会更新epoch,

     

    ## 节点之间通信的时候,会带着epoch版本号

    ## 加入 节点A接收了槽7373,更新了版本号到57,然后通信时A 给节点B发消息,说7373槽已经归我管了

    ## 现在有两种情况,一是节点B的epoch 比57 高,二是节点B的epoch版本号比57低

    ## 情况一:不理会节点A 的信息,这就导致数据不一致,节点B仍然认为槽7373 归源节点管

    ## 情况二: 更新信息,同步认为槽7373已经归节点A 管

    3.cluster 操作命令

    cluster info :打印集群的信息
    cluster nodes :列出集群当前已知的所有节点( node),以及这些节点的相关信息。
    节点
    cluster meet <ip> <port> :将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子。
    cluster forget <node_id> :从集群中移除 node_id 指定的节点。
    cluster replicate <master_node_id> :将当前从节点设置为 node_id 指定的master节点的slave节点。只能针对slave节点操作。
    cluster saveconfig :将节点的配置文件保存到硬盘里面。
    槽(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)。

    cluster keyslot <key> :计算键 key 应该被放置在哪个槽上。
    cluster countkeysinslot <slot> :返回槽 slot 目前包含的键值对数量。
    cluster getkeysinslot <slot> <count> :返回 count 个 slot 槽中的键 

    4. cluster setslot命令的作用

    4.1 cluster setslot <slot> migrating <node_id>

    测试1:

    ## step 1

    ## 测试:执行脚本对slot批量操作(把脚本中除了setslot migrating命令注释掉,只执行 cluster setslot <slot> migrating <node_id>):

    ## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7004 127.0.0.1:7009

    ## 把 7009 中 8000个slot 设置migrating flag 指向7004

     

    ## 尽管在7004 上面先执行asking 后 再set 也不行

    ## 因为7004 上面没有设置importing flag的原因,不允许新的键在7004上面执行

    ## 但是加上cluster setslot <slot> importing <node_id> 就没问题,详细在测试6中

    ## 所以只加上migrating flag 不加上importing flag 没办法设置新值

    测试2:

    ## step 1

    ## set madasdsaasdsd lasmd

    ## set madasdscxzaasdsd lasmad

     

    ## step 2 

    ## 测试:执行脚本对slot批量操作(把脚本中除了setslot migrating命令注释掉,只执行 cluster setslot <slot> migrating <node_id>): 

    ## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7009 127.0.0.1:7004

    ## 把 7004 中 8000个slot 设置migrating flag

     

    ## step 3

    ## get madasdsaasdsd

    ## get madasdscxzaasdsd

     ## 结合实验一说明 处于migrating flag状态的slot 都不能存放新值 但可以获取和设置旧值

    4.2 cluster setslot <slot> importing <node_id>

    测试3:

    ## step 1

    ## 对接收节点直接执行 cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## 执行直接迁移到源节点

    ## 观察日志发现没有生成新的epoch 版本号

    ## 所以其他节点也不会同步该变更信息

    ## 说明可能没有执行cluster setslot <slot> importing <node_id> 给迁移的槽设置importing flag的话不会生成新的epooch版本号

    测试4:

    ## step 1

    ## 接收节点先执行: redis-cli -p 7009 -c cluster setslot 7373 importing cd8cd114215e4c2869d6d31ae7fe5f5cff922ed3  (给slot7373 设置importing flag)

    ## 接收节点执行: redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## 生成了新的epoch版本号,同时 变更也会被其他节点应用

     

     

     ## cluster setslot <slot> importing <node_id> 语句第一个作用是让cluster setslot... node 命令生成新的epoch版本号

    测试5

    ## step 1

    ## 测试:执行脚本对slot批量操作(把脚本中除了setslot importing命令注释掉,只执行 cluster setslot <slot> importing <node_id>):

    ## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7009 127.0.0.1:7004

    ## 把 7004 中 8000个slot 设置importing flag

    ## 单独执行这个命令没有什么影响,读取和新设置的键都会继续使用原来的节点来存、

    测试6

    ## step 1

    ## 测试:执行脚本对slot批量操作(把脚本中除了setslot importing 和 miragting 以外命令注释掉,只执行 cluster setslot <slot> importing <node_id> 和 cluster setslot <slot> migrating <node_id> ):

    ## python /apps/dbdat/python3/test2/redis-expand.py 8871 127.0.0.1:7004 127.0.0.1:7009  (移动的槽数量 target  source)

    ##  7004 是目标节点, 7009 是源节点

    ## redlis-cli -p 7004 -c

    ## set asdk asmdzmc

     

    ## 可测试1 情况一样,仍然设置不了值,这个error 意思是请去7004 中执行操作?

    ## redis-cli -p 7004 -c

    ## asking

    ## set asdk qnwien

    4.3 cluster setslot <slot> node <node_id>

    ## 那所有master节点或者所有节点都执行一次,不就可以了吗,理论上对所有节点所拥有的集群信息都修改一次,是否可以达成目标

    测试7

    ## step 1

    ## 执行以下命令,对三个主节点执行cluster setslot ... node ...

    ## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

    ## redis-cli -p 7009 -c cluster setslot 7373 node 273289d0a17cd30fecc7a3db6fb2fd1cb06b3e55

     ## 7001 上来没有了7373 这个slot ( 7001 为7009的从节点)

    ## get gw

    ## 也确实是迁移成功了

    ## 问题是 从节点是否有epoch信息?

    ## 现在kill 掉 7009, 让它的从节点做故障迁移

    ## 故障迁移成功后,集群状态就fail 了

    ## 在 7001 节点上面查看slot信息

     ## 在7004 上面查看slot信息,也没有了7373 的踪影

    ## 主要是由于 故障迁移后 新的slave 会产生最新的epoch版本号,然后按新slave 的集群信息来更新,所以直接执行cluster setslot ... node ...是不行的。

    ## 这种情况应该是slave的信息中没有7373slot的信息,所以导致这种情况

    4.4 MIGRATE 命令测试

     测试8:

    ## step 1

    ## 脚本 设置一个 200M的值】

     

    limit = 273741824
    n = 100000
    s = generate_random_str(n)
    while sys.getsizeof(s) < limit:
    n += 100000
     s += generate_random_str(n)
    print(sys.getsizeof(s))

    # print('success')
    r = RedisCluster(host='127.0.0.1', port=7001)
    r.set('lxl', s)

    ## step 2 

    ## 已经 lxl 这个键存在720 的槽上

    ## 720 在 7008 上,我把键移动到 7006 上

    ## redis-cli -p 7008 -c cluster nodes | sort -k3 | grep master

    ## step 3

    ## 源节节点执行 redis-cli -p 7006 -c cluster setslot 720 importing f96767c817d322943ba0f7b3124d5cccf79931a5

    ## 接收节点执行 redis-cli -p 7008 -c cluster setslot 720 migrating 99ffb47ef64d489e81d3a203a76ff099d79b6f79

    ## 执行完以上命令就可以在7006设置importing flag 和在7008上面设置migrating flag

    ## step 4

    ## 键gw 存在在 7008 上面

     ## 键hjw 存在 7006上面

    ## 开三个窗口

    ## 窗口1 执行:

    ## redis-cli -p 7008 -c MIGRATE 127.0.0.1 7006 lxl 0 100000000000 replace

    ## 窗口2 执行:

    ## redis-cli -p 7008 -c get gw

    ## 可以看出源节点从一开始一直阻塞

    ## 窗口3 执行:

    ## redis-cli -p 7006 -c get hjw

    ## 目标节点阻塞一段时间

    ## 因为键大小大概200M,可能只需要一次就能发送完,所以目标节点只在接收数据,和restore过程会阻塞,但是如果键值很大,需要发多次 (接收缓冲区满了->接收数据-> restore数据 -> 缓冲区满了或者源节点发完了-> 接收数据->restore数据,因为是redis 单线程的,所以会阻塞

    4.5 Migrate 流程

    1. 执行的时候会阻塞进行迁移的两个实例,直到以下任意结果发生:迁移成功,迁移失败,等到超时
    2. 当前实例对给定 key 执行 DUMP 命令 ,将它序列化,然后传送到目标实例,目标实例再使用 RESTORE对数据进行反序列化,并将反序列化所得的数据添加到数据库中;当前实例就像目标实例的客户端那样,只要看到 RESTORE 命令返回 OK ,它就会调用 DEL 删除自己数据库上的 key 
    3. timeout 参数以毫秒为格式,指定当前实例和目标实例进行沟通的最大间隔时间。这说明操作并不一定要在 timeout 毫秒内完成,只是说数据传送的时间不能超过这个 timeout 数
    4. 超时会返回IOERR,当返回IOERR时,会出现两种情况, 1.key存在两个实例中,2.key 只存在当前实例
    5. 如果有其他错误发生,那么 MIGRATE 保证 key 只会出现在当前实例中
  • 相关阅读:
    [COGS2580]偏序 II
    [COGS2479]偏序
    [BZOJ2716]天使玩偶
    [BZOJ4237]稻草人/[JOISC2014]かかし
    Ynoi2015 世上最幸福的女孩
    ARC098D Donation
    BZOJ3691 游行
    CF923E Perpetual Subtraction
    Luogu P4191 [CTSC2010]性能优化
    Topcoder SRM 590 Fox And City
  • 原文地址:https://www.cnblogs.com/start-from-zero/p/12821608.html
Copyright © 2020-2023  润新知