乐趣区

关于redis:Redis为什么这么快

首先 Redis 是一个应用 ANSI C 编写的、开源的、反对网络的、基于内存的、可选长久化的键值对存储系统。

1 Redis 的发家史
2009 年由 Salvatore Sanfilippo(Redis 之父)公布初始版本
2013 年 5 月之前,由 VMare 资助
2013 年 5 月 -2015 年 6 月,由 Pivotal 资助
2015 年 6 月起,由 Redis Labs 资助依据 db-engines.com 上的排名,到目前为止 Redis 仍然是最风行的键值对存储系统。

2 Redis 次要版本
2009 年 5 月公布 Redis 初始版本
2012 年公布 Redis 2.6.0
2013 年 11 月公布 Redis 2.8.0
2015 年 4 月公布 Redis 3.0.0,在该版本 Redis 引入集群
2017 年 7 月公布 Redis 4.0.0,在该版本 Redis 引入了模块零碎
2018 年 10 月公布 Redis 5.0.0,在该版本引入了 Streams 构造
2020 年 5 月 2 日公布 6.0.1(稳定版),在该版本中引入多线程、RESP3 协定、无盘复制正本
2022 年 1 月 31 日公布 7.0 RC1,在该版本中次要是对性能和内存进行优化,新的 AOF 模式

3 Redis 有多快?

Redis 中带有一个能够进行性能测试的工具 redis-benchmark,通过这个命令,咱们就能够模仿多个客户端同时发动申请的场景,并且能够检测 Redis 解决这些申请所须要的工夫。

依据官网的文档,Redis 曾经在超过 60000 个连贯上进行了基准测试,并且在这些条件下依然可能维持 50000 q/s。同样的申请量如果打到 MySQL 上,那必定扛不住,间接就崩掉了。

With high-end configurations, the number of client connections is also an important factor. Being based on epoll/kqueue, the Redis event loop is quite scalable. Redis has already been benchmarked at more than 60000 connections, and was still able to sustain 50000 q/s in these conditions. As a rule of thumb, an instance with 30000 connections can only process half the throughput achievable with 100 connections. Here is an example showing the throughput of a Redis instance per number of connections;

4 Redis 为什么能够这么快?

那是什么起因造就了 Redis 能够具备如此高的性能?次要分为以下几个方面

4.1 基于内存实现

Mysql 的数据存储长久化是存储到磁盘上的,读取数据是内存中如果没有的话,就会产生磁盘 I /O,先把数据读取到内存中,再读取数据。而 Redis 则是间接把数据存储到内存中,缩小了磁盘 I / O 造成的耗费。

4.2 高效的数据结构

正当的数据结构,就是能够让你的利用 / 程序更快。Mysql 索引为了提高效率,抉择了 B + 树的数据结构。先看下 Redis 的数据结构 & 外部编码图:

4.2.1 SDS 简略动静字符串

Redis 没有采纳原生 C 语言的字符串类型而是本人实现了字符串构造 - 简略动静字符串(simple dynamic string)

SDS 与 C 语言字符串的区别:
获取字符串长度:C 字符串的复杂度为 O(N),而 SDS 的复杂度为 O(1)。
杜绝缓冲区溢出(C 语言每次须要手动扩容),如果 C 字符串想要扩容,在没有申请足够多的内存空间下,会呈现内存溢出的状况,而 SDS 记录了字符串的长度,如果长度不够的状况下会进行扩容。
缩小批改字符串时带来的内存重调配次数
空间预调配,
规定 1:批改后长度 < 1MB,预调配同样大小未应用空间,free=len;
规定 2:批改后长度 >= 1MB,预调配 1MB 未应用空间。
惰性空间开释,SDS 缩短时,不是回收多余的内存空间,而是 free 记录下多余的空间,后续有变更,间接应用 free 中记录的空间,缩小调配。

4.2.2 embstr & raw

Redis 的字符串有两种存储形式,在长度特地短时,应用 emb 模式存储 (embeded),当长度超过 44 时,应用 raw 模式存储。

为什么分界线是 44 呢?
在 CPU 和主内存之间还有一个高速数据缓冲区,有 L1,L2,L3 三级缓存,L1 级缓存时间隔 CPU 最近的,CPU 会无限从 L1 缓存中获取数据,其次是 L2,L3。

L1 最快然而他的存储空间也是无限的,大略 64 字节,抛去对象固定属性占用的空间,以及‘\0’,残余的空间最多也就是 44 个字节,超过 44 字节 L1 缓存就存不下了。

4.2.3 字典(DICT)
Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比方 HashMap,通过 key 就能够间接获取到对应的 value。而哈希表的个性,在 O(1)工夫复杂度就能够取得对应的值。

// 字典构造数据 typedef struct dict {dictType *type;  // 接口实现,为字典提供多态性    void *privdata;  // 存储一些额定的数据    dictht ht[2];   // 两个 hash 表    long rehashidx. // 渐进式 rehash 时记录以后 rehash 的地位} dict;

两个 hashtable 通常状况下只有一个 hashtable 是有值的,另外一个是在进行 rehash 的时候才会用到,在扩容时逐步的从一个 hashtable 中迁徙至另外一个 hashtable 中,搬迁完结后旧的 hashtable 会被清空。

hashtable 的构造如下:typedef struct dictht {dictEntry **table;  // 指向第一个数组    unsigned long size;  // 数组的长度    unsigned long sizemask; // 用于疾速 hash 定位    unsigned long used; // 数组中元素的个数} dictht;typedef struct dictEntry {void *key;    union {        void *val;        uint64_t u64;        int64_t s64;        double d;   // 用于 zset,存储 score 值} v;    struct dictEntry *next;} dictEntry;

4.2.4 压缩列表(ziplist)

redis 为了节俭内存空间,zset 和 hash 对象在数据比拟少的时候采纳的是 ziplist 的构造,是一块间断的内存空间,并且反对双向遍历

4.2.5 跳跃表

跳跃表是 Redis 特有的数据结构,就是在链表的根底上,减少多级索引晋升查找效率。跳跃表反对均匀 O(logN), 最坏 O(N)复杂度的节点查找,还能够通过程序性操作批量解决节点。

4.3 正当的数据编码

Redis 反对多种数据类型,每种根本类型,可能对多种数据结构。什么时候, 应用什么样数据结构,应用什么样编码,是 redis 设计者总结优化的后果。

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.4 正当的线程模型

首先是单线程模型 - 防止了上下文切换造成的工夫节约,单线程指的是网络申请模块应用了一个线程,即一个线程解决所有网络申请,其余模块该应用多线程,仍会应用了多个线程,应用多线程,如果没有一个良好的设计,很有可能造成在多线程数减少的时候后期吞吐率减少,前期吞吐率反而增长没有那么显著了。

多线程的状况下通常会呈现共享一部分资源,当多个线程同时批改这一部分共享资源时就须要有额定的机制来进行保障,就会造成额定的开销

另外一点则是 I / O 多路复用模型,在不理解原理的状况下,咱们类比一个实例:在课堂上让全班 30 集体同时做作业,做完后老师查看,30 个学生的作业都查看实现能力下课。如何在无限的资源下,以最快的速度下课呢?

第一种:安顿一个老师,按程序一一查看。先查看 A,而后是 B,之后是 C、D。。。这两头如果有一个学生卡住,全班都会被耽搁。这种模式就好比,你用循环挨个解决 socket,基本不具备并发能力。这种形式只须要一个老师,然而耗时工夫会比拟长。

第二种:安顿 30 个老师,每个老师查看一个学生的作业。这种相似于为每一个 socket 创立一个过程或者线程解决连贯。这种形式须要 30 个老师(最耗费资源),然而速度最快。

第三种:安顿一个老师,站在讲台上,谁解答完谁举手。这时 C、D 举手,示意他们作业做完了,老师上来顺次查看 C、D 的答案,而后持续回到讲台上等。此时 E、A 又举手,而后去解决 E 和 A。这种形式能够在最小的资源耗费的状况下,最快的解决完工作。

多路 I / O 复用技术能够让单个线程高效的解决多个连贯申请,而 Redis 应用用 epoll 作为 I / O 多路复用技术的实现。并且,Redis 本身的事件处理模型将 epoll 中的连贯、读写、敞开都转换为事件,不在网络 I / O 上节约过多的工夫。

5 应用场景

6 参考文档
DB-Engines Ranking https://db-engines.com/en/ran… benchmarks 
https://redis.io/topics/bench… 中的 IO 多路复用机制 
https://www.cnblogs.com/reece…

退出移动版