1.数据类型
string
1. 命令
命令 | 介绍 |
---|---|
SET | $1600 |
SETNX | $12 |
SETEX | $1 |
GET | $1600 |
INCR | $12 |
MSET | $1 |
MGET | $1 |
MSETNX | 1 |
2. 使用场景
1. 缓存功能
2. 计数
3. 共享session
4. 限速
5.分布式锁
hash
哈希类型是指键值本身又是一个键值对结构(就是多维数组), value={{field, value},…{fieldN, valueN}}
1. 命令
命令 | 介绍 |
---|---|
SET | $1600 |
SETNX | $12 |
SETEX | $1 |
GET | $1600 |
INCR | $12 |
MSET | $1 |
MGET | $1 |
MSETNX | 1 |
2. 使用场景
1. 记录用信息
list
列表类型是用来存储多个有序的字符串,如图 a,b,c,d,e 从左到右组成一个有序的列表,列表中的每个字符串称为元素。在redis中,可以对列表两端插入(push)和弹出(pop),还可以指定范围的元素列表,获取指定索引下表的元素等。列表是种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
1. 命令
命令 | 介绍 |
---|---|
SET | $1600 |
SETNX | $12 |
SETEX | $1 |
GET | $1600 |
INCR | $12 |
MSET | $1 |
MGET | $1 |
MSETNX | 1 |
2. 使用场景
1. 消息队列
2.文章列表
set
每个用户有属于自己的文章列表,先需要分页展示文章列表。此时可以考虑使用列表因为列表不但是有序的,同时支持按照索引范围获取元素集合(set)类型也是用来保存多个的字符串元素,但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取原生。集合user:1:name包含着“peter”、“shineyork”、“cara”、“will”四个元素。
1. 命令
命令 | 介绍 |
---|---|
SET | $1600 |
SETNX | $12 |
SETEX | $1 |
GET | $1600 |
INCR | $12 |
MSET | $1 |
MGET | $1 |
MSETNX | 1 |
2. 使用场景
1. 给用户添加标签
2.给标签添加用户
zset
有序集合相对哈希、列表、集合来说会有点点陌生, 但既然叫有序集合,那么它和集合必然有着联系,它保留了集合不能有重复成员的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下标作为排序依据不同的是,它给每个元素设置-个分数(score)作为排序的依据。
1. 命令
命令 | 介绍 |
---|---|
SET | $1600 |
SETNX | $12 |
SETEX | $1 |
GET | $1600 |
INCR | $12 |
MSET | $1 |
MGET | $1 |
MSETNX | 1 |
2. 使用场景
1. 添加用户赞数
2.取消用户赞数
3. 展示获取赞数最多的是个用户
4.展示用户信息以及用户分数
Streams
这是redis5放出来的新特性,其实Antirez在几年前开了一个新项目叫做disque, 也是用来做消息队列的;而stream可以说是基于disque而设计的,它是一个新的强大的支持多播的可持久化的消息队列,作者坦言 Redis Stream 狠狠地借鉴了 Kafka 的设计。
-- xadd 追加消息
-- 格式:xadd {stream name} */{表示服务器自动生成id} key1 value1 key2 value2
-- 实例
> xadd sixstar_teacher * name shineyork class php
"1590221117374-0"
-- xdel :删除消息,这里的删除仅仅是设置了标志位,不影响消息总长度
-- 格式:xdel {stream name} {消息id}
-- 实例
> xdel sixstar_teacher 1590221117374-0
(integer) 1
-- xrange :获取消息列表,从小到大排序
-- 格式:xrange {stream name} -/{start} +/{end} ; -表示最小值 , + 表示最大值
-- 实例
> xadd sixstar_teacher * name shineyork class php
> xrange sixstar_teacher 1590221479900-0 +
-- xrevrange :获取消息列表,从大到小排序
-- 格式: xrevrange {stream name} -/{start} +/{end} ; -表示最小值 , + 表示最大值
-- 实例
> xrevrange sixstar_teacher + -
-- xlen :消息长度
-- 格式:xlen {stream name}
-- 实例
> xlen sixstar_teacher
(integer) 1
-- del :删除Stream
-- 格式:del {stream name}
-- 实例
> del sixstar_teacher
(integer) 1
2.主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave),数据的复制是单向的,只能由主节点到从节点。
作用
1、数据冗余
2、故障恢复
3、负载均衡
4、读写分离
5、高可用基石
全量复制
用于初次复制或其它无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作,当数据量较大时,会对主从节点和网络造成很大的开销
全量复制的过程如下:
- Redis 内部会发出一个同步命令,刚开始是 Psync 命令,Psync ? -1表示要求 master 主机同步数据
- 机会向从机发送 runid 和 offset,因为 slave 并没有对应的 offset,所以是全量复制
- 从机 slave 会保存 主机master 的基本信息 save masterInfo
- 主节点收到全量复制的命令后,执行bgsave(异步执行),在后台生成RDB文件(快照),并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令
- 主机send RDB 发送 RDB 文件给从机
- 发送缓冲区数据
- 刷新旧的数据,从节点在载入主节点的数据之前要先将老数据清除
- 加载 RDB 文件将数据库状态更新至主节点执行bgsave时的数据库状态和缓冲区数据的加载。
部分复制
用于处理在主从复制中因网络闪断等原因造成的数据丢失场景,当从节点再次连上主节点后,如果条件允许,主节点会补发丢失数据给从节点。因为补发的数据远远小于全量数据,可以有效避免全量复制的过高开销,需要注意的是,如果网络中断时间过长,造成主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制
部分复制的过程如下:
- 如果网络抖动(连接断开 connection lost)
- 主机master 还是会写 replbackbuffer(复制缓冲区)
- 从机slave 会继续尝试连接主机
- 从机slave 会把自己当前 runid 和偏移量传输给主机 master,并且执行 pysnc 命令同步
- 如果 master 发现你的偏移量是在缓冲区的范围内,就会返回 continue 命令
- 同步了 offset 的部分数据,所以部分复制的基础就是偏移量 offset。
问题处理
3.持久化
持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从 内存保存到硬盘。 当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。Redis持久化分为 RDB持久化和AOF持久化,前者将当前数据保存到硬盘,后者则是将每次执行的写命令保存到硬盘。
RDB
RDB是一种快照存储持久化方式,具体就是将Redis某一时刻的内存数据保存到硬盘的文件当中,默认保存的文件名为dump.rdb,而在Redis服务器启动时,会重 新加载dump.rdb文件的数据到内存当中恢复数据。触发 RDB 持久化过程分为手动触发和自动触发。
手动
手动触发分别对应 save 和 bgsave 命令:
·save 命令:阻塞当前 Redis 服务器,直到 RDB 过程完成为止,对于内存比较大的实例会造成长时间阻塞,线上环境不建议使用。 ·
bgsave 命令:Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。 显然 bgsave 命令是针对 save 阻塞问题做的优化。因此 Redis 内部所有的涉及 RDB 的操作都采用 bgsave 的方式 除了执行命令手动触发之外,Redis 内部还存在自动触发 RDB 的持久化机制,例如以下场景:
1)使用 save 相关配置,如“save m n”。表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave。
2)如果从节点执行全量复制操作,主节点自动执行 bgsave 生成 RDB 文件并发送给从节点。
3)执行 debug reload 命令重新加载 Redis 时,也会自动触发 save 操作。
4)默认情况下执行 shutdown 命令时,如果没有开启 AOF 持久化功能则自动执行 bgsave。
bgsave
bgsave 是主流的触发 RDB 持久化方式,根据下图了解它的运作流程
自动
除了通过客户端发送命令外,还有一种方式,就是在Redis配置文件中的save指定到达触发RDB持久化的条件,比如【多少秒内至少达到多少写操作】就开 启RDB数据同步。
例如我们可以在配置文件redis.conf指定如下的选项:
#900s内至少达到一条写命令
save 900 1
#300s内至少达至10条写命令
save 300 10
#60s内至少达到10000条写命令
save 60 10000
优点
- RDB 是一个非常紧凑的文件,它保存了 Redis 在某个时间点上的数据集。 这种文件非常适合用于进行备份:
比如说,你可以在最近的 24 小 时内,每小时备份一次 RDB 文件,并且在每个月的每一天,也备份一个 RDB 文件。 这样的话,即使遇上问题,也可以随时将数据集还原到不同的版本。 - RDB 可以最大化 Redis 的性能:父进程在保存 RDB 文件时唯一要做的就是 fork 出一个子进程,然后这个子进程就会处理接下来的所有保存工 作,父进程无须执行任何磁盘 I/O 操作。
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点
- RDB 方式数据没办法做到实时持久化/秒级持久化 如果服务器宕机的话,采用RDB的方式会造成某个时段内数据的丢失,
比如我们设置10分钟同步一次或5分钟达到1000次写入就同步一次,那么如果还没达 到触发条件服务器就死机了,那么这个时间段的数据会丢失。 - 使用bgsave命令在forks子进程时,如果数据量太大,forks的过程也会发生阻塞,另外,forks子进程会耗费内存。
针对 RDB 不适合实时持久化的问题,Redis 提供了 AOF 持久化方式来解决。
AOF
AOF(append only file)持久化:与RDB存储某个时刻的快照不同,AOF持久化方式会记录客户端对服务器的每一次写操作命令到日志当中,并将这些写操作以 Redis协议追加保存到以后缀为aof文件末尾
工作流程说明
- 所有的写入命令会追加到 aof_buf(缓冲区)中。
- AOF 缓冲区根据对应的策略向硬盘做同步操作。
- 随着 AOF 文件越来越大,需要定期对 AOF 文件进行重写,达到压缩的目的。
- 当 Redis 服务器重启时,可以加载 AOF 文件进行数据恢复。
重写机制说明
AOF将客户端的每一个写操作都追加到aof文件末尾,随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,Redis 引入 AOF 重写机制压缩文件 体积。
AOF 文件重写是把 Redis 进程内的数据转化为写命令同步到新 AOF 文件的过程。
比如: 多条写命令可以合并为一个,如:lpush list a、lpush list b、lpush list c 可以转化为:lpush list a b c。
AOF 重写降低了文件占用空间,除此之外,另一个目的是:更小的 AOF 文件可以更快地被 Redis 加载
处罚机制
自动
根据 auto-aof-rewrite-min-size和auto-aof-rewrite-percentage 参数确定自动触发时机。
auto-aof-rewrite-min-size:表示运行 AOF 重写时文件最小体积,默认为 64MB。
auto-aof-rewrite-percentage:代表当前 AOF 文件空间(aof_current_size)和上一次重写后 AOF 文件空间(aof_base_size)的比值。
示例:
auto-aof-rewrite-percentage:100
auto-aof-rewrite-min-size:64mb
默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
手动
直接调用 bgrewriteaof 命令。
优点
- AOF可以设置 完全不同步、每秒同步、每次操作同,默认是每秒同步。因为AOF是操作指令的追加,所以可以频繁的大量的同步。
- AOF文件是一个值追加日志的文件,即使服务宕机为写入完整的命令,也可以通过redis-check-aof工具修复这些问题。
- 如果AOF文件过大,Redis会在后台自动地重写AOF文件。重写后会使AOF文件压缩到最小所需的指令集。
- AOF文件是有序保存数据库的所有写入操作,易读,易分析。即使如果不小心误操作数据库,也很容易找出错误指令,恢复到某个数据节点。例如不小 心FLUSHALL,可以非常容易恢复到执行命令之前。
缺点
- 相同数据量下,AOF的文件通常体积会比RDB大。因为AOF是存指令的,而RDB是所有指令的结果快照。但AOF在日志重写后会压缩一些空间。
- 在大量写入和载入的时候,AOF的效率会比RDB低。因为大量写入,AOF会执行更多的保存命令,载入的时候也需要大量的重执行命令来得到最后的结果。 RDB对此更有优势。
持久化选择
- 如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机, 还是主从架构,都可以不进行任何持久化。
- 在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择RDB对Redis的性能更加有利; 如果只能接受秒级别的数据丢失,应该选择AOF。
- 但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求, 以及在master宕掉后继续提供服务。
在这种情况下的做法是:
master:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好;
slave:关闭RDB,开启AOF(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好 备份的时间);然后关闭AOF的自动重写,添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。
这里需要解释一下,为什么开启了主从复制,可以实现数据的热备份,还需要设置持久化呢?因为在一些特殊情况下,主从复制仍然不足以保证数 据的安全丢失。
例如:
master和slave进程同时停止:考虑这样一种场景,如果master和slave在同一个机房,则一次停电事故就可能导致master和slave机器同时关机, Redis进程停止;如果没有持久化,则面临的是数据的完全丢失。
master误重启:考虑这样一种场景,master服务因为故障宕掉了,如果系统中有自动拉起机制(即检测到服务停止后重启该服务)将master自动重 启,由于没有持久化文件,那么master重启后数据是空的, slave同步数据也变成了空的;如果master和slave都没有持久化,同样会面临数据的完全丢失。需要注意的是,即便是使用了哨兵进行自 动的主从切换, 也有可能在哨兵轮询到master之前,便被自动拉起机制重启了。因此,应尽量避免“自动拉起机制”和“不做持久化”同时出现。
- 异地灾备:上述讨论的几种持久化策略,针对的都是一般的系统故障,如进程异常退出、宕机、断电等,这些故障不会损坏硬盘。但是对于一些可能 导致硬盘损坏的灾难情况,如火灾地震,就需要进行异地灾备。
例如对于单机的情形,可以定时将RDB文件或重写后的AOF文件,通过scp拷贝到远程机器,如阿里云;对于主从的情形,可以定时在master上执行 bgsave,然后将RDB文件拷贝到远程机器, 或者在slave上执行bgrewriteaof重写AOF文件后,将AOF文件拷贝到远程机器上。 一般来说,由于RDB文件文件小、恢复快,因此灾难恢复常用RDB文件;异地备份的频率根据数据安全性的需要及其它条件来确定,但最好不要低于一 天一次。
4.内存优化
缓存淘汰优化
内存淘汰的过程
首先,客户端发起了需要申请更多内存的命令(如set)。
然后,Redis检查内存使用情况,如果已使用的内存大于maxmemory则开始根据用户配置的不同淘汰策略来淘汰内存(key),从而换取一定的内存。
最后,如果上面都没问题,则这个命令执行成功。
内存淘汰策略
- volatile-lru 从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰。
- allkeys-lru 从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- volatile-lfu 从设置了过期时间的数据集(server.db[i].expires)中选择某段时间之内使用频次最小的键值对清除掉
- allkeys-lfu 从所有的数据集(server.db[i].dict)中选择某段时间之内使用频次最少的键值对清除
- volatile-ttl 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-random 从数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction 当内存达到限制的时候,不淘汰任何数据,不可写入任何数据集,所有引起申请内存的命令会报错。
如何选择淘汰策略
- allkeys-lru :如果我们的应用对缓存的访问符合幂律分布,也就是存在相对热点数据,或者我们不太清楚我们应用的缓存访问分布状况,我们可以选择 allkeys-lru策略。
- allkeys-random :如果我们的应用对于缓存key的访问概率相等,则可以使用这个策略。
- volatile-ttl:这种策略使得我们可以向Redis提示哪些key更适合被eviction。
- volatile-lru策略和volatile-random策略适合我们将一个Redis实例既应用于缓存和又应用于持久化存储的时候,然而我们也可以通过使用两个Redis实例来达 到相同的效果,值得一提的是将key设置过期时间实际上会消耗更多的内存,因此我们建议使用allkeys-lru策略从而更有效率的使用内存
5.哨兵
哨兵的功能
- 监控:Sentinel 节点会定期检测 Redis 数据节点、其余 Sentinel 节点是否可达
- 通知:Sentinel 节点会将故障转移的结果通知给应用方
- 主节点故障转移: 实现从节点晋升为主节点并维护后续正确的主从关系
- 配置提供者: 在 Redis Sentinel 结构中,客户端在初始化的时候连接的是 Sentinel 节点集合 ,从中获取主节点信息。
哨兵的切换实现原理
3.0 发布订阅基础
3.1 哨兵的实现原理
主要分为以下三个步骤
- 检测问题,主要讲的是三个定时任务,这三个内部的执行任务可以保证出现问题马上让 Sentinel 知道。
- 发现问题,主要讲的是主观下线和客观下线。当有一台 Sentinel 机器发现问题时,它就会主观对它主观下线。 但是当多个 Sentinel 都发现有问题的时候,才会出现客观下线。
- 找到解决问题的人,主要讲的是领导者选举,如何在 Sentinel 内部多台节点做领导者选举,选出一个领导者。
- 解决问题,主要讲的是故障转移,即如何进行故障转移
三个定时任务
- 每10秒每个 Sentinel 对 Master 和 Slave 执行一次 Info Replication 。
- 每2秒每个 Sentinel 通过 Master 节点的 channel 交换信息(pub/sub)。
- 每1秒每个 Sentinel 对其他 Sentinel 和 Redis 执行 ping
第一个定时任务,指的是 Redis Sentinel 可以对 Redis 节点做失败判断和故障转移,在 Redis 内部有三个定时任务作为基础,来 Info Replication 发现 Slave 节点, 这个命令可以确定主从关系。
第二个定时任务,类似于发布订阅, Sentinel 会对主从关系进行判定,通过 sentinel:hello 频道交互。了解主从关系可以帮助更好的自动化操作 Redis 。然后 Sentinel 会告知系统消息给其它 Sentinel 节点,最终达到共识,同时 Sentinel 节点能够互相感知到对方。
第三个定时任务,指的是对每个节点和其它 Sentinel 进行心跳检测,它是失败判定的依据
主观和客观下线
- 主观下线:每个 Sentinel 节点对 Redis 节点失败的“偏见”。之所以是偏见,只是因为某一台机器30秒
- 客观下线:这个时候需要所有 Sentinel 节点都发现它30秒内无回复,才会达到共识。
领导者选举
选举算法Raft:http://raft.github.io/ 或者 https://www.cnblogs.com/xybaby/p/10124083.html
- 每个做主观下线的sentinel节点,会向其他的sentinel节点发送命令,要求将它设置成为领导者
- 收到命令sentinel节点,如果没有同意通过其它节点发送的命令,那么就会同意请求,否则就会拒绝
- 如果sentinel节点发现自己票数超过半数,同时也超过了 sentinel monitor mymaster 127.0.0.1 6379 2 超过2个的时候,就会成为领导者
- 进行故障转移操作
问题处理
1. 异步复制导致数据丢失
描述:因为master->slave的复制是异步,所以可能有部分还没来得及复制到slave就宕机了,此时这些部分数据就丢失了。
解决:在异步复制的过程当中,通过 min-slaves-max-lag 这个配置,就可以确保的说,一旦 slave 复制数据和 ack 延迟时间太长,就认为可能 master 宕机 后损失的数据太多了,那么就拒绝写请求, 这样就可以把master宕机时由于部分数据未同步到 slave 导致的数据丢失降低到可控范围内
2. 集群脑裂导致数据丢失
描述: 脑裂,也就是说,某个master所在机器突然脱离了正常的网络,跟其它slave机器不能连接,但是实际上master还运行着此时哨兵可能就会认为master宕机 了,然后开始选举,将其它 slave 切换成 master 。这时候集群里就会有2个 master ,也就是所谓的脑裂。此时虽然某个 slave 被切换成了 master ,但是可能client 还没来得及切换成新的 master ,还继续写向旧的 master 的数据可能就丢失了。因此旧master再次恢复的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会被清空,重新从新的 master 复制数据。
怎么解决:集群脑裂因为 client 还没来得及切换成新的 master ,还继续写向旧的 master 的数据可能就丢失了 通过 min-slaves-to-write 确保必须是有多少个从 节点连接,并且延迟时间小于 min-slaves-max-lag 多少秒
对于客户端来说:就需要做些处理,比如先将数据缓存到内存当中,然后过一段时间处理,或者连接失败,接收到错误切换新的 master 处理
6.集群
数据分布理论
常见的分区规则有哈希分区和顺序分区两种
哈希分区和顺序分区对比
分区方式 | 特点 | 代表产品 |
---|---|---|
哈希分区 | 离散度好 数据分布业务无关 无法顺序访问 | RedisCluster Cassandra Dynamo |
顺序分区 | 离散度易倾斜 数据分布业务相关 可顺序访问 | Bigtable HBase Hypertable |
顺序分区
顺序分布就是把一整块数据分散到很多机器中,顺序分布一般都是平均分配的
哈希分区
1~100这整块数字,通过 hash 的函数,取余产生的数。这样可以保证这串数字充分的打散,也保证了均匀的分配到各台机器上。
哈希分区方式
节点取余分区
使用特定的数据(包括redis的键或用户ID),再根据节点数量N,使用公式:hash(key)%N计算出一个0~(N-1)值,用来决定数据映射到哪一个节点上。即哈希值 对节点总数取余。 缺点:当节点数量N变化时(扩容或者收缩),数据和节点之间的映射关系需要重新计算,这样的话,按照新的规则映射,要么之前存储的数据找不到,要么之前 数据被重新映射到新的节点(导致以前存储的数据发生数据迁移) 实践:常用于数据库的分库分表规则,一般采用预分区的方式,提前根据数据量规划好分区数,比如划分为512或1024张表,保证可支撑未来一段时间的数据量, 再根据负载情况将表迁移到其他数据库中。
一致性哈希
一致性哈希分区(Distributed Hash Table)实现思路是为系统中每个节点分配一个 token,范围一般在0~232,这些 token 构成一个哈希环。数据读写执行节点查 找操作时,先根据 key 计算 hash 值,然后顺时针找到第一个大于等于该哈希值的 token 节点
假设我们有 n1~n4 这四台机器,我们对每一台机器分配一个唯一 token,每次有数据(图中黄色代表数据),一致性哈希算法规定每次都顺时针漂移数据,也就 是图中黄色的数 据都指向 n3。 这个时候我们需要增加一个节点 n5,在 n2 和 n3 之间,数据还是会发生漂移(会偏移到大于等于的节点),但是这个时候你是否注意到,其实只有 n2~n3 这部 分的数据被漂移,其他的数据都是不会变的,这种方式相比节点取余最大的好处在于加入和删除节点只影响哈希环中相邻的节点,对其他节点无影响 缺缺点点:每个节点的负载不相同,因为每个节点的hash是根据key计算出来的,换句话说就是假设key足够多,被hash算法打散得非常均匀,但是节点过少,导致 每个节点处理的key个数不太一样,甚至相差很大,这就会导致某些节点压力很大 实实践践:加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
虚拟槽分区
虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽(slot)。这个范围一般远远大于节 点数,比如 Redis Cluster 槽范围是 0~16383 (也就是说有16383个槽)。槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要目的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽,下图所示。 当前集群有5个节点,每个节点平均大约负责3276个槽。由于采用高质量的哈希算法,每个槽所映射的数据通常比较均匀,将数据平均划分到5个节点进行数据分区
故障转移与恢复
故障发现
当集群内某个节点出现问题时,需要通过一-种健壮的方式保证识别出节点是否发生了故障。Redis 集群内节点通过ping/pong消息实现节点通信,消息不但可以传 播节点槽信息,还可以传播其他状态如:主从状态、节点故障等。因此故障发现也是通过消息传播机制实现的,主要环节包括:主观下线(pfail)和客观下线(fail)。
- 主观下线:指某个节点认为另一一个节点不可用,即下线状态,这个状态并不是最终的故障判定,只能代表一个节 点的意见,可能存在误判情况。
- 客观下线:指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的结果。如果是持有槽的主节点故障,需要为该节点进行故障转 移。
主观下线
集群中每个节点都会定期向其他节点发送ping消息,接收节点回复pong消息作为响应。如果在cluster-node- timeout时间内通信一直失败, 则发送节点会认为接收 节点存在故障,把接收节点标记为主观下线(pfail)状态
- 节点a发送ping消息给节点b,如果通信正常将接收到pong消息,节点a更新最近一次与节点b的通信时间。
- 如果节点a与节点b通信出现问题则断开连接,下次会进行重连。如果直通信失败,则节点a记录的与节点b最后通信时间将无法更新。
- 节点a内的定时任务检测到与节点b最后通信时间超高cluster-node-timeout时,更新本地对节点b的状态为主观下线(pfail)。
客观下线
当某个节点判断另一个节点主管下线后,相应的节点状态会跟随消息在集群内传播。ping/pong消息的消息体会携带集群1/10的其他节点状态数据,当接收节点发 现消息体中含有主管下线的节点状态时、会在本地找到故障及诶单clusterNode结构,保存到下线报告连接中 clusterNode fail_reportsx 通过Gossip消息传播,集群内节点不断收集到故障节点的下线报告。当半数以上持有槽的主节点都标记某个节点是主观下线时。触发客观下线流程。 假设节点a标记节点b为主观下线,一段时间后节点a通过消息把节点b的状态发送到其他节点,当节点c接受到消息并解析出消息体含有节点b的pfail状态时,会触发 客观下线
- 当消息体内含有其他节点的pfail状态会判断发送节点的状态,如果发送节点是主节点则对报告的pfail状态处理,从节点则忽略
- 找到pfail对应的节点结构,更新clusterNode内部下线报告链表
- 根据更新后的下线报告链接表尝试进行客观下线。
故障恢复
故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。下线主节点的所有从节点承担故障恢 复的义务,当从节点通过内部定时任务发现自身复制的主节点进人客观下线时,将会触发故障恢复流程
- 资格检查
- 准备选举时间
- 发起选举
- 选举投票
- 替换主节点
7.缓存优化
缓存击穿
描述:缓存击穿,是指某个极度“热点”数据在某个时间点过期时,恰好在这个时间点对这个 KEY 有大量的并发请求过来,这些请求发现缓存过期一般都会从 DB 加载 数据并回设到缓存,但是这个时候大并发的请求可能会瞬间 DB 压垮。
解决方案:
1、设置热点数据永远不过期。
2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准 备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。
3、主动更新缓存:默认缓存是被动更新的。只有在终端请求发现缓存失效时,它才会去数据库查询新的数据。那么,如果我们把缓存的更新,从被动改为主 动,也就可以直接绕开缓存风 暴的问题了,
4、使用互斥锁:请求发现缓存不存在后,去查询 DB前,使用锁,保证有且只有一个请求去查询 DB ,并更新到缓存。
- 1、获取锁,直到成功或超时
- 2、再去缓存中。如果存在值,则直接返回;如果不存在,则继续往下执行如果成功获取到锁的 话,就可以保证只有一个请求去数据源更新数据,并更新到 缓存中了。
- 3、查询 DB ,并更新到缓存中,返回值。
缓存穿透
描述:缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次 请求都要到存储层去查询,失去了缓存的意义。
解决方案:
1、缓存空对象
2、bloomfilter提前拦截
缓存雪崩
描述:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
解决方案:
1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
3、设置热点数据永远不过期。
缓存与db一致性
产生原因
主要有两种情况,会导致缓存和 DB 的一致性问题:
缓存和 DB 的操作,不在一个事务中,可能只有一个操作成功,而另一个操作失败,导致不一致
二种更新策略
先删缓存,再更新数据库
我们会基于这个方案去实现缓存更新,但是不代表这个方案在并发情况下没问题 该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。
那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
上述情况就会导致不一致的情形出现
解决方案(数据库与缓存更新与读取操作进行异步串行化)
更新数据的时候,将操作任务,发送到一个队列中。
读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作任务,也发送同一个 队列中。
每个个队列可以对应多个消费者,每个队列拿到对应的消费者,然后一条一条的执行。