Redis是基于内存K-V数据库,然而内存资源又是非常宝贵的,所以使用最合适的存储结构,做到尽量节省内存资源,又对性能的影响不大,成为一个至关重要的问题。
redisObject数据结构:
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:占用4个bit位,记录了对象的类型
STRING=0
LIST=1
SET=2
ZSET=3
HASH=4
encoding:记录了对象所使用的编码,也就是说对象使用了什么数据结构作为对象底层实现
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ // 已废弃
#define OBJ_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
lru:表示当内存超限时采用LRU算法清除内存中的对象。
refcount:表示对象的引用计数。ptr:指针指向对象底层的数据结构,这个数据对象有encoding决定
sdshdr数据结构:
typedef char *sds;
struct sdshdr {
int len;
int free;
char buf[];
};
len:表示缓存中已经占用的空间长度
free:表示buf中可剩余的空间长度,当free为0的时候,表示buf中没有剩余的缓冲空间
buf:数据空间数组
使用sds字符串的好处
1.获取字符串的长度,时间复杂度为o(1)
2.减少内存空间分配次数,redis的sds会进行空间的预分配,
3.使用惰性空间释放,当字符串的长度缩短的时候,不会立即free空间,而是使用free将字段数量记录下来,等待后续的使用。
4.使用C字符串可能因为忘记分配内存空间,字符串长度增大时造成内存溢出,对于SDS,由于len和free的存在,对于修改字符串使用空间预分配和队形空间释放两种策略,降低了内存溢出的可能
5.二级制安全,SDS的API使用二进制的方式来处理buf里面的元素,并且以len属性表示的长度判断字符串是否结束。
String类型:
String对象的编码可以是int,raw,embstr,
如果一个对象保存的是整数,并且这个数可以被long识别,那么这个对象的编码则设置为int
如果保存长度小于44字节,这个对象编码使用的是embstr编码
如果保存长度大于44字节,这个对象编码使用的是raw编码
embstr编码是用来保存短字符串的一种编码方式,这种编码和raw编码一样,都使用redisObject结构和sdshdr结构来标识字符串对象。
raw编码会调用两次内存分配函数来创建redisObject结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来分配一块连续的空间,空间中一次包含redisObject和sdshdr两个结构。
List类型:
在版本3.2之前,当链表数量比较小的时候,使用的是ziplist实现,长度比较大或数量比较多的时候,使用linkedlist来实现。
但是在版本3.2之后,可能考虑到彼此转换很麻烦,可能导致内存碎片,重新分配内存区域可能涉及大量数据拷贝,重新引入了一个 quicklist 的数据结构,列表的底层都由quicklist实现。
quicklistNode 数据结构
typedef struct quicklistNode {
struct quicklistNode *prev; // 指向上一个ziplist节点
struct quicklistNode *next; // 指向下一个ziplist节点
unsigned char *zl; // 数据指针,如果没有被压缩,就指向ziplist结构,反之指向quicklistLZF结构
unsigned int sz; // 表示指向ziplist结构的总长度(内存占用长度)
unsigned int count : 16; // 表示ziplist中的数据项个数
unsigned int encoding : 2; // 编码方式,1--ziplist,2--quicklistLZF
unsigned int container : 2; // 预留字段,存放数据的方式,1--NONE,2--ziplist
unsigned int recompress : 1; // 解压标记,当查看一个被压缩的数据时,需要暂时解压,标记此参数为1,之后再重新进行压缩
unsigned int attempted_compress : 1; // 测试相关
unsigned int extra : 10; // 扩展字段,暂时没用
} quicklistNode;
具体结构解析待完善
Hash类型:
hash的编码可以是 ziplist或hashTable
编码转换:当元素的个数小于512个的时候,并且每个元素的长度小于64字节的时候,hash使用的是 ziplist
超过这个限制的话,则使用的是hashtable
hashTable的哈希表使用的是字典数据结构,hash对象中的每个对象使用一个字典键值对。
但是先对于简单的字典数据结构,使用压缩链表就可以满足,可以集中存储,节约空间。
Set类型:
set集合对象的编码可以是intset或者是hashtable
intset编码的集合对象使用整数集合作为底层实现
hashtable使用字典作为底层实现
Zset类型:
有序集合使用的编码可以是 ziplist或者 skiplist
转换编码条件:当元素的数量小于128,并且所有元素的长度小于64字节,则使用ziplist,不符合的话使用的是跳表
ziplist,每个集合元素使用两个紧挨在一起的压缩列表节点来保存。集合元素按分数从小到大排
跳表结构
typedef struct zset{
zskiplist *zsl; //跳跃表
dict *dice; //字典
} zset;
一个zset结构同时包含一个字典和一个跳跃表,
字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的成员,跳跃表节点的 score 属性保存元素的分值。
总结五大数据结构的应用场景:
String:二进制安全,可以用来存放图片,视频,另外redis基于内存的高性能读写,可以用来保存秒杀场景中商品库存,分布式锁,同时由于具有过期时间机制,可以用来设置短信验证码过期校验
hash:KV结构,可以做单点登陆用户信息的存放
list:可以实现简单的消息队列推送,有序列表
set:由于底层是基于字典实现,查询速度十分快,由于其存放不可重复的数据,可以做平台用户名是否重复,利用api对交集、并集、差集等对支持操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
zset :有序的集合,可以做范围查找(redis geo的底层是基于zset),排行榜应用