redis 是一款高性能的内存型数据库,广泛应用在当前的各类系统当中。
redis的部署模式主有四种:
- 单机模式
- 主从模式
- 哨兵模式
- 集群模式
单机模式
物理机上安装一个redis,然后以默认方式启动起来,其实就是单机模式。
这是最简单的部署方式,在不要求高可用的系统中,很多都是采用的这种部署方式
单机模式部署的优点:
- 部署简单,0成本。
- 成本低,没有备用节点,不需要其他的开支。
- 高性能,单机不需要同步数据,数据天然一致性
同时,单机模式部署也有不少缺点:
- 可靠性保证不是很好,单节点有宕机的风险。也存在数据丢失的风险。
- 单机高性能受限于CPU的处理能力,redis是单线程的。
主从模式
类似于MySQL数据库的主从模式,redis的主从复制也是由主节点负责写入,然后复制到各个从节点;主从节点都提供读的能力。
redis默认情况下都是主节点,在启动时设置参数或者配置文件增加
slaveof <master-ip> <master-port> // 5.0之前
replicaof <master-ip> <master-port> //5.0之后
可配置成从节点。
一个redis主节点可拥有0个到多个从节点,但从节点只能有1个主节点。
主从复制流程
主从复制指的是将一台Redis主服务器的数据,复制到其他的Redis从服务器;数据的复制是单向的,只能由主节点到从节点。
主从节点之间的第一次数据同步使用的是全量同步,全量同步的流程大致如图
- 主从库间建立连接、协商同步: 从库向主库发起同步请求
- 主库响应请求,返回FULLRESYNC响应命令带上两个参数:主库runID和主库目前的复制进度offset(每个redis启动时)
- 主库执行bgsave命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。
- 在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。但是,这些请求中的写操作并没有记录到刚刚生成的 RDB 文件中。为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。当主库完成 RDB 文件发送后,就会把此时 replication buffer 中的修改操作发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了
对于断开重连后的从节点,2.8后的版本则是采用的增量同步的方式,减少数据量。
总结一下,主从部署模式的好处是:
- 它可以扩展主节点的读能力,分担主节点读压力。
- 主从复制还是哨兵模式和集群模式能够实施的基础,因此说主从复制是Redis高可用的基石。
但这种模式的坏处是
- Redis 主从模式不具备自动容错和恢复功能,如果主节点宕机,Redis 集群将无法工作,此时需要人为干预,将从节点提升为主节点。
- 如果主机宕机前有一部分数据未能及时同步到从机,即使切换主机后也会造成数据不一致的问题,从而降低了系统的可用性。
- 因为只有一个主节点,所以其写入能力和存储能力都受到一定程度地限制。
- 在进行数据全量同步时,若同步的数据量较大可能会造卡顿的现象
哨兵模式(sentinel)
哨兵模式的出现用于解决主从模式中无法自动升级主节点的问题,一个哨兵是一个节点,用于监控主从节点的健康,当主节点挂掉的时候,自动选择一个最优从节点升级为主节点。
客户端连接Redis,会首先连接Sentinel,通过Sentinel查询master地址,然后再连接master进行数据交互。当master挂了,客户端重新跟Sentinel要master地址,连接新的master。
哨兵模式解决了主从模式需要人工提升主节点的问题,但依然没有突破单机性能的限制。
集群模式
相比于主从模式和哨兵模式,cluster集群模式真正实现了Redis高可用,具有 高可用、可扩展性、分布式、容错 等特性。
下图是集群模式的示意图
一个redis集群由多个Redis主从节点组成,每一个主从代表一个节点,每个节点负责一部分数据,他们之间通过一种特殊的二进制协议(Gossip)交互集群信息。
数据分片
Redis Cluster会将所有数据分片,分成16384个槽位。
对于来自client的数据,Redis Cluster对key值使用crc16算法进行hash,然后用取模除16384得到具体的槽位,每个节点负责其中一部分槽位。
比如有五套主从节点,每套节点复制的slot则如下所示
当客户端连接集群,会得到一份集群的槽位匹配信息,当客户端要查找key,可以直接定位到目标节点。
使用这种方式,每套节点存储的数据不同,减少了数据冗余带来的浪费,同时也横向扩展了性能,解决了单机性能的限制。
数据扩容/缩容
这种模式也很容易添加或者删除节点。
如果增加一个节点 6,就需要从节点 1 ~ 5 获得部分槽分配到节点 6 上。
如果想移除节点 1,需要将节点 1 中的槽移到节点 2 ~ 5 上,然后将没有任何槽的节点 1 从集群中移除即可。
添加和删除节点的本质其实是slot迁移,其步骤如下:
- 标识 slot 为中间过渡状态(如下图,从节点 A 迁出,则 A 上标slot 为 migrating 状态,迁入到节点 B,则 B上标记 slot 为 importing 状态)
- 按照 slot 里的 key 逐个迁移,同步阻塞迁移
- A 将 slot 里某个 key发送给 B
- B 收到数据后存入本地,回复 OK
- A 收到回复 OK 后,删除本地 key
在数据迁移的过程中,redis不会停止服务;
但是在slot迁移过程中,该slot里的key部分在 A 节点,部分在 B 节点,因此 client 的请求处理会发生变化。
- client 先访问 slot 对应的旧节点
- 若数据还在旧节点,则旧节点正常处理
- 若数据已经不在旧节点了,旧节点向 client 返回ask B 重定向令
- client 先执行 ask B
- client 再执行 get 操作
请求路由
客户端连接redis集群某个实例时,如果操作的key不在该实例上,会被重定向(Move)到对应的实例去获取结果。
以集群模式登录客户端(注意命令的差别:-c 表示集群模式),则可以清楚的看到“重定向”的信息. 当我们使用直连模式连接某个集群的实例时,有时候也会看到报错 Moved to xxx,此时需要使用集群模式连接。
故障识别
Redis Cluster中每个节点都存有集群中所有节点的信息。它们之间通过互相ping-pong去判断节点是否可以连接。
如果有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机。
如果有的主节点挂了,则从节点会自动发起选举,选择新的主节点,然后通知集群其他节点(不需要人工干预或者哨兵)。
同时Cluster是去中心化,由多个节点组成,客户端连接时可以只用一个节点的地址,其余节点可通过该节点自动发现,但如果该节点挂了,就必须手动更换地址,因此连接多个地址安全性更高。(go的客户端好像是根据地址个数判断是否使用集群模式连接)
最后总结一下集群模式的优点
- 无中心架构,支持动态扩容
- Cluster自动具备哨兵监控和故障转移(主从切换)能力
- 客户端连接集群内部地址可自动发现
- 高性能、高可用,有效解决了Redis分布式需求
缺点
- 运维复杂
- 只能使用0号数据库