进阶之路|大聪明教你学Java | 面试官(请你说说 Redis 为什么这么快())

前言

作者简介: 不肯过江东丶,一个来自二线城市的程序员,致力于用“猥琐”办法解决繁琐问题,让复杂的问题变得通俗易懂。
支持作者: 点赞、关注、留言~
作为程序员的我们,工作中肯定避免不了和 Redis 打交道。除了日常工作以外,我们在面试的时候也常常会被问到一些关于 Redis 的问题,其中出场率最高的就是:请你说说 Redis 为什么这么快?我们都知道 Redis 很快,它QPS可达10万(每秒请求数),但是很多小伙伴知道 Redis 快仅仅因为它是基于内存实现的,对于其它原因倒不是很清楚,也就不能回答出面试官想听到的东西。那么今天咱们就一起总结一下 Redis 为什么会这么快。
Redis 为什么这么快 原因一:基于内存实现 Redis 是基于内存存储实现的一个数据库,而其他传统数据库(如 MySQL)则是依赖磁盘实现的(我们暂且称之为磁盘数据库)。Redis 与传统磁盘数据库相比来说,Redis 将数据存放在内存,需要查询或存储数据时,直接对内存进行操作,省掉了磁盘 I/O 的步骤,也就减少了对应的消耗、加快了速度。
原因二:拥有高效的数据结构 进阶之路|大聪明教你学Java | 面试官(请你说说 Redis 为什么这么快())
文章图片

Redis 中一共有5种数据类型,分别是 String 数据类型、List 数据类型、Hash 数据类型(散列类型)、set 数据类型(无序集合)、SortedSet 数据类型(zset、有序集合)。不同的数据类型底层使用了一种或者多种数据结构来支撑,目的就是为了追求更快的速度。下面就对这些数据结构进行一个简单的讲解
P.S. 主要是本人经验有限,太深入的东西也讲不出来,各位小伙伴见笑了
简单动态字符串(SDS)
我们知道 Redis 的底层是用C语言来编写的,Redis 的 String 底层数据结构实现并没有直接使用C语言中的数据结构,它为了实现方便的扩展,考虑到安全和性能,自己定义了一个结构用来存储字符串,这个数据结构就是:简单动态字符串(Simple Dynamic String 简称SDS),并将 SDS 用作 Redis 的默认字符串。
struct sdshdr { //用于记录buf数组中使用的字节的数目,和SDS存储的字符串的长度相等 int len; //用于记录buf数组中没有使用的字节的数目 int free; //字节数组,用于储存字符串 char buf[]; //buf的大小等于len+free+1,其中多余的1个字节是用来存储’\0’的 };

上面的代码就是 Redis 中保存字符串对象的数据结构,相比于C语言来说,它也就多了 len 和 free 这两个变量,但就是两个变量却成了神来一笔
  • Redis 可以通过 len 保存字符串的长度,在查询字符串长度信息时,时间复杂度仅为O(1) ,而传统的C语言在遍历字符串的长度时,时间复杂度却是O(n)。
  • SDS 被修改后,程序不仅会为 SDS 分配所需要的必须空间,还会分配额外的未使用空间,也就是实现了空间预分配。分配规则如下:SDS修改后,len 的长度小于1M,那么将额外分配与 len 相同长度的未使用空间。比如len=100,重新分配后,buf 的实际长度会变为100(已使用空间)+100(额外空间)+1(空字符),也就是201。如果 SDS 修改后 len 长度大于1M,那么程序将分配1M的未使用空间。
  • 当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配。这也就是 Redis 的惰性空间释放。
  • 在 Redis 中不仅可以存储 String 类型的数据,也可能存储一些二进制数据。二进制数据并不是规则的字符串格式,其中会包含一些特殊的字符如 ‘\0’,在 C 语言中遇到 ‘\0’ 则表示字符串的结束,但在 SDS 中,标志字符串结束的是 len 属性,SDS也保证了二进制安全。
intset 整数集合
intset 是集合键的底层实现之一,如果一个集合满足只保存整数元素和元素数量不多这两个条件,那么 Redis 就会采用 intset 来保存这个数据集。intset 整数集合支持三种编码类型,分别是int16_t、int32_t、int64_t。通过 intset 整数集合。其数据结构如下
typedef struct intset { uint32_t encoding; // 编码模式 uint32_t length; // 长度 int8_t contents[]; // 数据部分 } intset;

其中,contents 字段用于保存整数,数组中的元素要求不重复且按照从小到大的顺序排列;encoding 字段表示该整数集合的编码模式,在读取和写入的时候,均按照指定的 encoding 编码模式读取和写入。Redis 提供三种模式的宏定义如下:
// 数据以int16_t类型存放,每个占2个字节,能存放-32768~32767范围内的整数 #define INTSET_ENC_INT16 (sizeof(int16_t)) // 数据以int32_t类型存放,每个占4个字节,能存放-2^32-1~2^32范围内的整数 #define INTSET_ENC_INT32 (sizeof(int32_t)) // 数据以int64_t类型存放,每个占8个字节,能存放-2^64-1~2^64范围内的整数 #define INTSET_ENC_INT64 (sizeof(int64_t))

可以看出,虽然 contents 字段是通过 int8_t 类型来声明是,但是存储数据时并不以这个类型来存放数据。
【进阶之路|大聪明教你学Java | 面试官(请你说说 Redis 为什么这么快())】intset 整数集合中最值得一提的就是升级操作。当 intset 中添加的整数超过当前编码类型的时候,它就会自动升级到能容纳该整数类型的编码模式。比如我们需要创建一个集合来存储1、2、3、4这四个元素,在创建该集合的时候,采用 int16_t 的类型来存储,如果此时需要在集合中存储一个更大的元素X,且元素X超出了当前集合能存放的最大范围,这个时候 Redis 就会自动对该整数集合进行升级操作,将 encoding 字段改成 int32_6 类型,并对contents 字段内的数据进行重排列。
zipList 压缩列表
压缩列表是 List 、hash、 sorted Set 三种数据类型底层实现之一。当只有少量数据,并且每个列表项要么为小整数值,要么是长度比较短的字符串时, Redis 就会使用压缩列表来做列表键的底层实现。
linkedlist 双向链表
当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,Redis 就会使用链表作为列表键的底层实现。
skipList 跳跃表(跳表)
SortedSet 数据类型的排序功能就是通过 skipList 跳跃表数据结构来实现的。
skipList 跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。跳表在链表的基础上,增加了多层级索引,通过索引位置的几个跳转,实现数据的快速定位。
字典
Redis 的键值对存储就是用字典实现的,Hash 的底层实现之一也是字典。因为本人对字典部分还不是很了解,这里就不多说了,有兴趣的小伙伴可以自行百度查阅。如果哪位小伙伴对字典部分比较了解,欢迎在评论区留言哦~
原因三:Redis 采用单线程模型 Redis 采用单线程模型并不是说 Redis 就只有一个线程,而是 Redis 对数据的所有操作都是由一个线程按顺序依次执行的。使用单线程模型可以有效的避免线程之间的竞争问题(如添加锁、释放锁、死锁等)、减少线程创建导致的性能消耗,同时也让代码变得更清晰,处理逻辑也更简单。Redis 官方也针对单线程模型给出了一个解释
因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
原因四:Redis 采用I/O多路复用模型 我们解释一下“I/O多路复用模型”这句话,I/O 就很好理解了,它代表了网络 I/O;多路代表着多个套接字(socket)连接;复用的含义是共用一个线程或进程。简单来说就是,Redis 使用 I/O 多路复用模型同时监听多个套接字,并将这些事件推送到一个队列里,然后逐个被执行,最终将执行结果返回给客户端。采用I/O多路复用模型保证了 Redis 线程不会阻塞在某一个特定的监听或已连接套接字上,也就是说,不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis 可以同时和多个客户端连接并处理请求,从而提升并发性和吞吐量。
原因五:Redis 采用高效的的 Gossip 通信协议 Gossip 通信协议是针对于 Redis 集群来讲的,Gossip 通信协议是 P2P 方式的通信协议,它拥有瘟疫一般的传播速度。当 Redis 集群中的一个节点 A 广播自身信息,其他节点收到了A节点广播出来的信息,那么这些节点再继续在集群中传播这个A节点的信息,一段时间后整个集群中所有的节点就都有了A节点的信息。是不是有点像村里聊八卦的大妈们,无论大妈们知道了什么小道消息,用不了两天,全村人就都知道了~
进阶之路|大聪明教你学Java | 面试官(请你说说 Redis 为什么这么快())
文章图片

小结 本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨?
希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (????) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●’?’●)
如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。
你在被打击时,记起你的珍贵,抵抗恶意;
你在迷茫时,坚信你的珍贵,抛开蜚语;
爱你所爱 行你所行 听从你心 无问东西

    推荐阅读