大厂面试官都服了,你竟然掌握了这四种Redis高可用架构?

2 阅读14分钟

前言

Hi 你好,我是东东拿铁,一个正在探索个人IP&副业的后端程序员。

如果你是一个后端开发同学,那你一定对Redis不陌生,但用了这么多年,你真正搞懂过Redis吗?

所以,文章开头,我想让你先问自己几个问题:

  1. Redis高可用架构,你都了解哪几种?
  2. 你们生产服务,用的是哪一种高可用架构?(不清楚的抓紧去弄清楚)
  3. 主从复制是什么?数据分片又是什么?
  4. 哨兵之间如何通信,选主又是什么意思?

如果你还有些许疑问,没关系,看完这篇文章,你可以快速了解Redis的四种高可用架构模式与优缺点。

image.png

主从架构

假设我们主节点ip为192.168.0.100,我们新上一个从节点,那就在从节点执行命令。

replicaof 192.168.0.100 6379

主从复制流程

第一阶段

从节点发送命令psync,有两个参数

runId:redis启动的随机id,一开始从库并不知道这个值,所以传‘?’

offset:设置-1,表示第一次复制。

主库收到命令后,会返回FULLRESYNC响应命令,并带上上面两个参数

第二阶段

主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。

在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis 的服务就被中断了。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录RDB 文件生成后收到的所有写操作。

具体细节,可以查看我另一篇文章。

第三阶段

主库会把第二阶段执行过程中新收到的写命令,再发送给从库。具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修

改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了。

主从复制的架构很简单,但是有一个很明显的弊端,如果主库挂了,整个服务职能提供读请求,写请求无法处理了,这是业务不能接受的,所以我们来介绍下一种模式,哨兵模式。

image.png

哨兵(Redis Sentinel)

哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。

流程

监控

我们先看监控。监控是指哨兵进程在运行时,周期性地给所有的主从库发送 PING 命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的 PING 命令,哨兵就会把它标记为“下线状态”;

同样,如果主库也没有在规定时间内响应哨兵的 PING 命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。

选主

一旦主节点被标记为“客观下线”,哨兵节点开始执行选举新主节点的流程。

  1. 哨兵节点会从所有可用的从节点中选举一个新的主节点。它会评估每个从节点的性能和复制偏移量等指标,并选择最适合的节点作为新的主节点。

  2. 如果有多个从节点符合条件,则哨兵节点可能会使用一些额外的逻辑来选择最合适的节点。例如,它可能会考虑节点的复制延迟、磁盘使用率等因素。

  3. 一旦选举出新的主节点,哨兵节点会更新集群的配置信息,并通知其他哨兵节点和客户端进行相应的调整。

通知

一旦新的主节点选举出来,哨兵节点会更新集群的配置信息,并通知客户端重新连接到新的主节点。客户端会重新连接到新的主节点,并继续进行数据操作。

image.png

主观下线和客观下线

为了增加判断的准确性,一般会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好,而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。

因此在判断主库是否下线时,不能由一个哨兵说了算,只有大多数的哨兵实例,都判断主库已经“主观下线”了,主库才会被标记为“客观下线”,这个叫法也是表明主库下线成为一个客观事实了。这个判断原则就是:少数服从多数。同时,这会进一步触发哨兵开始主从切换流程。

哨兵通信

Redis Sentinel(哨兵)可以使用发布/订阅(Pub/Sub)功能来实现节点之间的通信和事件通知。通过发布/订阅机制,哨兵节点可以向其他节点发送消息,以通知它们发生了某些事件,比如主节点故障、故障转移等。以下是如何在Redis Sentinel中使用发布/订阅功能:

  1. 订阅频道:

哨兵节点可以通过订阅一个或多个频道来接收消息。在Redis Sentinel中,通常会使用特定的频道来发布各种事件,比如+switch-master用于通知主节点切换,+sdown用于通知节点下线等。

SUBSCRIBE +switch-master

  1. 发布消息:

当发生某些事件时,比如主节点故障或故障转移,哨兵节点可以使用PUBLISH命令向订阅者发送消息。

PUBLISH +switch-master master-name new-ip new-port

这条命令会向订阅了+switch-master频道的所有节点发送一条消息,通知它们主节点切换的情况。

主库切换

任何一个实例只要自身判断主库“主观下线”后,就会给其他实例发送 is-master-down-by-addr 命令。接着,其他实例会根据自己和主库的连接情况,做出Y或N的响应,Y同意,N反对。

一个哨兵获得了仲裁所需的赞成票数后,就可以标记主库为“客观下线”。这个所需的赞成票数是通过哨兵配置文件中的 quorum 配置项设定的。

Leader选举,这个哨兵就可以再给其他哨兵发送命令,表明希望由自己来执行主从切换,并让所有其他哨兵进行投票。因为最终执行主从切换的哨兵

称为 Leader,投票过程就是确定 Leader。

选择为Leader的条件

  1. 拿到半数以上的赞成票;
  2. 拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

注:如果哨兵集群只有 2 个实例,此时,一个哨兵要想成为 Leader,必须获得2 票(需要超过半数)。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切换的。因此,通常我们至少会配置3个哨兵实例。

Redis Cluster

假如我们的用户量千万,需要缓存的数据越来越大,我们应该怎么处理呢?

纵向扩展:升级单个 Redis 实例的资源配置,包括增加内存容量、增加磁盘容量、使用更高配置的 CPU。

横向扩展:横向增加当前 Redis 实例的个数,把数据等分成几个部分。

虽然纵向扩展简单直接,直接扩大内存就好了,但是有一些缺点:

  1. 硬件限制,比如我们有1TB的内存数据,单机就很难支持了,线上机器实例很少会这样配置,有也不给你Redis

  2. 持久化,大数据量持久化时,fork导致主线程阻塞看,数据越大,阻塞时间越长

从 3.0 开始,官方提供了一个名为 Redis Cluster 的方案,用于实现切片集群。Redis Cluster 方案中就规定了数据和实例的对应规则。

image.png

如何通信

一个Redis集群由多个节点组成,各个节点之间是怎么通信的呢?通过Gossip协议!

常用的Gossip消息分为4种,分别是:ping、pong、meet、fail。

meet消息:通知新节点加入。消息发送者通知接收者加入到当前集群,meet消息通信正常完成后,接收节点会加入到集群中并进行周期性的ping、pong消息交换。

ping消息:集群内交换最频繁的消息,集群内每个节点每秒向多个其他节点发送ping消息,用于检测节点是否在线和交换彼此状态信息。

pong消息:当接收到ping、meet消息时,作为响应消息回复给发送方确认消息正常通信。pong消息内部封装了自身状态数据。节点也可以向集群内广播自身的pong消息来通知整个集群对自身状态进行更新。

fail消息:当节点判定集群内另一个节点下线时,会向集群内广播一个fail消息,其他节点接收到fail消息之后把对应节点更新为下线状态。

数据切片后,数据在多个实例之间如何分布?

Redis Cluster 方案采用哈希槽(Hash Slot,接下来我会直接称之为 Slot)来处理数据和实例之间的映射关系。在Redis Cluster方案中,一个切片集群共有16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中。

具体的映射过程分为两大步:首先根据键值对的 key,按照CRC16算法计算一个16bit的值;然后,再用这个16bit值对16384取模,得到0~16383范围内的模数,每个模数代表一个相应编号的哈希槽。

使用Redis Cluster 方案时,可以使用 cluster create 命令创建集群,此时Redis会自动把这些槽平均分布在集群实例上。例如,如果集群中有 N 个实例,每个实例上的槽个数为 16384/N 个。

当然我们也可以手动设置机器上面的hash槽个数,应对线上机器不同配置的情况,这个大家去查看官方文档就好了。

客户端怎么确定数据在哪个实例上?

假设数据一成不变

在定位键值对数据时,客户端发送请求时,来计算它所处的哈希槽。但是,要进一步定位到实例,还需要知道哈希槽分布在哪个实例上。

但集群模式下,Redis实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。

客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键所对应的哈希槽,然后就可以给相应的实例发送请求了。

当然,有一些特殊情况,hash槽对应的实例会有变化

  1. 在集群中,实例有新增或删除,Redis 需要重新分配哈希槽。

  2. 为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。

此时客户端缓存的数据就不准了,可能出现以下情景:

本地缓存的实例ip是192.168.0.1,但是此时该hash槽的对应ip变成了192.168.0.2。


GET helloworld

(error) MOVED 2400 192.168.0.2:6379

MOVED命令含义:表示请求的key,对应的hash槽与对应的实例信息,然后客户端只需要重新在对应的实例去操作就可以了。

当迁移的数据量过大时,就可能出现一种情况,请求时这个hash槽只迁移了一部分数据到新实例,还有一部分数据在老实例。


GET helloworld

(error) ASK 2400 172.16.19.5:6379

ASK命令含义,当在Redis Cluster中使用ASK(Ask Redirect)返回报错时,通常表示在执行命令时客户端与集群之间发生了某种异常情况。ASK是Redis Cluster中的一种特殊机制,用于在槽迁移时指示客户端重定向到另一个节点。

Cluster Proxy(集群代理)

Proxy模式通过在Redis客户端和服务端之间插入代理(Proxy)来实现负载均衡、故障转移和数据分片等功能。Proxy可以根据负载情况将请求分发到不同的Redis节点上,并在节点故障时自动切换到备用节点。Proxy模式提供了更灵活的架构设计和更好的可扩展性,但也增加了系统的复杂性和延迟。

优点:

  1. 负载均衡: Redis Proxy可以实现负载均衡,将客户端请求分发到多个后端的Redis服务器上,从而平衡集群中各个节点的负载,提高系统的整体性能和吞吐量。

  2. 故障转移: 当后端的Redis服务器出现故障或不可用时,Redis Proxy可以自动将请求重新路由到其他可用的节点上,从而实现故障转移,保证系统的高可用性。

  3. 数据分片: Redis Proxy可以将数据分片存储在多个后端的Redis服务器上,并根据数据的键来选择合适的服务器,从而增加系统的存储容量和并发处理能力。

  4. 协议转换: Redis Proxy可以支持多种Redis协议,如RESP(REdis Serialization Protocol)、Memcached协议等,可以根据客户端的请求协议,将请求转换成后端服务器所支持的协议格式。

  5. 缓存: 一些Redis Proxy还提供了缓存功能,可以缓存常用的请求结果,并在后续的请求中直接返回缓存的结果,对于热点数据,非常好用。

缺点:

  1. 单点故障: Redis Proxy本身也可能成为系统的单点故障,如果Proxy出现故障或不可用,会影响整个系统的稳定性和可用性。

  2. 性能损耗: 由于Redis Proxy介于客户端和后端Redis服务器之间,数据传输需要经过额外的中间层,可能会引入一定的性能损耗和延迟。

  3. 复杂性: 引入Redis Proxy会增加系统的复杂性,需要额外的管理和维护工作,包括配置管理、监控和故障排查等,增加了系统的开发和运维成本。

  4. 一致性问题: 如果Redis Proxy缓存了部分数据,并且与后端Redis服务器之间存在数据不一致的情况,可能会导致数据一致性的问题,需要谨慎处理。

说在最后

本文带大家了解了Redis的四种高可用架构模式

  1. 主从架构,提供了读写分离,但主节点故障,则会影响线上业务。
  2. 哨兵模式,提供了哨兵节点,主节点故障,则可以自动切换。
  3. 集群模式,提供了分片功能,极大的降低了但实例的数据量,适合大规模的Redis
  4. 集群代理,在集群模式上做了优化,但也有一些缺点。

先说结论,高可用架构中,很多实践细节都偏向于运维来操作,对于后端研发来说,不要太深究运维细节,切忌让自己深入在命令的操作中去,

重点要了解四种架构的优缺点,每种架构的底层原理是什么,为什么会有这样的演进方向,每种架构都解决了什么问题,知其然,知其所以然。

不知道你在工作中,处理过哪些Redis的问题,可以和大家分享呢?欢迎你在评论区与我交流。希望本文能够为您工作和面试中提供一些参考和帮助,看到这里,希望点赞评论支持一下,也欢迎你加我的wx:Ldhrlhy10,一起交流~