Redis
redis为什么这么快
一方面是因为它是内存数据库,所有操作都在内存中实现,内存的访问速度非常快。
另一方面,归功于它的数据结构,高效的数据结构是redis快速处理数据的基础
redis有哪些数据类型
redis有string,list,hash,sorted set,set类型,其中除了string外都是集合数据类型,一个键对应了一个集合
它们的底层实现如下:
键和值之间使用的是什么结构组织
键和值之间是使用的哈希表的形式组织起来的,使用了一个哈希表来保存所有的键值对。我们也称它为全局哈希表。
那么既然除string类型的四种数据类型的值是集合,那么本质是数组的哈希表是如何来存储他们的呢?
其实数组的每个元素都是一个哈希桶,它保存的是两个指针,一个指向实际的键和值,这样一来,即使值是一个集合,也可以通过指针找到。
这个查找过程主要依赖于哈希计算,和数据量的多少并没有直接关系。也就是说,不管哈希表里有 10 万个键还是 100 万个键,我们只需要一次计算就能找到相应的键。
哈希冲突
哈希冲突就是有一些 key 的哈希值对应到了同一个哈希桶中
解决方法:链式哈希,也就是同一个哈希桶的元素按照链表存储,它们之间用指针来链接。
问题:链表必须要通过指针逐一查找,效率低,速度慢,因此要避免过长的链表
解决方案:rehash操作,也就是增加现有的哈希桶数量
rehash
redis默认使用两个全局哈希表,哈希表1和哈希表2。
步骤:
1.将哈希表2的容量扩容,比如扩容成哈希表1的两倍
2.将哈希表1的数据重新映射拷贝到哈希表2中
3.释放哈希表1的空间
但是这其中的第二个步骤涉及到大量的拷贝操作,如果redis一次性进行所以拷贝操作会线程阻塞,无法服务其他请求。因此采用了渐进式rehash。
简单说就是在拷贝数据的时候正常处理请求,每处理一个请求,就从哈希表1的第一个索引开始,顺带把这个索引位置下的所有entry拷贝到哈希表2中去,第二个请求就从哈希表1的下一个索引开始拷贝。这样就巧妙地把一次性大量拷贝的开销,分摊到了多次处理请求的过程中,避免了耗时操作,保证了数据的快速访问。
有哪些底层数据结构
整数数组、双向链表、哈希表、压缩列表和跳表。
压缩列表:实际是一个数组,和数组不同的是压缩列表有zlbyte,zltail,zllen三个字段,分别表示,列表长度,列表偏移量,列表中entry的个数。表尾还有一个zlend
压缩列表定位第一个元素和最后一个元素可以通过三个字段直接定位,复杂度为O(1)。
跳表:跳表在链表的基础上加上了多级索引,通过索引位置的几次跳转来实现数据的快速定位。
当数据量很大时,跳表的查找复杂度就是 O(logN)
整数数组和压缩列表在查找时间复杂度上不占优势,为什么redis仍然使用
1、内存利用率,数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。Redis是内存数据库,大量数据存到内存中,此时需要做尽可能的优化,提高内存的利用率。
2、数组对CPU高速缓存支持更友好,所以Redis在设计时,集合数据元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。
Redis的单线程
redis的单线程主要指的是网络IO 和键值对读写的时候是由一个线程完成的。但其实redis的持久化,异步删除,集群数据同步等操作是由额外的线程来执行的。
为什么用单线程:
首先要知道多线程的开销,多线程编程模式面临共享资源的并发访问控制问题。
单线程redis为什么会这么快
一方面,Redis 的大部分操作在内存上完成,再加上它采用了高效的数据结构,例如哈希表和跳表,这是它实现高性能的一个重要原因。
另一方面,就是 Redis 采用了多路复用机制,使其在网络 IO 操作中能并发处理大量的客户端请求,实现高吞吐率。
基于多路复用的高性能IO模型
在redis只运行单个线程的情况下,该机制允许内核同时存在多个监听套接字和已连接套接字。内核会一直监听这些套接字的链接情况和数据情况,一旦有请求到达,立马通知redis线程处理,这就实现了redis单线程同时处理多个IO流的效果。
为了保证请求到达时,可以及时通知redis线程,select/epoll提供了事件回调机制,即针对不同事件的发生,调用相应的处理函数。
回调机制:select/epoll 一旦监测到 FD 上有请求到达时,就会触发相应的事件。这些事件会被放入一个事件队列,redis单线程对该事件队列不断进行处理,并且调用对应事件回调函数。
redis的持久化
AOF机制
定义:
redis先执行命令,把数据写入内存,然后才记录日志。AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。
好处:
避免出现记录错误命令的情况。它是在命令执行后才记录日志,所以不会阻塞当前的写操作。
风险:
如果刚执行完一个命令,还没有来得及记日志就宕机了,那么这个命令和相应的数据就有丢失的风险。
其次,AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。
写回策略:
Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
Everysec,每秒写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,每隔一秒把缓冲 区中的内容写入磁盘;
No,操作系统控制的写回:每个写命令执行完,只是先把日志写到 AOF 文件的内存缓冲区,由操作系 统决定何时将缓冲区内容写回磁盘。
AOF文件过大导致的问题:
一是,文件系统本身对文件大小有限制,无法保存过大的文件;
二是,如果文件太大,之后再往里面追加命令记录的话,效率也会变低;
三是,如果发生宕机,AOF 中记录的命令要一个个被重新执行,用于故障恢复,如果日志文件太大,整个恢复过程就会非常缓慢,这就会影响到 Redis 的正常使用。
因此需要AOF重写日志的出现
:就是在redis重写时,根据数据库中现有的键值对创建一个新的AOF文件,每一个键值对都用一个命令来存储。由于AOF文件是追加记录的,可能某一个键值对被多次修改,重写日志只保留它的最新状态,这样大大降低了AOF的大小
重写会阻塞主线程吗?
不会,重写是在子线程中进行的,每次 AOF 重写时,Redis 会先执行一个内存拷贝,用于重写;然后,使用两个日志保证在重写过程中,新写入的数据不会丢失。而且,因为 Redis 采用额外的线程进行数据重写,所以,这个过程并不会阻塞主线程。