目录
一、前言
二、redis单线程模型
2.1 什么是单线程模型?
2.2 单线程模型优缺点
三、五大类数据类型底层设计
3.1 String
3.2 Hash
3.3 List
3.4 Set
3.5 Zset
四、小结
一、前言
前面的文章中,我们了解了redis客户端、基础命令和过期策略。本篇文章我将和大家探讨有关redis数据类型与单线程模型间的底层逻辑关系。
很多人在学习redis的过程中,会将redis的数据类型和单线程模型这两个知识点分开来记忆,但实际上,这两者是存在密切联系的,单线程是底层约束,而多数据类型是为适配这个约束,才做出来的核心解决方案。
二、redis单线程模型
2.1 什么是单线程模型?
提到单线程模型,首先就得澄清一个误区:redis不是整个进程就只有一个线程!
对于像处理网络IO、实现RDB/AOF持久化 和 清理过期数据这些业务,是由redis后台的子线程去实现的;而真正用来执行redis命令,操作内存数据的线程,只有一个。
那么redis的执行流程是怎样的?
- redis使用IO多路复用(epoll)去监听上万个客户端的连接;
- 所有客户端发来命令,进入一个FIFO的队列中;
- redis会按照串行依次取出队列中的命令,执行,最后返回结果;
2.2 单线程模型优缺点
(1)单线程模型的优点在于两点:
- 线程安全:是单线程,那就无需担心什么线程安全问题,本身他就是一个线程执行命令,就保证了原子性;
- 线程并发开销小:单线程,就不存在锁竞争和多线程间的上下文切换开销,轻量高效。
- 内存操作极快:大部分简单的命令的耗时都是微秒级的。
(1)单线程模型的缺点在于:单线程不能跑慢指令,比如说像keys *这样的命令,时间复杂度可达到O(n),假说redis中存储了上10w条数据,全部遍历一遍,此时线程就进入了阻塞状态,那redis服务器还要不要活啦?
总的来说redis的单线程模型就是为了提高数据访问的效率,而redis设计丰富的数据类型,就是为了规避单线程阻塞,减少网络IO,保证高效性。
三、五大类数据类型底层设计
首先需要明白,我们讨论的关于redis的丰富的数据结构,是指的redis的KV结构中value的数据类型,因为redis规定了key的数据类型只能为String。
value的五大数据类型概览:
数据类型 | 底层实现 | 设计目的 |
String | int 整型 raw 字符串 embstr 优化字符串 | 数据存储,INCR 原子计数,不用客户端并发处理。 |
Hash | hashtable 哈希表 ziplist 压缩列表 | 存储对象多字段,HGET/HSET 单独操作字段,不用序列化整个对象。 |
List | quicklist 快速列表 | 消息队列、消息流,头尾插入弹出极快;禁止大范围 LRANGE 遍历。 |
Set | hashtable 哈希表 intset 整型列表 | 自动去重、交集差集,服务端完成集合运算,减少客户端交互。 |
Zset | skiplist 跳表 ziplist 压缩列表 | 排行榜、延时队列,范围查询 O(logN),复杂度可控。 |
下文将依次介绍各类数据类型介绍其底层实现的编码方式。
在介绍各类数据类型前,介绍2个redis命令:
- “type key” 用来查看当前key所对应的value值是什么数据类型。就是这5大类种的一种。
- “object encoding key”用来查看当前数据类型的编码方式,是其底层实现,后续详细介绍。
3.1 String
针对String类型,它的底层实现有3种,分别是int、raw和embstr。
👉int:大家熟知,int表示整型。用get命令拿到value看着是字符串,但其底层的编码是设计成整型的。如下图:
int的编码方式通常是为例实现 “计数” 功能,例如短视频平台的视频播放量、点赞量、评论数等。计数功能主要是通过 incr、decr等命令实现,后续发布的文章会详细介绍。
👉raw:raw是针对较长字符串实现的编码方式。例如数据量较大的json格式的数据,本质上也是较长的字符串。如下图:
👉embstr:embstr是针对较短的字符串,进行特殊优化的一种编码方式。就是假说你存储的value字符串本来长度就短,那么就没有必要申请较大的内存空间去存储该数据,使用更小的空间,可以有效地利用redis的内存空间。如下图:
3.2 Hash
针对Hash数据类型,其底层实现的编码方式有2种,分别是hashtable和ziplist。
👉hashtable:hashtable和学习Java中的哈希表类似,就是以key - value这样的键值对组成的数据集。但此处的hashtable和Java中的hashtable要区分开来,虽然两者都叫hashtable,但本质上他俩没有关系。
👉ziplist:ziplist是压缩列表,为啥要将列表进行压缩?这里实则也是和embstr有异曲同工之妙,也是为了节省空间,当哈希表中的元素较少时,就会使用ziplist编码方式去存储,提高空间利用率。由于一般较少元素都是用ziplist实现的,所以这里只举例ziplist:
3.3 List
在redis 3.2版本以前,list数据类型的底层实现分为linkedlist和ziplist两种,分别表示链表和压缩列表,但在3.2版本之后,redis就将这两个合并为quicklist了——将各个元素用链表串起来,而每个元素又是一个压缩列表,形如:
3.4 Set
针对set数据类型,其底层实现分为hashtable和intset两种。
👉intset:实现集合中都是整型数据的编码方式,例如:
👉hashtable:上文介绍过,本质就是哈希表;但在set数据类型中,只有当intset中的元素达到redis配置文件中的阈值:
set-max-intset-entriesX
set-max-listpack-entriesX
set-max-listpack-valueX
其底层编码方式才会转为hashtable。
3.5 Zset
针对zset数据类型,其底层实现分为skiplist和ziplist两种。
👉ziplist:上文中提到的压缩列表,同样,当ziplist中的元素达到一定的阈值,则会转为跳表。
👉skiplist:skiplist跳表,本质上是链表,只不过该链表的每个节点有多个指针域,可以指向其他节点,实现逻辑上的 “跳” 。
上述的这些数据类型的丰富的编码方式,其实都是服务于单线程模型,例如String的int,为实现原子性计数功能、
四、小结
本文介绍了redis单线程模型与单线程模型,提及两者之间的联系,但不够详细,要通过每种数据类型的具体命令,才能真正体会到数据类型的使用是如何服务于单线程模型的。
下一节将对redis各大数据类型的各种命令,应用场景等进行详细的介绍,感兴趣的小伙伴不妨给博主个关注再走叭~