redis的五种基本数据类型及其内部实现
⼀、redis的五种数据类型
redis作为⽬前最流⾏的Key-Value类型的内存数据库,对于数据库的操作都在内存中进⾏,并可定期的将数据异步的持久化到磁盘之上。由于是纯内存的操作,因此它的性能⽐普通的关系型数据库⾼出很多,同时由于是单线程串⾏的执⾏指令,因此也避免了加锁和释放锁的开销。相⽐于memcache,redis的每个value值最⼤可存储1GB,⽽memcache只有10MB,同时redis在速度上也快于memcache,还可以持久化。redis最⼤的特点则是,它可以⽀持五种基本数据类型,分别是字符串(string),列表(list),集合(set),有序集合(zset)以及哈希(hash),下⾯具体介绍下它们的特点以及内部实现。
⼆、redisObject
在redis中每个value都是以⼀个redisObject结构来表⽰,如下:
typedef struct redisObject{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
//引⽤计数器
int refCount;
//最后⼀次的访问时间
unsigned lru:
}
type:
type就是指这个对象的数据类型,即我们平常所认知的redis的五种数据类型,可以通过TYPE命令查看⼀个对象的数据类型:
127.0.0.1:6379> set a 123
OK
127.0.0.1:6379> type a
string
127.0.0.1:6379> hmset b name jack age 12
OK
127.0.0.1:6379> type b
hash
127.0.0.1:6379> lpush c 123
(integer) 3
127.0.0.1:6379> type c
list
127.0.0.1:6379> sadd d 123
(integer) 3
127.0.0.1:6379> type d
set
127.0.0.1:6379> zadd e 2 a 3 b
(integer) 2
127.0.0.1:6379> type e
zset
127.0.0.1:6379>
encoding:
表⽰redisObject对象的底层编码实现,主要有简单动态字符串,链表,字典,跳跃表,整数集合以及压缩列表,每⼀个value都是由两种及以上的上述编码所构成,后⾯详细展出
*ptr
⽤于指向底层实现数据结构的指针
lru:
最后⼀次访问该对象的时间,可以通过Object idletime查看当前时间距离该键的lru的时间,即空转时间如下:
127.0.0.1:6379> set cbd 123
OK
127.0.0.1:6379> object idletime cbd
(integer) 90
127.0.0.1:6379> object idletime cbd
(integer) 95
127.0.0.1:6379> object idletime cbd
(integer) 98
127.0.0.1:6379> get cbd //访问了该键,因此lru重置
"123"
127.0.0.1:6379> object idletime cbd
(integer) 3
127.0.0.1:6379>
refCount
引⽤计数器,当创建⼀个对象的时间便将它的值初始化为1,当它被其它程序引⽤之时则加1,不再被引⽤则减1,当它的引⽤计数值变为0时,对象所占⽤的内存就会被释放。因此它主要有两个⽤途,内存回收的标志以及⽤于对象共享。
对象共享:当新建的两个或多个键都是整数值并且相同时,则它们的键会共享这⼀个值对象,这样可以减少内存的分配和回收,可以⽤OBJECT REFCOUNT查看引⽤情况,如下:
127.0.0.1:6379> set first123
OK
127.0.0.1:6379> OBJECT refcount first
(integer) 1
127.0.0.1:6379> set second123
OK
127.0.0.1:6379> OBJECT refcount second
(integer) 2
127.0.0.1:6379> OBJECT refcount first
(integer) 2
127.0.0.1:6379>
redis在初始化服务器之时,会默认创建包含了值为0-9999的对象,当新建的键的值处于这个范围之时,则直接添加引⽤即可。
三、字符串对象
简单动态字符串(SDS):
字符串作为redis中最常见的数据结构,所有键值对的键,字符串对象的值底层都是由简单动态字符串实现的。在redis中,它并未使⽤C语⾔中的字符串,⽽是⾃⼰实现了⼀种叫做SDS的数据结构,它的结构表⽰如下:
struct sdshdr{
//记录buf数组中已使⽤字节的长度
int len;
//记录buf数组中剩余空间的长度redis支持的五种数据类型
int free;
//字节数组,⽤于存储字符串
char buf[];
};
⼀个SDS⽰例如下:
当free为0时表⽰没有任何未使⽤的空间,len表⽰字符串的长度,buf中存储每⼀个字节以及空字符,由于SDS遵循了C字符串以
为’\0’结尾的特点,因此它也可以直接重⽤部分C函数库⾥的函数。
相⽐于C字符串,SDS具有以下特点:获取长度的时间复杂度为O(1),因为len直接保存了当前字符串
的长度;避免缓存区溢出,当C字符串进⾏拼接之时,如果未事先对当前字符串的空间进⾏调整,则可能会导致当前字符串的数据溢出,导致拼接过来的字符串内容被意外的修改,⽽SDS在拼接之前会进⾏⾃动的调整和扩展;减少内存分配次数,在SDS拼接发⽣以后,如果此时的len⼩于1MB则它会多分配和len ⼤⼩相同的未使⽤空间,⽤free表⽰,如果⼤于1MB,则会分配1MB的为使⽤空间;惰性空间释放,当字符串进⾏缩短的时候,程序并不会⽴即回收空间(也可以调⽤API⽴即释放),⽽是记录到free之中,以便于后序拼接的使⽤。
编码:
字符串对象的编码可以是int、raw或者embstr。其中int表⽰整型的值,embstr表⽰⼩于等于39字节的字符串值,剩余的均⽤raw表⽰,并且int和embstr都是只读的,⼀旦发⽣了append操作,即会转换为raw。如下:
127.0.0.1:6379> set age 12
OK
127.0.0.1:6379> Object encoding age
"int"
127.0.0.1:6379> APPEND age 3
(integer) 3
127.0.0.1:6379> Object encoding age
"raw"
127.0.0.1:6379> set name jack
OK
127.0.0.1:6379> Object encoding name
"embstr"
127.0.0.1:6379> APPEND name ja
(integer) 6
127.0.0.1:6379> Object encoding name
"raw"
四、列表对象
链表提供了节点重排以及节点顺序访问的能⼒,redis中的列表对象主要是由压缩列表和双端链表实现。
双端链表(linkedlist)结构如下:
type struct list{
//表头节点
listNode *head;
//表尾节点
listNode *tail;
//包含的节点总数
unsigned long len;
//⼀些操作函数 dup
};
⼀个链表⽰例:
其中每个节点都有⼀个prev指针和⼀个next指针,⽽节点中的value则是列表对象具体的值。
压缩列表(ziplist)结构如下:
⼀个压缩列表⽰例:
⽽每个列表节点中主要包括以下⼏项:previous_entry_length,记录了压缩列表中前⼀节点的字节长度,当⼩于254字节时,它的长度为1字节,当⼤于254字节时,长度为5字节且后4字节保存真正的长度,⽤于表尾向表头遍历;content,节点所存储的内容,可以是⼀个字节数组或者整数;encoding,记录content属性中所保存的数据类型以及长度。
编码:
当列表对象所存储的字符串元素长度⼩于64字节并且元素数量⼩于512个时,使⽤ziplist编码,否则使⽤linkedlist编码,如下:
五、集合对象
整数集合与字典:
集合对象的编码可以是整数集合(intset)或者字典(hashtable)。 整数集合结构如下:
type struct ziplist {
//整个压缩列表的字节数
uint32_t zlbytes;
//记录压缩列表尾节点到头结点的字节数,直接可以求节点的地址
uint32_t zltail_offset;
//记录了节点数,有多种类型,默认如下
uint16_t zllength;
//节点
列表节点 entryX;
127.0.0.1:6379> rpush zip "hello" "world"
(integer) 2
127.0.0.1:6379> OBJECT encoding zip
"ziplist"
127.0.0.1:6379> rpush zip "fffffffffffffffffffffzzzzzzzzzzzzzzzzzzzzzzzzzzzzzkkkkkkkkkkkkkkkkkkkkkkkkkcccccccccc"
(integer) 3
127.0.0.1:6379> OBJECT encoding zip
"linkedlist"
127.0.0.1:6379>
typedef struct intset{
//编码⽅式
uint32_t encoding;
//元素数量
uint32_t length;
//存储元素的数组
int8_t contents[];
}
整数集合的每个元素都是contents数组的⼀个数组项,各个项在数组中按值得⼤⼩从⼩到⼤有序排列,并且不包含重复的项。contents数组中元素的类型由encoding决定,当新加⼊元素之时,如果元素的编码⼤于contents是数组的编码,则会将所有元素的编码升级为新加⼊元素的编码,然后再插⼊。编码不会发⽣降级。
字典的结构如下:
⽽哈希表的结构如下:
⼀个字典⽰例如下:
其中键值对都保存在节点dictEntry之中,并且通过拉链法解决哈希冲突,存储时通过来计算键的哈希值,能够更好的提供随机分布性且速度也快。扩容时时采⽤渐进式的rehash,采⽤分⽽治之的⽅法,通过改变rehashidx的值,来⼀个个将元素移动到ht[1]中,完成以后将ht[1]变为ht[0],原先的ht[0]变为ht[1],同时将redhashidx置为-1。
编码: 当集合对象所保存的元素都是整数值且元素数量不超过512个时,使⽤intset编码,否则使⽤hashtable编码,如下:
typedef struct dict{
//类型特定函数
dictType *type ;
//哈希表 两个,⼀个⽤于实时存储,⼀个⽤于rehash
dictht ht[2];
//rehash 索引 数据迁移时使⽤
unsigned rehashidx;}
typedef struct dictht{
//哈希表数组
dictEntry **table;
//哈希表⼤⼩
unsigned long size;
//哈希表掩码,总是等于size-1,存储时计算索引值
unsigned long sizemask;
//已有元素数量
unsigned long used;}
127.0.0.1:6379> sadd cloud 1 2 3 4 5
(integer) 5
127.0.0.1:6379> object encoding cloud
"intset"
127.0.0.1:6379> sadd cloud a b c
(integer) 3
127.0.0.1:6379> object encoding cloud
"hashtable"
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论