对象
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
为字典的ht[1]分配空间,这个哈希表大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(即ht[0].used属性的值) 扩展操作:ht[1]的大小为第一个大于等于ht[0].used*2的2n 收缩操作:ht[1]的大小为第一个大于等于ht[0].used的2n
将保存在ht[0]中的所有键值对rehash(即重新计算键的哈希值和索引值)到ht[1]上
当ht[0]包含的所有键值都迁移到了ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]处设置新建一个空白哈希表
扩展和收缩 负载因子计算 = 哈希表已保存节点数量(ht[0].used)/哈希表大小(ht[0].size)
扩展: 服务器目前没有执行bgsave或bgrewriteaof,并且哈希表的负载因子>=1 服务器目前正在执行bgsave或bgrewriteaof,并且哈希表的负载因子>=5
收缩: 哈希表负载因子<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属性决定。