Redis
5种数据结构: string, list, set, zset,hash
1. 简单动态字符串
SDS定义
redis> set msg "hello"
key msg是一个SDS, value hello也是一个SDS
SDS 除了用来保存数据库中的字符串值以外,还用来做缓冲区buffer:AOF模块中的AOF缓冲区、客户端缓冲区的输入缓冲区
struct sdshdr {
int len;
int free;
char buf[];
}
SDS与C字符串差异
-
常数复杂度获取字符串长度
-
杜绝缓冲区溢出,C字符串不记录长度,strcat()函数拼接时会造成覆盖临近内存空间数据的覆盖。而SDS会先计算长度,拼接的空间不足时,先扩容。
-
减少修改字符串时带来的内存重分配次数。通过free属性,实现了空间预分配、惰性空间释放。
SDS修改后小于1M, buf[] 长度为 2 * SDS字符串长度 + 1 SDS修改后大于1M, buf[] 长度为 SDS长度 + 1M + 1byte, 例如: 30M + 1M + 1byte -
二进制安全。
C字符串只能保存文本,只能有一个空字符串,无法保存图片等二进制数据。SDS以处理二进制数据的方式处理buf属性中的数据,数据写入时怎么样,读取时就怎么样。buf[]成为字节数组。SDS通过len判断字符串是否结束,而不是 空字符串\0。
2. 链表
双端链表,list结构持有双端链表的头结点和尾节点。
typedef struct list { listNode *head; listNode *tail; unsigned long len; // 节点值复制函数 void *(*dup) (void *ptr); // 节点值释放函数 void *(*free) (void *ptr); // 节点值对比函数 int (*match) (void *ptr, void *key); }特点
双端,无环,带表头节点和表尾节点,带链表长度计数器
常用命令
LLEN key
LRANGE key 0 10
3. 字典
字典中的所有键都是唯一的。
字典、哈希表、哈希节点
哈希表
typedef struct dictht {
dictEntry **table;
unsigned long size; // hash表大小
unsigned long sizemask; // hash表掩码, 用于计算索引值,总是等于size - 1
unsigned long used;
} dictht;
table中每个元素是一个指向dictEntry的指针
typedef struct dictEntry {
void *key;
// 值
union {
void *val;
uint64_tu64;
int64_ts64;
}
struct dictEntry *next;
} dictEntry
字典
typedef struct dict {
// 常用函数,例如rehash函数 murmurhash2, hash = dict -> type -> hashFunction(key)
dictType *type;
void *privdata;
dictht ht[2];
int rehashidx; // rehash标志,未rehash时为-1
}
解决键冲突
链地址法。
rehash
Murmurhash2, 扩容,ht[1]扩容为 ht[0].used * 2的 2的n次方;缩容,ht[1]为大于等于ht[0].used的2的n次方
哈希表扩容缩容条件
1)服务器没有执行BGSAVE或BGREWRITEAOF时,负载因子大于等于1
2)服务器执行BGSAVE或BGREWRITEAOF时,负载因子大于等于5
负载因子: load_factor = ht[0].used / ht[0].size
渐进式rehash
- 为ht[1]分配空间
- rehashidx设置为0,rehash正式开始
- 对字典进行操作(增删改查)时,对ht[0] rehashidx索引处的所有键值对rehash到ht[1]上,
- 每次操作rehashidx + 1, 结束时置为-1。
渐进式rehash期间的,查找与增加
查找时,ht[0] ht[1]都查找
增加时,只往ht[1]增加,保证ht[0]只减不增。
跳跃表
skiplist
跳跃表节点
4. 跳跃表
整体结构
5. 整数集合intset
用于保存整数型的集合,可保存int16_t,int32_t,int64_t,并且保证集合中不会出现重复元素。
typedef struct intset {
// 编码方式
uint32_t encoding;
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset;
整数集合升级(编码升级16bit, 32bit, 64bit)
整数集合升级的好处
- 提升开发灵活性,任意存放16、32、64位的整数
- 节约内存
6. 压缩列表
是列表键和哈希键的实现之一。
列表键只包含少量列表项,且要么是小整数值,要么是短字符串时,使用压缩列表。
redis> RPUSH lst 1 3 5 10086 "hello" "world"
(integer)6
redis> OBJECT ENCODING lst
"ziplist"
hash键也是,少量键值对,小整数,短字符串
redis> HMSET profile "name" "jack" "age" "28" "job" "programmer"
7. 对象
string, list, hash, set, zset
redisObject的三个优点:
1. 对象系统通过引用计数的内存回收机制,程序不再使用此对象时,会被自动释放。
1. redis对象带有访问时间记录信息,系统开启maxmemory时,空转时间较长的会被优先删除
1. 5种类型,更灵活
执行此操作时,会创建两个string类型的redisObject
redis> set msg "hello world"
// 查看对象的类型
redis> TYPE msg
string // 还有 list, hash, set, zset, 操作为 RPUSH HMSET SADD ZADD
redisObject
typedef struct redisObject {
// 类型
unsigned type:4;
// 编码
unsigned encoding:4;
// 指向底层实现数据结构的指针
void *ptr;
// ...
} robj;
对象类型与编码
string类型:int, embstr, int, SDS
list: ziplist, linkedlist
hash: ziplist,dict
set: intset, dict(ht)
zset: ziplist, zset
字符串对象
编码可以为:int, embstr, raw, raw即为SDS
long类型,小于等于32byte, 32字节以上
raw