概述
前面几张介绍了一些Redis的数据结构,比如SDS,集合,字典等,但是Redis并不会直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这些对象包括字符串对象,列表对象,哈希对象,集合对象和有序集合对象。每种对象都用到了一种或多种前面介绍的数据结构。
通过不同类型的对象,Redis在执行命令之前可以根据类型来判断一个对象是否可以执行给定的命令。
Redis对象还使用了基于引用计数的内存回收机制,当程序不再使用某个对象时,这个对象占用的内存就会释放。
Redis的对象带有访问时间记录信息,可以用于计算数据空键的空转时长,在服务器启用了maxmemory的情况下,空转时长比较大的键的可能会被服务器删除。
对象类型与编码
首先看一下对象的数据结构:
typedef struct redisObject { unsigned type:4; //类型,主要包括: unsigned encoding:4; //编码 unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr; //执行底层实现的数据结构的指针 } robj;
type有以下几种类型,在redis客户端可通过type key来查看对应key的类型
/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
encoding类型有,在redis客户端可通过OBJECT ENCONDING key来查看对应key的encoding类型
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_HT 2 /* Encoded as hash table */
#define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
接下来对五中类型的对象一一讲解下:
字符串对象
字符串对象的编码可以是int,raw或者embstr
如果一个字符串key保存的是整数值,而且这个整数可以通过long类型的表示,那么字符串对象就会将整数值保存在字符串对象结构的ptr里面(void *long),并将字符串的编码类型设置为int,如下:
redis 127.0.0.1:6379> set msg 10086
OK
redis 127.0.0.1:6379> object encoding msg
"int"
但是如果保存是字符串类型呢?
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
127.0.0.1:6379[15]> set msg "hello"
OK
127.0.0.1:6379[15]> object encoding msg
"embstr"
127.0.0.1:6379[15]> set msg "helloasdfasdfsadfasdfasdfsadfsadfasdfasdfasdfsadf"
OK
127.0.0.1:6379[15]> object encoding msg
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
"raw"
可以看到,分布使用了embstr和raw编码来保存字符串值,为什么会有这种区别呢? 还有什么情况下使用raw什么时候使用embstr呢?
当字符串长度超过32时,字符串对象采用raw来编码,如果小于32时,则使用embstr。那ptr怎么指向呢? Redis里面的string通常都使用SDS来表示。所以数据结构如果:
embstr是专门用于保存短字符串的一种优化编码方式,和raw一样,都需要使用redisObject和sds结构来表示字符串对象,不同的是raw会调用两次内存分配来分别为两个数据结构分配空间大小,而embstr只需要调用一次来分配连续空间。
使用embstr的好处在哪呢?
1.刚才讲到的,内存分配次数从两次降到一次;
2.所以内存释放也只需要调用一次了;
3.连续内存能够更好的利用缓存优势。
embstr的结构如下:
最后浮点数也是以字符串的方式来保存的,可以看下:
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
127.0.0.1:6379[15]> set msg 3.14159
OK
127.0.0.1:6379[15]> object encoding msg
"embstr"
编码是可以转换的,比如msg刚开始是int,后来追加了一条数据,编码就会改变
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
127.0.0.1:6379[15]> set msg 3
OK
127.0.0.1:6379[15]> object encoding msg
"int"
127.0.0.1:6379[15]> append msg abc
(integer) 4
127.0.0.1:6379[15]> object encoding msg
"raw"
127.0.0.1:6379[15]> get msg
"3abc"
列表对象
列表对象的编码方式可以是ziplist或者linkedlist。
如果是ziplist,那么对象应该为下图,ptr执行ziplist结构
如果是linkedlist,对象结构如下,stringObject代表是SDS,这里是为了简化一下
什么时候使用ziplist,什么时候使用linkedlist?(版本仅针对3.0.x的,3.2.x版本上目前使用的都是quicklist)
当列表对象同时满足下面两个条件时,使用ziplist,否则使用linkedlist:
1.列表对象保存的所有字符串长度都小于64;
2.列表对象保存的元素个数小于512;
哈希对象
hash对象的编码方式由ziplist和hashtable
使用ziplist保存结构如下图
使用hashtable保存结构如下,其中stringObject也是为SDS结构
什么时候使用的ziplist,什么时候使用hashtable?
同时满足下面两个条件时,使用ziplist,否则使用hashtable:
1.hash的键值对的长度均小于64;
2.hash对象的保存的键值对数量小于512;
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
127.0.0.1:6379[15]> object encoding book
"ziplist"
127.0.0.1:6379[15]> hset book content "C++asdfasdkfjlasdjfl;sajdfljsadlkfjsakldfjlksadjfklsdjflksajdflkasjdlfkjasdl;fjasl;dfjl;sadfjlsadkjflsadf"
(integer) 1
127.0.0.1:6379[15]> object encoding book
"hashtable"
集合对象
集合对象的编码方式由intset和hashtable
intset和hashtable表示的结构分布如下:
什么时候用hashtable,什么时候用intset?
同时满足下面两个条件时,使用intset,否则使用hashtable:
1.所有对象元素为整数值;
2.集合对象的元素数量不超过512个;
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 11.0px Menlo }
span.s1 { }
127.0.0.1:6379[15]> sadd keyset 1 2 3
(integer) 3
127.0.0.1:6379[15]> object encoding keyset
"intset"
127.0.0.1:6379[15]> sadd keyset "aa"
(integer) 1
127.0.0.1:6379[15]> object encoding keyset
"hashtable"
有序集合对象
有序集合对象的编码可以是ziplist和skiplist
ziplist编码,压缩列表中的元素按照score从小到大排序,结构如下:
skiplist编码方式使用的zset结构作为底层实现,一个zset结构同时包含了一个skiplist和一个hashtable
typedef struct zset { dict *dict; zskiplist *zsl; } zset;
zsl跳跃表按照分值从小到大保存了所有元素,每个跳跃点都保存了一个集合元素,跳跃节点的object属性保存了元素的成员,score属性保存了分值,通过跳跃表程序可以对有序集合进行范围操作,比如zrange等。
为什么还需要给dict呢?
dict为有序集合创建了一份从成员到分值的映射,