redis底层结构

SDS 简单动态字符串

每个 sds.h/sdshdr 结构表示一个 SDS 值:

struct sdshdr {

    // 记录 buf 数组中已使用字节的数量
    // 等于 SDS 所保存字符串的长度
    int len;
    // 记录 buf 数组中未使用字节的数量
    int free;
    // 字节数组,用于保存字符串
    char buf[];
};

为什么不使用c语言简单字符串

原因有如下

获取长度复杂度为O(1)
避免缓冲区溢出
优化策略

redis非常注重性能,于是优化是非常必要的。

减少内存重分配
空间预分配
内存的分配设计到复杂算法或可能的系统调用,比较耗时

于是,每次再字符串增长操作时,不仅仅会扩容到刚好所需的空间,而是会通过一定策略额外分配空间

惰性空间释放
字符串的缩短操作,不会进行内存重分配,而是会通过free记录起来,为未来可能存在的增长提供优化

2.链表

typedef struct listNode {

    // 前置节点
    struct listNode *prev;

    // 后置节点
    struct listNode *next;

    // 节点的值
    void *value;

} listNode;

还有有一个头节点指向表头和表尾

typedef struct listNode {	

	listNode *prev;

	listNode *next;

	void *value;

}listNode;

typedef struct list{

	listNode *head;

	listNode *tail;

	unsigned  long len;

}list;

字典

/*
* 字典
** 每个字典使用两个哈希表,用于实现渐进式 rehash
*/
typedef struct dict {
// 特定于类型的处理函数
dictType *type;
// 类型处理函数的私有数据
void *privdata;
// 哈希表(2 个)
dictht ht[2];
// 记录 rehash 进度的标志,值为-1 表示 rehash 未进行
int rehashidx;
// 当前正在运作的安全迭代器数量
int iterators;
} dict;
/*
* 哈希表
*/
typedef struct dictht {
// 哈希表节点指针数组(俗称桶,bucket)
dictEntry **table;
// 指针数组的大小
unsigned long size;
// 指针数组的长度掩码,用于计算索引值
unsigned long sizemask;
// 哈希表现有的节点数量
unsigned long used;
} dictht;

table 属性是一个数组,数组的每个元素都是一个指向 dictEntry 结构的指针。
每个 dictEntry 都保存着一个键值对,以及一个指向另一个 dictEntry 结构的指针:

/*
* 哈希表节点
*/
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 链往后继节点
struct dictEntry *next;
} dictEntry;
/*
* 哈希表节点
*/
typedef struct dictEntry {
// 键
void *key;
// 值
union {
void *val;
uint64_t u64;
int64_t s64;
} v;
// 链往后继节点
struct dictEntry *next;
} dictEntry;

next 属性指向另一个 dictEntry 结构,多个 dictEntry 可以通过 next 指针串连成链表,从
这里可以看出,dictht 使用链地址法来处理键碰撞:当多个不同的键拥有相同的哈希值时,哈
希表用一个链表将这些键连接起来。
整个字典结构可以表示如下:
字典
1.新创建的两个哈希表都没有为 table 属性分配任何空间:
• ht[0]->table 的空间分配将在第一次往字典添加键值对时进行;
• ht[1]->table 的空间分配将在 rehash 开始时进行;
2.根据字典所处的状态,将一个给定的键值对添加到字典可能会引起一系列复杂的操作:
• 如果字典为未初始化(也即是字典的 0 号哈希表的 table 属性为空),那么程序需要对 0
号哈希表进行初始化;
• 如果在插入时发生了键碰撞,那么程序需要处理碰撞;
• 如果插入新元素使得字典满足了 rehash 条件,那么需要启动相应的 rehash 程序;
添加键的示意图

跳跃表

跳跃表
表头(head):负责维护跳跃表的节点指针。
• 跳跃表节点:保存着元素值,以及多个层。
• 层:保存着指向其他元素的指针。高层的指针越过的元素数量大于等于低层的指针,为了
提高查找的效率,程序总是从高层先开始访问,然后随着元素值范围的缩小,慢慢降低层
次。
• 表尾:全部由 NULL 组成,表示跳跃表的末尾

typedef struct zskiplist {
// 头节点,尾节点
struct zskiplistNode *header, *tail;
// 节点数量
unsigned long length;
// 目前表内节点的最大层数
int level;
} zskiplist;
typedef struct zskiplistNode {
// member 对象
robj *obj;
// 分值
double score;
// 后退指针
struct zskiplistNode *backward;
// 层
struct zskiplistLevel {
// 前进指针
struct zskiplistNode *forward;
// 这个层跨越的节点数量
unsigned int span;
} level[];
} zskiplistNode;

整数集合

整数集合(intset)用于有序、无重复地保存多个整数值,它会根据元素的值,自动选择该用什
么长度的整数类型来保存元素。
举个例子,如果在一个 intset 里面,最长的元素可以用 int16_t 类型来保存,那么这个 intset
的所有元素都以 int16_t 类型来保存。
另一方面,如果有一个新元素要加入到这个 intset ,并且这个元素不能用 int16_t 类型来保存
——比如说,新元素的长度为 int32_t ,那么这个 intset 就会自动进行“升级” :先将集合中现
有的所有元素从 int16_t 类型转换为 int32_t 类型,接着再将新元素加入到集合中。
根据需要,intset 可以自动从 int16_t 升级到 int32_t 或 int64_t ,或者从 int32_t 升级到
int64_t 。
整数集合

压缩列表

Ziplist 是由一系列特殊编码的内存块构成的列表,一个 ziplist 可以包含多个节点(entry),每
个节点可以保存一个长度受限的字符数组(不以 \0 结尾的 char 数组)或者整数,包括:
• 字符数组
– 长度小于等于 63 (26 − 1)字节的字符数组
– 长度小于等于 16383 (2
14 − 1)字节的字符数组
– 长度小于等于 4294967295 (2
32 − 1)字节的字符数组
• 整数
– 4 位长,介于 0 至 12 之间的无符号整数
– 1 字节长,有符号整数
– 3 字节长,有符号整数
– int16_t 类型整数
– int32_t 类型整数
– int64_t 类型整数
因为 ziplist 节约内存的性质,它被哈希键、列表键和有序集合键作为初始化的底层实现来使用

ziplist的构成
ziplistnode
entry
pre_entry_length 记录了前一个节点的长度,通过这个值,可以进行指针计算,从而跳转到上一个节点。
encoding 和 length 两部分一起决定了 content 部分所保存的数据的类型(以及长度)。
content 部分保存着节点的内容,它的类型和长度由 encoding 和 length 决定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值