文章目录

  • Reids 分布式缓存
  • Redis 的持久化
    • RDB 持久化
    • RDB 原理
    • AOF 原理
    • AOF 持久化
    • RDB 和 AOF 的对比
  • Redis 主从
    • 全局同步
    • 增量同步
    • repl_backlog原理
    • 主从同步优化
    • 小结
  • Redis 哨兵
    • 哨兵机制的结构
    • 集群的监控和恢复原理
  • Redis分片集群
    • Redis 中的插槽机制
    • 集群伸缩
    • 故障转移

Reids 分布式缓存

基于 Reids 集群来解决单机的 Reids 存在的问题。

在单机的 Reids 中存在着一共有 4 大问题。

  1. 数据丢失问题;要实现的是 Reids 的数据持久化。
  2. 并发能力问题;搭建主从集群,实现读写分离。
  3. 故障恢复问题;利用 Reids 哨兵机制,实现健康监测和自动恢复能力。
  4. 存储能力问题;搭建分片集群,利用插槽机制来实现动态扩容。

Redis 的持久化

在 Redis 中有着两种持久化的方案

  • RDB持久化
  • AOE持久化

RDB 持久化

RDB 全称 Redis Database Backup file(Redis数据备份文件),也被叫做Redis 数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis 实例故障重启后,从磁盘中读取快照文件,恢复数据。快照文件称为RDB 文件,默认是保存在当前运行目录。

RDB 持久化会在四种情况下会执行:
● 执行 save 命令
● 执行 bgsave 命令
● Redis 停机时
● 触发 RDB 条件时

执行 save 命令的时候,save 会导致 RDB,这个过程中其它所有命令都会被阻塞。只有在数据迁移时可能用到。平常情况下,不建议使用。

执行 bgsave 命令的时候。这个命令执行后会开启一个独立的进程完成 RDB,主进程可以持续处理用户请求,而不受印象。

在 Redis 停机的时候,也会触发一次 RDB 的。

在退出前的时候,就会执行一次 RDB 。

触发 RDB 的条件
在 Redis 内部也是有着触发 RDB 的机制,可以在 redis.conf 文件中找到,格式如下:

# 900秒内,如果至少有1个key被修改,则执行bgsave , 如果是 save "" 则表示禁用RDBsave 900 1save 300 10save 60 10000 

RDB 的也有一些其它配置也可以在 redis.conf 文件中设置:

# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱rdbcompression yes# RDB文件名称dbfilename dump.rdb# 文件保存的路径目录dir ./ 

RDB 原理

在 bgsave 开始的时候就会 fork 主进程而得到一个子进程,子进程共享主进程的内存数据,完成 fork 以后读取内存数据并写入 RDB 文件。

以这样的方式,看似在持久化的时候,不会阻塞主进程,但是在 fork 的时候,还是会短暂性的阻塞一下主进程,在 fork 之后,主进程就不会再受打扰了。

完整流程:
当发生 bgsave 的时候,先会短暂性的暂停一下主进程,将主进程里的页表复制到子进程里面,然后将物理内存(就是磁盘)里的 Redis 数据改为 read-only 模式,只能读取模式。然后子进程通过 fork 过来的页表对数据进行操作,形成新的 RDB 文件,在完成之后,用新的 RDB 文件覆盖替换旧的 RDB 文件。当在子进程工作的时候,磁盘文件是只读模式。那么这个时候主进程产生需要对物理内存中数据修改的操作时,物理内存会将需要修改的数据复制出来,形成一个副本数据,对这个副本数据进行操作。这种情况下,以理论角度来思考,是会发生将原 RDB 数据全部进行了修改,那个这个时候,就将内存的大小进行了翻倍。

页表:就是将虚拟地址转换为物理地址 ;管理 cpu 对物理页的访问,如读写执行权限 。它就是一种映射关系。将虚拟内存与物流内存之间连接起来。

fork 采用的是 copy-on-write 技术:

  • 当主进程执行读操作时,访问共享内存;
  • 当主进程执行写操作时,则会拷贝一份数据,执行写操作。

RDB方式bgsave的基本流程概述:

  1. fork主进程得到一个子进程,共享内存空间
  2. 子进程读取内存数据并写入新的RDB文件
  3. 用新RDB文件替换旧的RDB文件

AOF 原理

AOF全称为Append Only File(追加文件)。Redis 处理的每一个写命令都会记录在 AOF 文件,可以看做是命令日志文件。

比如我们对 redis 里保存一个 SET num 123 命令
在 AOF 保存的文件里就是这样的命令:

AOF 持久化

在 Redis 配置文件里面 AOF 默认是关闭的。
需要修改 redis.conf 的配置文件来开启 AOF:

# 是否开启AOF功能,默认是noappendonly yes# AOF文件的名称appendfilename "appendonly.aof"

AOF 的命令记录的频率也是可以通过 redis.conf 文件来配置的。(也是三种策略)

# 表示每执行一次写命令,立即记录到AOF文件appendfsync always # 写命令执行完先放入AOF缓冲区,然后表示每隔1秒将缓冲区数据写到AOF文件,是默认方案appendfsync everysec # 写命令执行完先放入AOF缓冲区,由操作系统决定何时将缓冲区内容写回AOF文件appendfsync no

三种策略对比:

配置项刷盘时机优点缺点
Always同步刷盘可靠性高几乎不丢数据性能影响大
Everysec每秒刷盘性能适中最多丢失1秒的数据
no由操作系统控制性能好可靠性差,可能丢失大量的数据

也 AOF 因为是记录命令的,AOF 文件肯定会比 RDB 文件大的多。而且AOF 会记录对同一个 key 的多次写操作,但只有最后一次写操作才有意义,造成一些无意义指令。

在这就可以通过执行 bgrewriteaof 命令,可以让 AOF 文件执行重写功能,用最少的命令达到相同效果。

在 Redis 内也会在触发阈值的时候自动去重写 AOF 文件。阈值也是可以在 redis.conf 中配置:

# AOF文件比上次文件 增长超过多少百分比则触发重写auto-aof-rewrite-percentage 100# AOF文件体积最小多大以上才触发重写 auto-aof-rewrite-min-size 64mb 

RDB 和 AOF 的对比

RDB 和 AOF 各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。

RDBAOF
持久化方式定时对整个内存做快照记录每一次执行的命令
数据完整性不完整,两次备份之间会丢失相对完整,取决于刷盘资源
文件大小会有压缩,文件体积小记录命令,文件体积大
宕机恢复速度很快
数据恢复优先级低,因为数据完整性是不如 AOF高,因为数据完整性更高
系统资源占用高,大量 CPU 和内存消耗低,主要是磁盘 IO 资源,但 AOF 重写时会占用大量的 CPU 和内存资源。
使用场景可以容忍数分钟的数据丢失,追求更快的启动速度对数据安全性要求较高

Redis 主从

再快的单节点 Redis 的并发能力也是有上限的,这时就需要进一步的提高 Redis 的并发能力,就需要搭建主从集群,从而实现读写分离。

主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。 前者称为主节点(master),后者称为从节点(slave),而数据的复制是单向的,只能由主节点到从节点。

具体的实现思想:使用一个 Redis 实例作为主机,其余的作为备份机。主机和备份机的数据完全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的同步和读取。也就是说,客户端可以将数据写入到主机,由主机自动将数据的写入操作同步到从机。

主从模式很好的解决了数据备份问题,并且由于主从服务数据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离的目的。

在主节点上进行写操作,在从节点上进行读操作。

全局同步

Redis 的主从在第一次建立的时候,就会执行全量同步,将 master 节点的所有数据拷贝到给 slave 节点。

那么具体的全量同步的流程:

之后的操作就是无限接受 repl_baklog 中的指令来操作进行的。

在这里就会产生一个问题,master 是如何知道 servlet 是第一次来连接的。

所以这也有两个概念,还需要了解一下:

  • Replication Id:简称replid,是数据集的标记,id 一致则说明是同一数据集。每一个 master 都有唯一的replid,slave 则会继承 master 节点的 replid
  • offset:偏移量,随着记录在 repl_baklog 中的数据增多而逐渐增大。slave 完成同步时也会记录当前同步的 offset。如果 slave 的 offset 小于 master 的 offset,说明 slave 数据落后于master,需要更新。

因此 slave 要做到数据同步,必须向 master 声明自己的 replication Id 和 offset,master 才可以判断到底需要同步哪些数据。

因为 slave 本身开始也是一个 master,所以它有自己的 replid 和 offset,当第一次变成 slave,与 master 建立连接时,发送的 replid 和 offset 都是自己的 replid 和 offset。

master 判断发现 slave 发送来的 replid 与自己的不一致,说明这是一个全新的 slave,就知道要做全量同步了。需要进行 RDB 操作。

master 也会将自己的 replid 和 offset 都发送给这个 slave,slave 保存这些信息。以后 slave 的 replid 就与 master 一致了。

因此,master 判断一个节点是否是第一次同步的依据,就是看 replid(版本号) 是否一致

而完整的流程描述:

  1. slave 节点请求增量同步
  2. master 节点判断 replid,发现不一致,拒绝增量同步
  3. master 将完整内存数据生成 RDB,发送 RDB 到 slave
  4. slave 清空本地数据,加载 master 的 RDB
  5. master将 RDB 期间的命令记录在 repl_baklog,并持续将 log 中的命令发送给 slave
  6. slave 执行接收到的命令,保持与 master 之间的同步

增量同步

全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步

增量同步就是:只更新slave与master存在差异的部分数据。

repl_backlog原理

master 怎么知道 slave 与自己的数据差异在哪里呢” />

slave 与 master 的 offset 之间的差异,就是 salve 需要增量拷贝的数据了。

随着不断有数据写入,master 的 offset 逐渐变大,slave 也不断的拷贝,追赶master 的 offset。

一直向下,会使得数组被填满。此时,当有新的数据要写入,就会覆盖数组中的旧数据。不过,旧的数据只要是绿色的,说明是已经被同步到slave 的数据,即便被覆盖了也没什么影响。因为未同步的仅仅是红色部分。

如果 master 继续写入新数据,其 offset 就会覆盖旧的数据,直到将 slave 现在的 offset 也覆盖,那么此时如果 slave 想要恢复,就需要同步,却发现自己的 offset 都没有了,无法完成增量同步了。只能做全量同步。

注意:repl_baklog 大小有上限,写满后会覆盖最早的数据。如果 slave 断开时间过久,导致尚未备份的数据被覆盖,则无法基于 log 做增量同步,只能再次全量同步。

主从同步优化

  • 在 master 中配置 repl-diskless-sync yes 启用无磁盘复制,避免全量同步时的磁盘IO。

  • Redis 单节点上的内存占用不要太大,减少 RDB 导致的过多磁盘IO(就是不要给主Redis上连过多的从 Redis,可以将过多的 Redis,连接到从 Redis 上。)

  • 适当提高 repl_baklog 的大小,发现 slave 宕机时尽快实现故障恢复,尽可能避免全量同步

  • 限制一个 master 上的 slave 节点数量,如果实在是太多 slave,则可以采用主-从-从链式结构,减少 master 压力

小结

全量同步执行

  • slave节点第一次连接master节点时
  • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

增量同步执行

  • slave节点断开又恢复,并且在repl_baklog中能找到offset时

全量同步和增量同步区别

  • 全量同步:master 将完整内存数据生成 RDB,发送 RDB 到 slave。后续命令则记录在 repl_baklog,逐个发送给slave。
  • 增量同步:slave 提交自己的 offset 到 master,master 获取 repl_baklog 中从 offset 之后的命令给 slave。

Redis 哨兵

在上面所说的 Redis 的主从机制中,若是我们的 slave 宕机后,可以解决问题。但当我们的 master 主Redis宕机后,显然主从机制是没有办法解决的,所以就有了 Redis 中的哨兵机制。

Redis 提供的哨兵(Sentinel)机制来实现主从集群的自动故障恢复。

哨兵模式就是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。

哨兵机制的结构

哨兵机制也是一个集合群,避免哨兵机制宕机。

哨兵机制就是在监控 Redis 集合群,每个一秒给每一个Redis 发送一个 PING ,看每一个是否有回应。就是在不停的监控当中。

哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的 master 和 slave 是否按预期工作
  • 自动故障恢复:如果 master 故障,Sentinel 会将一个 slave 提升为master。当故障实例恢复后也以新的master为主
  • 通知:Sentinel 充当 Redis 客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给 Redis 的客户端。

集群的监控和恢复原理

  1. Sentinel 是基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping 命令查看是否收到回复。

● 主观下线:如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线

● 客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半。(quorum的值在配置的时候,是要求我们自己去配置的)。

  1. 一旦发现master故障,sentinel 需要在 salve 中选择一个作为新的master,选择依据是这样的:

● 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该 slave 节点。

● 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是0则永不参与选举。(这个值默认都是一样的)

● 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高

● 最后是判断 slave 节点的运行id (runid)大小,越小优先级越高。(这里的id是在一个 Redis 启动的时候就赋给的id值)。

  1. 当选出一个新的 master 后,就要实现从从节点到主节点的切换。

● sentinel 给备选的 slave1 节点发送 slaveof no one (不再为奴)的命令,让该节点成为 master。

● sentinel 给所有其它 slave 发送 slaveof 192.168.150.101 7002 命令,让这些 slave 成为新 master 的从节点,开始从新的 master 上同步数据。(皇上到下了,大太子成功上位)

● 最后,sentinel 将故障节点标记为 slave,当故障节点恢复后会自动成为新的 master 的 slave 节点。(旧皇康复了,但被打落下去,只能辅佐现皇)

Redis分片集群

前面的主从哨兵机制解决的是,高可用,高并发读的问题,但依然还有两个问题没有解决。

  • 海量数据存储的问题。
  • 高并发写的问题。

而在这使用分片集群的思想来解决这个问题的。
设立多个 master 主 Redis,然后给每个 master 都再分配多个 slave 的 Redis,就是以这样的机制来实现 Redis 的分片集群思想。

分片集群的特征:

  • 集群中有多个 master,每个 master 保存不同数据。
  • 每个master都可以有多个slave节点
  • master 之间通过 ping 监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

Redis 中的插槽机制

在 Redis 中的分片集群模式下,会将每一个 master 节点映射到 0~16383 共 16384 个插槽上(hash slot),在查看集群消息的时候就能看到。

在集群模式下,数据的 key 不是与节点进行绑定的,而是与插槽进行绑定的。Redis 会根据 key 的有效部分来计算出插槽值

而计算插槽的值一般分为两种情况:

  • key 中包含 “{}”,且 “{}” 中至少包含1个字符,“{}” 中的部分是为有效部分;
  • key 中不包含 “{}”,整个key都是有效部分

而具体的计算出插槽值是根据 key 的有效部分计算出哈希值,然后对 16384 进行取余。将余数作为插槽值,寻找出插槽的实例即可。

当在设计思路的时候,若想将一类数据保存固定到同一个 Redis 的实例上时,就使用 “{}” 符号,将公共一样的字符放到括号里面。例如,可以将传统数据库中的表名放到括号里面,这样,一个表的所有数据都会放到同一个 Redis 当中去。

集群伸缩

在 Redis 当中,为了更好的对集群进行扩展和管理,所以就有了伸缩机制,就是在初始建立好的集群上添加或删除节点,在这主要说明添加主节点。

大致思路:
首先,创建新的主节点;
创建一个文件夹:

mkdir 7004

拷贝配置文件:

cp redis.conf /7004# 根据自己的实际情况修改

修改配置文件:

sed /s/6379/7004/g 7004/redis.conf

启动

redis-server 7004/redis.conf

然后,将设计好的主节点添加到集群当中。(这时是不会自动的给新的节点分配插槽的,因此更本没有任何数据可以存储到上)

执行命令:

redis-cli --cluster add-node192.168.150.101:7004 192.168.150.101:7001

通过命令查看集群状态:

redis-cli -p 7001 cluster nodes

最后,选择一个存在的节点,将它当中的节点的一部分分配到这个新的节点上(需要手动进行分配)。
在这要说明的是,在给新的 master 分配节点时,会询问新的插槽从哪里移动过来

  • all:代表全部,也就是三个节点各转移一部分
  • 具体的id:目标节点的id
  • done:没有了

故障转移

故障转移可分为自动故障转移和手动故障转移。

自动故障转移流程:

  1. 首先是一个 master 与其它 master 失去连接。
  2. 然后是疑似宕机
  3. 最后是确定下线,自动提升一个slave为新的master
  4. 当丢的 master 恢复后,就会变为一个slave节点了

手动故障转移流程:
利用 cluster failover 命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover 命令的这个 slave 节点,实现无感知的数据迁移。

手动的古装转移一般都是人为控制的,进行 Redis 节点的迭代。退下来的老的 master 节点成为了 slave 节点,就完全可以自主的退出整个 master 集群了。

在堆 Redis 进行操作的时候,会出现 failover 命令,这种命令可以指定三种模式:

  • 缺省:默认的流程,如图1~6歩
  • force:省略了对offset的一致性校验
  • takeover:直接执行第5歩,忽略数据一致性、忽略master状态和其它master的意见