最近看了下huangz1990写的《redis设计与实现》,做一下笔记。
内部数据结构
【sds】
struct sdshdr {
int len; // buf已用长度
int free; // 剩余空间
char buf[]; // 保存字符串的char指针
}
简单动态字符串:主要用于存储redis里面的字符串对象,不过只有这个对象存储的是字符串的时候,才是sdsi,否则就是long类型。
append操作:如果添加后长度超过SDS_MAX_PREALLOC(默认1M),则空间追加加多1M,否则为添加后长度的两倍,显然append的字符串不能超过1M。
可以看出一个明显的弊端,就是会有比较多的剩余空间,这部分空间是不会主动释放的,如果要释放需要调用对应的API(sdsRemoveFreeSpace)。
【双端链表】
// 节点
typedef struct listNode {
struct listNode *prev; //前指针
struct listNode *next; //后指针
void *value; //值
} listNode;
//链表
typedef struct list {
listNode *head; //头指针
listNode *tail; //尾指针
unsigned long len; //表长
void *(*dup)(void *ptr); //复制函数
void (*free)(void *ptr); //释放函数
int (*match)(void *ptr, void *key); //对比函数
} list;
因为是无类型指针存值,所以可以保存任意的值,双端队列的优势在于队头尾的操作方便,但是链表的查找效率显然是瓶颈,注意带查找(非头尾)的操作效率。
【字典】
typedef struct dict {
dictType *type; //处理特定类型的函数
void *privdata; //处理函数的私有数据
dictht ht[2]; //用于rehash的滚动数组
int rehashidx; //rehash进度,-1标示未开始
int iterators; //正常使用的迭代器数量
} dict;
typedef struct dictht {
dictEntry **table; //桶,一个指针数组,取模后的值就是对应数组下标
unsigned long size; //指针数组大小
unsigned long sizemask; //取模掩码
unsigned long used; //节点数量
} dictht;
typedef struct dictEntry {
void *key; //键
union { //值
void *val;
uint64_t u64;
int64_t s64;
}
struct dictEntry *next; //下一个指针,取模后值相同的节点
} dictEntry;
rehash是重新建一个大小于现在不同的表,将原来的hash表迁移到这个新的hash表的过程。
rehash条件:
(1) ratio = used/size >= 1,且dict_can_resize为真(持久化操作的时候一般为假)。
(2) ratio > dict_force_resize_ratio(默认5)。rehash方式:渐进式(dictRehashStep, dictRehashMilliseconds)。
【跳跃表】
跳表是一种神奇的数据结构,依赖随机概率,但是效率却可以和平衡树相媲美,与平衡树相比,编程复杂度低,但是空间复杂度比较高。
不知道怎么加图进来,就不多说了,插入是随机插入某一层,查找从最上层开始找,查找,插入,删除都是log(N)(期望值,有最坏的情况,数据量越大越接近期望值)。
主要用来实现有序集合。
redis数据类型
typedef struct redisObject {
unsigned type:4; //类型
unsigned notused:2; //对齐位
unsigned encoding:4; //编码
unsigned lru:22; //lru时间
int refcount; //引用次数
void *ptr; //对象值
}
redisObject 类型:字符串(整数,字符串), 列表(双端队列,压缩链表) 有序集合(跳跃表,压缩列表),哈希表(压缩列表,字典), 集合(字典,整数集合)。
字符串:REDIS_ENCODING_INT(long)和REDIS_ENCODING_RAW(sds), 除了能表示成long的,其他都保存为sds.
哈希表: 创建空白的哈希表的时候,是用压缩列表的,当哈希表的键或值长度大于某个值时(默认64),或者当节点数大于某个值(默认512)的时候,切换到哈希链表。
列表:先用压缩列表,达到某种条件(与哈希表条件类似),切换为双端链表。
阻塞:先记录相关阻塞信息,如阻塞时间,key,客户端等,server.db[i]->blocking_keys记录因为某个key被阻塞的客户端(多个)。
当push一个值的时候,server.ready_keys->ready_list添加这个key和对应的DB,并添加对应的值,注意解除阻塞为先阻塞先服务。
集合: 如果第一元素能用longlong表示,那么就用intset,否则用哈希表。
有序集合:先是ziplist,元素个数大于128时,或者元素长度大于64,转换成跳表。
事务
redis事务SMEMBERS事务进队,DISCARD取消事务,EXEC执行事务,WATCH监视改动。
watch命令用于事务执行之前监视任意数量的键,如果这些键被改变了,那么事务停止,直接返回失败。
watch某个key时,redisDb.wathced_key存储这个key对应的watch客户端,当有对键的操作的时候,会触发查找key是否在watch_key里面,在就会改变客户端的REDIS_DIRTY_CAS,执行事务前会检查这个,如果有那么就回复空,表示失败。
ACID: A(原子性),C(一致性),I(隔离性),D(持久性), redis里面只有保证了CI。
lua: 使用lua执行redis函数,当执行自身的函数时,会创建一个伪客户端执行函数,返回给服务器。
订阅。
慢查询日志:日志滚动删除。
内部运作机制
键过期:保存在expires字典里面,存着每一个键的过期时间,使用定期删除(间隔一段时间清理一次)和惰性删除(用到这个键的时候,才删除)清除键值。
RDB: 将内存镜像写入RDB文件(SAVE,BGSAVE),SAVE阻塞, BGSAVE 非阻塞。
AOF: 将操作写入AOF日志(BGREWRITEAOF),rewrite. 异步每秒一次,高于BGSAVE。