声明:这一系列关于redis的文章都是基于redis5.0.0。因为redis在版本迭代过程中为了追求更好的性能以及更优的内存使用会不断的进行优化,甚至连底层的数据结构的数据体都进行了较大的改变,而5.0.0版本的改变也是比较大的,所以在一些技术点的描述上可能和大家现在所用的redis的使用有出入,希望大家可以正确使用!
简介
Redis是一个使用ANSI C编写的,开源的,支持网络的,基于内存的,可选持久化的键值对存储系统,以出色的性能著称,官方提供的数据是可以支持100000以上的QPS。
redis的优点
- redis是内存型数据库
redis绝大部分的命令处理知识单纯的内存操作,因此读写速度比磁盘型的快的多 - 工作模式为单线程
实际上一个正在运行的redis Server肯定不止一个线程,但只有一个线程来处理网络请求,避免了不必要的上下文切换,同时不存在加锁/释放锁等同步操作。而其他的功能,如持久化、异步删除、集群数据同步等,也是由其他的线程执行 - redis中的数据结构是专门设计的
redis中的值支持多数数据类型的存储,且是二进制安全的。并且redis的数据结构都加入了专门的设计,使得增、删、改、查等操作相对简单 - redis使用多路I/O复用模型,可以高效处理大量并发连接
- redis支持持久化
- Redis支持数据持久化,可以采用RDB、AOF、RDB&AOF三种方案。计算机重启后可以在磁盘中进行数据恢复。
- redis支持主从结构
redis支持主从结构,可以利用从实例进行数据备份。
其中前四项也是redis为什么具有高性能的原因。
数据结构和对象
众所周知,redis以高性能著称,毫无疑问,redis的高性能一定要有一些自己设计的特殊、易用且安全的数据结构作为支撑,来满足不同场景下的存储需求。下面我们就对redis常使用的几种数据结构进行详解,从中大家可以看到redis是如何使用这些数据结构,选择这些数据而不使用市面已存在的类似数据结构的原因以及这些数据结构是如何保证redis的高性能的。
我们知道Redis是key-value形式的数据库。键值对中的键可以是字符串、整型、浮点型等,且键是唯一的。键值对中的值的类型可以是String、Hash、List、Set、SertedSet,而这五种对象又是使用不同的底层数据结构实现的。redis提供六种基本的数据结构:简单动态字符串、整数集合、压缩列表、跳跃表、字典、quicklist。
简单动态字符串(SDS)
简单动态字符串(SDS)是Redis的基本数据类型之一,用于存储字符串和整型数据。详见:
redis之动态字符串(SDS)
字典
字典又称散列表,是用来存储键值(key-value)对的一种数据结构。但是C语言中并没有字典这种数据结构,Redis是K-V型数据库,整个数据库使用字典来存储的,所以了解Redis如何实现字典,字典的基本操作与应用有哪些,对于我们全面了解Redis都至关重要。详情请见:redis之字典
跳跃表
有序集合在生活中比较常见,如根据成绩对学生进行排名、根据得分对游戏玩家进行排名等。对于有序集合的底层实现,可以使用数组、链表、平衡树等。但数组不便于元素的插入和删除;链表的查询效率又太低,需要遍历所有元素;平衡树或者红黑树等结构虽然效率高但是实现复杂。Redis采用了一种新型的数据结构----跳跃表。跳跃表的效率堪比红黑树,然而其实现却远比红黑树简单。详见Redis之跳跃表
整数集合
暂略,后续补充
压缩列表
暂略,后续补充
quicklist
暂略,后续补充
持久化
详见redis之持久化.
复制原理
详见redis之主从复制.
事件
暂略,后续补充
事务
暂略,后续补充
内存淘汰机制
过期删除策略
在了解Redis的过期删除策略之前,我们先了解一下Redis是如何保存过期时间的。在Redis中,redisDb结构的expires字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典:
- 过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也就是某个数据库键)。
- 过期字典的值是一个long,long类型的整数,这个整数保存了键所指向的数据库键的过期时间----一个毫秒精度的UNIX时间戳。
常见的三种删除策略:
- 定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
优点:对内存是最友好的,因为定时器的存在,可以非常及时的删除过期键,释放内存空间。
缺点:对CPU时间最不友好,在过期键比较多的情况下会占用相当一部分CPU时间,甚至会阻碍正常的命令执行,另外,创建一个定时器需要用到Redis服务器的时间时间,,而当前时间事件的实现方式----无序链表,查找一个事件的时间复杂度O(N)----并不能高效的处理大量时间事件。所以,要让Redis服务器创建大量的定时器,从而实现定时删除策略,在现阶段来说并不现实。 - 惰性删除
放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
优点:对CPU时间来说是最友好的:程序只会在取出键时才对键进行过期检查,这可以保证删除过期键的操作只会在非做不可的情况下进行,并且删除的目标仅限于当前处理的键,这个策略不会在删除其他无关的过期键上花费任何CPU时间。
缺点:对内存是最不友好的:如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么它们也许永远也不会被删除(除非用户手动执行FLUSHDB)。 - 定期删除
每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则有算法决定。
这是对定时删除和惰性删除的折中方案,为什么这么说呢?因此定期检查过期键是定时删除的特点,但是每次又不是检查所有的key,也就是说每次不会删除所有的过期key,这又是惰性删除的特点。定期删除策略的难点是确定删除操作执行的时长和频率。
下面我们重点了解一下Redis中的过期删除策略。Redis服务器实际使用了惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存之间取得平衡。具体实现如下:
Redis中惰性删除策略的实现
所有读写数据库的Redis命令在执行之前都会调用expireIfNeeded函数对输入键进行检查:
- 如果输入键已经过期,那么expireIfNeeded函数将输入键从数据库中删除。
- 如果输入键未过期,那么expireIfNeeded函数不做动作。
expireIfNeeded就像是一个过滤器,它可以在命令真正执行之前,过滤掉过期的输入键,从而避免命令接触到过期键。
Redis中定期删除策略的实现
每当Redis的服务器周期性操作serverCron函数执行时,activeExpireCvcle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。
activeExpireCvcle函数的工作模式可以总结如下:
- 函数每次运行时,都从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
- 全局变量current_db会记录当前activeExpireCvcle函数检查的进度,并在下次activeExpireCvcle函数调用时,接着上一次的进度进行处理。
- 随着activeExpireCvcle函数的不断执行,服务器中的所有数据库都会被检查一遍,这时函数将current_db变量重置为0,然后再次开始新一轮的检查工作。
AOF、RDB和复制功能对过期键的处理
生成RDB文件
在执行SAVE命令或者BGSAVE命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件。
载入RDB文件
在启动Redis服务器时,如果服务器开启了RDB功能,那么服务器将对RDB文件进行载入:
- 如果服务器以主服务器模式运行,那么在载入RDB文件时,程序会对文件中保存的键进行检查,未过期的键会被载入到数据库中,而过期键则会被忽略,所以过期键对载入RDB文件的主服务器不会造成影响。
- 如果服务器以从服务器的模式运行,那么在载入RDB文件时,文件中保存的所有键,不论是否过期,都会被载入到数据库中。不过,如果主从进行完全从同步时,从服务器的数据库会被清空,所以,此时过期键对载入RDB文件耳朵从服务器不会造成任何影响。
AOF文件写入
当服务器以AOF持久化模式运行时,如果数据库中的某个键已经过期,但它还没有被惰性删除或者定期删除,那么AOF文件不会因为这个过期键而产生任何影响。
当过期键惰性删除或定期删除之后,程序会向AOF文件追加一条DEL命令,来显示地记录该键已被删除。
AOF重写
和生成RDB文件时类似,在执行AOF重写的过程中,程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中。
复制
当服务器运行在复制模式下时,从服务器的过期键删除由主服务器控制:
- 主服务器在删除一个过期键之后,会显示地想所有从服务器发送一个DEL命令,告知从服务器删除这个过期键。
- 从服务器在执行客户端发送的读命令时,即时碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键。
- 从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键。
通过由主服务器来控制从服务器统一地删除过期键,可以保证主从服务器数据的一致性。
排序
暂略,后续补充