redis是c语言实现的基于内存的kv数据存储系统,由于都是内存操作,速度非常快。单线程接受处理请求,保证了操作的原子性。为了高效利用内存,其内部自定义了多种数据类型,包括string、list、set、sorted set、hash。
string
string 通过sds来实现的,数据结构如下:
struct sdshdr {
int len;
int free;
char buf[];
};
len - 记录string的总长度。
free - 记录string剩余的空间。
buf - 数据实际存放数组。
和c语言一样末尾也保留了“\0”,但不是作为结束符,redis增加了len字段来确定string的长度,便于获取string长度,同时也可以实现任意数据的存储,包括“\0”。为了便于长度的扩展,初始分配时会分配冗余空间。
list
列表用于保存一组有序数据,当原素个数小于list-max-ziplist-entries(默认512)且单个元素大小小于list-max-ziplist-value(默认64字节)时,使用ziplist(压缩链表);否则使用linkedlist(双向链表)。3.2之后引入了quicklist,quicklist是一个双向链表,内部节点由一个个ziplist组成。
ziplist
ziplist通过一块连续内存空间来模拟链表数据结构,连续空间结构如下:
<zlbytes><zltail><zllen><entry>...<entry><zlend>
zlbytes - 列表所占内存总长度
zltail - 列表中最后一个元素位置(方便定位列表尾部)
zllen - 列表中数据个数
entry - 数据项(有其内部结构)
zlend - 值为255的一个结束标示位。linkedlist
标准的双向链表,保存head、tail指针,方便快速的实现头尾插入和读取。
set
当集合中存储数据全为整型且数量较少时使用intset,否则使用dict(字典),其key存储数据,value存储null。
- intset分为3种数字类型:
typedef struct intset {
uint32_t encoding; //记录int编码类型
uint32_t length;//元素个数
int8_t contents[];//数据存储数组
} intset;
#define INTSET_ENC_INT16 (sizeof(int16_t))
#define INTSET_ENC_INT32 (sizeof(int32_t))
#define INTSET_ENC_INT64 (sizeof(int64_t))
- dict是通过hash表实现的键值对存在结构:
typedef struct dict {
dictType *type; // 支持多种数据类型操作,及多态
void *privdata;
dictht ht[2]; //数据存储数组,数组2用于rehash
long rehashidx; // 记录rehash位置,实现渐进式hash
unsigned long iterators;
} dict;
sorted set
有序集合是通过skip list(跳跃表)实现的。
跳跃表在数组的基础上为每个节点随机生成指向后续节点的指针,从而加快数组的便利过程。
hash
hash的实现和java中类似,通过一个数组存储元素,通过链表法解决冲突。
typedef struct dictht {
dictEntry **table; // 数组
unsigned long size; // 数组的大小
unsigned long sizemask;
unsigned long used; // 已有节点的数量
} dictht;