首先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...