Redis_集群

1/18/2021 Redis

# Redis集群

# 集群介绍

Redis 集群是一个提供在多个Redis间节点间共享数据的程序集。

Redis集群并不支持处理多个keys的命令,因为这需要在不同的节点间移动数据,从而达不到像Redis那样的性能,在高负载的情况下可能会导致不可预料的错误.

Redis 集群通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下继续处理命令.

Redis 集群的优势:

  • 自动分割数据到不同的节点上。
  • 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。

# 数据分片

Redis 集群没有使用一致性hash, 而是引入了 哈希槽的概念.

image-20210714165213581

Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽.集群的每个节点负责一部分hash槽,举个例子,比如当前集群有3个节点,那么:

  • 节点 1 包含 0 到 5500号哈希槽.
  • 节点 2 包含5501 到 11000 号哈希槽.
  • 节点 3 包含11001 到 16384号哈希槽.

这种结构很容易添加或者删除节点. 比如如果我想新添加一个节点D, 我需要从节点 A, B, C中得部分槽到D上. 如果我想移除节点A,需要将A中的槽移到B和C节点上,然后将没有任何槽的A节点从集群中移除即可. 由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态.

  • 无主模型

  • 客户端请求任意一个节点,hash之后如果是映射到自己就直接返回数据,如果映射到不是自己就将实际存储数据的节点id返回给客户端,让客户端重定向到对应的节点取数据

  • 每个节点都需要存储hash算法和所有节点的映射关系

  • 数据分治很难实现事务

    • hashtag

      {ooo}k1

      {ooo}k2

#

# 主从复制模型

为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有N-1个复制品.

# Redis一致性保证

Redis 并不能保证数据的强一致性. 这意味这在实际中集群在特定的条件下可能会丢失写操作,原因有如下两点

  1. 集群是用了异步复制,写操作的过程如下

    • 客户端向主节点B写入一条命令.
    • 主节点B向客户端回复命令状态.
    • 主节点将写操作复制给他得从节点 B1, B2 和 B3
  2. 集群出现了网络分区

    客户端写入的节点由于网络分区已经被认为是掉线了,重新选择了主节点,从而丢失数据

# 集群的弊端

  • 部分命令不可用(keys、watch)

# 集群搭建

# 官方集群支持

# 命令创建

# 创建实例
  • 集群的最小选项配置文件

    port 7000
    cluster-enabled yes
    cluster-config-file nodes.conf
    cluster-node-timeout 5000
    appendonly yes
    
    1
    2
    3
    4
    5

    cluster-enabled 选项用于开实例的集群模式

    cluster-conf-file 选项则设定了保存节点配置文件的路径, 默认值为 nodes.conf.节点配置文件无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新

  • 添加配置文件

    mkdir cluster-test
    cd cluster-test
    mkdir 7000 7001 7002 7003 7004 7005
    
    1
    2
    3

    在文件夹 7000 至 7005 中, 各创建一个 redis.conf 文件, 文件的内容可以使用上面示例的最少选项配置, 但记得将配置中的端口号从 7000 改为与文件夹名字相同的号码。

  • 开启实例

    image-20211009145112321

    实例打印的日志显示, 因为 nodes.conf 文件不存在, 所以每个节点都为它自身指定了一个新的 ID :

    59834:M 09 Oct 2021 14:50:17.008 * No cluster configuration found, I'm 87dab917a8fc09c47d2582ac450c8365a40aecee

    实例会一直使用同一个 ID , 从而在集群中保持一个独一无二(unique)的名字。

    按照同样的方法开启剩余的5个实例

# 创建集群
  • 通过redis-trib.rb创建集群(v5以前)

    ./redis-trib.rb create --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
    # --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点
    
    1
    2

    redis-trib 位于 Redis 源码的 src 文件夹中, 它是一个 Ruby 程序, 这个程序通过向实例发送特殊命令来完成创建新集群, 检查集群, 或者对集群进行重新分片(reshared)等工作

  • 通过redis-cli创建集群(v5及higher)

    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 --cluster-replicas 1
    # --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点
    
    1
    2

执行过程中会展示槽位分配方案,让你选择是否按照这个方案分配,选择yes后 执行结果如下

➜  ~ 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 --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 127.0.0.1:7004 to 127.0.0.1:7000
Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
Adding replica 127.0.0.1:7003 to 127.0.0.1:7002
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: fd116c19cd332d72c4025f80eb8bbac90868cc9c 127.0.0.1:7000
   slots:[0-5460] (5461 slots) master
M: 87dab917a8fc09c47d2582ac450c8365a40aecee 127.0.0.1:7001
   slots:[5461-10922] (5462 slots) master
M: 7356a44bf9141527e851b7b447fdfc204c05774a 127.0.0.1:7002
   slots:[10923-16383] (5461 slots) master
S: 06101da098ae8d52ab31b63de44fb9c4af07e216 127.0.0.1:7003
   replicates 7356a44bf9141527e851b7b447fdfc204c05774a
S: e9a610f702cde191f99484dc170fd48c5626a19e 127.0.0.1:7004
   replicates fd116c19cd332d72c4025f80eb8bbac90868cc9c
S: c9d23b834961984f22e6e199afadeb0a08691a22 127.0.0.1:7005
   replicates 87dab917a8fc09c47d2582ac450c8365a40aecee
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 127.0.0.1:7000)
M: fd116c19cd332d72c4025f80eb8bbac90868cc9c 127.0.0.1:7000
   slots:[0-5460] (5461 slots) master
   1 additional replica(s)
S: c9d23b834961984f22e6e199afadeb0a08691a22 127.0.0.1:7005
   slots: (0 slots) slave
   replicates 87dab917a8fc09c47d2582ac450c8365a40aecee
S: e9a610f702cde191f99484dc170fd48c5626a19e 127.0.0.1:7004
   slots: (0 slots) slave
   replicates fd116c19cd332d72c4025f80eb8bbac90868cc9c
S: 06101da098ae8d52ab31b63de44fb9c4af07e216 127.0.0.1:7003
   slots: (0 slots) slave
   replicates 7356a44bf9141527e851b7b447fdfc204c05774a
M: 87dab917a8fc09c47d2582ac450c8365a40aecee 127.0.0.1:7001
   slots:[5461-10922] (5462 slots) master
   1 additional replica(s)
M: 7356a44bf9141527e851b7b447fdfc204c05774a 127.0.0.1:7002
   slots:[10923-16383] (5461 slots) master
   1 additional replica(s)
[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

# utils/create-cluster

#

无主模型,多节点的cluster

官方脚本:utils/create-cluster/

  • 配置&搭建

    • 修改配置启动脚本

      vi ./create-cluster

    • 启动

      ./create-cluster start

      image-20210715100758179
    • 分槽位

      • 脚本自动分槽位

        ./create-cluster create

        ➜  create-cluster ./create-cluster create
        >>> Performing hash slots allocation on 6 nodes...
        Master[0] -> Slots 0 - 5460
        Master[1] -> Slots 5461 - 10922
        Master[2] -> Slots 10923 - 16383
        Adding replica 127.0.0.1:30005 to 127.0.0.1:30001
        Adding replica 127.0.0.1:30006 to 127.0.0.1:30002
        Adding replica 127.0.0.1:30004 to 127.0.0.1:30003
        >>> Trying to optimize slaves allocation for anti-affinity
        [WARNING] Some slaves are in the same host as their master
        M: 2d552911484d6f5954f240457d876a6b5fa1b274 127.0.0.1:30001
           slots:[0-5460] (5461 slots) master
        M: 616223b6cc540e0d25649a22b84d98b04cc0af31 127.0.0.1:30002
           slots:[5461-10922] (5462 slots) master
        M: 1c74e104764f70555293efe4b66020e679b06eb0 127.0.0.1:30003
           slots:[10923-16383] (5461 slots) master
        S: b7ce6fcf77fc6625c3d7d90b39e4c8e346595731 127.0.0.1:30004
           replicates 616223b6cc540e0d25649a22b84d98b04cc0af31
        S: 7fbcb7abf5308680549519d6c531dfee8e873685 127.0.0.1:30005
           replicates 1c74e104764f70555293efe4b66020e679b06eb0
        S: 13b1a1acc9ef359b72a4fd9026bb8127d3f20870 127.0.0.1:30006
           replicates 2d552911484d6f5954f240457d876a6b5fa1b274
        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 127.0.0.1:30001)
        M: 2d552911484d6f5954f240457d876a6b5fa1b274 127.0.0.1:30001
           slots:[0-5460] (5461 slots) master
           1 additional replica(s)
        S: 13b1a1acc9ef359b72a4fd9026bb8127d3f20870 127.0.0.1:30006
           slots: (0 slots) slave
           replicates 2d552911484d6f5954f240457d876a6b5fa1b274
        M: 616223b6cc540e0d25649a22b84d98b04cc0af31 127.0.0.1:30002
           slots:[5461-10922] (5462 slots) master
           1 additional replica(s)
        S: b7ce6fcf77fc6625c3d7d90b39e4c8e346595731 127.0.0.1:30004
           slots: (0 slots) slave
           replicates 616223b6cc540e0d25649a22b84d98b04cc0af31
        M: 1c74e104764f70555293efe4b66020e679b06eb0 127.0.0.1:30003
           slots:[10923-16383] (5461 slots) master
           1 additional replica(s)
        S: 7fbcb7abf5308680549519d6c531dfee8e873685 127.0.0.1:30005
           slots: (0 slots) slave
           replicates 1c74e104764f70555293efe4b66020e679b06eb0
        [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-cli --cluster create 127.0.0.1:30001 127.0.0.1:30002 127.0.0.1:30003 127.0.0.1:30004 127.0.0.1:30005 127.0.0.1:30006 --cluster-replicas

    • 客户端连接

      redis-cli -c -p 30001

    • 关闭节点

      ./create-cluster stop

    • 清理

      ./create-cluster clean

    • 其他命令

      redis-cli --cluster help

  • 测试

    • 直接连接端口redis-cli -p 30001

      • 操作的key不在当前节点的槽位中时会报错

        (error) MOVED 449 127.0.0.1:30001

      • 可以正常开启事务,如果事务中操作的key都在同一个槽位可以成功,否则失败

        (error) CROSSSLOT Keys in request don't hash to the same slot

      • 在匹配槽位的实例上操作支持hasttag事务,但需要在指定的槽位端口上

    • 集群方式连接redis-cli -c -p 30001

      • 开启事务操作不同的key

        操作不同端口槽位的数据时会重定向到对应的端口,再执行exec时会提示没有开启事务

        (error) ERR EXEC without MULTI

      • 支持

# 第三方代理

# predixy

# 概述
# 环境搭建

# twemproxy

# 概述
# 环境搭建
  • 通过源码构建twemproxy

    cd /opt
    git clone git@github.com:twitter/twemproxy.git
    cd twemproxy
    autoreconf -fvi
    ./configure --enable-debug=full
    make
    src/nutcracker -h
    
    1
    2
    3
    4
    5
    6
    7
  • 将twemproxy配置成服务

    cd /opt/twemproxy
    # 将官方准备的脚本拷贝到/etc/init.d中
    cp scripts/nutcracker.init /etc/init.d/twemproxy
    cd /etc/init.d
    # 增加可执行权限
    chmod +x twemproxy
    cd /opt/twemproxy
    mkdir /etc/nutcracker
    cp conf/* /etc/nutcracker
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
  • 修改配置conf/nutcracker.yml

    alpha:
      listen: 127.0.0.1:22121
      hash: fnv1a_64
      distribution: ketama
      auto_eject_hosts: true
      redis: true
      server_retry_timeout: 2000
      server_failure_limit: 1
      servers:
       - 127.0.0.1:6379:1
       - 127.0.0.1:6380:1
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  • 启动需要代理的Redis实例

    参考Redis安装与配置启动需要代理的Redis实例,如:redis_6379,redis_6380

  • 启动twemproxy

    service twemproxy start 
    # 也可以不用将twemproxy配置成服务,直接通过可执行程序制定配置文件启动
    # ./src/nutcracker -c conf/nutcracker.yml
    
    1
    2
    3

# 集群使用

# redis-cli

$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7002> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"
1
2
3
4
5
6
7
8
9
10
11
12
13

# redis-rb-cluster

  • github:https://github.com/antirez/redis-rb-cluster (opens new window)

  • 运行example

    git clone https://github.com/antirez/redis-rb-cluster
    cd redis-rb-cluster
    ruby example.rb
    
    1
    2
    3

    执行脚本报错

    <internal:/usr/local/Cellar/ruby/3.0.2/lib/ruby/3.0.0/rubygems/core_ext/kernel_require.rb>:85:inrequire': cannot load such file -- redis (LoadError)`

    原因:ruby环境未安装Redis

    解决方案:gem install redis

# 集群重新分片

  • redis-trib.rb

  • redis-cli

    redis-cli --cluster reshard <host>:<port> --cluster-from <node-id> --cluster-to <node-id> --cluster-slots <number of slots> --cluster-yes
    
    1
  • redis-

# 测试故障转移

# 手动故障转移

# 添加一个新节点

# 添加一个从节点

# 移除一个节点

# 从节点迁移