redis-cluster大规模集群架构

上篇文章说到最小资源下的redis主从高可用架构,但毕竟单台服务器的性能有限,随着业务的发展也会出现请求变慢的情况,这时就该对redis做拆分了,和数据库类似,有垂直拆分和水平拆分。

所谓垂直拆分,就是再搭建多个其他的redis主从结构,将部分业务的缓存迁移到这些新redis上;比如在电商业务中,订单服务、购物车服务、支付服务、库存服务等均使用自己独立的redis。

垂直拆分也有一些问题:①架构变得很冗余,每个业务都要维护自己的redis服务;②不适合大型服务,毕竟不可能拆分出出太多redis服务,那维护成本就不可计了。这时候会采用水平拆分的方法,即通过分片的方式,根据某些算法,将不同key-value值,存储到不同的redis分片上;这些分片会按照既定策略存储在不同redis机器中。

redis-cluster的基本原理

redis官方提供了大规模集群方案,redis-cluster方案中,会将整个redis数据库切成16384份(2^14次方),redis集群会将这些分片尽可能均匀的分给所有的redis-node(主节点);为了防止redis-node主节点宕机,每个主节点node也会有自己的slave节点,主节点把自己分到的数据都会同步给slave节点,一旦主节点宕机,redis集群会将其对应的slave节点升级为主节点。
假设集群中有三个主节点A、B、C,每个主节点都有自己的备节点A1、B1、C1,则每个节点默认分得的分片如下:

  • A节点包含0 - 5500分片,A1节点会复制A节点的0-5500分片
  • B节点包含5501 - 11000分片,B1节点会复制B节点的5501-11000分片
  • C节点包含11001 - 16383分片,C1节点会复制C节点的11001 - 16383分片

redis-cluster key值到切片的分配规则

redis-cluster在收到存储数据请求时,会将key值进行CRC16(一种通信领域的冗余校验算法,效率很高)计算,得到32位的数字,之后再 % 16384,根据取模的结果来决定将该key-value对应哪个分片(官方叫hash slot,哈希槽),再根据分片找到对应的redis-node,即完成了key值到node的映射。
redis-cluster支持在一条命令中操作多个key-value值,但前提是这些key-value值都在同一个分片;可以在key中使用{}的方式强制将某些key-value对应到一个分片上,redis只会取{}中的数据进行分片计算。 如下面两个key计算出来的hash slot是一致的。

    127.0.0.1:7000> set user:lily{1024} "lily-hello"
    OK
    127.0.0.1:7000> set email:lily{1024} "lily@123.com"
    OK
    127.0.0.1:7000> get user:lily{1024}
    "lily-hello"
    127.0.0.1:7000> get email:lily{1024}
    "lily@123.com"
    127.0.0.1:7000> CLUSTER KEYSLOT user:lily{1024}
    (integer) 2776
    127.0.0.1:7000> CLUSTER KEYSLOT email:lily{1024}
    (integer) 2776

redis-cluster需要注意的几点

生产环境的redis-cluster集群肯定有多台主节点,每个主节点也会有至少一个备节点,这种架构虽然已经尽量高可用,但也有需要注意的地方。假设集群有主节点A、B、C,以及备节点A1、B1、C1

  • 如果主节点A宕了,则集群会将A1提升为主节点,继续提供服务;但如果A、A1同时宕,则整个集群就无法工作了。
  • redis主从会自动同步,这种异步操作,也就是主节点先给客户端响应,再同步数据给备节点;如果主节点恰好在同步之前宕了,则本次写入就丢失了。redis本身不提供强完整性存储,这也是其在吞吐率与完整性之间的取舍。
  • 如果生产环境网络故障,A、C、A1、B1、C1之间可以互通;B没有宕,而是网络与其他节点不通,客户端能访问B,这时客户端还可以对B进行读写;如果网络在B1晋升主节点之前恢复,则数据不会丢;但如果网络迟迟不通,B1晋升为主节点,待网络恢复后,B会被判定为备节点,客户端在网络中断期间写入的数据就丢了。

搭建集群

  1. 启动集群模式的redis-server,配置文件redis-server.conf,如下:

    port 7000
    bind 0.0.0.0
    protected-mode no
    cluster-enabled yes
    cluster-config-file nodes.conf #nodes.conf会自动生成
    cluster-node-timeout 5000
    appendonly yes

    直接用命令启动即可:

    nohup redis-server  redis-server.conf  > redis.log 2>&1 &
  2. 复制上面的配置文件,再多启动几台redis-server,假设启动了8台
  3. 使用上面的8台redis组建集群,四个主、四个备

    redis-cli --cluster create 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 127.0.0.1:7006 127.0.0.1:7007 \
    --cluster-replicas 1

    组完集群之后,可以看下集群状态:

    # redis-cli -p 7006 cluster nodes
    0a7f459a99ba85e8e36d865aeb7692b2ebc13444 10.57.5.31:7007@17007 master - 0 1764389404276 12 connected 12288-16383
    f6d4d103c5f8d208139a172d422f5a74f436a8b1 10.57.5.31:7005@17005 master - 0 1764389405000 14 connected 0-4095
    f31d653e839715bdb66fe60fa0fa25510af3049e 10.57.5.31:7000@17000 slave f6d4d103c5f8d208139a172d422f5a74f436a8b1 0 1764389404577 14 connected
    fde095c3f5808e534066ce45de3b32eb541597c3 10.57.5.31:7001@17001 slave 0a7f459a99ba85e8e36d865aeb7692b2ebc13444 0 1764389404075 12 connected
    079934e5c72c08fbdf0e685c3f87aecf6f3a9849 10.57.5.31:7002@17002 slave de98a29bf19da52ea6f7e7b3d691f05602e930b9 0 1764389405581 13 connected
    c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 10.57.5.31:7003@17003 master - 0 1764389405581 15 connected 8192-12287
    f7175fe56837212df30592a92ae0a1c879371cab 10.57.5.31:7006@17006 myself,slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764389405000 9 connected
    de98a29bf19da52ea6f7e7b3d691f05602e930b9 10.57.5.31:7004@17004 master - 0 1764389405280 13 connected 4096-8191

可以看到上面有4个主节点,瓜分了16384个分片,并且每个主节点有1个备节点。

redis-cluster扩容和缩容

上面提到redis-cluster默认会尽可能平均地将分片分配到各个主节点上,但同时也支持人工调整分片,可以在不影响整个集群读写的情况下,把一些分片从一个node迁移至其他node,这就为redis-cluster集群扩容、缩容提供了便利。

扩容

4个主节点如下:

    # redis-cli -p 7006 cluster nodes | grep master
    0a7f459a99ba85e8e36d865aeb7692b2ebc13444 10.57.5.31:7007@17007 master - 0 1764388865648 12 connected 12288-16383
    f6d4d103c5f8d208139a172d422f5a74f436a8b1 10.57.5.31:7005@17005 master - 0 1764388866000 14 connected 0-4095
    c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 10.57.5.31:7003@17003 master - 0 1764388865549 15 connected 8192-12287
    de98a29bf19da52ea6f7e7b3d691f05602e930b9 10.57.5.31:7004@17004 master - 0 1764388866651 13 connected 4096-8191

现在要再扩容1主+1备节点

  1. 继续按照上面的方式,再启动两台redis-server 7008、7009

    # ps aux | grep redis
    root      248584  0.2  0.0  63040  7844 pts/1    Sl   10:50   0:10 redis-server 0.0.0.0:7000 [cluster]
    root      248638  0.2  0.0  63040  7848 pts/1    Sl   10:51   0:10 redis-server 0.0.0.0:7001 [cluster]
    root      248754  0.2  0.0  63040  7820 pts/1    Sl   10:54   0:10 redis-server 0.0.0.0:7002 [cluster]
    root      248769  0.3  0.0  67136  7724 pts/1    Sl   10:54   0:19 redis-server 0.0.0.0:7003 [cluster]
    root      248784  0.4  0.0  64576  7860 pts/1    Sl   10:54   0:22 redis-server 0.0.0.0:7004 [cluster]
    root      248799  0.3  0.0  60992  7864 pts/1    Sl   10:54   0:19 redis-server 0.0.0.0:7005 [cluster]
    root      248814  0.2  0.0  63040  7884 pts/1    Sl   10:54   0:10 redis-server 0.0.0.0:7006 [cluster]
    root      248832  0.4  0.0  60992  7876 pts/1    Sl   10:55   0:23 redis-server 0.0.0.0:7007 [cluster]
    root      249143  0.1  0.0  55872  7396 pts/1    Sl   12:17   0:00 redis-server 0.0.0.0:7008 [cluster]
    root      249177  0.1  0.0  55872  7308 pts/1    Sl   12:18   0:00 redis-server 0.0.0.0:7009 [cluster]
  2. 将7008节点加入到集群中

    redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7000

    可以查看一下节点的状态,如下面7008已经加入到集群中,成为了主节点,但是并没有分得切片:

    # redis-cli -p 7006 cluster nodes
    0a7f459a99ba85e8e36d865aeb7692b2ebc13444 10.57.5.31:7007@17007 master - 0 1764390168039 12 connected 12288-16383
    f6d4d103c5f8d208139a172d422f5a74f436a8b1 10.57.5.31:7005@17005 master - 0 1764390168541 14 connected 0-4095
    c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 10.57.5.31:7003@17003 master - 0 1764390168541 15 connected 8192-12287
    f31d653e839715bdb66fe60fa0fa25510af3049e 10.57.5.31:7000@17000 slave f6d4d103c5f8d208139a172d422f5a74f436a8b1 0 1764390168000 14 connected
    ee1de1a198b083429ccf79e784464cc5564138de 10.57.5.31:7008@17008 master - 0 1764390169000 0 connected
    fde095c3f5808e534066ce45de3b32eb541597c3 10.57.5.31:7001@17001 slave 0a7f459a99ba85e8e36d865aeb7692b2ebc13444 0 1764390169043 12 connected
    f7175fe56837212df30592a92ae0a1c879371cab 10.57.5.31:7006@17006 myself,slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764390167000 9 connected
    079934e5c72c08fbdf0e685c3f87aecf6f3a9849 10.57.5.31:7002@17002 slave de98a29bf19da52ea6f7e7b3d691f05602e930b9 0 1764390167537 13 connected
    de98a29bf19da52ea6f7e7b3d691f05602e930b9 10.57.5.31:7004@17004 master - 0 1764390169545 13 connected 4096-8191
  3. 将7009节点以备节点身份加入到集群中

    redis-cli --cluster add-node 127.0.0.1:7009 127.0.0.1:7000 --cluster-slave

    如下执行结果,可以看到集群自动将7009设置为7008的备用节点:

    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    Automatically selected master 10.57.5.31:7008
    >>> Send CLUSTER MEET to node 127.0.0.1:7009 to make it join the cluster.
    Waiting for the cluster to join
    
    >>> Configure node as replica of 10.57.5.31:7008.
    [OK] New node added correctly.

    当然也可以手动指定主节点,如下(不一定非得是7008,任何其他主节点都可以):

    redis-cli --cluster add-node 127.0.0.1:7009 127.0.0.1:7000 --cluster-slave --cluster-master-id ee1de1a198b083429ccf79e784464cc5564138de
  4. 手动修改集群中节点的身份

    • 主节点修改为备用节点,将7008设置为其他主节点的备用节点:

      # redis-cli -p 7008 -c
      127.0.0.1:7008> cluster replicate c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9
      OK
      127.0.0.1:7008> cluster nodes
      f7175fe56837212df30592a92ae0a1c879371cab 10.57.5.31:7006@17006 slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764390644105 15 connected
      0a7f459a99ba85e8e36d865aeb7692b2ebc13444 10.57.5.31:7007@17007 master - 0 1764390646514 12 connected 12288-16383
      f6d4d103c5f8d208139a172d422f5a74f436a8b1 10.57.5.31:7005@17005 master - 0 1764390645000 14 connected 0-4095
      c540cbb9f7ad78223e3c7adfc647bf0067f2b3a9 10.57.5.31:7009@17009 slave ee1de1a198b083429ccf79e784464cc5564138de 0 1764390645000 15 connected
      079934e5c72c08fbdf0e685c3f87aecf6f3a9849 10.57.5.31:7002@17002 slave de98a29bf19da52ea6f7e7b3d691f05602e930b9 0 1764390644808 13 connected
      fde095c3f5808e534066ce45de3b32eb541597c3 10.57.5.31:7001@17001 slave 0a7f459a99ba85e8e36d865aeb7692b2ebc13444 0 1764390645000 12 connected
      ee1de1a198b083429ccf79e784464cc5564138de 10.57.5.31:7008@17008 myself,slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764390644000 0 connected
      de98a29bf19da52ea6f7e7b3d691f05602e930b9 10.57.5.31:7004@17004 master - 0 1764390645510 13 connected 4096-8191
      f31d653e839715bdb66fe60fa0fa25510af3049e 10.57.5.31:7000@17000 slave f6d4d103c5f8d208139a172d422f5a74f436a8b1 0 1764390645000 14 connected
      c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 10.57.5.31:7003@17003 master - 0 1764390645811 15 connected 8192-12287 

      7008成为7003的备用节点后,会自动同步7003节点上的数据;同时7009依旧是7008的备用节点,其也会同步7008的数据。

    • 备用节点,晋升为主节点,其实就是手动切换主从

      redis-cli -p 7008 CLUSTER FAILOVER TAKEOVER

      上面的命令会,让7008晋升为主节点,然后7003降级为备节点

    • 备用节点,断开与之前主节点的关系,晋升新的主节点,这种情况需要操作两步:
      ① 断开7008与7003的主从关系:

      redis-cli -p 7008 CLUSTER RESET SOFT  不删除7008上面的数据
      redis-cli -p 7008 CLUSTER RESET 删除7008上面的数据

      ② 断开之后,7008就游离于集群之外了,将其重新加进来即可;可以看到下面7008成为主节点,但不持有分片

      # redis-cli -p 7008 -c
      127.0.0.1:7008> CLUSTER MEET 10.57.5.31 7000
      OK
      127.0.0.1:7008> cluster nodes
      f7175fe56837212df30592a92ae0a1c879371cab 10.57.5.31:7006@17006 slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764392884566 20 connected
      0a7f459a99ba85e8e36d865aeb7692b2ebc13444 10.57.5.31:7007@17007 master - 0 1764392884566 12 connected 12288-16383
      f6d4d103c5f8d208139a172d422f5a74f436a8b1 10.57.5.31:7005@17005 master - 0 1764392884767 14 connected 0-4095
      c540cbb9f7ad78223e3c7adfc647bf0067f2b3a9 10.57.5.31:7009@17009 slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764392882560 20 connected
      079934e5c72c08fbdf0e685c3f87aecf6f3a9849 10.57.5.31:7002@17002 slave de98a29bf19da52ea6f7e7b3d691f05602e930b9 0 1764392884566 13 connected
      fde095c3f5808e534066ce45de3b32eb541597c3 10.57.5.31:7001@17001 slave 0a7f459a99ba85e8e36d865aeb7692b2ebc13444 0 1764392883062 12 connected
      ee1de1a198b083429ccf79e784464cc5564138de 10.57.5.31:7008@17008 myself,master - 0 1764392884000 19 connected
      de98a29bf19da52ea6f7e7b3d691f05602e930b9 10.57.5.31:7004@17004 master - 0 1764392883000 13 connected 4096-8191
      f31d653e839715bdb66fe60fa0fa25510af3049e 10.57.5.31:7000@17000 slave f6d4d103c5f8d208139a172d422f5a74f436a8b1 0 1764392882560 14 connected
      c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 10.57.5.31:7003@17003 master - 0 1764392884000 20 connected 8192-12287  
  5. 给7008分配数据分片
    可以强制redis集群将分片分给空节点:

    # redis-cli --cluster rebalance 127.0.0.1:7000 --cluster-use-empty-masters
    >>> Performing Cluster Check (using node 127.0.0.1:7000)
    [OK] All nodes agree about slots configuration.
    >>> Check for open slots...
    >>> Check slots coverage...
    [OK] All 16384 slots covered.
    >>> Rebalancing across 5 nodes. Total weight = 5.00
    Moving 820 slots from 10.57.5.31:7003 to 10.57.5.31:7008
    Moving 820 slots from 10.57.5.31:7005 to 10.57.5.31:7008
    Moving 820 slots from 10.57.5.31:7007 to 10.57.5.31:7008
    Moving 820 slots from 10.57.5.31:7004 to 10.57.5.31:7008

    也可以手动迁移:

    redis-cli --cluster reshard 127.0.0.1:7000
  6. 将7009设置为7008的备用节点,类似第4步中的操作,最终5主5备的效果如下:

    127.0.0.1:7009> cluster nodes
    f7175fe56837212df30592a92ae0a1c879371cab 10.57.5.31:7006@17006 slave c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 0 1764393418677 20 connected
    0a7f459a99ba85e8e36d865aeb7692b2ebc13444 10.57.5.31:7007@17007 master - 0 1764393419178 12 connected 13108-16383
    f31d653e839715bdb66fe60fa0fa25510af3049e 10.57.5.31:7000@17000 slave f6d4d103c5f8d208139a172d422f5a74f436a8b1 0 1764393418000 14 connected
    fde095c3f5808e534066ce45de3b32eb541597c3 10.57.5.31:7001@17001 slave 0a7f459a99ba85e8e36d865aeb7692b2ebc13444 0 1764393418576 12 connected
    ee1de1a198b083429ccf79e784464cc5564138de 10.57.5.31:7008@17008 master - 0 1764393419078 21 connected 0-819 4096-4915 8192-9011 12288-13107
    de98a29bf19da52ea6f7e7b3d691f05602e930b9 10.57.5.31:7004@17004 master - 0 1764393419580 13 connected 4916-8191
    f6d4d103c5f8d208139a172d422f5a74f436a8b1 10.57.5.31:7005@17005 master - 0 1764393419580 14 connected 820-4095
    079934e5c72c08fbdf0e685c3f87aecf6f3a9849 10.57.5.31:7002@17002 slave de98a29bf19da52ea6f7e7b3d691f05602e930b9 0 1764393418174 13 connected
    c33283d8114d8f3a0a9f3d5f0af29fb0e030e4a9 10.57.5.31:7003@17003 master - 0 1764393420181 20 connected 9012-12287
    c540cbb9f7ad78223e3c7adfc647bf0067f2b3a9 10.57.5.31:7009@17009 myself,slave ee1de1a198b083429ccf79e784464cc5564138de 0 1764393418000 16 connected

缩容

当业务流量高峰过后,会对集群进行缩容,节约成本。这里再把7008、7009两个节点剔除集群。

  1. 首先要把7008这个主节点上的分片迁移到其他主节点上

    redis-cli --cluster reshard 127.0.0.1:7000
  2. 断开7009和7008与集群的关系

    redis-cli -p 7008 cluster reset
    redis-cli -p 7009 cluster reset
  3. 上面两部操作之后,7008、7009会成为两个主节点,但不承载分片;最后从集群删掉即可。

    # redis-cli --cluster del-node 127.0.0.1:7000 ee1de1a198b083429ccf79e784464cc5564138de
    >>> Removing node ee1de1a198b083429ccf79e784464cc5564138de from cluster 127.0.0.1:7000
    >>> Sending CLUSTER FORGET messages to the cluster...
    []()>>> SHUTDOWN the node.

结言

  1. redis-cluster提供了大规模集群的成熟解决方案,具体采用sentinel主从结构,还是集群,最终还是要看业务的现状与规划,适合自己才是最好的。
  2. redis的吞吐率非常高,这跟其异步同步的架构设计有关,也决定了其在一致性方案有点缺陷。
  3. redis的异步同步机制,决定了即使采用了高可用架构,在极端场景下也会丢数据;如果是对一致性要求非常高的业务,需要从业务层面进行兜底。
  4. 高可用设计是个很宏大的方向,除了软件架构层面、也涉及到底层基础设施;比如现实中,也会出现一条光缆、网络线路被挖断,整个机房宕掉的情况。最终还是要回归业务需求,设计出最适合当前业务的架构,就是最佳的。
版权声明

本站文章、图片、视频等(除转载外),均采用知识共享署名 4.0 国际许可协议(CC BY-NC-SA 4.0),转载请注明出处、非商业性使用、并且以相同协议共享。

© 空空博客,本文链接:https://www.yeetrack.com/?p=1731