Redis数据结构总体分为: 字符串、链表、字典、跳跃表、整数集合、压缩列表、对象八种类型。
- Redis并未采用C语言的字符串,而是实现了新的字符串类型(sds.h/sdshdr) , 与C字符串一样,采用 ‘\0’ 表示结尾(SDS函数会自动处理该结尾,对使用者是完全透明),这样做的好处是可以重用部分库函数(如:printf) 。
struct sdshdr {
int len; //buf 长度
int free; //buf剩余可用空间长
char buf[]; //数据
};
//1. 使strlen函数的时间复杂度由O(N) 降低到O(1)
//2. 有效避免缓冲区溢出
/*3. 减少字符串修改时带来的内存重新分配,长度小于1MB时,重新申请的内存为实际需要的2倍+1byte,长度大于等于1MB时,新申请的内存为实际需要+1MB +1byte*/
/*4. 延迟空间释放,例如sdstrim(sds,"trim")函数,释放出来的内存并不会立即释放,通过修改free来保存大小的改变 */
/*5. 二进制安全,通过len来标示字符串长度,数组中间可以存储'\0'字符*/
- Redis 采用C语言实现,没有像STL 这种高级组件,为此Redis需要实现自己的链表。Redis的链表实现在(adlist.h/listNode,list),类似STL一个双向链表的实现。
typedef struct listNode {
struct listNode *prev; // 前驱节点
struct listNode *next; // 后继节点
void *value; // 节点的值
} listNode;
typedef struct list {
listNode *head; // 表头节点
listNode *tail; // 表尾节点
void *(*dup)(void *ptr); // 节点值复制函数
void (*free)(void *ptr); // 节点值释放函数
int (*match)(void *ptr, void *key); // 节点值对比函数
unsigned long len; // 链表所包含的节点数量
} list;
- 和链表一样字典是现在(dict.h/listNode,list),类似STL一个双向链表的实现.
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 哈希表
int rehashidx; // rehash 索引,当 rehash 不在进行时,值为 -1
int iterators; // 目前正在运行的安全迭代器的数量
} dict;
//1. 采用链表发解决hash冲突问题
//2. ht[1] 哈希表只会在ht[0] rehash时使用
/*3. 采用渐进式rehash,对ht[0]上的键执行delete,find,update时,转移到ht[1]*/
- 跳跃表是现在 redis.h/zskiplistNode 和redis.h/zskiplist中
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 表头节点和表尾节点
unsigned long length; // 表中节点的数量
int level; // 表中层数最大的节点的层数
} zskiplist;
typedef struct zskiplistNode {
robj *obj; // 成员对象
double score; // 分值
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel { // 层
struct zskiplistNode *forward; // 前进指针
unsigned int span;// 跨度
} level[];
} zskiplistNode;
- 整数集合实现在initset.sh/intset, 用于保存整数值(int16_t, int32_t,int64_t )的集合,并且保证集合中不会出现重复的元素。
typedef struct intset {
uint32_t encoding;// 编码方式
uint32_t length; // 集合包含的元素数量
int8_t contents[];// 保存元素的数组
} intset;
//1. 数组中按照数组项的值从大到小排列,并且不包含重复的项
/*2. encoding 的值为INTSET_ENC_INT16,INTSET_ENC_INT32,INTSET_ENC_INT64*/
/*3. 主要原理整数的内存表示,当encoding为INTSET_ENC_INT16时,int16_t占用2个byte,contents的长度为2*length.*/
/*4. 通过升降级来达到节省内存的目的*/
- 压缩列表是为了节省内存而开发的,是列表和哈希键的底层实现之一,当一个列表键只包含少量列表项,当每个列表项占用内存比较少时采用。如小整数,短字符串。是现在ziplist.c, 基于数组实现。
typedef struct zlentry {
// prevrawlen :前置节点的长度
// prevrawlensize :编码 prevrawlen 所需的字节大小
unsigned int prevrawlensize, prevrawlen;
// len :当前节点值的长度
// lensize :编码 len 所需的字节大小
unsigned int lensize, len;
// 当前节点 header 的大小
// 等于 prevrawlensize + lensize
unsigned int headersize;
// 当前节点值所使用的编码类型
unsigned char encoding;
// 指向当前节点的指针
unsigned char *p;
} zlentry;
压缩链表内存结构:
+-----------------------------------------------------+
|zlbytes|zltail|zllen|entry1|entry2|.....|entryN|zlend|
+-----------------------------------------------------+
zlbytes : 整个压缩表占用的内存大小
ztail : 压缩列表尾节点到头节点的字节数,用于计算尾节点地址
zllen : 记录压缩列表的节点数量
entryx : 节点x
zlend : 特殊值0xFF,用户标记压缩列表的末端
// 当存在大量entry时存在连锁更新问题,性能无法保障。
- Redis并没有直接使用上述数据类型来实现数据库,而是封装成相应的对象,字符串对象、列表对象、hash表对象、集合对象和有序集合对象。这样做的好处是: 在执行命令之前可以根据对象的类型来判断是否可以执行相应的命令,同时可以在不同的场景中为同一个对象实现不同的底层数据结构,以及基于对象引用计数的对象共享和释放。实现在redis.h/redisObject
typedef struct redisObject {
// 对象类型
unsigned type:4;
// 编码, 对象的内部类型
unsigned encoding:4;
// 对象最后一次被访问的时间
unsigned lru:REDIS_LRU_BITS;
// 引用计数
int refcount;
// 指向实际值的指针
void *ptr;
} robj;
//1. type= REDIS_STRING,REDIS_LIST,REDIS_HASH,REDIS_SET,REDIS_ZSET
//2. 通过type和encoding,实现类似多态的机制