写点什么

我画了 19 张图,彻底帮你搞定 Redis,mybatisgenerator 教程

用户头像
极客good
关注
发布于: 刚刚

这些事件绑定了回调函数,会调用 Redis 的处理函数进行处理。


[](


)Redis 底层数据结构


===============================================================================


1.Redis 有 5 种数据类型,包括:字符串、列表、集合、有序集合和字典。


2.Redis 底层的数据结构有 6 种,包括:动态字符串、双向链表、压缩列表(ziplist)、hash 表、跳表(skip list)和整数数组。


[](


)Redis 数据类型和底层数据结构有如下对应关系:


============================================================================================



[](


)1.字符串类型




底层数据结构是动态字符串。


[](


)2.列表




如果同时满足下面条件,就使用压缩列表,否则使用双向链表:


  • 列表中单个元素小于 64 字节

  • 列表中元素个数少于 512


压缩列表在内存中是一块儿连续的内存空间,结构如下:



压缩列表查找时间复杂度是 o(n)。


[](


)3.集合




如果同时满足下面条件,就使用有序整数数组,否则使用 hash 表:


  • 集合中元素都是整数类型

  • 集合中元素个数不超过 512 个


[](


)4.有序集合




如果同时满足下面 2 个条件,就使用压缩列表,否则使用跳表:


  • 集合中元素都小于 64 字节

  • 集合中元素个数小于 128 个


注意:有序集合还有一个 HASH 表用于保存集合中元素的分数,做 ZSCORE 操作时,查询的就是这个 HASH 表,所以效率很高。


跳表的结构如下:



如果不加索引,查找 10 这个数字需要查询 10 次,使用了二级索引,查找 10 这个数字需要 5 次,而使用一级索引,需要查询 3 次。


跳表的每一层都是一个有序链表,最下面一层保存了全部数据。跳表插入、删除、查询的时间复杂度是 o(logN)。跳表需要存储额外的索引节点,会增加额外的空间开销。


[](


)5.字典




如果同时满足下面 2 个条件,就使用压缩列表,否则使用 hash 表:


  • 字典中每个 entry 的 key/value 都小于 64 字节

  • 字典中元素个数小于 512 个


Redis 缓存淘汰策略


Redis 总共有 8 种淘汰策略,如下图:



volatile-lfu 和 allkeys-lfu 策略是 4.0 版本新增的:


  • lru:是按照数据的最近最少访问原则来淘汰数据,可能存在的问题是如果大批量冷数据最近被访问了一次,就会占用大量内存空间,如果缓存满了,部分热数据就会被淘汰掉。

  • **lfu:**是按照数据的最小访问频率访问次数原则来淘汰数据,如果两个数据的访问次数相同,则把访问时间较早的数据淘汰。


[](


)Redis 数据持久化


==============================================================================


Redis 持久化的方式有 2 种,一种是写后日志(AOF),一种是内存快照(RDB)。


[](


)1.AOF 日志




AOF 日志记录了每一条收到的命令,Redis 故障宕机恢复时,可以加载 AOF 日志中的命令进行重放来进行故障恢复。


AOF 有 3 种同步策略,如下图:



如果不是对丢失数据特别敏感的业务,推荐使用 everysec,对主线程的阻塞少,故障后丢失数据只有 1s。


[](


)2.RDB 快照




RDB 快照是一个内存快照,记录了 Redis 某一时刻的全部数据。


[](


)3.混合日志




从 Redis 4.0 开始,AOF 文件也可以保存 RDB 快照,AOF 重写的时候 Redis 会把 AOF 文件内容清空,先记录一份 RDB 快照,这份数据以"REDIS"开头。


记录 RDB 内容后,AOF 文件会接着记录 AOF 命令。故障恢复时,先加载 AOF 文件中 RDB 快照,然后回放 AOF 文件中后面的命令。


[](


)4.主从同步




Redis 主从同步时,主节点会先生成一份 RDB 快照发送给从节点,把快照之后的命令写入主从同步缓存区(replication buffer),从节点把 RDB 文件加载完成后,主节点把缓存区命令发送给从节点。


[](


)5.AOF 重写




AOF 日志是用记录命令的方式追加的,这样可能存在对同一个 key 的多条命令,这些命令是可以合并成 1 条的。比如对同一个 key 的多个 set 操作日志,可以合成一条。


[](


)6.阻塞点




AOF 重写和 RDB 快照执行的过程中,Redis 都会 Fork 一个子进程来执行操作,子进程执行过程中是不是阻塞主线程的。


但是要注意 2 点:


  • Fork 子进程的过程中,Redis 主线程会拷贝一份内存页表(记录了虚拟内存和物理内存的映射关系)给子进程,这个过程是阻塞的,Redis 主线程内存越大,阻塞时间越长。

  • 子进程和 Redis 主线程共用一块儿物理内存,如果新的请求到来,必须使用 copy on write 的方式,拷贝要修改的数据页到新的内存空间进行修改。如下图:



注意:如果开启了内存大页,每次拷贝都需要分配 2MB 的内存。


[](


)Redis 高可用


============================================================================


下图是一个“一主二从三哨兵”的架构图:



从图我们可以看到哨兵之间、哨兵和主从节点之间、哨兵和客户端之间都建立了连接。


如果主节点挂了,哨兵集群需要完成主从切换,如下图:



下面我们依次来聊一下这 4 个步骤。


[](


)1.判断主节点下线




当一个哨兵监控到主节点下线时,就会给其他哨兵发送确认命令,其他命令会根据自己的判断回复"Y"或"N"。


如果有 n/2+1 以上数量的哨兵都认为主节点下线了,才会判定主节点下线。这里的 n 是哨兵集群的数量。


n/2+1 这个参数由 quorum 参数配置,比如有 5 个哨兵,这里一般配置成 3。也可以配置成其他值。


[](


)2.选举新主节点




主节点被判定下线后,哨兵集群会重新选择新的主节点。


淘汰不稳定从节点:根据配置参数 down-after-milliseconds * 10 来淘汰。


down-after-milliseconds 表示主从节点断开时间,10 表示次数,如果从节点跟主节点断开时间超过 down-after-milliseconds 的次数达到了 10 次以上,从节点就被淘汰了。


slave-priority 参数:配置了从节点的优先级,选择从节点时哨兵会优先选择优先级高的从节点。


复制进度:Redis 有一个记录主从增量复制的缓存区叫 repl_backlog_buffer。


这是一个环形结构的缓冲区,如下图:



主节点有一个写偏移量 master_repl_offset,从节点也有一个偏移量 slave_repl_offset。


优先选择 slave_repl_offset 最接近 master_repl_offset 的从节点作为新的主节点。


所以,上图中偏移量为 114 的从节点优先被选为新的主节点。


ID 编号:优先级和参数都一样的情况下,ID 编号小的从节点优先被选为新主节点。


[](


)3.选举哨兵 Leader




第一个判断主节点下线的哨兵节点收到其他节点的回复并确定主节点下线后,就会给其他哨兵发送命令申请成为哨兵 Leader。


成为 Leader 的条件如下:


  • 收到赞成票必须大于等 quorum 值

  • 必须拿到半数以上的赞成票


如果集群配置了 5 个哨兵,quorum 的值设置为 3,其中一个哨兵节点挂了,很有可能会判断到主节点下线,但是因为选举不出哨兵 Leader 而不能切换。


如果集群有 2 个哨兵,其中一个挂了,那必定选不出哨兵 Leader。


下面的图展示了哨兵一成功当选 Leader 的过程:



[](


)4.主节点切换




【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码




选出新主节点和哨兵 Leader 后,哨兵 Leader 会执行主从切换的操作。


完成后会做一些事件通知:


  • 通知其他哨兵新主节点地址

  • 通知所有从节点新的主节点地址,从节点收到后向新主节点请求主从同步

  • 通知客户端连接新主节点


[](


)5.主从切换过程中请求处理




如果客户端的读请求会发送到从节点,可以正常处理。在客户端收到新主节点地址通知前写请求会失败。客户端可以采取一些应急措施应对主节点下线,比如缓存写请求。


为了能够及时获取到新主节点信息,客户端可以订阅哨兵的主节点下线事件和新主节点变更事件。


[](


)Redis 为什么变慢了


===============================================================================


Redis 变慢了的原因有很多,总结一下有 11 个,见下图:



从图中看出,Redis 变慢原因主要有两类:阻塞主线程和操作系统限制。


[](


)1.主线程阻塞




AOF 重写和 RDB 快照:前面已经讲过了,Redis 在 AOF 重写时,主线程会 Fork 出一个 bgrewriteaof 子进程。Redis 进行 RDB 快照时主线程会 Fork 出一个 bgsave 子进程。


这两个操作表面上看不阻塞主线程,但 Fork 子进程的这个过程是在主线程完成的。


Fork 子进程时 Redis 需要拷贝内存页表,如果 Redis 实例很大,这个拷贝会耗费大量的 CPU 资源,阻塞主线程的时间也会变长。


内存大页:Redis 默认支持内存大页是 2MB,使用内存大页,一定程度上可以减少 Redis 的内存分配次数,但是对数据持久化会有一定影响。


Redis 在 AOF 重写和 RDB 快照过程中,如果主线程收到新的写请求,就需要 CopyOnWrite。


使用了内存大页,即使 Redis 只修改其中一个大小是 1kb 的 key,也需要拷贝一整页的数据,即 2MB。在写入量较多时,大量拷贝就会导致 Redis 性能下降。


命令复杂度高:执行复杂度高的命令是造成 Redis 阻塞的常见原因。比如对一个 set 或者 list 数据类型执行 SORT 操作,复杂度是 O(N+M*log(M))。


bigkey 操作:如果一个 key 的 value 非常大,创建的时候分配内存会很耗时,删除的时候释放内存也很耗时。


Redis 4.0 以后引入了 layfree 机制,可以使用子进程异步删除,从而不影响主线程执行。用 UNLINK 命令替代 DEL 命令,就可以使用子进程异步删除。


Redis 6.0 增加了配置项 lazyfree-lazy-user-del,配置成 yes 后,del 命令也可以用子进程异步删除。


如果 lazyfree-lazy-user-del 不设置为 yes,那 Redis 是否采用异步删除,是要看删除的时机的。


对于 String 类型和底层采用整数数组和压缩列表的数据类型,Redis 是不会采用异步删除的。


从节点全量同步:从节点全量同步过程中,需要先清除内存中的数据,然后再加载 RDB 文件,这个过程中是阻塞的,如果有读请求到来,只能等到加载 RDB 文件完成后才能处理请求,所以响应会很慢。


另外,如果 Redis 实例很大,也会造成 RDB 文件太大,从库加载时间长。所以尽量保持 Redis 实例不要太大,比如单个实例限制 4G,如果超出就采用切片集群。


AOF 同步写盘:appendfsync 策略有 3 种:always、everysec、no,如果采用 always,每个命令都会同步写盘,这个过程是阻塞的,等写盘成功后才能处理下一条命令。


除非是严格不能丢数据的场景,否则尽量不要选择 always 策略,推荐尽量选择 everysec 策略,如果对丢失数据不敏感,可以采用 no。


内存达到 maxmemory:需要使用淘汰策略来淘汰部分 key。即使采用 lazyfree 异步删除,选择 key 的过程也是阻塞的。


可以选择较快的淘汰策略,比如用随机淘汰来替换 LRU 和 LFU 算法淘汰。也可以扩大切片数量来减轻淘汰 key 的时间消耗。


[](


)2.操作系统限制




使用了 swap:使用 swap 的原因是操作系统不能给 Redis 分配足够大的内存,如果操作其他开启了 swap,内存数据就需要不停地跟 swap 换入和换出,对性能影响非常大。


操作系统没有能力分配内存的原因也可能是其他进程使用了大量的内存。


网络问题:如果网卡负载很大,对 Redis 性能影响会很大。这一方面有可能 Redis 的访问量确实很高,另一方面也可能是有其他流量大的程序占用了带宽。


这个最好从运维层面进行监控。


线程上下文切换:Redis 虽然是单线程的,但是在多核 CPU 的情况下,也可能会发生上下文切换。


如果主线程从一个物理核切换到了另一个物理核,那就不能使用 CPU 高效的一级缓存和二级缓存了。


如下图所示:



为防止这种情况,可以把 Redis 绑定到一个 CPU 物理核。


磁盘性能低:对于 AOF 同步写盘的使用场景,如果磁盘性能低,也会影响 Redis 的响应。可以优先采用性能更好的 SSD 硬盘。


[](


)设计排行榜功能


==========================================================================

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
我画了19张图,彻底帮你搞定Redis,mybatisgenerator教程