基于 Reids 集群来解决单机的 Reids 存在的问题。
在单机的 Reids 中存在着一共有 4 大问题。
在 Redis 中有着两种持久化的方案
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 "" 则表示禁用RDB
save 900 1
save 300 10
save 60 10000
RDB 的也有一些其它配置也可以在 redis.conf 文件中设置:
# 是否压缩 ,建议不开启,压缩也会消耗cpu,磁盘的话不值钱
rdbcompression yes
# RDB文件名称
dbfilename dump.rdb
# 文件保存的路径目录
dir ./
在 bgsave 开始的时候就会 fork 主进程而得到一个子进程,子进程共享主进程的内存数据,完成 fork 以后读取内存数据并写入 RDB 文件。
以这样的方式,看似在持久化的时候,不会阻塞主进程,但是在 fork 的时候,还是会短暂性的阻塞一下主进程,在 fork 之后,主进程就不会再受打扰了。
完整流程:
当发生 bgsave 的时候,先会短暂性的暂停一下主进程,将主进程里的页表复制到子进程里面,然后将物理内存(就是磁盘)里的 Redis 数据改为 read-only 模式,只能读取模式。然后子进程通过 fork 过来的页表对数据进行操作,形成新的 RDB 文件,在完成之后,用新的 RDB 文件覆盖替换旧的 RDB 文件。当在子进程工作的时候,磁盘文件是只读模式。那么这个时候主进程产生需要对物理内存中数据修改的操作时,物理内存会将需要修改的数据复制出来,形成一个副本数据,对这个副本数据进行操作。这种情况下,以理论角度来思考,是会发生将原 RDB 数据全部进行了修改,那个这个时候,就将内存的大小进行了翻倍。
页表:就是将虚拟地址转换为物理地址 ;管理 cpu 对物理页的访问,如读写执行权限 。它就是一种映射关系。将虚拟内存与物流内存之间连接起来。
fork 采用的是 copy-on-write 技术:
RDB方式bgsave的基本流程概述:
AOF全称为Append Only File(追加文件)。Redis 处理的每一个写命令都会记录在 AOF 文件,可以看做是命令日志文件。
比如我们对 redis 里保存一个 SET num 123
命令
在 AOF 保存的文件里就是这样的命令:
在 Redis 配置文件里面 AOF 默认是关闭的。
需要修改 redis.conf 的配置文件来开启 AOF:
# 是否开启AOF功能,默认是no
appendonly 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 | |
---|---|---|
持久化方式 | 定时对整个内存做快照 | 记录每一次执行的命令 |
数据完整性 | 不完整,两次备份之间会丢失 | 相对完整,取决于刷盘资源 |
文件大小 | 会有压缩,文件体积小 | 记录命令,文件体积大 |
宕机恢复速度 | 很快 | 慢 |
数据恢复优先级 | 低,因为数据完整性是不如 AOF | 高,因为数据完整性更高 |
系统资源占用 | 高,大量 CPU 和内存消耗 | 低,主要是磁盘 IO 资源,但 AOF 重写时会占用大量的 CPU 和内存资源。 |
使用场景 | 可以容忍数分钟的数据丢失,追求更快的启动速度 | 对数据安全性要求较高 |
再快的单节点 Redis 的并发能力也是有上限的,这时就需要进一步的提高 Redis 的并发能力,就需要搭建主从集群,从而实现读写分离。
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器。 前者称为主节点(master),后者称为从节点(slave),而数据的复制是单向的,只能由主节点到从节点。
具体的实现思想:使用一个 Redis 实例作为主机,其余的作为备份机。主机和备份机的数据完全一致,主机支持数据的写入和读取等各项操作,而从机则只支持与主机数据的同步和读取。也就是说,客户端可以将数据写入到主机,由主机自动将数据的写入操作同步到从机。
主从模式很好的解决了数据备份问题,并且由于主从服务数据几乎是一致的,因而可以将写入数据的命令发送给主机执行,而读取数据的命令发送给不同的从机执行,从而达到读写分离的目的。
在主节点上进行写操作,在从节点上进行读操作。
Redis 的主从在第一次建立的时候,就会执行全量同步,将 master 节点的所有数据拷贝到给 slave 节点。
那么具体的全量同步的流程:
之后的操作就是无限接受 repl_baklog 中的指令来操作进行的。
在这里就会产生一个问题,master 是如何知道 servlet 是第一次来连接的。
所以这也有两个概念,还需要了解一下:
因此 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(版本号) 是否一致。
而完整的流程描述:
全量同步需要先做RDB,然后将RDB文件通过网络传输个slave,成本太高了。因此除了第一次做全量同步,其它大多数时候slave与master都是做增量同步。
增量同步就是:只更新slave与master存在差异的部分数据。
master 怎么知道 slave 与自己的数据差异在哪里呢?
这就要说到全量同步时的 repl_baklog 文件了。
这个文件是一个固定大小的数组,只不过数组是环形,也就是说角标到达数组末尾后,会再次从0开始读写,循环进行的,这样数组头部的数据就会被覆盖。
repl_baklog 中会记录 Redis 处理过的命令日志及 offset,包括 master 当前的 offset,和 slave 已经拷贝到的 offset:
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 压力
全量同步执行:
增量同步执行:
全量同步和增量同步区别:
在上面所说的 Redis 的主从机制中,若是我们的 slave 宕机后,可以解决问题。但当我们的 master 主Redis宕机后,显然主从机制是没有办法解决的,所以就有了 Redis 中的哨兵机制。
Redis 提供的哨兵(Sentinel)机制来实现主从集群的自动故障恢复。
哨兵模式就是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
哨兵机制也是一个集合群,避免哨兵机制宕机。
哨兵机制就是在监控 Redis 集合群,每个一秒给每一个Redis 发送一个 PING ,看每一个是否有回应。就是在不停的监控当中。
哨兵的作用如下:
● 主观下线:如果某 sentinel 节点发现某实例未在规定时间响应,则认为该实例主观下线。
● 客观下线:若超过指定数量(quorum)的 sentinel 都认为该实例主观下线,则该实例客观下线。quorum 值最好超过 Sentinel 实例数量的一半。(quorum的值在配置的时候,是要求我们自己去配置的)。
● 首先会判断 slave 节点与 master 节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该 slave 节点。
● 然后判断 slave 节点的 slave-priority 值,越小优先级越高,如果是0则永不参与选举。(这个值默认都是一样的)
● 如果 slave-prority 一样,则判断 slave 节点的 offset 值,越大说明数据越新,优先级越高
● 最后是判断 slave 节点的运行id (runid)大小,越小优先级越高。(这里的id是在一个 Redis 启动的时候就赋给的id值)。
● sentinel 给备选的 slave1 节点发送 slaveof no one (不再为奴)的命令,让该节点成为 master。
● sentinel 给所有其它 slave 发送 slaveof 192.168.150.101 7002 命令,让这些 slave 成为新 master 的从节点,开始从新的 master 上同步数据。(皇上到下了,大太子成功上位)
● 最后,sentinel 将故障节点标记为 slave,当故障节点恢复后会自动成为新的 master 的 slave 节点。(旧皇康复了,但被打落下去,只能辅佐现皇)
前面的主从哨兵机制解决的是,高可用,高并发读的问题,但依然还有两个问题没有解决。
而在这使用分片集群的思想来解决这个问题的。
设立多个 master 主 Redis,然后给每个 master 都再分配多个 slave 的 Redis,就是以这样的机制来实现 Redis 的分片集群思想。
分片集群的特征:
在 Redis 中的分片集群模式下,会将每一个 master 节点映射到 0~16383 共 16384 个插槽上(hash slot),在查看集群消息的时候就能看到。
在集群模式下,数据的 key 不是与节点进行绑定的,而是与插槽进行绑定的。Redis 会根据 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-node 192.168.150.101:7004 192.168.150.101:7001
通过命令查看集群状态:
redis-cli -p 7001 cluster nodes
最后,选择一个存在的节点,将它当中的节点的一部分分配到这个新的节点上(需要手动进行分配)。
在这要说明的是,在给新的 master 分配节点时,会询问新的插槽从哪里移动过来
故障转移可分为自动故障转移和手动故障转移。
自动故障转移流程:
手动故障转移流程:
利用 cluster failover 命令可以手动让集群中的某个 master 宕机,切换到执行 cluster failover 命令的这个 slave 节点,实现无感知的数据迁移。
手动的古装转移一般都是人为控制的,进行 Redis 节点的迭代。退下来的老的 master 节点成为了 slave 节点,就完全可以自主的退出整个 master 集群了。
在堆 Redis 进行操作的时候,会出现 failover 命令,这种命令可以指定三种模式: