一.Redis的数据类型有哪些
- 5 种基础数据类型:String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)。
- 3 种特殊数据类型:HyperLogLog(基数统计)、Bitmap (位图)、Geospatial (地理位置)。
redis的5种基础数据类型是直接提供给用户使用的,是数据的保存形式,其底层实现主要依赖这 8 种数据结构:简单动态字符串(SDS)、LinkedList(双向链表)、Dict(哈希表/字典)、SkipList(跳跃表)、Intset(整数集合)、ZipList(压缩列表)、QuickList(快速列表)。
String
虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串(Simple Dynamic String,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
List
Redis 中的 List 其实就是链表数据结构的实现。许多高级编程语言都内置了链表的实现比如 Java 中的 LinkedList,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 List 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
Hash
Redis 中的 Hash 是一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值。
Set
Redis 中的 Set 类型是一种无序集合,集合中的元素没有先后顺序但都唯一,有点类似于 Java 中的 HashSet 。当你需要存储一个列表数据,又不希望出现重复数据时,Set 是一个很好的选择,并且 Set 提供了判断某个元素是否在一个 Set 集合内的重要接口,这个也是 List 所不能提供的。
你可以基于 Set 轻易实现交集、并集、差集的操作,比如你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。这样的话,Set 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
Sorted Set(ZSet)
Sorted Set 类似于 Set,但和 Set 相比,Sorted Set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
二.Redis为什么这么快
1.基于内存实现
Redis 将数据存储在内存中,读写操作不会受到磁盘的 IO 速度限制,所以Redis的读写速度会非常的快。
2.使用I/O多路复用模型

传统阻塞 IO ,在执行accept 、recv 等网络操作时,如遇到异常情况会一直处于阻塞状态。多路指的是多个 socket 连接,复用指的是复用一个线程。多路复用主要有三种技术:select,poll,epoll。epoll 是最新的也是目前最好的多路复用技术。
Redis 单线程情况下,内核会一直监听 socket 上的连接请求或者数据请求,一旦有请求到达就交给 Redis 线程处理,这就实现了一个 Redis 线程处理多个 IO 流的效果。
select/epoll 提供了基于事件的回调机制,即针对不同事件的发生,调用相应的事件处理器。所以 Redis 一直在处理事件,提升了 Redis 的响应性能。
Redis 线程不会阻塞在某一个特定的客户端请求处理上。正因为此,Redis可以同时和多个客户端连接并处理请求,从而提升了并发性。
3.采用单线程模型
Redis 的单线程指的是 Redis 的网络 IO 以及键值对指令读写是由一个线程来执行的。 对于 Redis 的持久化、集群数据同步、异步删除等都是多线程执行。
单线程的优势
- 不会因为线程创建导致的性能消耗
- 避免上下文切换引起的 CPU 消耗,没有多线程切换的开销
- 避免了线程之间的竞争问题,比如添加锁、释放锁、死锁等,不需要考虑各种锁问题
- 代码更清晰,处理逻辑简单
Redis6.0后引入了多线程网络IO来提升连接性能,但是数据读取仍然使用的是单线程。
4.高效的数据结构
为了追求速度,不同数据类型使用不同的数据结构速度才得以提升。每种数据类型都有一种或者多种数据结构来支撑,Redis数据类型和底层数据结构的关系如下图所示。

SDS的特性
SDS是String的底层实现结构。
低时间复杂度,SDS的len保存了已使用空间的长度,获取字符串长度的时间复杂度为O(1)
空间预分配,SDS被修改后,会被分配所需要的必须空间以及额外的未使用空间。
分配规则:如果SDS被修改后,len的长度小于1M,那么SDS将被分配和len相同长度的未使用空间。举个例子,如果 len=10,重新分配后,buf的实际长度会变为10(已使用空间)+10(额外空间)+1(空字符)=21。如果SDS被修改后,len长度大于1M,那么SDS将分配1M的未使用空间。
ZipList的特性
ZipList是List 、hash、sorted Set三种数据类型底层实现之一。
ZipList是由一系列特殊编码的连续内存块组成的顺序型的数据结构,不容易产生内存碎片,内存利用率高。
适用情景:一个列表只有少量数据,并且每个列表项是小整数或短字符串
ZipList的结构。
查找第一个元素和最后一个元素,可以通过表头三个字段的长度直接定位,时间复杂度是O(1)。而查找其他元素时,只能逐个查找,此时的时间复杂度是O(N)。
ZipList在表头有三个字段:zlbytes、zltail和zllen,分别表示列表占用字节数、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
ZipList的缺点
插入和删除操作需要频繁的申请和释放内存,同时会发生内存拷贝,数据量大时内存拷贝开销较大。
LinkedList的特性
LinkedList是List的底层实现结构之一。
- 双端带有prev和next指针,定义前后节点的时间复杂度为O(1)。
- 无环表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL 为终点。
- 表头指针和表尾指针通过 list 结构的 head 指针和 tail 指针,获取链表的表头节点和表尾节点的时间复杂度为O(1)。
- 链表长度计数器使用list结构的len属性来对list持有的链表节点进行计数,获取链表中节点数量的时间复杂度为O(1)。
- 链表节点使用void*指针来保存节点值,并且可以通过 list 结构的dup、free、match 三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
LinkedList的缺点
除保存数据外还需要保存prev、next两个指针,内存利用率低,LinkedList的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片。
ZipList和LinkedList区别
内存使用
- ziplist:通过紧凑存储数据来减少内存使用,适用于元素数量少且元素值小的场景。ziplist不存储指向上一个节点和下一个节点的指针,而是存储上一个节点的长度和当前节点的长度,从而节省内存。
- linkedlist:每个节点都会存储指向上一个节点和指向下一个节点的指针,这会导致大量的内存碎片,因为指针本身也占用内存。
访问速度
- ziplist:由于数据是连续存储的,ziplist在访问时可以利用CPU缓存,提高读取速度。但是,修改中间元素可能需要重构整个列表,这可能会影响性能。
- linkedlist:不支持随机访问,要访问链表中的某个元素,必须从头节点开始遍历到目标节点,这在大型链表中可能会导致较慢的访问速度。

最低0.47元/天 解锁文章
834

被折叠的 条评论
为什么被折叠?



