https://www.jianshu.com/p/4001f4373ce0
https://www.jianshu.com/p/b7585c0340e0
Redis为什么这么快
基于C语言实现,底层代码执行效率高,且依赖低
所有数据都存到内存中,没有IO操作
单线程模型,不涉及线程切换和锁处理,以及不同CPU之间的数据同步
使用IO多路复用模型,Linux中使用epoll的网络模型
支持的数据类型
字符串、哈希表、列表、集合和有序集合
列表
实现
压缩列表:类似与数组,是连续的存储,只不过每个元素的长度不一致。结构首先是元素总数,然后每个元素,每个元素内表明该元素的长度和实际内容。当列表内的单个数据和元素个数较小时,会采用压缩列表的存储形式。因为较长时涉及到位置的计算、数据的移动、重新分配空间等费时操作。
双向链表:当数据较多时,就采用双向链表的形式。
哈希表
实现
采用压缩列表和哈希表两种存储结构,当数据量较小时就采用压缩列表的形式,否则就采用哈希表的形式。对于哈希冲突,采用链表法解决,另外还根据负载因子动态的进行的缩容和扩容。因为缩容和扩容设计到数据的迁移和哈希值的重新计算,因此采用渐进式的方式,将数据的迁移分批进行,避免集中操作对性能造成很大影响,在分配新的空间之后,并不马上旧的空间进行回收,每次向新空间插入一个元素之后同时从旧空间取出一个元素重新计算器哈希值和位置,对其进行移动,然后将原来位置的元素删除,知道旧空间不存在元素位置,而此时查找元素时,回去新老两个空间查找。
集合
实现
根据存储元素和大小的不同,也是分为压缩列表和哈希表两种实现方式。
有序集合
又称为ZSET,在普通集合的基础上增加了一个分数,会根据集合来进行自动排序。
实现
根据存储元素和大小的不同,也是分为压缩列表和跳表加散列表两种实现方式。
跳表
其实是链表结构的二分查找实现,通过多链表每两个元素就建立一个索引,每两个索引再建索引,这样通过多层索引,达到二分查找的效率,采用的还是空间换时间的方案。当然也可以隔三个或四个元素建立索引,会减少索引空间,例如每个两个元素时,索引需要n-2个元素的空间;每隔三个元素需要n/2个空间的元素。
索引的更新:如果不断向某个索引下插入元素,那么就会退化成一个链表,所以要动态的更新索引。跟平衡二叉树的左右旋方式不同,跳表采用随机插入的方式,在插入一个新的元素时,生成一个随机数K,然后将元素也插入到第一到第K层的索引中。
散列表
使用跳表只解决了集合元素按照分数的有序问题,并不能实现根据key值的快速查找功能,这还需要散列表来实现。散列表中的各元素通过前后指针也是一个有序链表,当然链表也有多层索引又构成了跳表。从而实现了既可以根据分数得到有序集合,又可以通过key值快速得到value值。
持久化
RDB
全量保存某一个时刻的快照,因此保存一次的耗时较长。分为自动和手动两种方式,手动保存时是直接在主进程中进行,这个时候会阻塞外部请求,因此谨慎使用。
自动执行是通过fork一个子进程来进行来执行持久化动作的,此时主进程是不会阻塞的,而自动备份的频率是可以自定义的。
AOF
如果说RDB时全量持久化的话,RDB就是增量,每一个操作都对应一条aof日志,日志先保存在内存中,然后每隔一段时间比如1秒中保存到硬盘中,因为时顺序保存所以速度也是很快的。
aof文件会越来越大,为了避免这个问题,需要对文件进行重写。
1、新建一个临时文件,然后将内存中缓存数据转为命令写入该临时文件,而不是旧的aof日志,这些可以避免分析和合并工作。
2、重写时,主进程还是可以响应新的请求的,为防止重写失败,新请求要同时写入到新旧两个文件中
3、重写完成之后,将临时文件重命名为原来的aof文件名。
恢复
当redis重启时,会先检查是否存在aof文件,有就优先使用,因为aof文件的数据比较全和新,没有才会使用rdb文件。
混合持久化
aof文件中的数据虽然全,但是在重写时,需要将内存中数据转化成命令才能保存到文件中,并且在重启时还需要再将命令转化成数据,绕了一个弯子,因此在重写时可以直接将内存中数据保存为RDB文件,然后再在此基础上追加aof日志,这样在redis重启时恢复数据也快了很多。