Redis数据类型

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

编码转换条件:所有元素都是整数,并且元素的个数超过512个,使用的是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),排行榜应用

 

 

















 

posted @ 2019-09-24 10:23  Jemb  阅读(145)  评论(0编辑  收藏  举报