redis--数据结构与对象

数据结构

- 字符串
struct sdshdr
{
    
    
	int len;// 已用部分尺寸
	int free;// 可用部分尺寸
	char buf[];
};
注意点:
len若不是0,字符串结尾含有一个'\0',占据1字节空间,但不计入len。
优势:
类似基于字符的动态数组,可动态内存增长与收缩。记录了长度,容量等。保留'\0'来兼容c字符串。

- 链表
typedef struct listNode
{
    
    
	struct listNode *prev;
	struct listNode *next;
	void* value;
}listNode;

typedef struct list
{
    
    
	listNode *head;
	listNode *tail;
	unsigned long len;
	void* (*dup)(void *ptr);
	void (*free)(void *ptr);
	int (*match)(void *ptr, void *key);
}list;
- 基于链式哈希表的字典
typedef struct dictht
{
    
    
	dictEntry **table;// table是一个数组,其中元素指向单链表首个节点指针
	unsigned long size;// 数组尺寸
	unsigned long sizemask;// 数组尺寸-1
	unsigned long used;// 哈希表维护的有效元素个数【等于各个单链表中有效元素之和】
} dictht;

typedef struct dictEntry
{
    
    
	void *key;// 键
	union
	{
    
    
		void *val;
		uint64_t u64;
		int64_t s64;
	}v;// 值
	
	struct dictEntry* next;// 单链表
};

typedef struct dict
{
    
    
	dictType *type;
	void *privdata;
	dictht ht[2];
	int trehashidx;// 用于记录rehash进度。-1表示不处于rehash过程。
} dict;

typedef struct dictType
{
    
    
	unsigned int (*hashFunction)(const void *key);
	void *(*keyDup)(void *privdata, const void *key);
	void *(*valDup)(void *privdata, const void *obj);
	int (*keyCompare)(void *privdata, const void *key1, const void *key2);
	void (*keyDestructor)(void *privdata, void *key);
	void (*valDestructor)(void *privdata, void *obj);
} dictType;

// 从键得到哈希索引
hash = dict->type->hashFunction(key);
index = hash & dict->ht[x].sizemask;
// 哈希算法具体为:MurmurHash2

// 动态哈希--rehash
1.为字典的ht[1]分配空间。
如动态扩展,ht[1]的大小等于第一个大于等于ht[0].used*22^n
如动态收缩,ht[1]的大小为第一个大于等于ht[0].used的2^n
2.将保存在ht[0]的所有键值对rehash到ht[1]上面。
键-值对在ht[1]上执行插入
3.释放ht[0],将ht[1]变更为ht[0],ht[1]重新设置为空。

注意点:
哈希表存储元素数量规模较大时,一次完整rehash时间会不可接受。
故引入渐进式rehash
1.为ht[1]分配空间,
2.在字典中维持一个索引计数器变量rehashidx,将其设为0
3.rehash期间,每次执行操作时,额外将ht[0]在rehashidx槽上所有键值对rehash到ht[1],此后rehashidx加1
4.rehashidx达到ht[0]槽数时,将其设为-1,表示rehash操作完成。

rehash执行期间,查找先针对ht[0]进行,不成功再对ht[1]进行。添加元素必定落在ht[1]
- 跳跃表
跳跃表支持平均O(log(N)),最坏O(N)的查找。
实现上相比红黑树简单,可作为红黑树的一种简单替代。
typedef struct zskiplistNode
{
    
    
	struct zskiplistNode *backward;
	double score;
	robj *obj;
	
	// 每次创建一个新跳跃表节点时,
	// 随机生成一个介于1和32间的值作为level数组大小
	// 通过层加快访问其他节点速度
	struct zskiplistLevel
	{
    
    
		struct zskiplistNode *forward;
		unsigned int span;// forward执行节点与元素所在节点距离.
	} level[];
}zskiplistNode;

typedef struct zskiplist
{
    
    
	struct zskiplistNode *header, *tail;
	// 不含header指向头节点的长度
	unsigned long length;
	// 非header指向头节点其他节点level最大值
	int level;
};
- 整数集合
typedef struct intset
{
    
    
	// INTSET_ENC_INT16,则contents是int16_t类型数组
	// INTSET_ENC_INT32,则contents是int32_t类型数组
	// INTSET_ENC_INT64,则contents是int64_t类型数组
	uint32_t encoding;
	// 数组长度--encoding指定类型元素个数
	uint32_t length;
	// 数组中元素按值递增存储,不含重复元素
	int8_t contents[];
} intset;
注意:
向一个底层为int16_t数组的整数集合添加一个int64_t类型整数值时,
整数集合已有的所有元素都会被转换成int64_t类型.

升级:发生在新元素类型比现有元素类型长.
1.根据新元素类型,扩展空间大小,新元素空间也需考虑.
2.已有元素变为新类型再放入.
3.插入新元素.

- 压缩列表
typedef struct zlist
{
    
    
	// 压缩列表整体尺寸
	uint32_t zlbytes;
	// 给定列表起始地址a,通过此偏移直接定位到尾元素起始位置
	uint32_t zltail;
	// 压缩列表节点数.
	// 为UINT16_MAX时,实际节点数需统计后才知道
	uint16_t zlen;
	entry nodes[];
	// 结束标记,0xFF
	uint8_t zlend;
}zlist;

// 字节可保存字节数组或整数.
// 字节数组可是以下三种长度之一
// 1.长度小于等于2^-1字节
// 2.长度小于等于2^14-1字节
// 3.长度小于等于2^32-1字节
// 整数值可为
// 1.4位长,介于0到12之间
// 2.1字节长有符号
// 3.3字节长有符号
// 4.int16_t
// 5.int32_t
// 6.int64_t
typedef struct zlnode
{
    
    
	union
	{
    
    
		// 前一节点长度小于254字节使用1字节.
		// 否则用5字节表长度.
		uint8_t len;
		struct 
		{
    
    
			char flag;// 0xFE
			uint32_t len;
		}b;
	}previous_entry_length;
	
	union
	{
    
    
		// 00xxxxxx content为字节数组,
		// xxxxxx表示字节数组长度
		// 11000000 content为int16_t整数
		// 11010000 content为int32_t整数
		// 11100000 content为int64_t整数
		// 11110000 content为24位有符号
		// 11111110 content为8位有符号
		// 1111xxxx 无需content,xxxx已经用于保持0-12间值
		uint8_t a;
		// 01xxxxxx xxxxxxxx content为字节数组
		// xxxxxx xxxxxxxx表示字节数组长度
		uint16_t b;
		struct
		{
    
    
			// 10xxxxxx content为字节数组
			// xxxxxx+d2二进制展开 表示字节数组长度
			uint8_t d1;
			uint32_t d2;	
		}d;
	}encoding;
	
	union
	{
    
    

	}content;
}zlnode;
注意:
结点记录前一结点长度,1字节或5字节性质可能在执行新结点插入,结点删除时导致连锁更新.

对象

Redis基于之前数据结构创建了一个对象系统.
包含字符串对象,列表对象,哈希对象,集合对象,有序集合对象这五种类型的对象.
Redis可在执行命令前,根据对象的类型来判断一个对象是否可执行给定的命令.
可针对不同使用场景,为对象设置多种实现.对象支持引用计数及共享.对象带有访问时间信息.

- 对象的类型与编码
Redis中的每个对象都由一个redisObject结构表示
typedef struct redisObject
{
    
    
	// 类型
	// REDIS_STRING 字符串
	// REDIS_LIST 列表
	// REDIS_HASH 哈希
	// REDIS_SET 集合
	// REDIS_ZSET 有序集合
	// 键值对下,键必然是字符串对象,值具体对象类型可变
	unsigned type:4;
	// 编码
	// REDIS_ENCODING_INT  long
	// REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串
	// REDIS_ENCODING_RAW 简单动态字符串
	
	// REDIS_ENCODING_HT 字典
	
	// REDIS_ENCODING_LINKDLIST 双端链表
	// REDIS_ENCODING_ZIPLIST 压缩列表
	
	// REDIS_ENCODING_INTSET 整数集合
	// REDIS_ENCODING_SKIPLIST 跳跃表和字典
	unsigned encoding:4;
	// 指向底层对象指针
	void *ptr;
	// ...
};

// msg为键值对的键,TYPE返回其关联的值的类型
TYPE msg
REDIS_STRING可能通过INT/EMBSTR/RAW编码实现
REDIS_LIST可能通过ZIPLIST/LINKEDLIST编码实现
REDIS_HASH可能通过ZIPLIST/HT编码实现
REDIS_SET可能通过INTSET/HT编码实现
REDIS_ZSET可能通过ZIPLIST/SKIPLIST编码实现

- 字符串对象
字符串对象的编码可以是int, raw或embstr
	
如一个字符串对象保存的是整数,且可用long表示.
则ptr直接存储值[void*转为long]
如字符串对象保存一个字符串,字符串长度大于39字节,
则将用一个简单动态字符串保存这个字符串值.
如字符串对象保存一个字符串,字符串长度小于等于39字节,
则将用embstr保存.
embstr下redisObject和sdshdr在物理内存和逻辑内存上是连续的

在有需要时,程序会将存在字符串对象里的字符串转为数值,执行数值运算,再将结果以字符串存储.
	
可用long表示的整数,编码用int
超出long的整数,编码用embstr或raw
- 编码转换
一个int编码的值对象,执行APPEND时,编码切换为raw
对embstr编码的字符串执行修改时,编码切换为raw

- 字符串命令的实现
- 编码转换
列表对象可同时满足以下两条件时,用ziplist编码
a.列表对象保存的所有字符串元素长都小于64字节
b.列表对象保存的元素数量小于512个
不能满足条件a,b的需使用linkedlist
- 列表命令的实现

哈希对象

哈希对象的编码可为ziplist或hashtable
- 编码转换
哈希对象同时满足以下两个条件时,哈希对象用ziplist编码.
1.哈希对象保存的所有键值对的键和值的字符串长度均小于64字节
2.哈希对象保存的键值对数量小于512个.

集合对象

集合对象的编码可是intset或hashtable
intset编码的集合对象用整数集合作为底层实现.
hashtable编码的集合对象用字典作底层实现.
- 编码转换
集合对象满足以下两个条件时,用intset编码
1.集合对象保存的所有元素都是整数值
2.集合对象保存的元素数量不超过512个.

有序集合

有序集合的编码可是ziplist或skiplist
typedef struct zset
{
    
    
	zskiplist *zsl;
	dict *dict;
} zset;
- 编码转换
有序集合对象可同时满足以下两条件时,用ziplist编码
1.有序集合保存的元素数量小于128个
2.有序集合保存的所有元素成员长都小于64字节.

类型检查与命令多态

Redis用于操作键的命令可分为两种类型
体现在,同一操作对象类型不同时,执行不同.
体现在,同一对象,编码类型不同时,执行不同.

内存回收

typedef struct redisObject
{
    
    
	...
	int refcount;
	...
}robj;

对象的空转时长

typedef struct redisObject
{
    
    
	...
	// 记录了对象最后一次被命令程序访问的时间
	unsigned lru:22;
	...
}robj;

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/114787782