对于Redis这种内存数据库来讲,除了访问的是内存以外,Redis访问速度飞快还取决于其余的一些因素,而这些都跟Redis的高可用性有很大关系。下面是衡量Redis的三个纬度:
1.高性能:线程模型、网络I/O模型、数据结构,合理的数据编码2.高可用性:主从复制、哨兵模式、Cluster分片集群和持久化机制3.高拓展性:负载均衡本篇文章,咱们主要来介绍Redis的高性能特性的几个相关因素。 根据官方数据,Redis的QPS能够达到约100000/秒,横轴表示链接数,纵轴表示QPS。
Redis4.0以前是单线程模型,缘由是由于在此以前CPU不是瓶颈,网络I/O才是瓶颈,而单线程模型一来避免了多线程模型之间的上下文切换,二来又能够经过多路复用来实现并发,而且代码更容易维护。html
不过在Redis4.0以后,redis新增了一些能够被其余线程异步处理的删除操做,例如:UNLINK、FLUSHALL ASYNC 和 FLUSHDB ASYNC。缘由是有一些超大的键值对占用了很大的内容,例如几十 MB 或者几百 MB 的数据,这些数据的删除在几百毫秒内结束不了,若是是同步的就会阻塞待处理的任务,因此加入了多线程,目的就是为了异步处理这些大的数据。
redis
参考资料为:https://zhuanlan.zhihu.com/p/91490643https://stackoverflow.com/questions/10489298/redis-is-single-threaded-then-how-does-it-do-concurrent-i-ohttp://www.odbms.org/2018/03/the-little-known-feature-of-redis-4-0-that-will-speed-up-your-applications/
由于Redis是单线程模型的缘由,实现并发操做的话,是须要采用了多路复用机制的,例如:epoll,关于epoll的介绍能够参考个人另一篇文章4.同时管理多个socket的高效方法-epoll
算法
参考资料:https://draveness.me/redis-io-multiplexing/https://www.jianshu.com/p/8f2fb61097b8https://baijiahao.baidu.com/s?id=1676709704453688282&wfr=spider&for=pc
首先,Redis整个数据库就是一个全局哈希表,而哈希表的时间复杂度是 O(1),只须要计算每一个键的哈希值,便知道对应的哈希桶位置,定位桶里面的 entry 找到对应数据,这个也是 Redis 快的缘由之一。数据库
其次,不一样的数据类型使用不一样的数据结构速度才得以提高,而且每种数据类型都有一种或者多种数据结构来支撑。数组
这也是咱们将 SDS 的 buf 属性称为字节数组的缘由 —— Redis 不是用这个数组来保存字符, 而是用它来保存一系列二进制数据。安全
好比说, 使用 SDS 来保存以前提到的特殊数据格式就没有任何问题, 由于 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束, 如图 2-18 所示。网络
参考资料:http://redisbook.com/preview/sds/different_between_sds_and_c_string.html
压缩列表是 List 、hash、 sorted Set 三种数据类型底层实现之一。数据结构
当一个列表只有少许数据的时候,而且每一个列表项要么就是小整数值,要么就是长度比较短的字符串,那么 Redis 就会使用压缩列表来作列表键的底层实现。多线程
Ziplist 是由一系列特殊编码的内存块构成的列表, 一个 ziplist 能够包含多个节点(entry), 每一个节点能够保存一个长度受限的字符数组(不以 \0 结尾的 char 数组)或者整数。并发
字符数组长度小于等于 63 (26−1)字节的字符数组长度小于等于 16383 (214−1) 字节的字符数组长度小于等于 4294967295 (232−1)字节的字符数组整数4 位长,介于 0 至 12 之间的无符号整数1 字节长,有符号整数3 字节长,有符号整数int16_t 类型整数int32_t 类型整数int64_t 类型整数
ziplist 在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表占用字节数、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。
若是咱们要查找定位第一个元素和最后一个元素,能够经过表头三个字段的长度直接定位,复杂度是 O(1)。而查找其余元素时,就没有这么高效了,只能逐个查找,此时的复杂度就是 O(N)。
3)双端列表
Redis List 数据类型一般被用于队列、微博关注人时间轴列表等场景。不论是先进先出的队列,仍是先进后出的栈,双端列表都很好的支持这些特性。
双端链表仍是 Redis 列表类型的底层实现之一, 当对列表类型的键进行操做 —— 好比执行 RPUSH 、 LPOP 或 LLEN 等命令时, 程序在底层操做的可能就是双端链表。
双端链表及其节点的性能特性以下:
1.节点带有前驱和后继指针,访问前驱节点和后继节点的复杂度为 O(1)O(1) ,而且对链表的迭代能够在从表头到表尾和从表尾到表头两个方向进行;2.链表带有指向表头和表尾的指针,所以对表头和表尾进行处理的复杂度为 O(1)O(1) ;3.链表带有记录节点数量的属性,因此能够在 O(1)O(1) 复杂度内返回链表的节点数量(长度);
除此以外,Redis为双端链表还实现了一个迭代器, 这个迭代器能够从两个方向对双端链表进行迭代:
1.沿着节点的 next 指针前进,从表头向表尾迭代;2.沿着节点的 prev 指针前进,从表尾向表头迭代;
双端链表的实现:
参考资料:http://origin.redisbook.com/internal-datastruct/adlist.html
4)skipList 跳跃表
从图中能够看到, 跳跃表主要由如下部分构成: 1.表头(head):负责维护跳跃表的节点指针。2.跳跃表节点:保存着元素值,以及多个层。3.层:保存着指向其余元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了提升查找的效率,程序老是从高层先开始访问,而后随着元素值范围的缩小,慢慢下降层次。4.表尾:所有由 NULL 组成,表示跳跃表的末尾。
参考资料:https://redisbook.readthedocs.io/en/latest/internal-datastruct/skiplist.html
5)整数集合(intset)
Intset 只支持升级,不支持降级。
Intset 是有序的,程序使用二分查找算法来实现查找操做,复杂度为 O(lgN) 。
Intset 是集合键的底层实现之一,若是一个集合是下面的状况,那么 Redis 就会使用 intset 来保存集合元素。
只保存着整数元素;
元素的数量很少;
参考资料:https://redisbook.readthedocs.io/en/latest/compress-datastruct/intset.html
因素4:合理的数据编码
Redis 使用对象(redisObject)来表示数据库中的键值,当咱们在 Redis 中建立一个键值对时,至少建立两个对象,一个对象是用作键值对的键对象,另外一个是键值对的值对象。
typedef struct redisObject{ //类型:包含字符串对象、列表对象、哈希对象、集合对象、有序集合对象。 unsigned type:4; //编码 unsigned encoding:4; //指向底层数据结构的指针 void *ptr; //... }robj;
编码介绍:
1)String:存储数字的话,采用 int 类型的编码,若是是非数字的话,采用 raw 编码;
2)List:List 对象的编码能够是 ziplist 或 linkedlist,字符串长度 < 64 字节且元素个数 < 512 使用 ziplist 编码,不然转化为 linkedlist 编码;
备注:这两个条件是能够修改的,在 redis.conf 中:list-max-ziplist-entries 512list-max-ziplist-value 64
3)Hash:Hash 对象的编码能够是 ziplist 或 hashtable。
当 Hash 对象同时知足如下两个条件时,Hash 对象采用 ziplist 编码,不然就是 hashtable 编码。
1.Hash 对象保存的全部键值对的键和值的字符串长度均小于 64 字节。2. Hash 对象保存的键值对数量小于 512 个。
4)Set:Set 对象的编码能够是 intset 或 hashtable,intset 编码的对象使用整数集合做为底层实现,把全部元素都保存在一个整数集合里面。
保存元素为整数且元素个数小于必定范围使用 intset 编码,任意条件不知足,则使用 hashtable 编码。
5)Zset:Zset 对象的编码能够是 ziplist 或 zkiplist,当采用 ziplist 编码存储时,每一个集合元素使用两个紧挨在一块儿的压缩列表来存储。
Ziplist 压缩列表第一个节点存储元素的成员,第二个节点存储元素的分值,而且按分值大小从小到大有序排列。
当 Zset 对象同时知足一下两个条件时,采用 ziplist 编码,若是不知足以上条件的任意一个,ziplist 就会转化为 zkiplist 编码。
Zset 保存的元素个数小于 128。Zset 元素的成员长度都小于 64 字节。
备注:这两个条件是能够修改的,在 redis.conf 中:zset-max-ziplist-entries 128zset-max-ziplist-value 64