Redis面试加分项-底层数据结构

157 阅读7分钟

Redis底层数据结构

1.基本数据结构

对应关系

www.51cto.com/article/667…

String - embstr + int

List - 双向链表 + zipList压缩链表

Hash - 字典 + zipList压缩链表

Set - 字典 + intset整形数组

ZSet - 数据少ZipList 数据多 字典+跳表

img

单线程模型

单线程模型

  Redis客户端对服务端的每次调用都经历了发送命令,执行命令,返回结果三个过程。计算向存储侧移动其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。并且多个客户端发送的命令的执行顺序是不确定的。但是可以确定的是不会有两条命令被同时执行,不会产生并发问题,这就是Redis的单线程基本模型。

为啥是单线程

redis处理的瓶颈在于网络IO和内存,而不是cpu,不需要多个cpu并发处理,减少线程切换带来的损耗

多路复用处理请求

 多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉。当有一个或多个流有 I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。

a.非阻塞[忙轮询]:采用死循环方式轮询每一个流,如果有IO事件就处理,这样一个线程可以处理多个流,但效率不高,容易导致CPU空转。

   b.Select代理(无差别轮询):可以观察多个流的IO事件,如果所有流都没有IO事件,则将线程进入阻塞状态,如果有一个或多个发生了IO事件,则唤醒线程去处理。但是会遍历所有的流,找出流需要处理的流。如果流个数为N,则时间复杂度为O(N)

  c.Epoll代理:Select代理有一个缺点,线程在被唤醒后轮询所有的Stream,会存在无效操作。Epoll哪个流发生了I/O事件会通知处理线程,对流的操作都是有意义的,复杂度降低到了O(1)。

epoll通过在Linux内核中申请一个简易的文件系统(文件系统一般用B+树数据结构实现)。把原先的select/poll调用分成了3个部分:

  1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

  2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

  3)调用epoll_wait收集发生的事件的连接实现上面场景只需要在进程启动时建立一个epoll对象,在需要的时候向epoll对象中添加或者删除连接。同时epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。

Redis为什么快

  1. 纯内存操作。
  2. 核心是基于非阻塞的IO多路复用机制。
  3. 数据结构简单,高效,还可以进行少量计算
  4. 单线程同时也避免了多线程的上下文频繁切换问题,预防了多线程可能产生的竞争问题。
  5. 底层使用C语言实现,一般来说,C 语言实现的程序"距离"操作系统更近,执行速度相对会更快。

Redis事务

本质上讲,是一批命令的集合,通过MULTI、EXEC、WATCH等一组命令的集合,所以不支持回滚,保证隔离性和一致性,不保证原子性

隔离性:通过 单线程来保证

持久性:持久化到文件中

一致性:可以保证

对于 Redis 的持久化、集群数据同步、异步删除等都是其他线程执行。

主从复制原理

  1. 建立连接:从库会和主库建立连接,从库执行 replicaof 并发送 psync 命令并告诉主库即将进行同步,主库确认回复后,主从库间就开始同步了。
  2. 主库同步数据给从库:master 执行 bgsave命令生成 RDB 文件,并将文件发送给从库,同时主库为每一个 slave 开辟一块 replication buffer 缓冲区记录从生成 RDB 文件开始收到的所有写命令。从库保存 RDB 并清空数据库再加载 RDB 数据到内存中。
  3. 发送 RDB 之后接收到的新写命令到从库:在生成 RDB 文件之后的写操作并没有记录到刚刚的 RDB 文件中,为了保证主从库数据的一致性,所以主库会在内存中使用一个叫 replication buffer 记录 RDB 文件生成后的所有写操作。并将里面的数据发送到 slave。

详解

  1. 1.ZipList是一种压缩列表,就是普通的双向队列实现,元素紧挨着,经过特殊的编码,占用空间小。
  1. 2.跳表

跳表为什么比红黑树好:

1.范围查找场景跳表更好

2.插入、删除节点更快,红黑树需要做旋转

3.跳表实现简单,可读性好

  1. 3.QuickList

是一种zipList结合list的结构

底层结构

1、简单动态字符串SDS

2、链表

3、字典

4、跳跃表

5、整数集合

6、压缩列表

7、快速列表

1.字符串SDS

free + len + buffer 组成,\0结尾

2.String存储形态

根据存储的类型不同,实际存储字符串的底层结构可能有int,raw,embstr,几种类型

如果String中的对象是整数值 - 使用int编码

String中的对象大于32字节 - 使用raw编码

String中的对象小于32字节 - 使用embstr编码

raw:redisObject和SDS分开存储

embstr:空间连续存储

3.List列表对象

在redis的早期版本中,列表对象使用的编码是ziplist或linkedlist。

但是现在使用的是快速列表(quicklist) img

4.Hash哈希对象

哈希对象的底层编码是ziplist或者hashtable(字典)

当哈希对象保存的所有键对值的键和值的长度都是小于64字节并且键对值数量小于512个的时候,使用ziplist。

保存键对值的时候,现将键压至栈底,再将值压至栈底。

存储的元素比较多的时候,使用hashtable

5.Set集合对象

集合对象可使用的编码是intset或hashtable

当集合中所有元素都是整数并且元素数量小于512个,intset底层是使用整数集合实现的。

当不满足用intset的条件的时候,使用hashtable

6.Zset有序集合

有序结合对象使用的是ziplist或者是skiplist

当有序集合中元素小于128个并且所有元素的长度都小于64字节,使用ziplist,ziplist保存的方式也是先保存键,再保存值,键和值是挨着的,元素是按照值由小变大排序的。

img

当不满足ziplist的两个条件的时候,使用的是skiplist,skiplist底层是zset结构,包含一个字典和一个跳跃表。

服务器在执行某个命令的时候,会先根据redisObject里的type属性判断是否可以执行指定的命令。

Redis使用引用计数实现内存回收机制(在JVM垃圾回收的时候说过,此机制不能解决循环引用的问题,所以JVM不用此机制)。