Redis设计与实现学习--数据结构与对象

对象

Redis对象系统

  • 简介
    Redis使用对象来表示数据库中的键和值,每当我们在Redis数据库中新建一个键值对时,我们会创建两个对象:键对象和值对象。Redis对象由redisObject结构表示。
  • 内存回收
    a. 采用引用计数的内存回收机制,当程序不在使用某个对象的时候,这个对象所占用的内存会自动释放
    b. redis的对象都带有访问时间的记录信息,该信息可以用于计算数据库键的空转时长,在服务器启用了maxmemory功能的情况下,空转时长较大的那些键可能会优先被服务器删除;若服务器用于内存回收的算法为volatile-lru或allkeys-lru,那么服务器所占用内存数超过maxmemory选项设置的上限值时,空转时长较长的那部分键会优先被服务器释放,从而回收内存。
127.0.0.1:6379> object idletime zsetinfos
(integer) 1779
127.0.0.1:6379> zcard zsetinfos
(integer) 129
127.0.0.1:6379> object idletime zsetinfos
(integer) 2
  • 对象共享 – 只对包含整数值(0~9999)的对象字符串对象进行共享
    通过引用计数实现共享机制
typedef struct redisObject {
	//类型 
	//字符串对象(REDIS_STRING)、列表对象(REDIS_LIST)、哈希对象(REDIS_HASH)、集合对象(REDIS_SET)、有序集合对象(REDIS_ZSET)
	unsigned type:4;
	//编码
	//记录该对象所使用的编码,即这个对象使用了什么数据结构作为对象的底层实现
	//REDIS_ENCODEING_INT(long类型的整数)、REDIS_ENCODEING_EMBSTER(embstr编码的简单动态字符串)、REDIS_ENCODEING_RAW(简单动态字符串)、REDIS_ENCODEING_HT(字典)、REDIS_ENCODEING_LINKEDLIST(双端链表)、REDIS_ENCODEING_ZIPLIST(压缩列表)、REDIS_ENCODEING_INTSET(整数集合)、REDIS_ENCODEING_SKIPLIST(跳跃表和字典)
	unsigned encoding:4;
	//指向底层实现数据结构的指针
	void *ptr;
	// ...
} robj;

字符串(REDIS_STRING)

  • 常用操作
    set key value:设置指定key的值
    get key:获取指定key的值
    incr|decr key:将key中存储的数字值增/减一
    incrby|decrby key decrement:将key中存储的数值值增/减给定的值
  • 编码
    REDIS_ENCODEING_INT:使用整数值实现的字符串对象
    REDIS_ENCODEING_EMBSTER:使用embstr编码的简单动态字符串实现的字符串对象
    REDIS_ENCODEING_RAW:使用简单动态字符串实现的字符串对象(字符串>39bytes)
  • 示例
127.0.0.1:6379> object encoding story
"raw"
127.0.0.1:6379> set story "long long long long long long long long long "
OK
127.0.0.1:6379> object encoding story
"raw"
127.0.0.1:6379> set story "long long long long long long long long long"
OK
127.0.0.1:6379> object encoding story
"embstr"
127.0.0.1:6379> set msg1 1
OK
127.0.0.1:6379> object encoding msg1
"int"

哈希

  • 常用命令
    hgetall key:获取哈希表中指定key的所有字段和值
    hget key field:获取哈希表中指定字段的值
    hmget key field1 field2 …:获取所有给定字段的值
    hexists key field:查询哈希表key中指定的字段是否存在
    hset key field value:将哈希表 key 中的字段 field 的值设为 value
    hmset key field1 value1 field2 value2 … :同时将多个 field-value (域-值)对设置到哈希表 key 中
    hincrby key field increment:为哈希表 key 中的指定字段的整数值加上增量 increment
    hincrbyfloat key field increment:为哈希表 key 中的指定字段的浮点数值加上增量 increment
    hlen key:获取哈希表中字段的数量
    hkeys key:获取所有哈希表中的字段
  • 编码
    REDIS_ENCODEING_ZIPLIST:
    1. 哈希对象保存的所有键值对的键和值的长度都小于64字节
    2. 哈希对象保存的键值对数量小于512个
    不满足以上条件使用hashtable编码
    REDIS_ENCODEING_HT(hashtable的底层实现):
  • 示例
127.0.0.1:6379> eval "for i =1,512 do redis.call('hset', KEYS[1], i, i) end" 1 "infos"
(nil)
127.0.0.1:6379> llen infos
(error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> hget infos 1
"1"
127.0.0.1:6379> hlen infos
(integer) 512
127.0.0.1:6379> object encoding infos
"ziplist"
127.0.0.1:6379> hset infos 513  513
(integer) 1
127.0.0.1:6379> hlen infos
(integer) 513
127.0.0.1:6379> object encoding infos
"hashtable"

列表

  • 常用操作
    llen key:获取列表长度
    lpop key:移除并获取列表的第一个元素
    rpop key:移除并获取列表的最后一个元素
    lpush key value1 value2 …:添加一个或多个值插入到列表头部
    rpush key value1 value2 …:添加一个或多个值插入到列表
    lrange key start stop:获取列表指定范围内的元素
    lrem key count value:移除列表中value值(count个)
  • 编码
    REDIS_ENCODEING_ZIPLIST:
    REDIS_ENCODEING_LINKEDLIST:
    REDIS_ENCODEING_QUICKLIST:redis3.2以后启用quicklist
  • 示例
127.0.0.1:6379> eval "for i =1,512 do redis.call('rpush', KEYS[1], i) end" 1 "integer"
(nil)
127.0.0.1:6379> llen integer
(integer) 512
127.0.0.1:6379> object encoding integer
"quicklist"
127.0.0.1:6379> rpush integer 513
(integer) 513
127.0.0.1:6379> llen integer
(integer) 513
127.0.0.1:6379> object encoding integer
"quicklist"
127.0.0.1:6379> rpush qwe 1
(integer) 1
127.0.0.1:6379> object encoding qwe
"quicklist"

集合

  • 常用操作
    scard key:获取集合中的成员数
    sadd key member1 member2:向集合中添加一个或多个成员
    spop key:移除并返回集合中的一个随机元素
    smem key member1 member2 … :移除集合中一个或多个成员
    smembers key:返回集合中的所有成员
    sdiff key1 key2 … :返回给定所有集合的差集
    sinter key1 key2 … :返回给定所有集合的交集
    sunion key1 key2 …:返回所有给定集合的并集
  • 编码
    REDIS_ENCODEING_INTSET:
    1. 集合对象中保存的所有元素都是整数值
    2. 集合对象中保存的元素数量不超过512个
    不满足以上条件使用hashtable编码
    REDIS_ENCODEING_HT(hashtable的底层实现):
  • 示例
127.0.0.1:6379> sadd numbers 1 2 3
(integer) 3
127.0.0.1:6379> object encoding numbers 
"intset"
127.0.0.1:6379> sadd fruits "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> object encoding fruits
"hashtable"
127.0.0.1:6379> sadd numbers "apple" "banana" "cherry"
(integer) 3
127.0.0.1:6379> object encoding numbers 
"hashtable"
127.0.0.1:6379>  eval "for i =1,512 do redis.call('sadd', KEYS[1], i) end" 1 "zetinfos"
(nil)
127.0.0.1:6379> scard zetinfos
(integer) 512
127.0.0.1:6379> object encoding zetinfos
"intset"
127.0.0.1:6379> sadd zetinfos 513
(integer) 1
127.0.0.1:6379> object encoding zetinfos
"hashtable"

有序集合

  • 常用操作
    zcard key:获取有序集合的成员数
    zadd key score1 member1 socre2 member2 …:向有序集合天剑一个或多个成员,或者更新已存在成员的分数
    zcount key min max:计算在有序集合中指定分数区间的成员数
    zlexcount key min max:计算在有序集合中指定字典区间内成员数量
    zrange key start stop:通过索引区间返回有序集合成指定区间内的成员
    zrank key member:返回有序集合中指定成员的索引
    zrem key member1 member2 …:移除有序集合中的一个或多个成员
    zscore key member:返回有序集合中成员的分数值
  • 编码
    REDIS_ENCODEING_ZIPLIST:
    1. 有序集合中保存的元素数量小于128
    2. 有序集合中保存的所有元素成员的长度都小于65字节
    不满足以上条件使用skiplist编码
    REDIS_ENCODEING_SKIPLIST:
  • 示例
127.0.0.1:6379> zadd price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
127.0.0.1:6379> zcard price
(integer) 3
127.0.0.1:6379> object encoding price
"ziplist"
127.0.0.1:6379>  eval "for i =1,128 do redis.call('zadd', KEYS[1], i, i) end" 1 "zsetinfos"
(nil)
127.0.0.1:6379> zcard zsetinfos
(integer) 128
127.0.0.1:6379> object encoding zsetinfos
"ziplist"
127.0.0.1:6379> zadd zsetinfos 129 129
(integer) 1
127.0.0.1:6379> zcard zsetinfos
(integer) 129
127.0.0.1:6379> object encoding zsetinfos
"skiplist"

数据结构

简单动态字符串

  • SDS定义
    SDS采用空间预分配,对于增长字符串其采用的策略是检查修改之后的长度大小,如果小于1MB,则分配2倍的修改后的长度+1;如果sds长度大于等于1MB,则会分配1MB未使用空间。
struct sdshdr {
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量
int free;
//字节数组,用于保存字符串
char buf[];
}

链表

  • 定义
链表节点定义:
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;

字典

  • 定义
    又称为符号表(symbol table)、关联数组(associative array)、映射(map)
哈希表
typedef struct dictht {
	// 哈希表数组
	dicEntry **table;
	// 哈希表大小
	unsigned long size;
	// 哈希表大小掩码,用于计算索引值
	//总是等于size - 1
	unsigned long sizemask;
	//哈希表已使用的节点数量
	unsigned long used;
} dictht;

哈希表节点
typedef struct dictEntry {
	// 键
	void *key;
	// 值
	union {
		void *val;
		uint64_t u64;
		int64_t s64;
	} v;
	// 指向下个哈希节点,形成链表
	struct dictEntry *next;
} dictEntry;

字典
typedef struct dict {
	// 类型特定函数
	dictType *type;
	// 私有数据
	void *privdata;
	// 哈希表,一般情况下字典只使用ht[0]哈希表,ht[1]哈希表只会对ht[0]哈希表进行rehash时使用
	dictht ht[2];
	// rehash索引
	// 当rehash不在进行时,值为-1;记录了rehash目前的进度
	int trehashidx;
} dict;

特定类型函数
typedef struct dictType {
	// 计算哈希值函数
	unsigned int (*hashFunction) (const void *key);
	// privdata保存了需要传给这些特定类型函数的可选参数
	// 复制键的函数
	void *(* keyDup) (void *privdata, const void *key)
	// 复制值的函数
	void *(* valueDup) (void *privdata, const void *obj)
	// 对比键的函数
	int (*keyCompare) (void *privdata, const void *key1, const void *key2)
	// 销毁键的函数
	int (*keyDestructor) (void *privdata, const void *key)
	// 销毁值的函数
	int (*valDestructor) (void *privdata, const void *obj)
} dictType;
  • rehash
    1. 为字典的ht[1]分配空间,这个哈希表大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(即ht[0].used属性的值)
      扩展操作:ht[1]的大小为第一个大于等于ht[0].used*2的2n
      收缩操作:ht[1]的大小为第一个大于等于ht[0].used的2n
    2. 将保存在ht[0]中的所有键值对rehash(即重新计算键的哈希值和索引值)到ht[1]上
    3. 当ht[0]包含的所有键值都迁移到了ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]处设置新建一个空白哈希表
  • 扩展和收缩
    负载因子计算 = 哈希表已保存节点数量(ht[0].used)/哈希表大小(ht[0].size)
    1. 扩展:
      服务器目前没有执行bgsave或bgrewriteaof,并且哈希表的负载因子>=1
      服务器目前正在执行bgsave或bgrewriteaof,并且哈希表的负载因子>=5
    2. 收缩:
      哈希表负载因子<0.1

跳跃表

  • 定义
跳跃表(zskiplist)
typedef struct zskiplist {
	// 表头节点和表尾节点
	structz skiplistNode *header, *tail;
	// 表中节点的数量
	unsigned long length;
	// 表中层数最大的节点的层数
	int level;
} zskiplist;

跳跃表节点(zskiplistNode)
typedef struct zskiplistNode {
	// 后退指针
	struct zskiplistNode *backward;
	// 分值
	double score;
	// 成员对象
	robj *obj;
	// 层
	struct zskiplistLevel {
		// 前进指针
		struct zskiplistNode *forward;
		// 跨度
		unsigned int span;
	} level [ ];
} zskiplistNode;

整数集合

  • 定义
typedef struct intset {
	// 编码方式,INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64
	uint32_t encoding;
	// 集合包含的元素数量
	uint32_t length;
	// 保存元素的数组,整数集合的每个元素都是contents数组的一个数据项(item),每个项在数组中按值的从小到大有序排列,且数组中不包含任何重复项
	int8_t contents[ ];
} intset;
  • 升级和降级
    当新添加的元素的类型比整数集合中现在所有元素的类型都要长时,整数集合需要先进行升级,然后将新元素添加到整数集合里面。整数集合不支持降级。

压缩列表

  • 压缩列表的构成
    由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。
zlbytes zltail zllen entry1 entry2 entryN zlend

各个组成部分的详细说明

属性 类型 长度 用途
zlbytes uint32_t 4字节 记录整个压缩列表占用的内存字节数;在对压缩列表进行内存重分配,或者计算zlend的位置时使用
zltail uint32_t 4字节 记录压缩列表表尾节点距离压缩列表的起始地址有多少个字节;通过这个偏移量,程序无须遍历整个压缩列表就可以确定表尾节点的地址
zllen uint16_t 2字节 记录压缩列表包含的节点数量:当这个属性的值小于UNIT16_MAX(65535)时,这个属性的值就是压缩列表包含节点的数量;当这个值等于UNIT_MAX时,节点的真实数量需要遍历整个压缩列表才能计算出来
entryX 列表节点 不定 压缩列表包含的各个节点,节点长度由节点保存的内容决定
zlend uint8_t 1字节 特殊值0xFF(255),用于标记压缩列表的末端
  • 压缩列表节点的构成
    每个压缩列表节点可以保存一个字节数组或者一个整数值。
previous_entry_length encoding content

previous_entry_length:以字节为单位,记录了压缩列表中前一个节点的长度。其可以是1字节(前一节点的长度 < 254字节)或者5字节(前一节点的长度 >= 254字。其中第一个字节被设置为0xFE(254),而之后的四个字节则用于保存前一节点的长度)。
enconding:记录节点的content属性所保存数据的类型以及长度。
content:负责保存节点值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

猜你喜欢

转载自blog.csdn.net/qq_28376741/article/details/86689812