Redis划重点

Redis专题
redis底层数据结构由数组通过hash实现

dictEntry就是一个key/value对象:
	key:指向redisObject对象
	value:指向redisObject对象
	next:连接落入同一会hash槽的对象
	

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;


一、String类型
redis string类型转换:
	1、整数(int),存储字符串长度小于21且能够转化为整数的字符串。
	2、EmbeddedString(embstr),存储字符串长度小于39的字符串(REDIS_ENCODING_EMBSTR_SIZE_LIMIT)。
	3、SDS(raw),剩余情况使用sds进行存储。
	
embstr和sds的区别在于内存的申请和回收
	embstr的创建只需分配一次内存,而raw为两次(一次为sds分配对象,另一次为redisObject分配对象,embstr省去了第一次)。相对地,释放内存的次数也由两次变为一次。
	embstr的redisObject和sds放在一起,更好地利用缓存带来的优势
	缺点:redis并未提供任何修改embstr的方式,即embstr是只读的形式。对embstr的修改实际上是先转换为raw再进行修改。


string编码转换源码分析
	通过redis 内部的命令映射表我们找到set对应的处理函数为setCommand,相当于这个是处理set命令的入口函数,关注下tryObjectEncoding,内部对其实对Object进行转换。


整个尝试编码转换的逻辑过程通过代码的注释应该是比较清楚了,过程如下:
	1、只对长度小于或等于 21 字节,并且可以被解释为整数的字符串进行编码,使用整数存储
	2、尝试将 RAW 编码的字符串编码为 EMBSTR 编码,使用EMBSTR 编码
	3、这个对象没办法进行编码,尝试从 SDS 中移除所有空余空间,使用SDS编码
	
	
redis sds的介绍
/*
 * 保存字符串对象的结构
 */
struct sdshdr {
    
    // buf 中已占用空间的长度
    int len;
 
    // buf 中剩余可用空间的长度
    int free;
 
    // 数据空间
    char buf[];
};

sds对象创建:
	在创建sds对象的时候,我们上面提到过的涉及两次内存分配的过程,从下面的代码可以看出来:
		1、sds对象创建sdsnewlen分配了一次内存。
		2、robj对象的创建又分配了一次内存。
		3、整个sds对象的创建其实就是分配内存并初始化len和free字段。
		
sds内存扩容
	当字符串长度小于SDS_MAX_PREALLOC (1024*1024),那么就以2倍的速度扩容,当字符串长度大于SDS_MAX_PREALLOC,那么就以+SDS_MAX_PREALLOC的速度扩容。

sds内存缩容
	释放内存的过程中修改len和free字段,并不释放实际占用内存。



二、List类型
redis list数据结构底层采用压缩列表ziplist或linkedlist两种数据结构进行存储,首先以ziplist进行存储,在不满足ziplist的存储要求后转换为linkedlist列表

当列表对象同时满足以下两个条件时,列表对象使用ziplist进行存储,否则用linkedlist存储
	1、列表对象保存的所有字符串元素的长度小于64字节
	2、列表对象保存的元素数量小于512个
	
	
redis list元素添加过程
	list的数据添加根据传入的变量个数一个个顺序添加,整个顺序如下:
		1、创建list对象并添加到db的数据结构当中
		2、针对每个待插入的元素添加到list当中
		
		
	list的每个元素的插入过程中,我们会对是否需要进行转码作两个判断:
		1、对每个插入元素的长度进行判断是否进行ziplist->linkedlist的转码
		2、对list总长度是否超过ziplist最大长度的判断
		
	判断ziplist中单个元素的长度是否超过64的长度,如果超过了长度那么就需要转编码格式为linkedlist编码
	
	
redis ziplist数据结构
/*
 * 保存 ziplist 节点信息的结构
 */
typedef struct zlentry {
 
    // prevrawlen :前置节点的长度
    // prevrawlensize :编码 prevrawlen 所需的字节大小
    unsigned int prevrawlensize, prevrawlen;
 
    // len :当前节点值的长度
    // lensize :编码 len 所需的字节大小
    unsigned int lensize, len;
 
    // 当前节点 header 的大小
    // 等于 prevrawlensize + lensize
    unsigned int headersize;
 
    // 当前节点值所使用的编码类型
    unsigned char encoding;
 
    // 指向当前节点的指针
    unsigned char *p;
 
}


redis linkedlist数据结构
/*
 * 双端链表节点
 */
typedef struct listNode {
 
    // 前置节点
    struct listNode *prev;
 
    // 后置节点
    struct listNode *next;
 
    // 节点的值
    void *value;
 
} listNode;


/*
 * 双端链表迭代器
 */
typedef struct listIter {
 
    // 当前迭代到的节点
    listNode *next;
 
    // 迭代的方向
    int direction;
 
} listIter;
 
/*
 * 双端链表结构
 */
typedef struct list {
 
    // 表头节点
    listNode *head;
 
    // 表尾节点
    listNode *tail;
 
    // 节点值复制函数
    void *(*dup)(void *ptr);
 
    // 节点值释放函数
    void (*free)(void *ptr);
 
    // 节点值对比函数
    int (*match)(void *ptr, void *key);
 
    // 链表所包含的节点数量
    unsigned long len;
 
} list;



三、Hash类型
redis hash底层数据结构
	redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable。当hash对象可以同时满足一下两个条件时,哈希对象使用ziplist编码。
		1、哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
		2、哈希对象保存的键值对数量小于512个
	

redis hash数据结构
redis的hash架构就是标准的hashtab的结构,通过挂链解决冲突问题
dict
	type -> dictType
	ht[0] -> dictht(table, size, sizemask, used)   table[0] -> dictEntry(*val, *key, *next)   table[1] -> dictEntry(*val, *key, *next)    null
	ht[1] -> dictht(table, size, sizemask, used)   table[0] -> dictEntry(*val, *key, *next)
	*privdata
	rehashid
	iterators


redis ziplist数据结构
	ziplist的数据结构主要包括两层,ziplist和zipEntry。
		1、ziplist包括zip header、zip entry、zip end三个模块。
		2、zip entry由prevlen、encoding&length、value三部分组成。
		3、prevlen主要是指前面zipEntry的长度,coding&length是指编码字段长度和实际- 存储value的长度,value是指真正的内容。
		4、每个key/value存储结果中key用一个zipEntry存储,value用一个zipEntry存储。


redis hash存储过程源码分析
	以hset命令为例进行分析,整个过程如下:
		1、首先查看hset中key对应的value是否存在,hashTypeLookupWriteOrCreate。
		2、判断key和value的长度确定是否需要从zipList到hashtab转换,hashTypeTryConversion。
		3、对key/value进行string层面的编码,解决内存效率问题。
		4、更新hash节点中key/value问题。
		5、其他后续操作的问题

判断key/value的长度是否超过规定的长度64个字节,由REDIS_HASH_MAX_ZIPLIST_VALUE定义。如果超过64个字节那么久需要将ziplist转成hashtab对象

hash底层的更新操作函数hashTypeSet内部会根据是ziplist还是hashtab进行不同的处理逻辑,在ziplist当中会判断ziplist存储数据的长度来判断是否需要转为hashtab数据结构,其中长度判断是通过#define REDIS_HASH_MAX_ZIPLIST_ENTRIES 512定义的



四、Set类型
redis set底层数据结构
	redis的集合对象set的底层存储结构特别神奇,我估计一般人想象不到,底层使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,
		hashtable就是普通的哈希表(key为set的值,value为null)。是不是觉得用hashtable存储set是一件很神奇的事情

	set的底层存储intset和hashtable是存在编码转换的,使用intset存储必须满足下面两个条件,否则使用hashtable,条件如下:
		1、集合对象保存的所有元素都是整数值
		2、集合对象保存的元素数量不超过512个
		
		
intset的数据结构
	intset内部其实是一个数组(int8_t coentents[]数组),而且存储数据的时候是有序的,因为在查找数据的时候是通过二分查找来实现的。
	
	
typedef struct intset {
    
    // 编码方式
    uint32_t encoding;
 
    // 集合包含的元素数量
    uint32_t length;
 
    // 保存元素的数组
    int8_t contents[];
 
} intset;


redis set存储过程
	以set的sadd命令为例子,整个添加过程如下:
		1、检查set是否存在不存在则创建一个set结合。
		2、根据传入的set集合一个个进行添加,添加的时候需要进行内存压缩。
		3、setTypeAdd执行set添加过程中会判断是否进行编码转换。


稍微深入分析一下set的单个元素的添加过程,首先如果已经是hashtable的编码,那么我们就走正常的hashtable的元素添加,如果原来是intset的情况,那么我们就需要进行如下判断:
	1、如果能够转成int的对象(isObjectRepresentableAsLongLong),那么就用intset保存。
	2、如果用intset保存的时候,如果长度超过512(REDIS_SET_MAX_INTSET_ENTRIES)就转为hashtable编码。
	3、其他情况统一用hashtable进行存储



五、ZSet类型
zset底层存储结构
	zset底层的存储结构包括ziplist或skiplist,在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist,两个条件如下:
		1、有序集合保存的元素数量小于128个
		2、有序集合保存的所有元素的长度小于64字节

当ziplist作为zset的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值

当skiplist作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系


ziplist数据结构
	ziplist作为zset的存储结构时,格式如下图,细节就不多说了,我估计大家都看得懂,紧挨着的是元素memeber和分值socore,整体数据是有序格式
	
skiplist数据结构
	skiplist作为zset的存储结构,整体存储结构如下图,核心点主要是包括一个dict对象和一个skiplist对象。dict保存key/value,key为元素,value为分值;
	skiplist保存的有序的元素列表,每个元素包括元素和分值。两种数据结构下的元素指向相同的位置


skiplist的源码格式
	zset包括dict和zskiplist两个数据结构,其中dict的保存key/value,便于通过key(元素)获取score(分值)。zskiplist保存有序的元素列表,便于执行range之类的命令
	
	zskiplist作为skiplist的数据结构,包括指向头尾的header和tail指针,其中level保存的是skiplist的最大的层数。
	
	skiplist跳跃列表中每个节点的数据格式,每个节点有保存数据的robj指针,分值score字段,后退指针backward便于回溯,zskiplistLevel的数组保存跳跃列表每层的指针
	
	
	
zset存储过程
	zset的添加过程我们以zadd的操作作为例子进行分析,整个过程如下:

		1、解析参数得到每个元素及其对应的分值
		2、查找key对应的zset是否存在不存在则创建
		3、如果存储格式是ziplist,那么在执行添加的过程中我们需要区分元素存在和不存在两种情况,存在情况下先删除后添加;
			不存在情况下则添加并且需要考虑元素的长度是否超出限制或实际已有的元素个数是否超过最大限制进而决定是否转为skiplist对象。
		4、如果存储格式是skiplist,那么在执行添加的过程中我们需要区分元素存在和不存在两种情况,存在的情况下先删除后添加,不存在情况下那么就直接添加,
			在skiplist当中添加完以后我们同时需要更新dict的对象。

 

你可能感兴趣的:(Redis,redis)