Redis数据结构

79 阅读4分钟

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字符串差异
  1. 常数复杂度获取字符串长度

  2. 杜绝缓冲区溢出,C字符串不记录长度,strcat()函数拼接时会造成覆盖临近内存空间数据的覆盖。而SDS会先计算长度,拼接的空间不足时,先扩容。

  3. 减少修改字符串时带来的内存重分配次数。通过free属性,实现了空间预分配、惰性空间释放。

    SDS修改后小于1M, buf[]  长度为 2 * SDS字符串长度 + 1
    SDS修改后大于1M, buf[] 长度为 SDS长度 + 1M + 1byte, 例如: 30M + 1M + 1byte
    
  4. 二进制安全。

    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
  1. 为ht[1]分配空间
  2. rehashidx设置为0,rehash正式开始
  3. 对字典进行操作(增删改查)时,对ht[0] rehashidx索引处的所有键值对rehash到ht[1]上,
  4. 每次操作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)
整数集合升级的好处
  1. 提升开发灵活性,任意存放16、32、64位的整数
  2. 节约内存

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