一、Redis数据结构
string(字符串):整型 + 字符串
key: value
整数或者字符串,存入数据时会尝试强转 int,如果成功则为 int
还可以构建分布式锁 setNx
setNx:『SET if Not exists』(如果不存在,则 SET)的简写
因为加锁和过期时间设置非原子,存在设置超时时间失败情况,导致死锁
hash(哈希):哈希表 + 压缩表
key: {sKey1:value1,sKey2:value2...}
应用场景:
电商购物车:
list(列表):压缩表 + 双向链表
实现栈和队列结构
set(集合):哈希表 + 整数数组
收藏、点赞等基础功能 微博微信关注模型
zSet(sorted set:有序集合):压缩链表 + 跳表
二、Redis跳表
可以理解为一个可以进行二分查找的有序双向(redis改进)链表,因为是折半,所以中间空出一个元素
在原有的有序链表上面增加了多级索引链表(指向下一节点和下一级的原数据位置),通过索引来实现快速查找。
跳表不仅能提高搜索性能,同时也可以提高插入和删除操作的性能。有效降低了查询查询次数
redis是内存存储,不存在io的瓶颈,所以跳表的层数的耗时可以忽略不计,而且插入数据时不需要开销以平衡数据结构(写多)。而mysql数据库是持久化数据库,即是存储到磁盘上的,因此查询时要求更少磁盘IO,且mysql是读多写少的场景较多,显然B+树更加适合mysql。
三、Redis压缩链表
Ziplist 是一种内存紧凑型,经过特殊编码的双向链表,用来取代常规的双向链表
设计成了内存紧凑型,可以充分利用 CPU 高速缓存,并且可以给不同的数据类型进行编码,节省内存。
但是压缩链表也有缺点:
1. 保存的元素过多,查询会变慢。
2.插入或者删除一个元素时,需要重新分配内存。而且有可能会引发连锁更新。
四、Redis哈希冲突过多解决
redis 是维护一个全局哈希表进行键值的管理(其实就是一个数组),数组的每个元素称为一个哈希桶,哈希表是由多个哈希桶组成的,每个哈希桶中保存了键值对数据。
redis解决哈希冲突的方式,就是使用拉链法的链式哈希。链式哈希也很容易理解,就是指发生哈希冲突时,同一个哈希桶中的多个元素用一个链表来保存,它们之间依次用指针连接。
产生原因:hash冲突时,会在冲突的hash上拉出来一个链表,哈希冲突链上的元素只能通过指针逐一查找再操作,如果哈希表里写入的数据越来越多,
哈希冲突可能也会越来越多,这就会导致某些哈希冲突链过长,进而导致这个链上的元素查找耗时长,效率降低。
解决方式:
1.0 redis解决哈希冲突过多的方式——rehash
rehash 也就是增加现有的哈希桶数量,让逐渐增多的 entry 元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。
为了使 rehash 操作更高效,Redis 默认使用了两个全局哈希表:哈希表 1 和哈希表 2。一开始,当你刚插入数据时,默认使用哈希表 1,此时的哈希表 2 并没有被分配空间。
随着数据逐步增多,Redis 开始执行 rehash,这个过程分为三步:
1.0 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;
2.0 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;
3.0 释放哈希表 1 的空间。
到此,我们就可以从哈希表 1 切换到哈希表 2,用增大的哈希表 2 保存更多数据,而原来的哈希表 1 留作下一次 rehash 扩容备用。
这个过程看似简单,但是第二步涉及大量的数据拷贝,如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。
渐进式rehash:
为了避免以上问题,Redis 采用了渐进式 rehash。
简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求时,从哈希表 1 中的第一个索引位置开始,
顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,
再顺带拷贝哈希表 1 中的下一个索引位置的 entries。
这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。
五、Redis持久化
1.rdb 快照模式
保存策略:n秒内数据集至少有m个改动,则自动保存一次
优点:恢复数据快
缺点:当redis宕机可能会丢失临界数据
2.aof 日志模式
保存策略:
- 每次新加命令都会保存一次,非常慢,但非常安全
- 每秒保存一次,足够快,仅丢失1秒数据
- 将数据交给操作系统自行管理,更快也更不安全
**优点:**数据损失小
**缺点:**恢复数据很慢
aof重写:因为aof文件可能有 太多没用的指令,所以aof会定期根据内存的最新数据生成aof文件
注意,AOF重写redis会fork出一个子进程去做,不会对redis正常命令处理有太多影响
redis启动时如果既有rdb文件又有aof文件则优先选择aof文件恢复数据,因为aof一般来说数据更全一 点。
3.Redis4.0的混合持久化
重启 Redis 时,我们很少使用 RDB来恢复内存状态,因为会丢失大量数据。我们通常使用 AOF 日志重 放,但是重放 AOF 日志性能相对 RDB来说要慢很多,这样在 Redis 实例很大的情况下,启动需要花费很 长的时间。 Redis 4.0 为了解决这个问题,带来了一个新的持久化选项——混合持久化。
如果开启了混合持久化,AOF在重写时,不再是单纯将内存数据转换为RESP命令写入AOF文件,而是将 重写这一刻之前的内存做RDB快照处理,并且将RDB快照内容和增量的AOF修改内存数据的命令存在一 起,都写入新的AOF文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF文件才会进行改 名,原子的覆盖原有的AOF文件,完成新旧两个AOF文件的替换。 于是在 Redis 重启的时候,可以先加载 RDB 的内容,然后再重放增量 AOF 日志就可以完全替代之前的 AOF 全量文件重放,因此重启效率大幅得到提升。
1.为什么创建一个子进程而不是在主进程中创建一个线程呢?
(fork主进程,开启一个子进程执行RDB,子进程共享主进程的内存数据 + 页表,避免主线程收到影响)
六、Reidis管道与lua脚本
管道(Pipeline)
客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响 应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一 次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送,**redis必须在处理完所有命令前先缓 存起所有命令的处理结果。**打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。 pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信 息;也就是pipeline并不是表达“所有command都一起成功”的语义,管道中前面命令失败,后面命令 不会有影响,继续执行。
Redis Lua脚本
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1、减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器 上完成。使用脚本,减少了网络往返时延。这点跟管道类似。
2、原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。管道不是原子的,不过 redis的批量操作命令(类似mset)是原子的。
3、替代redis的事务功能:redis自带的事务功能很鸡肋,报错不支持回滚,而redis的lua脚本几乎实现了 常规的事务功能,支持报错回滚操作,官方推荐如果要使用redis的事务功能可以用redis lua替代。
注意,不要在Lua脚本中出现死循环和耗时的运算,否则redis会阻塞,将不接受其他的命令, 所以使用 时要注意不能出现死循环、耗时的运算。redis是单进程、单线程执行脚本。管道不会阻塞redis。
七、Redis对于过期键有三种清除策略
被动删除:读、写一个过期的key时触发
主动删除:定期清理已过期key
当前内存超过maxmemory先定个时,触发主动清除测率
八、redis 数据过期策略
惰性删除:过期后只有在查询的时候才会进行过期时间的校验,如果过期,则删除,占内存
定期删除:配置固定时间段进行定时一定量的清除(SLOW + FAST)
SLOW模式:定时任务,执行频率默认 10 hz, 每次执行时间不超过 25 ms,通过配置文件指定执行频率
FAST模式:执行频率不固定,但是执行间隔一般低于 2 ms,执行耗时不超过 1 ms
可以通过限制执行频率和执行时长减缓对CPU的影响
一般 惰性删除 + 定期删除 结合使用
九、Redis 数据淘汰策略(8种策略)
全体key 与 设置了定时的key
base random lru lfu
noevication(base): 不淘汰任何key,内存满了则不允许数据的写入新数据了,这是默认执行的策略
volatile-ttl(base): 对设置了 TTL 的 key,比较其 key 的剩余 TTL 值,TTL 越小优先被淘汰
allkeys-random(random):对全体 key 进行随机淘汰
volatile-random(random):对设置了 TTL 的 key,进行随机淘汰
allkeys-lru(lru):对全体 key,进行基于 LRU 算法(least Recently Used 最近最少使用,当前时间减去最后一次访问时间,值越大淘汰优先级越高)淘汰
volatile-lru(lru):对设置了 TTL 的 key,进行基于 LRU 算法(least Recently Used 最近最少使用,当前时间减去最后一次访问时间,值越大淘汰优先级越高)淘汰
allkeys-lfu(lfu):对全体 key,进行基于 LFU 算法(least Frequently Used 最少频率使用,统计每个key的访问频率,频率越低淘汰优先级越高)淘汰
volatile-lfu(lfu):对设置了 TTL 的 key,进行基于 LFU 算法(least Frequently Used 最少频率使用,统计每个key的访问频率,频率越低淘汰优先级越高)淘汰
使用建议:
一般使用 allkeys-lru 的数据淘汰策略;
如果有明显的冷热数据的区分,建议使用 allkeys-lru;
如果没有明显的冷热数据的区分,建议使用 allkeys-random,进行随机选择淘汰;
如果有置顶的需求,也可以通过 使用 volatile-lru ,同时不进行置顶 key 的过期时间 TTL 设置的方式来实现
如果有短时的高频访问区分,可以通过 allkeys-lfu 或者 volatile-lfu 来进行实现
十、Redis的主从复制
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个SYNC命 令(redis2.8版本之前的命令)给master请求复制数据。
master收到SYNC命令后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间, master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以 后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加 载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多 个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送 给多个并发连接的slave。 当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,master和slave断 开重连后支持部分复制。
部分复制就是使用psync增加了offset,如果此offset在master的repl_back_buffer中则将部分复制,否则全量
单节点redis的并发能力是有上限的,要进一步提高redis的并发能力就需要搭建主从集群,实现读写分离,一般来说是一主多从,主节点负责写,从节点负责读
全量同步:
1.0 从节点请求主节点同步数据(replication id, offset)
2.0 主节点负责判断是否是第一次请求,是第一次就会与从节点同步版本信息(replication id, offset)
3.0 主节点执行 bgsave ,生成一个RDB快照文件后,发送给从节点去执行
4.0 在RDB生成期间,主节点会以命令的方式记录到缓冲区(一个日志文件)
5.0 把上述生成之后的命令日志文件发送给从节点进行同步
增量同步:
1.0 从节点请求主节点同步数据,主节点负责判断是否是第一次请求,判断不是第一次请求就获取从节点的 offset 值
2.0 主节点从命令日志中获取 offset 值之后的数据,发送给从节点进行数据同步
replication id:简称 replid,是数据集的标记,id 一致说明是同一个数据集。每一个 master 都有一个唯一的 replid ,slave 则会继承 master 节点的 replid
offset:偏移量,随着记录在 repl_baklog 中的数据增多而主键增大。从节点完成同步时也会记录当前同步的 offset 。
如果从节点的 offset 小于主节点的 offset ,说明 slave 数据落后于主节点,需要进行更新
从节点执行 replicaof 命令 或者 从节点重启后,向主节点发送一个数据同步的请求(携带数据集标记 id ,数据偏移量),主节点先判断是否是首次同步,replid 是否一致
全量同步:基于 RDB 模式的全量同步
若为首次同步,则发送主节点的数据版本信息给从节点,从节点把主节点的数据版本信息保存至本地,
主节点开始执行 bgsave 命令,生成 RDB 文件,然后将其 RDB 文件发送给从节点,
从节点获取到主节点发送过来的 RDB 文件后,会清空本地的数据,然后加载主节点发送过来的 RDB 文件,
若在从节点同步过程中,主节点又有增量修改的话,主节点会将这一块增量修改打包成一个 repl_baklog 文件,发送给从节点,从节点同步完 repl_baklog 后。
增量同步:
若不为首次同步,则为增量同步,主节点会将这一块增量修改打包成一个 repl_baklog 文件(由 replid 和 offset 决定),发送给从节点,从节点同步完 repl_baklog 后。
全量同步:
1.0 从节点请求主节点同步数据(replication id, offset)
2.0 主节点负责判断是否是第一次请求,是第一次就会与从节点同步版本信息(replication id, offset)
3.0 主节点执行 bgsave ,生成一个RDB快照文件后,发送给从节点去执行
4.0 在RDB生成期间,主节点会以命令的方式记录到缓冲区(一个日志文件)
5.0 把上述生成之后的命令日志文件发送给从节点进行同步
增量同步:
1.0 从节点请求主节点同步数据,主节点负责判断是否是第一次请求,判断不是第一次请求就获取从节点的 offset 值
2.0 主节点从命令日志中获取 offset 值之后的数据,发送给从节点进行数据同步
十一、Redis哨兵高可用架构
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。是一个单独服务
哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过 sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
缺点:
在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态,如果master节点异常,则会 做主从切换,将某一台slave作为master,哨兵的配置略微复杂,并且性能和高可用性等各方面表现一般,特别是在主 从切换的瞬间存在访问瞬断的情况,而且哨兵模式只有一个主节点对外提供服务,没法支持很高的并发,且单个主节点 内存也不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率
哨兵leader选举流程
当一个master服务器被某sentinel视为客观下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故 障转移工作。每个发现master服务器进入客观下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举 是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。如 果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新 的master,这个选举过程跟集群的master选举很类似。 哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就 是哨兵leader了,可以正常选举新master。 不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。
主从模式保证不了集群的高可用。
哨兵基于心跳机制来监测服务状态,每隔1秒发送一个 ping 命令
主观下线:某个哨兵发现某个 redis 服务未在固定时间内响应,则认为是主观下线
客观下线:多个哨兵(一个数据阈值quorum,可以设置也有默认值,最好超过一半)
发现某个 redis 服务未在固定时间内响应,则认为是客观下线
哨兵机制会自动实现主从集群的自动故障恢复
监控:全程检查主从节点的是否正常工作
自动故障恢复:当主节点故障之后,会自动选举一个从节点为主节点,即使原主节点故障恢复之后会降级为从节点
通知:哨兵充当 redis 客户端的服务发现,当集群故障转移时,会通知 redis 客户端最新的集群信息
redis 主节点选举规则:
1 首先判断主从节点断开时间长短,如超过指定值就排除该从节点
2 判断一个自定义的优先值配置 slave-prority 值,值越小优先级越高
3 如果 slave-prority 一样,则判断从节点的 offset 值,值越大则优先级越高,代表着丢失的数据越小
4 如果 offset 一样,则判断从节点的 运行id 大小,越小优先级越高
十二、Redis集群
一般公司标配,1主1从 + 哨兵,单节点的内存尽量不超过10G
redis集群是一个由多个主从节点群组成的分布式服务器群,它具有复制、高可用和分片特性。Redis集群不需要 sentinel哨兵也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可 水平扩展,据官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。redis集群的性能和高可用性均优于 之前版本的哨兵模式,且集群配置非常简单。
redis集群需要至少要三个master节点,
Redis Cluster 将所有数据划分为 16384 个 slots(槽位),每个节点负责其中一部分槽位。槽位的信息存储于每个节点 中。
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地。这样当客户 端要查找某个 key 时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需 要纠正机制来实现槽位信息的校验调整。
槽位定位算法
Cluster 默认会对 key 值使用CRC16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体 槽位。 HASH_SLOT = CRC16(key) mod 16384
Redis集群节点间的通信机制:redis cluster节点间采取gossip协议进行通信 维护集群的元数据有两种方式:集中式和gossip
1)主从模式(读写分离 一主多从)
解决高并发问题
单节点redis的并发能力是有上限的,要进一步提高redis的并发能力就需要搭建主从集群,实现读写分离,一般来说是一主多从,主节点负责写,从节点负责读
replication id:简称 replid,是数据集的标记,id 一致说明是同一个数据集。每一个 master 都有一个唯一的 replid ,slave 则会继承 master 节点的 replid
offset:偏移量,随着记录在 repl_baklog 中的数据增多而主键增大。从节点完成同步时也会记录当前同步的 offset 。
如果从节点的 offset 小于主节点的 offset ,说明 slave 数据落后于主节点,需要进行更新
从节点执行 replicaof 命令 或者 从节点重启后,向主节点发送一个数据同步的请求(携带数据集标记 id ,数据偏移量),主节点先判断是否是首次同步,replid 是否一致
全量同步:基于 RDB 模式的全量同步
若为首次同步,则发送主节点的数据版本信息给从节点,从节点把主节点的数据版本信息保存至本地,
主节点开始执行 bgsave 命令,生成 RDB 文件,然后将其 RDB 文件发送给从节点,
从节点获取到主节点发送过来的 RDB 文件后,会清空本地的数据,然后加载主节点发送过来的 RDB 文件,
若在从节点同步过程中,主节点又有增量修改的话,主节点会将这一块增量修改打包成一个 repl_baklog 文件,发送给从节点,从节点同步完 repl_baklog 后。
增量同步:
若不为首次同步,则为增量同步,主节点会将这一块增量修改打包成一个 repl_baklog 文件(由 replid 和 offset 决定),发送给从节点,从节点同步完 repl_baklog 后。
全量同步:
1.0 从节点请求主节点同步数据(replication id, offset)
2.0 主节点负责判断是否是第一次请求,是第一次就会与从节点同步版本信息(replication id, offset)
3.0 主节点执行 bgsave ,生成一个RDB快照文件后,发送给从节点去执行
4.0 在RDB生成期间,主节点会以命令的方式记录到缓冲区(一个日志文件)
5.0 把上述生成之后的命令日志文件发送给从节点进行同步
增量同步:
1.0 从节点请求主节点同步数据,主节点负责判断是否是第一次请求,判断不是第一次请求就获取从节点的 offset 值
2.0 主节点从命令日志中获取 offset 值之后的数据,发送给从节点进行数据同步
2)哨兵模式(sentinel)
解决高并发、高可用问题
主从模式保证不了集群的高可用。
哨兵基于心跳机制来监测服务状态,每隔1秒发送一个 ping 命令
主观下线:某个哨兵发现某个 redis 服务未在固定时间内响应,则认为是主观下线
客观下线:多个哨兵(一个数据阈值quorum,可以设置也有默认值,最好超过一半)
发现某个 redis 服务未在固定时间内响应,则认为是客观下线
哨兵机制会自动实现主从集群的自动故障恢复
监控:全程检查主从节点的是否正常工作
自动故障恢复:当主节点故障之后,会自动选举一个从节点为主节点,即使原主节点故障恢复之后会降级为从节点
通知:哨兵充当 redis 客户端的服务发现,当集群故障转移时,会通知 redis 客户端最新的集群信息
redis 主节点选举规则:
1 首先判断主从节点断开时间长短,如超过指定值就排除该从节点
2 判断一个自定义的优先值配置 slave-prority 值,值越小优先级越高
3 如果 slave-prority 一样,则判断从节点的 offset 值,值越大则优先级越高,代表着丢失的数据越小
4 如果 offset 一样,则判断从节点的 运行id 大小,越小优先级越高
3)分片模式
高并发写、海量数据存储、高并发、高可用。各个主节点含有哨兵的功能
集群中有多个主节点(主节点可下行多个从节点),每个主节点存不同的内容,主节点之间通过心跳 ping 进行监控服务的监控状态
客户端可以访问集群中的任意节点,主节点会自动转发到正确的节点上去
redis 引入了 hash 槽(16384 - 2kb左右)会把每个 key 进行哈希计算,然后进行 16384 取余,得到得到余值匹配到节点上的槽值,然后寻找插槽所在的实例
集中式: 优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的 时候立即就可以立即感知到;不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力。
网络抖动 :真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现 象,突然之间部分连接变得不可访问,然后很快又恢复正常。 为解决这种问题,Redis Cluster 提供了一种选项clusternodetimeout,表示当某个节点持续 timeout 的时间失 联时,才可以认定该节点出现故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换 (数据的重 新复制)。
Redis集群选举原理分析:
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有 多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
1.slave发现自己的master变为FAIL
2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个epoch只发 送一次ack
4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其 中一个挂了,只剩一个主节点是不能选举成功的)
6.广播Pong消息通知其他集群节点。
从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待FAIL状态在 集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票 •延迟计算公式:
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
•SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持 有最新数据的slave将会首先发起选举(理论上)。
Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
reidis的高并发分布式锁
锁续命,子线程定时器,
Redisson实现分布式锁
4)集群脑裂问题
当主节点被哨兵进行客观下线之后,此时主节点与新的主节点转化有个时间差(此时哨兵还未发送新集群信息的通知),
-由于此时客户端连接的还是旧主节点,此时进行数据的写入修改时是不会同步到集群上去的,
-并且此时旧主节点恢复了心跳通讯之后会被强制降级为从节点(客观下线之后会进行重新选举主节点),
-旧主节点会清空数据,重新同步新主节点的数据,此时就会造成客户端连接未切换时修改的数据得不到更新造成丢失
-大概率规避方式:
redis的配置参数:
min-replicas-to-write X # 与主节点通信的从节点数量 X(可以为1个从节点) 必须大于等于该值主节点,否则主节点拒绝写入。
min-replicas-max-lag Y # 主节点与从节点通信的 ACK 消息延迟必须小于 Y(可以为5秒) ,主从同步延迟时间
这两个配置项必须同时满足,不然主节点拒绝写入。
5)Redis集群数据倾斜
以数据行为的角度划分:
数据增删改倾斜,数据访问倾斜
数据增删改倾斜:
根本原因是:数据在各个redis实例上分布不均匀。
1.0 存在bigkey
对于bigkey的操作通常会造成实例IO线程阻塞,使该实例对数据请求处理效率严重下降。
常用的做法是对bigkey进行"化整为零",在业务层面生成数据的时候,尽量避免把过多的数据保存到同一个键中
2.0 HashTag使用不当
HashTag:使用方式是 在 key中添加一对花括号 {},这个 {} 可以将key的一部分内容包裹起来,而redis server会对于加上{}的key进行识别,并进行特殊处理。
3.0 slot(槽点)分配不均匀
如果slot在实例上分配不均匀的话,使某个或者某些实例上分配过多的slot,那么这些实例上被分配数据量也会更多一些,也就会产生类似HashTag一样的数据倾斜问题。
对于由于slot分配不均导致的数据倾斜问题,可以在slot分配前,通过人工分配的方式,来避免多个slot分配到同一个实例的问题,如果一个集群中slot已经分配完毕,
可以通过redis提供的运维工具和相应的运维命令,来查看redis集群中,slot的分配情况,如果存在某些实例上slot分配过多的情况,
那么可以将这些过多的slot转移到其他实例上。
数据访问倾斜:
热点数据问题,通常采用的解决方案是多副本冗余。
我们把热点数据复制多份,在每一个数据副本的 key 中增加一个随机前缀,让它和其它副本数据不会被映射到同一个 Slot 中。
这样一来,热点数据既有多个副本可以同时服务请求,同时,这些副本数据的 key 又不一样,会被映射到不同的 Slot 中。
在给这些 Slot 分配实例时,我们也要注意把它们分配到不同的实例上,那么,热点数据的访问压力就被分散到不同的实例上了。
这样可以有效解决数据访问倾斜问题。
十三、Redisson
是一个在Redis的基础上实现的Java驻内存数据网格 主要是保证AP
核心是通过 lua 脚本,设置一个 哈希类型的 key1 - 线程idkey - value,以及设置一个过期时间,因为一个 lua 脚本就是一个原子操作
加锁过程:
加锁请求 ---> 查询Key是否存在 ---> 若不存在,创建锁并设置过期时间 ---> 返回空值 nil ---> 加锁流程结束
---> 若存在 ---> 查看 value 是否匹配 ---> 若匹配 ---> 执行重入 +1 并重置锁过期时间 ---> 加锁流程结束
---> 若不匹配 ---> 表示锁被其它线程占用 ---> 返回锁的过期时间 ---> 加锁流程结束
解锁过程:
解锁请求 ---> 查询Key是否存在 ---> 若不存在,取消刷新锁续期任务
---> 若存在 ---> 修改重入次数减 1 ---> 判断重入次数为0 ---> 删除锁 ---> 解锁流程结束
---> 判断重入次数不为0 ---> 重置过期时间
锁续期:
在获取锁的时候不能传 leaseTime 过期时间,否则 不会创建看门狗
Redisson默认加锁30秒,每隔10秒刷新加锁时间
watchdog的延时时间 可以由 lockWatchdogTimeout指定默认延时时间,但是不要设置太小
Redisson是通过Future和Timeout功能来实现异步延时
分布式锁主节点加锁后锁丢失问题:
如果一定得要确保锁加成功,则需要强一致性,这样还不如用zk的分布式锁,同步的间隔得要足够断或者甚至所有节点同步成功之后,才返回加锁成功才行
十四、Redis分布式锁的一种自定义实现
主要是利用 setNx(set if no exists 如果不存在 则set)命令来实现,可重入锁
获取锁
set key value NX EX 10
释放锁
del key
或者到达过期时间
十五、缓存穿透&缓存击穿&缓存雪崩
缓存穿透:请求不存在的 redis key,然后直接查 DB,导致每次此场景的请求都会查询 DB
缓存击穿:短时间大量请求一个已过期的 redis key
缓存雪崩:短时间内大量redis key过期,或者redis宕机,导致大量请求查询DB
缓存穿透:高并发时缓存无数据,数据库无数据,所有请求全部落到db上面,导致db压力过大
1.做接口校验,将没有的不规范的请求过滤掉
2.缓存值为null的请求,时间设置短一点
缓存击穿:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
1.设置热点数据永不过期
2.加互斥锁,或redis的incr
3.设置热点数据过期时间,加一个随机数
缓存雪崩: 缓存雪崩是指缓存中数据大批量到过期时间,redis宕机,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,
缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
1.限流降级,
2.高可用集群
十六、Redis执行快的原因
1 redis 是基于内存操作的,执行速度非常快
2 采用了单线程,避免了不必要的线程上下文切换可竞争条件,多线程还需要考虑线程安全问题
3 使用了 I/O 多路复用模型
3.1 一般 IO :用户缓冲区 ---> 内核缓冲区 ---> 硬件设备
linux 中有三种实现方式:
select: 遍历去处理消息,只会通知用户进程有就绪状态,不会告知是哪一个
poll: 遍历去处理消息,只会通知用户进程有就绪状态,不会告知是哪一个
epoll(linux独有): 通知用户进程有就绪状态,并且告知是哪一个消息
redis 网络模型:
·---> 连接应答处理器(tcpAcceptHandler)
事件 ---> IO多路复用 + 事件派发机制 ->·---> 命令请求处理器(readQueryFromClient) ---------------|
·---> 命令回复处理器(sendReplyToClient)(多线程) |
|-------------------------------------------------| |
| V
缓冲区 <--- 选择并执行命令,把结果写入缓冲队列(串行执行) <--- 把数据转换为redis命令(多线程) <--- 接收请求数据(多线程)
十七、Redis冷热备份
- Redis冷备用: Redis冷备用是指在系统出现故障时,使用备份数据来恢复系统,这种备份数据称为“冷备份”。冷备份可以保证系统恢复后的数据一致性,但是需要一定的时间来恢复数据,因此不适合用于高可用性的系统。
- Redis热备用: Redis热备用是指在系统出现故障时,使用另一台服务器上的数据来恢复系统,这种备份数据称为“热备份”。热备份可以在不停止系统的情况下进行,因此可以很快恢复数据,而且可以更好地保证系统的高可用性。 启用Rsync,配置Rsync,使用crontab定时任务定时备份,以及设定数据存储策略。有了这个过程,可以有效保护Redis数据,以避免灾难发生或丢失重要数据。
- 优势和不足: Redis冷备用和热备用都有它们的优势和不足,冷备用可以更好地保证数据的一致性,但是恢复数据需要花费更多的时间;而热备用可以更快地恢复数据,但是数据的一致性可能无法得到保证。
十八、双写一致性(视业务情况而定,强一致性和弱一致性)
1 强一致性:性能较差 .使用redisson读写锁进行同步更新,锁 + 事务的范围得要含DB的写入操作以及redis的更新操作,保证其原子性
2.最终一致性(允许延时):性能较好.
异步更新:
1. 使用mq异步更新:DB库更新成功之后,写入mq,进行异步更新
2. 使用线程池的异步线程更新:DB库更新成功之后,从线程池中取出线程,进行异步更新
3. 使用mysql的binlog进行更新:DB库更新成功之后,使用Canal或者flink cdc进行异步更新
1 强一致性:性能较差
使用redisson读写锁进行同步更新,锁 + 事务的范围得要含DB的写入操作以及redis的更新操作,保证其原子性
1. 共享锁:读锁readLock,加锁之后,其它线程可以共享读操作,代码示例如下:
RReadWriteLock rrwLock = redissonClient.getReadWriteLock("key");
RLock readLock = rrwLock.readLock();
2. 排他锁:独占锁writeLock,加锁之后,阻塞其它线程的读写操作,代码示例如下:
RReadWriteLock rrwLock = redissonClient.getReadWriteLock("key");
RLock writeLock = rrwLock.writeLock();
2 最终一致性(允许延时):性能较好
异步更新:
1. 使用mq异步更新:DB库更新成功之后,写入mq,进行异步更新
2. 使用线程池的异步线程更新:DB库更新成功之后,从线程池中取出线程,进行异步更新
3. 使用mysql的binlog进行更新:DB库更新成功之后,使用Canal或者flink cdc进行异步更新
其他问题
1.redis和mysql如何保证数据一致性
答:1.采用事务+锁的机,保证原子性
2.采用最终一致性:mq
2.redis为什么那么快?
答:1.网络请求采用多路复用设计
2.数据操作采用单线程,避免了上下文切换
3.基于内存
3.redis存在线程安全问题吗?为什么?
答:redis操作数据是单线程的,线程是安全的。虽然6.0增加了多线曾机制,但是他的多线程是用来处理网络io事件的
4.redis的内存淘汰算法和原理
1.采用随机random算法(随机移除),LRU算法(移除最近很少使用的key),LFU算法(移除最近很少使用的key),TTL算法(移除更早过期时间key有限移除)
5.分布式锁的理解,以及分布式锁定实现
分布式锁是垮进程跨机器节点的互斥锁,可以保证多机器节点对于共享资源访问的排他性。他和线程锁的本质都是一样的。但是生命周期不同,线程锁是单进程多线程使用,分布式锁是多进程多机器节点使用
1.redis锁超时怎么办? 看门狗续期
2.redis主从切换导致锁失效怎么办 ?redis提供了一个redlock的解决办法。
本文由 zzpp 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2024/09/12 09:04