Redis为何这么快——数据存储角度数据库

来源:互联网 / 作者:SKY / 2018-10-10 15:11 / 点击:
Redis是一个由ANSI C语言编写,性能优秀、支持网络、可持久化的K-K内存数据库,并提供多种语言的API。

一、简介和应用

Redis是一个由ANSI C语言编写,性能优秀、支持网络、可持久化的K-K内存数据库,并提供多种语言的API。它常用的类型主要是 String、List、Hash、Set、ZSet 这5种

Redis在互联网公司一般有以下应用:

 String:缓存、限流、计数器、分布式锁、分布式Session

 Hash:存储用户信息、用户主页访问量、组合查询

 List:微博关注人时间轴列表、简单队列

 Set:赞、踩、标签、好友关系

 Zset:排行榜

再比如电商在大促销时,会用一些特殊的设计来保证系统稳定,扣减库存可以考虑如下设计:

上图中,直接在Redis中扣减库存,记录日志后通过Worker同步到数据库,在设计同步Worker时需要考虑并发处理和重复处理的问题。

通过上面的应用场景可以看出Redis是非常高效和稳定的,那Redis底层是如何实现的呢?

二、Redis的对象redisObject

当我们执行set hello world命令时,会有以下数据模型:

 dictEntry:Redis给每个key-value键值对分配一个dictEntry,里面有着key和val的指针,next指向下一个dictEntry形成链表,这个指针可以将多个哈希值相同的键值对链接在一起,由此来解决哈希冲突问题(链地址法)。

 sds:键key“hello”是以SDS(简单动态字符串)存储,后面详细介绍。

 redisObject:值val“world”存储在redisObject中。实际上,redis常用5中类型都是以redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。

redisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要redisObject支持。这样设计的好处是,可以针对不同的使用场景,对5中常用类型设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

无论是dictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。比如jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。

前面说过,Redis每个对象由一个redisObject结构表示,它的ptr指针指向底层实现的数据结构,而数据结构由encoding属性决定。比如我们执行以下命令得到存储“hello”对应的编码:

redis所有的数据结构类型如下(重要,后面会用):

三、String

字符串对象的底层实现可以是int、raw、embstr(上面的表对应有名称介绍)。embstr编码是通过调用一次内存分配函数来分配一块连续的空间,而raw需要调用两次。

int编码字符串对象和embstr编码字符串对象在一定条件下会转化为raw编码字符串对象。embstr:<=39字节的字符串。int:8个字节的长整型。raw:大于39个字节的字符串。

简单动态字符串(SDS),这种结构更像C++的String或者Java的ArrayList<Character>,长度动态可变:

struct sdshdr {  

 // buf 中已占用空间的长度  

 int len;  

 // buf 中剩余可用空间的长度  

 int free;  

 // 数据空间  

 char buf[]; // ’’空字符结尾  

}; 

 get:sdsrange---O(n)

 set:sdscpy—O(n)

 create:sdsnew---O(1)

 len:sdslen---O(1)

常数复杂度获取字符串长度:因为SDS在len属性中记录了长度,所以获取一个SDS长度时间复杂度仅为O(1)。

预空间分配:如果对一个SDS进行修改,分为一下两种情况:

 SDS长度(len的值)小于1MB,那么程序将分配和len属性同样大小的未使用空间,这时free和len属性值相同。举个例子,SDS的len将变成15字节,则程序也会分配15字节的未使用空间,SDS的buf数组的实际长度变成15+15+1=31字节(额外一个字节用户保存空字符)。

 SDS长度(len的值)大于等于1MB,程序会分配1MB的未使用空间。比如进行修改之后,SDS的len变成30MB,那么它的实际长度是30MB+1MB+1byte。

惰性释放空间:当执行sdstrim(截取字符串)之后,SDS不会立马释放多出来的空间,如果下次再进行拼接字符串操作,且拼接的没有刚才释放的空间大,则那些未使用的空间就会排上用场。通过惰性释放空间避免了特定情况下操作字符串的内存重新分配操作。

阅读延展

1
3