redis为什么速度快?

image.png

1. 基于内存实现

内存读写比磁盘读写快很多,redis是基于内存存储实现的数据库,省去了磁盘i/o的消耗。mysql等磁盘数据库,需要建立索引来加快查询效率,而redis直接放在内存,直接操作内存。


image.png

2. 高效的数据结构

mysql索引为了提高索引效率,选择了B+树的数据结构。
redis的数据结构:


image.png

2.1 SDS简单动态字符串

image.png
struct sdshdr { //SDS简单动态字符串
    int len;    //记录buf中已使用的空间
    int free;   // buf中空闲空间长度
    char buf[]; //存储的实际内容
}

2.1.1 字符串长度处理

C语言中,要获取字符串长度,需要从头开始遍历,复杂度为O(n);在redis中,已经有一个len字段记录当前字符串的长度,直接获取即可,时间复杂度为O(1)

2.1.2 减少内存重新分配次数

C中,修改一个字符串,需要重新分配内存,修改越频繁,而分配内存会消耗性能。redis中,SDS提供了两种优化策略:空间预分配和惰性空间释放。

2.1.2.1 空间预分配

当SDS简单动态字符串修改和空间扩充时,出了分配必须的内存空间,还会额外分配未使用的空间
分配规则:

  • SDS修改后,len的长度小于1M,那么将额外分配与len相同长度的未使用空间。比如len=100,重新分配后,buf 的实际长度会变为100(已使用空间)+100(额外空间)+1(空字符)=201。
  • SDS修改后, len长度大于1M,那么程序将分配1M的未使用空间。

2.1.2.2惰性空间释放

当SDS缩短时,不是回收多余的内存空间,而是用free记录下多余的空间。后续再有修改操作时,直接使用free中的空间,减少内存分配

2.2 哈希

redis是K-V的内存数据库,使用一张全局的hash表来保存所有的键值对。这hash表由多个哈希桶组成,哈希桶中的entry元素保存了key和value指针,其中key指向了实际的键,value指向了实际的值。

image.png

hash表查询快,类似于java中的hashmap,时间复杂度为O(1)。首先通过key计算哈希值,找到对应的哈希桶位置,然后定位到entry,在entry中找到对应的数据。

redis为了解决哈希冲突问题,采用了链式哈希。


image.png

当为了保持高效,redis会对哈希表做rehash操作,也就是增加哈希桶,减少冲突。为了rehash更高效,redis默认使用了两个全局哈希表,一个用于当前使用,成为主哈希表,一个用于扩容,称为备用哈希表

2.3跳跃表

跳表是redis特有的数据结构,是在链表的基础上,增加多级索引以提高查询效率


image.png
  • 每一层都有一条有序的链表,最底层的链表包含了所有的元素
  • 跳跃表支持平均O(logN)最坏(N)复杂度的节点查找,海可以通过顺序性操作批量处理节点

2.4 压缩列表ziplist

压缩列表ziplist是列表键和字典键的底层实现之一。由一系列特殊编码保存的内存构成的列表,一个ziplist可以包含多个entry,每个entry可以保存一个长度受限制的字符数组或者整数:


image.png
  • zlbytes:记录整个压缩列表占用的内存字节数
  • zltail:尾节点至起始节点的偏移量
  • zllen:记录整个压缩列表包含的节点数
  • entryX: 压缩列表包含的各个节点
  • zlend:特殊值0XFF(十进制255),用于标记压缩列表末端
    由于内存是连续分配的,所以遍历速度很快

3. 合理的数据编码

Redis支持多种数据基本类型,每种基本类型对应不同的数据结构,每种数据结构对应不一样的编码。为了提高性能,Redis设计者总结出,数据结构最适合的编码搭配。

redis使用对象(redisObject)来表示数据库中的键值。在redis中创建一个键值对时,至少创建2个对象,一个用来做做键值对的键对象,另一个是键值对的值对象

typedef struct redisObject{
    //类型
   unsigned type:4;
   //编码
   unsigned encoding:4;
   //指向底层数据结构的指针
   void *ptr;
    //...
 }robj;

redisObject中,type 对应的是对象类型,包含String对象、List对象、Hash对象、Set对象、zset对象。encoding 对应的是编码。

  • string:如果存储数字,使用int类型的编码;如果存储非数字,小于等于39字节的字符串是embstr;大于39个字节,则是raw编码
  • List:如果列表的元素小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码
  • hash: 哈希类型元素小于512个没所有值小于64字节的话,使用ziplist编码,否则使用hashtable编码
  • set: 如果集合总的元素都是整数且元素个数小于512个,使用intset编码,否则使用hashtable编码
    -zset: 当有序集合的元素小于128个,每个元素的值都小于64字节时,使用ziplist编码,否则使用skiplist(跳表)编码

4. 合理的线程模型

单线程模型:避免了上下文切换

redis是单线程的,redis的网络IO和键值对读写是由一个线程完成的。redis的持久化、异步删除、集群数据同步等实际是由额外的线程执行的。

redis的单线程模型,避免了cpu不必要的上下文切换和竞争锁的消耗。由于是单线程的,如果某个名两个执行时间过长(如hgetall),会造成阻塞。所以姚胜勇如Irange、smembers、hgetall等命令

5.IO多路复用

什么是IO多路复用?

  • I/O:网络IO
  • 多路:多个网络连接
  • 复用: 复用同一个线程
  • IO多路复用是一种同步IO模型,实现了一个线程可以监视多个文件句柄;一旦某个文件句柄就绪。就能够通知应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出CPU

多路IO复用可以让单个线程搞笑的处理多个连接请求,erredis使用epoll作为IO多路复用技术的实现。并且redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为时间,不在网络IO上浪费过多的时间

6. 虚拟内存机制

redis直接自己构建了VM机制,不会像一般的系统会调用系统函数处理,会浪费一定的时间去移动和请求。

虚拟内存机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据),通过VM功能可以实现冷热数据分类,使热数据仍在内存中,冷数据保存到磁盘中。这样可以避免因为内存不足而造成访问速度下降的问题。

你可能感兴趣的:(redis为什么速度快?)