学习完《redis设计与实现》前面关于数据结构与对象的章节,以上问题都能获得解答。你也能了解到redis做者如此的煞费苦心设计了这么多丰富的数据结构,目的就是优化内存。学完这些内容,在使用redis的过程当中,也会合理的使用以适应它内部的特色。固然新版本的redis支持了更多更丰富的特性,该书基于redis3版本,尚未涉及到那些内容。ios
《redis设计与实现》这本书很是浅显易懂,做者黄建宏老师,90后。另外仍是《redis实战》的译者nginx
另外一篇可参考《redis设计与实现》2-数据库实现篇redis
struct sdshdr {
uint8_t len; /* used,使用的字节数 */
uint8_t alloc; /* excluding the header and null terminator,预分配总字节数,不包括结束符\0的长度 */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[]; /*c风格的字符,包括结束符\0*/
};
复制代码
位于adlist.h文件算法
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文件数据库
// 哈希表
typedef struct dictht {
dictEntry **table; // 一个数组,数组中每一个元素都是指向dictEntry结构的指针
unsigned long size; // table数组的大小
unsigned long sizemask; // 值总数size-1
unsigned long used; // 哈希表目前已有节点(键值对)的数量
} dictht;
复制代码
// 每一个dictEntry都保存着一个键值对,表示哈希表节点
typedef struct dictEntry {
void *key; // 键值对的键
// 键值对的值,能够是指针,整形,浮点型
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 哈希表节点指针,用于解决键冲突问题
} dictEntry;
复制代码
每一个字典类型保存一簇用于操做特定类型键值对的函数api
typedef struct dictType {
// 计算哈希值的函数
uint64_t (*hashFunction)(const void *key);
// 复制键的函数
void *(*keyDup)(void *privdata, const void *key);
// 复制值的函数
void *(*valDup)(void *privdata, const void *obj);
// 对比键的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 销毁键的函数
void (*keyDestructor)(void *privdata, void *key);
// 销毁值的函数
void (*valDestructor)(void *privdata, void *obj);
} dictType;
复制代码
// 字典
typedef struct dict {
dictType *type; // 不一样键值对类型对应的操做函数
void *privdata; // 须要传递给对应函数的参数
dictht ht[2]; // ht[0]用于存放数据,ht[1]在进行rehash时使用
long rehashidx; /* rehashing not in progress if rehashidx == -1,目前rehash的进度*/
unsigned long iterators; /* number of iterators currently running */
} dict;
复制代码
位于server.h文件中数组
// 跳跃表节点
typedef struct zskiplistNode {
sds ele; // 成员对象
double score; // 分值,从小到大排序
struct zskiplistNode *backward; // 后退指针,从表尾向表头遍历时使用
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned long span; // 跨度,记录两个节点之间的距离
} level[]; // 层,是一个数组
} zskiplistNode;
// 跳跃表相关信息
typedef struct zskiplist {
struct zskiplistNode *header, *tail; // 表头和表尾
unsigned long length; // 跳跃表长度(包含节点的数量)
int level; // 跳跃表内层数最大那个节点的层数(不包括表头节点层数)
} zskiplist;
复制代码
位于intset.h文件缓存
typedef struct intset {
uint32_t encoding; // 编码方式
uint32_t length; // 长度
int8_t contents[]; // 内容,数组内容类型取决于encoding属性,并非int8_t。按照大小排序,没有重复
} intset;
复制代码
属性 | 类型 | 长度 | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4字节 | 整个压缩列表占用的内存字节数 |
zltail | uint32_t | 4字节 | 表尾节点距离压缩列表起始地址有多少字节,无需遍历就可获得表尾节点 |
zllen | uint16_t | 2字节 | 节点数量,小于65535时是实际值,超过期须要遍历才能算出 |
entryN | 列表节点 | 不定 | 包含的各个节点 |
zlend | uint8_t | 1字节 | 特殊值0xFF,末端标记 |
最高两位取值 | 表示是数据类型 | encoding字节数 | 余下的bit数 | 最大范围 |
---|---|---|---|---|
00 | 字符数组 | 一个字节 | 6bit | 63位 |
01 | 字符数组 | 两个字节 | 14bit | 2^14-1 |
10 | 字符数组 | 五个字节 | 4*8,第一个字节余下的6bit留空 | 2^32-1位 |
11 | 整数 | 1个字节 | 000000 | int16_t类型整数 |
11 | 整数 | 1个字节 | 010000 | int32_t类型整数 |
11 | 整数 | 1个字节 | 100000 | int64_t类型整数 |
11 | 整数 | 1个字节 | 110000 | 24位有符号整数 |
11 | 整数 | 1个字节 | 111110 | 8位有符号整数 |
11 | 整数 | 1个字节 | xxxxxx | 没有content,xxxx自己就表示了0-12的整数 |
//server.h
typedef struct redisObject {
unsigned type:4; //类型
unsigned encoding:4; // 编码
// 对象最后一个被命令程序访问的时间
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount; // 引用计数
void *ptr; // 指向底层的数据结构指针
} robj;
复制代码
对象 | 对象type属性 | type命令的输出 |
---|---|---|
字符串对象 | REDIS_STRING | string |
列表对象 | REDIS_LIST | list |
哈希对象 | REDIS_HASH | hash |
集合对象 | REDIS_SET | set |
有序集合对象 | REDIS_ZSET | zset |
编码常量 | 对应的数据结构 | OBJECT ENCODING命令输出 |
---|---|---|
REDIS_ENCODING_INT | long类型的整数 | “int” |
REDIS_ENCODING_EMBSTR | embstr编码的简单动态字符串 | “embstr” |
REDIS_ENCODING_RAW | 简单动态字符串 | “raw” |
REDIS_ENCODING_HT | 字典 | “hashtable” |
REDIS_ENCODING_LINKEDLIST | 双端链表 | “linkedlist” |
REDIS_ENCODING_ZIPLIST | 压缩列表 | “ziplist” |
REDIS_ENCODING_INTSET | 整数集合 | “intset” |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 | “skiplist” |
字符串对象内容 | 长度 | 编码类型 |
---|---|---|
整数值 | - | int |
字符串值 | 小于32字节 | embstr |
字符串值 | 大于32字节 | raw |
embstr编码是专门用于保存短字符串的一种优化编码方式。这种编码和raw编码同样,都使用redisObject结构和sdshdr结构来表示对象。区别在于:安全
int编码和embstr编码的对象知足条件时会自动转换为raw编码的字符串对象bash
redis中全部键都是字符串对象,因此全部对于键的命令都是针对字符串键来构建的
使用ziplist编码的两个条件以下,不知足的都用linkedlist编码(这两个条件能够在配置文件中修改):
哈希对象的编码能够是
集合对象的编码能够是:
集合使用intset须要知足两个条件,不知足时使用hashtable(参数可经过配置文件修改)
有序集合的编码能够是
// 两种数据结构经过指针共享元素成员和分值,不会浪费内存
typedef struct zset {
zskplist *zsl; //跳跃表,方便zrank,zrange
dict *dict; //字典,方便zscore
}zset;
复制代码
当知足如下两个条件时,使用ziplist编码,不然使用skiplist(可经过配置文件修改)
redis的命令能够分为两大类:
redis经过对象的refcount属性记录对象引用计数信息,适当的时候自动释放对象进行内存回收
# 设置字符串
set msg "hello world"
rpush numbers 1 2 3 4 5
llen numbers
lrange numbers 0 5
# 获取键值使用的底层数据结构
object encoding numbers
# 查看对象的引用计数值
object refcount numbers
# 对象空转时长: value=now-object.lru
object idletime numbers
复制代码