1、数据结构
(1)简单动态字符串(SDS)
struct sdshrd{
// 记录buff数组中已使用字节的数量
int len;
// 记录未使用字节的数量
int free;
// 字节数组,用于保存字符串
char buf[];
}
与C字符串相比,可以O(1)获取长度、杜绝缓冲区溢出、减少修改字符串时的内存重分配次数、二进制安全、兼容部分C字符串函数;
(2)链表
用双向链表实现,维护头尾节点和节点数,可以保存不同类型的值;
(3)字典
Redis的数据库就是使用字典作为底层实现的。和HashMap类似,大小是2的幂次,负载因子≥1时扩容,≤0.1时收缩,如果正在执行BGSAVE则负载因子≥5时扩容,用分离链接法解决散列冲突,维护两张散列表,用于渐进式rehash。
渐进式rehash:维护int trehashidx,未rehash时为-1,为正数时表示正在rehash。为0时开始rehash,维护旧表和新表,add进新表,查找、修改、删除时从两个表里查询key,并将entry从旧表移入新表,trehashidx++,直至旧表为空,rehash完成,trehashidx = - 1;
(4)跳跃表
Redis只在两个地方用到了跳跃表,zset和集群节点中用作内部数据结构。
// 跳跃表节点
typedef struct zskiplistNode{
// 后退指针
struct zskiplistNode *backward;
// 分值
double score;
// 成员对象
robj *obj;
// 层
struct zskiplistLevel{
// 前进指针
struct zskiplistNode *forward;
// 跨度,计算rank
unsigned int span;
} level[];
} zskiplistNode;
// 跳跃表
typedef struct zskiplist{
// 表头节点和表尾节点
structz skiplistNode *header, *tail;
// 表中节点的数量
unsigned long length;
// 表中最大层数
int level;
} zskiplist;
每次创建一个新跳跃表节点时,程序根据幂次定律,越大的数出现的概率越小,随机生成一个介于1和32之间的值作为level数组的大小,就是层的高度;分值相同的对象按照成员对象的字典序排序;平均log(N),最坏O(N)查找,类似平衡树,实现简单,方便计算rank;
(5)整数集合
typedef struct intset{
// 编码方式
uint32_t encoding;
// 元素数量
uint32_t length;
// 保存元素的数组
int8_t contents[];
} intset;
用数组实现,有序、无重复得保存元素。添加新整数(O(N))时如果长度大于集合内所有整数,升级整数类型(提高位数),不支持降级;
(6)压缩列表
为了节约内存,由一系列特殊编码的连续内存块组成的顺序性数据结构,可以包含任意多个entry,每个entry保存一个字节数组或者一个整数;
过期策略:https://blog.csdn.net/yangtuogege/article/details/77970896
2、对象
Redis中每个对象都由一个redisObject结构表示。
typedef struct redisObject{
// 类型
unsigned type:4;
// 编码,即底层使用了什么数据结构
unsigned encoding:4;
// 指向底层数据结构的指针
void *ptr;
// 引用计数
int refcount;
// 记录了最后一次被访问的时间
unsigned lru:22;
} robj;
(1)字符串对象,编码可以是int,raw,embstr
int保存整数值;embstr保存≤39的字符串,调用一次内存分配函数分配一块连续空间,redisObject和sdshdr连续;raw调用两次内存分配函数,,不连续。embstr是只读的,append会变成raw类型。浮点型也是作为字符串存储的,用raw或embstr编码;
(2)列表对象,编码是ziplist或linkedlist
每个节点保存字符串对象,节点小而少时使用ziplist;
(3)哈希对象,编码是ziplist或hashtable
用ziplist实现时,添加时将两个节点加入表尾,entry变大或者变多时转化为hashtable;
(4)集合对象,编码是intset或hashtable
(5)有序集合对象,编码是ziplist或skiplist
ziplist内的集合元素按分值从小到大进行排序;skiplist编码的有序集合对象使用zset结构作为底层实现,包含一个字典和跳跃表。跳跃表有序,可以进行范围操作,ZRANK,ZRANGE等;字典用于O(1)查找给定成员的分值,ZSCORE命令,两者通过指针共享成员和分值,不会浪费内存;
3、命令
对任意键执行的命令:DEL,EXPIRE,RENAME,TYPE,OBJECT等;
对字符串键:SET,GET,APPEND,STRLEN等;
对哈希键:HDEL,HSET,HGET,HLEN等;
对列表键:RPUSH,LPOP,LINSERT,LLEN等;
对集合键:SADD,SPOP(移除随机元素),SINTER(返回交集),SCARD(返回大小)等;
对有序集合键:ZADD,ZSCARD,ZRANK,ZSCORE,ZREM等;
类型特定命令的类型检查通过redisObject结构的type属性来实现;
除了根据值类型,还会根据值编码使用不同的函数来实现命令。举个例子,如果执行LLEN命令,那么服务器除了要确保执行命令的是列表键之外,还需要根据键的值对象所使用的编码来选择正确的LLEN的命令实现;
Redis初始化服务器时,创建0-9999所有整数值的字符串对象。Redis只对整数值字符串对象进行共享,因为验证整数相等O(1),验证字符串相等O(N);