摘要
Redis是以键值对形式存储数据的内存数据库,因为其将数据存储在内存中,所以读写速度特别快,常用于作为缓存。该文章介绍了Redis常见数据类型、redis优势、线程模型、持久化、集群。持续完善与更新中。
1、五种常见数据类型结构及使用场景
redis是存储key-value对数据的数据库,它的key一般是String类型,但value有很多种,基本类型包括:
-
字符串(String):本质是字符串数组,最大不能超过512M。
-
链表(List):底层是一个链表,可以在链表头部、尾部删除或者插入数据,可以实现队列、栈的功能。
-
哈希表(Hash):存储键值对集合的数据,常用于存储json格式数据。底层和ZSET类型,只是去掉了跳表。
-
集合(Set):存储是数据是无序的集合,提供了两个几个之间的并较差集的计算。常用于存储好友列表,共同好友的计算。底层是一个Dict(哈希表),如果存储的元素是整数,那么将采用intSet编码,如果是字符,使用set编码。如果一开始是整数,未来插入了字符串,那么将会变为set编码。
集合存储的是一个元素,使用Dict键值对,所以在这里是将要保存的值作为key,值为null。
-
排序集合(SortedSet):存储值时需要指定每个值的分数,按分数排名有序的存储数据,默认升序。常用于排名榜业务。底层数据是一个Dict+跳表,其中Dict用于确保键唯一和快速查询,跳表用于排序,跳表结构如图:
显然:排序集合需要两种结构,很占存储空间,所以当元素数量小于128并且元素字节数小于64,将会使用ZipList(压缩链表)存储数据。
2、底层数据结构
指常见数据类型的底层数据存储结构。例如key的类型,链表中的数据,集合中的数据等底层都是简单动态字符串进行存储的。
2.1、简单动态字符串(SDS)
SDS和C语言字符串区别:
- 它不以
\0作为字符串结束的标识位,而是以长度来看,所有它能够存储\0这样特殊字符,是二进制安全的;
注意SDS为了和C语言兼容,所以字符串末尾存储了
\0的结束标识符
-
获取字符串长度的时间复杂度为O(1);
-
能够修改;
-
动态的扩容。向容量满了的SDS中添加字符串,将会触发扩容,扩容机制是: * 新字符串长度<=1M,则新空间为扩展后的两倍+1。 * 超过1M,新空间长度为扩展后的长度+1M+1。
它的数据结构如下
struct sdshdr {
uint8_t len; // buf数组中已使用字节的数量
uint8_t alloc; // buf数组中申请的总内存大小
unsigned char flags; // 不同的头类型,用来标识存储大小。uint8最大值是256,也就是说该buf最能存储256个字节的数据。Redis定义了多个尺度的SDS。
char buf[]; // 字符数组,存储数据
}
2.2、跳表
本质是一个链表,但是
- 其中的元素是按照分数升序排列的。
- 每个节点包含多个用来增加查询效率的不同跨度的指针。
2.3、zipList
压缩链表,底层实际是一个数组,每个"结点"的结构体是由:
- previous_entry_length:前一个结点从长度。
- encoding:当前节点的数据类型及长度。
- contents:当前结点的数据。
压缩链表可以通过encoding从前向后遍历,也可以通过previous_entry_length从后往前遍历。从而实现两端遍历的操作。
3、底层通信协议
3.1、RESP协议
Redis是CS架构的软件,即客户端-服务端,RESP协议是客户端和服务端通信的协议。它通过首个字节的字符来区分不同的数据类型,例如:
- 单行字符串:首字节是
+,后面跟上单行字符串,以CRLF("\r\n")结尾。 - 错误:首字节是
-,以CRLF("\r\n")结尾。 - 数字:首字节是
:,以CRLF("\r\n")结尾。 - 多行字符串:首字节是
$,后面紧跟字符串的长度,以\r\n结尾,再跟着需要读取的字符串,将会根据传入的字符串长度去读取后面的字符串。例如$5\r\nhello。多行字符串允许字符串内部存储\r\n这样的特殊字符串,所以是二进制安全的。
- 数组:首字节是
*,后面跟上数组元素个数,再跟上每个数组的元素,元素类型不受限制。
4、有MySQL不就够用了吗?为什么要用Redis这种新的数据库?
Mysql数据库是存在磁盘上的,读取速度慢,而Redis是内存数据库,访问内存比访问磁盘的速度快得多。直接访问内存能承受的请求远远大于直接访问数据库,所以Redis常常用于缓存,用来分担数据库的压力。
5、使用Redis的好处有哪些?
- 数据以键值对的形式存储在内存中,访问速度快。
- 数据类型丰富,能够存储字符串,列表、hash表、集合、有序集合等多种数据结构。
- 支持事务,操作是原子性的
- 特性丰富,可用于缓存,消息队列,过期删除。
6、缓存中常说的热点数据和冷数据是什么?
- 热点数据:指访问次数较多的数据;
- 冷数据:指访问频率低的数据;
注意:只有缓存热数据才有价值,对于大部分冷数据, 可能还未再次访问就已经过期了,所以,缓存冷数据是没有意义的。
7、Redis 为什么是单线程的而不采用多线程方案
Redis是放在内存中的数据库,其访问速度本身就很快,限制其性能的并不是CPU性能,更可能是网络带宽和机器的内存,所以不需要采用多线程。
8、单线程的Redis为什么这么快?
- Redis的操作是基于内存的。
- 使用单线程,避免了多线程的上下文切换。
- 使用非阻塞IO多路复用机制。
9、IO多路复用
利于单个线程去监听多个FD,在某个任务的文件描述符(FD)准备就绪,可读、写时得到通知,从而避免无效等待,充分利用CPU资源。Linux系统监听的方式有:
- select、poll、epoll
注意:select和poll只会通知用户进程有FD准备就绪,但不会通知具体是哪个FD,需要进程去遍历确认。而epoll则会直接通知用户进程就绪的FD是哪个。
10、了解Redis的线程模型吗?可以大致说说吗?
它包括套接字、IO多路复用程序、文件事件分派器、事件处理器组成。它采用IO多路复用程序去监听多个套接字,当套接字产生事件后,将事件放入队列中,由事件处理分派器从队列中取出事件交给对应处理器处理。文件分派器分派任务是单线程的,所以Redis是单线程的。事件处理器包括连接应答处理器、命令回复处理器、命令应答处理器。
11、Redis设置过期时间的两种方案是什么?
- 定时删除:每隔一段时间,随机抽取一些设置了过期事件的key,检查是否已经过期,将过期的删除。
- 惰性删除:当用到某个key时,再检查其是否过期,过期则删除。
12、定期和惰性一定能保证删除数据吗?如果不能,Redis会有什么应对措施?
不能,Redis有一个内存淘汰机制去删除数据,主要有4种方案:
- LRU:淘汰最近最少使用的数据;
- LFU:淘汰使用频率最低的数据;
- TTL:淘汰将要过期的数据;
- Random:随机淘汰数据;
13、Redis持久化机制
通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。持久化机制有两种:
- 快照持久化(RDB):通过创建某一个时刻的快照去保存和持久化数据。
- AOF持久化:每执行一次更改语句,都会将该命令写入AOF文件。
14、AOF重写
AOF持久化是将Redis执行的写入命令存入一个AOF文件中,恢复Redis时,加载文件重新执行命令即可。但久而久之,AOF文件会越来越大,非常占用内存,处理分析起来也需要较长时间,考虑到AOF中的很多写入命令是数据的中间状态,而不是最终状态,据此便有了AOF重写。
- AOF重写是为了降低AOF文件的大小及指令的数量。做法是去除掉AOF文件中的中间状态命令,将多条指令合并,只保留最终状态。Redis在重写时,会fork一个子进程去完成重写操作,为了防止子进程重写AOF文件期间,输入到redis的指令丢失,Redis会将该期间的写入指令暂存在重写缓冲区,最后将其添加到重写后的AOF文件中。
15、双写一致性问题及解决方案
指数据库数据被更新后,还未将更新同步至缓存,就有新的请求来访问了还未更新的旧数据,导致数据不一致的情况。解决方案:
-
先更新缓存、再更新数据库:一般不考虑,因为如果缓存更新成功、数据库更新失败,将导致缓存和数据库完全不一致,且很难察觉,因为缓存中一直有数据。
-
先更新数据库,再更新缓存:一般不考虑。因为更新数据库后,更新缓存可能会失败。
-
先删除缓存,再更新数据库: 因为在删除缓存和DB更新完之间,可能有新的请求访问了数据库并携带旧的结果存入了redis,从而导致数据不一致。可以使用延迟双删的办法解决,即更新完数据库后,延迟几秒再删除一次缓存。
弊端:还是会小概率出现数据不一致的情况。因为在这个时间差内,缓存中脏数据 可能会被其它进程读取。
-
先更新数据库再删除缓存:在更新数据库并删除缓存后,网络中可能存在读取了旧数据正准备用旧数据更新缓存的线程,此时对缓存进行了更新,而导致数据不一致(极短的时间不一致)。可以使用延迟双删解决该问题。
16、Redis集群详情
16.1 Redis主从架构及哨兵
主从架构是用来保证Redis的高可用,采用一个主节点、多个从节点进行架构。数据修改操作都是在主节点进行,从节点负责同步主节点数据,并提供读数据的服务,当主节点挂掉后,选择一个从节点作为新的主节点,怎么选?则是由哨兵控制的哨兵通常是由三个及三个以上的服务器组成,它会监控主节点与从节点的状态,当主节点挂掉后,由哨兵集群中推选出来的某个哨兵去选择从节点作为新的主节点。
16.2 Redis集群
主从架构保证了高可用,但处理请求只有一个Redis,性能不足。Redis集群保证了高并发。它采用了多个Redis主从集群处理请求,并使用一致性hash算法去选择服务器处理请求。一致性hash的算法是将真实主从集群数量映射至16384个虚拟节点槽位,每次数据请求时,计算数据hash值对16384取模,得到其需要放入的虚拟槽位,根据虚拟槽位操作对应的主从集群。