拉勾教育学习-笔记分享のRedis"苏醒" III

284 阅读32分钟

【文章内容输出来源:拉勾教育Java高薪训练营】
--- 所有脑图均本人制作,未经允许请勿滥用 ---
Redis 是分布式学习中必须要掌握的重点技术,可以多花时间啃一啃

合抱之木,生于毫末;九层之台,起于垒土

Redis官网

六、高可用方案

part 1 - 主从复制

「实际操作实例」

  1. 在 /usr/local/redis/ 中建立 mastersalve文件夹:
mkdir -p /usr/local/redis/master/
mkdir -p /usr/local/redis/slave/
  1. 将 redis压缩包解压到上述两个文件夹中:
tar –zxf redis-3.2.8.tar.gz
cd redis-3.2.8
# 编译
make 
# 期望全部测试通过
make test    

可能出现问题:缺少gcc或者tcl组件,使用命令 yum install gcc 或者 yum install tcl

  1. 双机配置

master の redis.conf 配置

bind 127.0.0.1  ----> bind 本机IP(绑定地址)
daemonize no   ----> daemonize yes(不影响当前会话,启动过程隐藏,守护进程)
protected-mode yes ---> protected-mode no(关闭保护模式,其他服务器可访问)

slave の redis.conf 配置

bind 127.0.0.1  ----> bind 本机IP(绑定地址)
daemonize no   ----> daemonize yes(不影响当前会话,启动过程隐藏,守护进程)
protected-mode yes ---> protected-mode no(关闭保护模式,其他服务器可访问)
port 6379  ---> port 6380(修改端口)
# -----------------------------
	# redis5-
slaveof master_redis所在机器IP 6379
	# redis5+
replicaof master_redis所在机器IP 6379
# -----------------------------
pidfile /var/run/redis_ 6379.pid ----> pidfile /var/run/redis_ 6380.pid

linux在vim编辑下,输入/后键入需要查找的字段可以快速定位字段,回车后,n -> 下一个匹配,N -> 上一个匹配

  1. 双机启动并连接客户端
cd /usr/local/redis/master/redis-3.2.8/src/
./redis-server  /usr/local/redis/master/redis-3.2.8/redis.conf(加载配置文件)
./redis-cli -h IP -p 6379 (客户端连接master_redis)
cd /usr/local/redis/slave/redis-3.2.8/src/
./redis-server  /usr/local/redis/slave/redis-3.2.8/redis.conf`(加载配置文件)
./redis-cli -h IP -p 6380` (客户端连接slave_redis)
  1. 使用 info replication 命令查看连接信息

主机下:

从机下:

  1. 测试主从复制,读写分离

测试1:主写后从可读

测试2: 从不可写

「基础配置理论」

Redis支持主从复制功能,可以通过执行slaveof(Redis5以后改成replicaof) 或者 在配置文件中设置 slaveof(Redis5以后改成replicaof)来开启复制功能;

因为Redis的持久化过程无法保证数据不丢失,因此单机Redis无法保证高可用性,于是乎,我们采用多机和集群的方式保证它的高可用性

【预先配置】

  • 主Redis配置
    • 无需特殊配置
  • 从Redis配置
    • 修改从服务器上的 redis.conf 文件:
    # slaveof <masterip> <masterport>
    # 表示当前【从服务器】对应的【主服务器】的IP是 192.168.10.135,端口是 6379
    replicaof 127.0.0.1 6379
    
  • 主从复制作用
    1. 读写分离:提升了整体性能和吞吐量(后期需要解决 数据的一致性问题)
    2. 数据容灾:主机宕机,从机备份(但从机只可读不可写,后期需要使用哨兵实现主从切换)

「实现流程理论」

  • 【保存节点信息】
    • 当客户端向从服务器发送 slaveof(replicaof) 主机地址(127.0.0.1) 端口(6379)时,从服务器将主机ip(127.0.0.1)和端口(6379)保存到 redisServermasterhost 以及 maseterport
    • 从服务器返回 OK 给客户端(注意:实际的复制工作在OK返回之后才执行)
  • 【建立socket连接】
    • slaver与master建立socket连接
    • slaver关联文件事件处理器
    • 该处理器接收RDB文件(全量复制)、接收Master传播来的写命令(增量复制)
    • 主服务器accept从服务器Socket连接后,创建相应的客户端状态。相当于从服务器是主服务器的Client端。
  • 【发送ping命令】
    • Slaver向Master发送ping命令
      1. 检测socket的读写状态
      2. 检测Master能否正常处理
    • Master的响应
      1. 发送“pong” , 说明正常
      2. 返回错误,说明Master不正常
      3. timeout,说明网络超时
  • 【权限验证配置】
    • 若主机 未设置密码(requirepass="")
      • 从机也不要设置密码(masterauth="")
    • 若主机 设置密码(requirepass!="")
      • 从机要设置密码(masterauth="requirepass对应的值")
      • 或者,从机通过 auth命令向主机发送密码
  • 【权限验证通过后】
    1. 从服务器执行命令 REPLCONF listening-port,向主服务器发送从服务器的监听端口
    2. 同步数据;(Redis2.8开始 分为 全量同步/增量同步)
    3. 同步完成后,主 <--> 从 进入命令传播阶段:主服务器只要将自己执行的写命令发送给从服务器,从服务器立即执行这些写操作;

「同步数据集理论」

  • 旧版本(2.8之前) 同步功能分为同步(sync)和命令传播(command propagate)
    1. 同步操作
      • 通过从服务器发送到 SYNC命令 给主服务器
      • 主服务器生成 RDB文件 并发送给从服务器,同时发送保存所有写命令给从服务器
      • 从服务器清空之前数据并执行解释 RDB文件
      • 保持数据一致(还需要命令传播过程才能保持一致)
    2. 命令传播操作
      • 同步操作完成后,主服务器执行写命令,该命令发送给从服务器并执行,使主从保存一致

    缺点:没有全量同步和增量同步的概念,从服务器在同步时,会清空所有数据。

  • 新版本(2.8之后) 使用PSYNC命令,具备 完整重同步 和 部分重同步模式
    • 除此之外的情况都是增量同步。
    • Redis 的全量同步过程主要分三个阶段:
      1. 同步快照阶段: Master 创建并发送快照RDB给 Slave , Slave 载入并解析快照。 Master 同时将此阶段所产生的新的写命令存储到缓冲区。
      2. 同步写缓冲阶段: Master 向 Slave 同步存储在缓冲区的写操作命令。
      3. 同步增量阶段: Master 向 Slave 同步写操作命令。
    • 增量同步
      • Redis增量同步主要指 Slave完成初始化后开始正常工作时, Master 发生的写操作同步到 Slave 的过程
      • 通常情况下, Master 每执行一个写命令就会向 Slave 发送相同的写命令,然后 Slave 接收并执行。
  • 心跳检测
    • 在命令传播阶段,从服务器默认会以每秒一次的频率向主服务器发送命令
    replconf ack <replication_offset>
    # ack : 应答 
    # replication_offset :从服务器当前的复制偏移量
    
    • 主要功能如下:
      1. 检测主从的连接状态
        通过向主服务器发送INFO replication命令,可以列出从服务器列表,可以看出从最后一次向主发送命令距离现在过了多少秒。lag的值应该在0或1之间跳动,如果超过1则说明主从之间的连接有故障。
      2. 辅助实现min-slaves
        Redis可以通过配置防止主服务器在不安全的情况下执行写命令

      min-slaves-to-write 3 (min-replicas-to-write 3 )
      min-slaves-max-lag 10 (min-replicas-max-lag 10)
      上面的配置表示:从服务器的数量少于3个,或者三个从服务器的延迟(lag)值都大于或等于10秒时,主服务器将拒绝执行写命令。这里的延迟值就是上面INFOreplication命令的lag值。

      1. 检测命令丢失
        如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么当从服务器向主服务器发送REPLCONF ACK命令时,主服务器将发觉从服务器当前的复制偏移量少于自己的复制偏移量,然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里面找到从服务器缺少的数据,并将这些数据重新发送给从服务器

part 2 - 哨兵模式

「概念」

哨兵(sentinel)是Redis的高可用性(High Availability)的解决方案:
由一个或多个 sentinel实例 组成 sentinel集群 可以监视一个或多个主服务器和多个从服务器。
当主服务器进入下线状态时,sentinel可以将该主服务器下的某一从服务器升级为主服务器继续提供服务,从而保证redis的高可用性。

【部署方案】

「实现哨兵模式的主从切换实例」

part 1 我们实现了 6379主机 和 6380从机 的主从复制,现在笔者已自行再加入了 6381从机;
接下来实现上述部署方案中的哨兵集群:

  1. 配置主从sentinel.conf(三个实例哨兵配置区别在于 端口号、选举判定阈值、实例失效判定时间)
# 守护进程,隐藏启动,不影响当前session
daemonize yes  

# 关闭保护模式,类似于防火墙的功能
protected-mode no   

# sentinel 端口默认26379
port <26379主哨兵...26380从哨兵1...26381从哨兵2>

# 哨兵监控的主redis 的IP 和端口,会自动监控到slave
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# 告诉sentinel去监听地址为ip:port的一个master,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效         
sentinel monitor master1 IP 6379 1

# master被当前sentinel实例认定为“失效”的间隔时间.
# sentinel down-after-milliseconds <mastername> <millseconds>  
# 如果当前sentinel与master直接的通讯中(不断发送程序包,并接受响应),在指定时间内没有响应或者响应错误代码,那么当前sentinel就认为master失效  
# 不配置则默认30s
sentinel down-after-milliseconds master1 3000

# 当failover(故障转移)开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failover失败
# 不配置则默认3min
sentinel failover-timeout master1 15000

# 当新master产生时,可以同时进行slaveof到新master并进行“SYNC”(同步)的slave个数。(建议使用默认值1)
# 在salve执行salveof与同步时,将会终止客户端请求。此值较大,意味着“集群”终止客户端请求的时间总和和较大.
# 此值较小,意味着“集群”在故障转移期间,多个salve向客户端提供服务时仍然使用旧数据.
sentinel parallel-syncs master1 1
  1. 确保redis均已关闭:
ps -ef | grep redis
  1. 依次启动redis和sentinel
./master/redis-6.0.8/src/redis-server master/redis-6.0.8/redis-master.conf
./slave/redis-slave-01/src/redis-server slave/redis-slave-02/redis-slave-6380.conf
./slave/redis-slave-02/src/redis-server slave/redis-slave-02/redis-slave-6381.conf

./master/redis-6.0.8/src/redis-sentinel master/redis-6.0.8/sentinel-master-6379.conf
./slave/redis-slave-01/src/redis-sentinel slave/redis-slave-01/sentinel-slave-6380.conf
./slave/redis-slave-02/src/redis-sentinel slave/redis-slave-02/sentinel-slave-6381.conf

查看线程状况:(redis:一主二从,sentinel:一主二从)

连接三个sentinel客户端后使用 info sentinel 查看监控详情(均是一样的监控详情如下):


此时主机为6379,有两个从机,它们由三个哨兵监控

  1. 模拟主机6379宕机

同时我们打开所有的 sentinel.conf 会发现:

若后期6379恢复上线了,打开6379的 redis.conf 会发现:

「哨兵模式的执行流程理论」

Sentinel是一个特殊的Redis服务器,不会进行持久化

Sentinel实例启动后每个Sentinel会创建2个连向主服务器的网络连接:

  1. 命令连接:用于向主服务器发送命令,并接收响应
  2. 订阅连接:用于订阅主服务器的 —sentinel—:hello 频道

  • 【获取主服务器信息】
    Sentinel默认每10s一次,向被监控的主服务器发送info命令,获取主服务器和其下属从服务器的信息
    可连接主机redis客户端后使用 info 查看详情
  • 【获取从服务器信息】
    当Sentinel发现主服务器有新的从服务器出现时,Sentinel还会向从服务器建立命令连接和订阅连接。在命令连接建立之后,Sentinel还是默认10s一次,向从服务器发送info命令,并记录从服务器的信息
  • 【向主服务器和从服务器发送消息(以订阅的方式)】
    默认情况下,Sentinel每2s一次,向所有被监视的主服务器和从服务器所订阅的 —sentinel—:hello 频道上发送消息,消息中会携带 Sentinel 自身的信息和主服务器的信息
  • 【接收来自主服务器和从服务器的频道信息】
    当Sentinel与主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令:
    subscribe —sentinel—:hello
    

    Sentinel彼此之间只创建命令连接,而不创建订阅连接,因为Sentinel通过订阅主服务器或从服务器,就可以感知到新的Sentinel的加入,而一旦新Sentinel加入后,相互感知的Sentinel通过命令连接来通信就可以了

  • 【检测主观下线状态】
    Sentinel 每秒一次向所有与它建立了命令连接的实例 (主服务器、从服务器和其他Sentinel) 发送 PING 命 令,如果:
    1. 实例在 down-after-milliseconds 毫秒内返回无效回复 (除了+PONG、-LOADING、-MASTERDOWN外)
    2. 实例在 down-after-milliseconds 毫秒内无回复(超时) 那么Sentinel就会认为该实例主观下线(SDown)
  • 【检测客观下线状态】
    当一个Sentinel将一个主服务器判断为主观下线后, Sentinel会向同时监控这个主服务器的所有其他Sentinel发送查询命令:
    • 主机sentinel询问:
      SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
      
    • 其余从机sentinel回复:
      <down_state>< leader_runid >< leader_epoch >
      

    判断它们是否也认为主服务器下线。如果达到Sentinel配置中的quorum数量的Sentinel实例都判断主服务器为主观下线,则该主服务器就会被判定为客观下线(ODown)。

  • 【选举Leader Sentinel】
    当一个主服务器被判定为客观下线后,监视这个主服务器的所有Sentinel会通过选举算法(raft),选出一个Leader Sentinel去执行failover(故障转移)操作

「哨兵模式的leader选举」

  • 【Raft算法】
    Raft协议是用来解决分布式系统一致性问题的协议。
    • Raft协议描述的节点共有三种状态:Leader, Follower, Candidate
    • Raft协议将时间切分为一个个的 Term(任期),可以认为是一种“逻辑时间”。
    • 采用 心跳机制 触发Leader选举
    1. 初始化时的Leader选举
      1. 系统启动后,全部节点初始化为Follower,term=0
      2. 节点如果收到了 RequestVote 或者 AppendEntries ,就会保持自己的Follower身份
      3. 节点如果一段时间内没收到 AppendEntries 消息,在该节点的超时时间内还没发现Leader,Follower就会转换成Candidate,自己开始竞选Leader
      4. 一旦转化为Candidate,该节点立即开始下面几件事情:
        • 增加自己的term
        • 启动一个新的定时器
        • 给自己投一票
        • 向所有其他节点发送RequestVote,并等待其他节点的回复
      5. 如果在计时器超时前,节点收到多数节点的同意投票,就转换成Leader。同时向所有其他节点发送 AppendEntries ,告知自己成为了Leader。

        ==> 每个节点在一个term内只能投一票,采取先到先得的策略,Candidate前面说到已经投给了自己,Follower会投给第一个收到RequestVote的节点
        ==> Raft协议的定时器采取随机超时时间,这是选举Leader的关键
        ==> 在同一个term内,先转为Candidate的节点会先发起投票,从而获得多数票

    2. 主机客观下线后的Leader选举
      1. 某Sentinel认定master客观下线后,该Sentinel会先看看自己有没有投过票,如果自己已经投过票给其他Sentinel了,在一定时间内自己就不会成为Leader
      2. 如果该Sentinel还没投过票,那么它就成为Candidate
      3. Sentinel需要完成几件事情:
        • 更新故障转移状态为start
        • 当前epoch加1,相当于进入一个新term(在Sentinel中,epoch就是Raft协议中的term)
        • 向其他节点发送 is-master-down-by-addr 命令请求投票。命令会带上自己的epoch
        • 给自己投一票(leader、leader_epoch)
      4. 当其它哨兵收到此命令时,可以同意或者拒绝它成为领导者;(通过判断epoch)
      5. Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配置的quorum,这时它就成为了Leader
      6. 其他Sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下线的标识
  • 【故障转移】
    当选举出Leader Sentinel后,Leader Sentinel会对下线的主服务器执行故障转移操作,主要有三个步骤:
    1. 会将失效 Master 的其中一个 Slave 升级为新的 Master , 并让失效 Master 的其他 Slave 改为复制新的 Master
    2. 当客户端试图连接失效的 Master 时,集群也会向客户端返回新 Master 的地址,使得集群可以使用现在的 Master 替换失效 Master
    3. Master 和 Slave 服务器切换后, Master 的 redis.conf 、 Slave 的 redis.conf 和 sentinel.conf 的配置文件的内容都会发生相应的改变——Master 主服务器的 redis.conf 配置文件中会多一行 replicaof 的配置, sentinel.conf 的监控目标会随之调换
  • 【主服务器的选择】
    哨兵leader根据以下规则从客观下线的主服务器的从服务器中选择出新的主服务器
    1. 过滤掉主观下线的节点
    2. 选择slave-priority最高的节点,如果由则返回没有就继续选择
    3. 选择出复制偏移量最大的系节点,因为复制偏移量越大则数据复制的越完整,如果由就返回了,没有就继续
    4. 选择 run_id 最小的节点,因为 run_id 越小说明重启次数越少

part 3 - 集群与分区

分区是将数据分布在多个Redis实例(Redis主机)上,以至于每个实例只包含一部分数据

「分区意义」

  1. 性能提升:
    • 单机Redis的网络I/O能力和计算资源是有限的,将请求分散到多台机器,充分利用多台机器的计算能力可网络带宽,有助于提高Redis总体的服务能力
  2. 存储能力的横向扩展:
    • 即使Redis的服务能力能够满足应用需求,但是随着存储数据的增加,单台机器受限于机器本身的存储容量,将数据分散到多台机器上存储使得Redis服务可以横向扩展

「分区方式」

A 根据区键(id)分区

根据id数字的范围比如1--10000、100001--20000.....90001-100000,每个范围分到不同的Redis实例中

  • 优势:实现简单,方便迁移和扩展
  • 缺点:热点数据分布不均,性能损失;非数字型key,比如uuid无法使用 ---> {可使用雪花算法替代}
B 利用hash算法分区

Redis实例=hash(key)%N
key:要进行分区的键,比如user_id
N:Redis实例个数(Redis主机)

  • 优势:支持任何类型的key;热点分布较均匀,性能较好
  • 缺点:迁移复杂,需要重新计算,扩展较差 ---> {可利用一致性hash环解决}

「client端分区」

对于一个给定的key,客户端直接选择正确的节点来进行读写。许多Redis客户端都实现了客户端分区(JedisPool),也可以自行编程实现

  • 【部署方案】
  • 【客户端选择算法】
    • 普通hash hash(key)%N(采用hash算法,比如CRC32、CRC16等)
      举个栗子:
      user_id : u001
      hash(u001) : 1844213068
      Redis实例=1844213068%3
      // 余数为2,所以选择 Redis3
      

      优势:实现简单,热点数据分布均匀
      缺点:节点数和key固定,扩展的话需要重新计算

    • 一致性hash
      普通hash是对主机数量取模,而一致性hash是对2^32(4 294 967 296)取模。我们把2^32想象成一个圆,就像钟表一样,钟表的圆可以理解成由60个点组成的圆,而此处我们把这个圆想象成由2^32个点组成的圆,示意图如下:

      圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、4、5、6……直到2^32-1
      也就是说0点左侧的第一个点代表2^32-1 。我们把这个由2的32次方个点组成的圆环称为 hash环

      假设我们有3台缓存服务器,服务器A、服务器B、服务器C,那么,在生产环境中,这三台服务器肯定有自己的IP地址,我们使用它们各自的IP地址进行哈希计算,使用哈希后的结果对2^32取模,可以使用公式:hash(服务器的IP地址) % 2^32
      通过上述公式算出的结果一定是一个0到2^32-1之间的一个整数,我们就用算出的这个整数,代表服务器A、服务器B、服务器C,既然这个整数肯定处于0到2^32-1之间,那么,上图中的hash环上必定有一个点与这个整数对应,也就是服务器A、服务器B、服务C就可以映射到这个环上:

      假设,我们需要使用Redis缓存数据,则有公式: hash(key) % 2^32

      由于被缓存对象与服务器hash后的值是固定的,所以,在服务器不变的情况下,数据必定会被缓存到固定的服务器上,那么,当下次想要访问这个数据时,只要再次使用相同的算法进行计算,即可算出这个数据被缓存在哪个服务器上,直接去对应的服务器查找对应的数据即可

      hash环偏移
      我们理想化的将3台服务器均匀的映射到了hash环上。也就是说数据的范围是2^32/N。但实际情况往往不是这样的。有可能某个服务器的数据会很多,某个服务器的数据会很少,造成服务器性能不平均。这种现象称为hash环偏移:

      理论上我们可以通过增加服务器的方式减少偏移,但成本太高,于是我们采用 虚拟节点 的方式解决:
      缺点

      1. 复杂度高:
        客户端需要自己处理数据路由、高可用、故障转移等问题
        使用分区,数据的处理会变得复杂,不得不对付多个redis数据库和AOF文件,不得在多个实例和主机之间持久化你的数据
      2. 不易扩展:
        一旦节点的增或者删操作,都会导致key无法在redis中命中,必须重新根据节点计算,并手动迁移全部或部分数据

「proxy端分区」

在客户端和服务器端引入一个代理或代理集群,客户端将命令发送到代理上,由代理根据算法,将命令路由到相应的服务器上。
常见的代理有 Codis(豌豆荚)和 TwemProxy(Twitter)

Codis由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。

Codis 专题博文撰写中......

「官方cluster分区 ☆」

Redis3.0之后,Redis官方提供了完整的集群解决方案。
方案采用去中心化的方式,包括:sharding(分区)、replication(复制)、failover(故障转移)。
称为RedisCluster

Redis5.0前, 采用redis-trib进行集群的创建和管理,需要ruby支持
Redis5.0后, 可以直接使用Redis-cli进行集群的创建和管理

  • 【部署架构】
  • 【去中心化】
    RedisCluster由多个Redis节点组构成,是一个P2P无中心节点的集群架构,依靠Gossip协议传播的集群
  • 【Gossip协议】
    Gossip协议是一个通信协议,一种传播消息的方式(起源于病毒的传播)。基本思想如下:
    • 一个节点周期性(每秒)随机选择一些节点,并把信息传递给这些节点。这些收到信息的节点接下来会做同样的事情,即把这些信息传递给其他一些随机选择的节点;信息会周期性的传递给 N 个目标节点。这个 N 被称为 fanout(扇出).
    • Gossip协议类型
      1. meet:sender向receiver发出,请求receiver加入sender的集群
      2. ping:节点检测其他节点是否在线
      3. pong:receiver收到meet或ping后的回复信息;在failover后,新的Master也会广播pong
      4. fail:节点A判断节点B下线后,A节点广播B的fail信息,其他收到节点会将B节点标记为下线
      5. publish:节点A收到publish命令,节点A执行该命令,并向集群广播publish命令,收到publish命令的节点都会执行相同的publish命令

      通过gossip协议,cluster可以提供集群间状态同步更新、选举自助failover等重要的集群功能

  • 【solt】
    redis-cluster把所有的物理节点映射到[0-16383]个slot上,基本上采用平均分配和连续分配的方式
    如上述部署架构中的图,有5个主节点,这样在RedisCluster创建时,slot槽可如下分配:
    1. Redis1: 0-3270
    2. Redis2: 3271-6542
    3. Redis3: 6543-9814
    4. Redis4: 9815-13087
    5. Redis5: 13088-16383

    当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

  • 【集群搭建实例】
    RedisCluster最少需要三台主服务器,三台从服务器
    1. 6*redis实例安装,其中额外的, redis.conf 中的 cluster-enable yes 开启(原本被注释)
    2. 书写批处理文件start.sh一次性启动6个redis实例:
      echo "===》7001开始启动 ===》"
      cd redis-7001/src/
      ./redis-server ../redis.conf
      echo "《=== 7001启动结束 《==="
      echo "===》7002开始启动 ===》"
      cd ../../
      cd redis-7002/src/
      ./redis-server ../redis.conf
      echo "《=== 7002启动结束 《==="
      echo "===》7003开始启动 ===》"
      cd ../../
      cd redis-7003/src/
      ./redis-server ../redis.conf
      echo "《=== 7003启动结束 《==="
      echo "===》7004开始启动 ===》"
      cd ../../
      cd redis-7004/src/
      ./redis-server ../redis.conf
      echo "《=== 7004启动结束 《==="
      echo "===》7005开始启动 ===》"
      cd ../../
      cd redis-7005/src/
      ./redis-server ../redis.conf
      echo "《=== 7005启动结束 《==="
      echo "===》7006开始启动 ===》"
      cd ../../
      cd redis-7006/src/
      ./redis-server ../redis.conf
      echo "《=== 7006启动结束 《==="
      
      并使用 chmod u+x start.sh 赋写和执行的权限
    3. 启动并检查线程:

    1. 创建Redis集群
      进入任一端口(此处示例进入7001)的redis执行以下命令:
      ./redis-7001/src/redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7003 127.0.0.1:7005 127.0.0.1:7002 127.0.0.1:7004 127.0.0.1:7006 --cluster-replicas 1
      # 命令的语义为:创建集群,按 '1' 为对应关系进行配置 ==> 一共有6个host地址,1对1表明 前三个为主、后三个为从
      

      接下来我们再次观察之前生成的 nodes.conf
    2. 客户端连接集群
      # -c 表示以集群方式进行连接
      ./redis-cli -h 127.0.0.1 -p 7001 -c
      

      可以使用 cluster nodes 命令即时查看节点和slot的匹配关系

  • 【分片】
    • 客户端路由
      Redis Cluster的客户端相比单机Redis 需要具备路由语义的识别能力,且具备一定的路由缓存能力
    • moved重定向
      1. 每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系
      2. 客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16384取余,计算自己的槽和对应节点
      3. 如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端
      4. 如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常
      5. 客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息
      6. 客户端向目标节点发送命令,获取命令执行结果

      上个实例中我们再7001 set 一个值被重定向到了7005就是 moved重定向

    • ask重定向
      在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移
      当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息
      如果此时正在进行集群扩展或者缩空操作,当客户端向正确的节点发送命令时,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制
      1. 客户端向目标节点发送命令,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端
      2. 客户端向新的节点发送Asking命令给新的节点,然后再次向新节点发送命令
      3. 新节点执行命令,把命令执行结果返回给客户端
    • moved 和 ask 区别
      • moved:槽已确认转移
      • ask:槽还在转移过程中
    • Smart智能客户端——JedisCluster
      JedisCluster是Jedis根据RedisCluster的特性提供的集群智能客户端
      • JedisCluster为每个节点创建连接池,并跟节点建立映射关系缓存(Cluster slots)
      • JedisCluster将每个主节点负责的槽位一一与主节点连接池建立映射缓存
      • JedisCluster启动时,已经知道key,slot和node之间的关系,可以找到目标节点
      • JedisCluster对目标节点发送命令,目标节点直接响应给JedisCluster
      • 如果JedisCluster与目标节点连接出错,则JedisCluster会知道连接的节点是一个错误的节点,此时节点返回moved异常给JedisCluster
      • JedisCluster会重新初始化slot与node节点的缓存关系,然后向新的目标节点发送命令,目标命令执行命令并向JedisCluster响应, 如果命令发送次数超过5次,则抛出异常"Too many cluster redirection!"
      • JedisPoolConfig config = new JedisPoolConfig();
        Set<HostAndPort> jedisClusterNode = new HashSet<HostAndPort>();
        jedisClusterNode.add(new HostAndPort("XXX.XXX.XXX.XXX", 7001));
        jedisClusterNode.add(new HostAndPort("XXX.XXX.XXX.XXX", 7002));
        jedisClusterNode.add(new HostAndPort("XXX.XXX.XXX.XXX", 7003));
        jedisClusterNode.add(new HostAndPort("XXX.XXX.XXX.XXX", 7004));
        jedisClusterNode.add(new HostAndPort("XXX.XXX.XXX.XXX", 7005));
        jedisClusterNode.add(new HostAndPort("XXX.XXX.XXX.XXX", 7006));
        JedisCluster jcd = new JedisCluster(jedisClusterNode, config);
        jcd.set("user:1:name","Archie");
        String value = jcd.get("user:1:name");
        
  • 【迁移】
    在RedisCluster中每个slot 对应的节点在初始化后就是确定的。
    在某些情况下,节点和分片需要变更:
    • 新的节点作为 master 加入
    • 某个节点分组需要下线
    • 负载不均衡需要调整 slot 分布 此时需要进行分片的迁移,迁移的触发和过程控制由外部系统完成。包含下面 2 种:
    • 节点迁移状态设置:迁移前标记源/目标节点
    • key迁移的原子化命令:迁移的具体步骤

    1. 向节点B发送状态变更命令,将B的对应slot 状态置为importing
    2. 向节点A发送状态变更命令,将A对应的slot 状态置为migrating
    3. 向A 发送migrate 命令,告知A 将要迁移的slot对应的key 迁移到B
    4. 当所有key 迁移完成后,cluster setslot 重新设置槽位
  • 【扩容】
    • 创建两个新的无数据的Redis实例——7007&7008
    • 和之前7001一样修改基础配置
    • 启动7007的服务 ./redis-7007/src/redis-server /redis-7007/redis.conf
    • 添加7007结点作为新节点,并启动
      ./redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
      
    • 登录任一客户端后,查看当前集群节点分配信息 cluster nodes
    • hash槽重新分配(数据迁移)
      1. 连接上集群
      ./src/redis-cli --cluster reshard 127.0.0.1:7007
      
      1. 输入要分配的槽数量
      How many slots do you want to move (from 1 to 16384)? "输入3000"
      
      1. 输入接收槽的结点id
      What is the receiving node ID? ==> "输入之前查询到的7007的id"
      
      1. 输入源结点id (输入all)
      Please enter all the source node IDs. 
      	Type 'all' to use all the nodes as source nodes for the hash slots. 
      	Type 'done' once you entered all the source nodes IDs.
      
      1. 输入yes开始移动槽到目标结点id
      Do you want to poceed with the proposed reshard plan (yes/no)? "输入yes"
      
      1. 查看结果
      cluster nodes
      
    • 添加7008为从节点给7007:
      1. 启动7008服务
      2. 使用redis-cli加入从节点给7008:
        ./src/redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7007 --cluster-slave --cluster-master-id a1e298448ae98d96e98dc59103c7d68a3930788b
        
      3. 登录任一reids客户端,查看集群中的结点
  • 【缩容】
    • 命令:./redis-cli --cluster del-node 192.168.127.128:7008 48d7a4697f89065c1b70297f530d3ba3be27a733

    删除已经占有hash槽的结点会失败,报错如下:
    [ERR] Node 192.168.127.128:7008 is not empty! Reshard data away and try again.
    需要将该结点占用的hash槽分配出去

  • 【容灾failover】
    • 故障检测
      集群中的每个节点都会定期地(每秒)向集群中的其他节点发送PING消息,如果在一定时间内(cluster-node-timeout),发送ping的节点A没有收到某节点B的pong回应,则A将B标识为pfail。
      A在后续发送ping时,会带上B的pfail信息, 通知给其他节点,如果B被标记为pfail的个数大于集群主节点个数的一半(N/2 + 1)时,B会被标记为fail,A向整个集群广播,该节点已经下线。
      其他节点收到广播,标记B为fail。
      • 【从节点选举】:每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举。slave 通过向其他master发送FAILVOER_AUTH_REQUEST 消息发起竞选,master 收到后回复FAILOVER_AUTH_ACK 消息告知是否同意。
        slave 发送FAILOVER_AUTH_REQUEST 前会将currentEpoch 自增,并将最新的Epoch 带入到FAILOVER_AUTH_REQUEST 消息中,如果自己未投过票,则回复同意,否则回复拒绝
        所有的Master开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(N/2 +1) 都投票给了某个从节点,那么选举通过,那个从节点可以切换成master

        RedisCluster失效的判定:

        1. 集群中半数以上的主节点都宕机(无法投票)
        2. 宕机的主节点的从节点也宕机了(slot槽分配不连续)
    • 变更通知
      当slave 收到过半的master 同意时,会成为新的master。此时会以最新的Epoch 通过PONG 消息广播自己成为master,让Cluster 的其他节点尽快的更新拓扑结构(node.conf)
    • 主从切换
      1. 自动切换:即上面讲的从节点选举
      2. 主动切换:人工故障切换是预期的操作,而非发生了真正的故障,目的是以一种安全的方式(数据无丢失)将当前master节点和其中一个slave节点(执行cluster-failover的节点)交换角色
        1. 向从节点发送cluster failover 命令(slaveof no one)
        2. 从节点告知其主节点要进行手动切换(CLUSTERMSG_TYPE_MFSTART)
        3. 主节点会阻塞所有客户端命令的执行(10s)
        4. 从节点从主节点的ping包中获得主节点的复制偏移量
        5. 从节点复制达到偏移量,发起选举、统计选票、赢得选举、升级为主节点并更新配置
        6. 切换完成后,原主节点向所有客户端发送moved指令重定向到新的主节点

        以上是针对主节点在线情况,如果主节点下线了,则采用 cluster failover forcecluster failover takeover 进行强制切换

    • 副本漂移
      我们知道在一主一从的情况下,如果主从同时挂了,那整个集群就挂了。为了避免这种情况我们可以做一主多从,但这样成本就增加了。
      • Redis提供了一种方法叫副本漂移,这种方法既能提高集群的可靠性又不用增加太多的从机
        Master1 宕机,则 Slaver11 提升为新的Master1; 集群检测到新的 Master1 是单点的(无从机),于是集群从拥有最多的从机的节点组(Master3)中,选择节点名称字母顺序最小的从机(Slaver31)漂移到单点的主从节点组(Master1)
      1. 将Slaver31的从机记录从Master3中删除
      2. 将Slaver31的的主机改为Master1
      3. 在Master1中添加Slaver31为从节点
      4. 将Slaver31的复制源改为Master1
      5. 通过ping包将信息同步到集群的其他节点