@(Redis)
SDS是Redis实现的一个字符串数据结构。
结构:
struct sdshdr {
int len; //字符串长度
int free; //未使用的字符串长度
char buf[] //保存字符
}
为什么不用c的字符数组
数据结构:
链表节点
typedef struct listNode {
struct
listNode *prev //前节点
struct listNode * next // 后节点
void *value; //节点的值
}
链表
typedef struct list {
listNode *head; // 头节点
listNode *tail ;// 尾节点
unsigned log len;// 节点长度
}
哈希表节点
typedef struct dictEntry{
void *key; //键
union{
void *val;
uint64_t u64;
int64_t s64;
} v; // 节点的值,可以是指针(val),也可以是数值(u64,s64)
struct dictEntry *next ; //下一个节点,形成链表
}
哈希表
typedef struct dictht {
dictEntry **table; //哈希表数组
unsigned log size; //哈希表大小
unsigned log sizemask;// 大小掩码,总是等于size-1
unsigned log used;//已有节点数量
}
字典
typedef struct dict {
dictType *type; //类型特定函数
void *pricdata; // 私有数据
dictht ht[2]; // 哈希表
int trehashidx; // rehash索引,当rehash不在进行时,值为-1
}
一个字典有两张哈希表,0用于存储数据,1用于rehash。
当要存储k0=v0的时候,首先程序会算出k0的hash值,例如是8,假如sizemask是3,size是4,然后计算index=8&3=0(不知道&是什么操作,但是应该是8mode4=0,取余)。然后去ht[0].table数据中,找index等于0的哈希节点,然后是否有哈希节点,如果有,就看节点的next是否为空,直到找到next为空的节点,然后创建一个新的节点,使上一个节点的next指向新节点
如果是获取k0的值,计算index后,遍历节点连表,直到找到key等于k0的节点。
所以
rehash
rehash的过程就是
负载因子= used / size
当服务器执行BGSAVE或者BGREWRITEAOF操作,而且负载因子大于5,执行扩展操作
跳跃表,主要用于加快遍历的速度。
例如有序列表,1,2,3,4,5,50
如果要查找50的节点在哪里,需要对前面的5个节点都遍历一次,比较慢。如果再第一个节点记录了50在哪里,或者5在哪里,程序就可以直接跳过前面的节点,直接到达,或者接近目标节点。
注意列表必须是有序的。
跳跃表结构
跳跃表节点结构
typedef struct zskiplistNode {
struct zskiplistNode *backward;// 上一个节点的指针
double score;// 节点的分值
robj *obj; // 节点的存储对象
struct zkiplistLevel{// 节点的层,是一个列表
struct zskiplistNode *forward; //下一个节点的指针
unsigned int span; //前进的跨度
} level[];
}
一个列表有多少个元素,就会有多少个跳跃表节点
当创建一个新节点时,程序会根据幂次定律,随机生成1和32直接的值作为level数组的大小,这个大小就是这个节点的高度。
例如上面的元素1
的节点,会有一个level指向50
的节点,跨度是5。这样当程序需要找到50的时候,遍历1的节点,发现1节点的值小于50,就去遍历1节点的level,
当一个集合(Set)只包含整数元素,而且数量不多时,就会使用整数集合。
type struct intset {
uint32_t encoding;//编码方式,整数的大小,例如32位,16位
uint32_t length;//集合的长度
int8_t contents[];//集合的元素,类型取决于encoding属性的值
}
升级
如果集合的encoding是16位,当要插入一个32位的整数时,数据结构就会升级,例如升级为32位的。
但是不会进行降级操作。
当一个列表和字典的长度比较短,就会使用压缩列表。
属性:
压缩列表不同上面的数据结构是redis里面的struct,压缩列表是一段内存数据。
如果要查找一个元素,
如果新增元素,会在头部插入,然后更新前一个节点的previous_entry_length 。
如果删除元素,修改前一个节点的previous_entry_length为空。
压缩列表的优点
Redis有五种对象
redis中,通过type
命令,可以查看一个key的对象类型
这5中对象在不同的时候,会采用上面介绍的数据结构的其中一种。而且在操作的过程中,数据结构也会变化。
通过命令object encoding
可以查看key使用的数据结构
redis 127.0.0.1:6701> set test 'aaa'
OK
redis 127.0.0.1:6701> type test
string
redis 127.0.0.1:6701> object encoding test
"embstr"
redis 127.0.0.1:6701>
embstr就是上面的SDS。
redis使用的数据结构有
对象的数据结构
typedef struct redisObject {
unsigned type:4; //对象的类型,对应上面的五种对象
unsigned encoding:4; //使用的数据结构,对应上面的几种结构
void *ptr ;//底层的数据结构的指针
}
数据结构可以是int raw和embstr
如果字符串全是数字,就会使用int类型
如果字符串的大小大于39字节,使用raw来存储
如果大小小于39字节,使用embstr来存储
raw和embstr的区别是,raw需要申请两次内存,embstr只需要一次
列表对象可以是ziplist或者linkedlist
如果列表元素的长度都小于64字节,而且元素的数量小于512个,就会使用ziplist,否则使用linkedlist
哈希对象可以是ziplist或者hashtable
如果键值对的长度都小于64字节,而且键值对的数量小于512个,就会使用ziplist,否则使用linkedlist
可以是intset 或者hashtable
当元素都是整数值,元素不超过512,使用intset,否则使用hashtable
数据结构可以是ziplist或者skiplist
当元素小于128个,而且元素长度都小于64字节,使用ziplist,否则使用skiplist。
redis使用引用计数器来实现内存的回收
每个对象都有一个int refcount属性,来记录被引用的次数。
在业务端,
数字0到9999,这些值的字符串对象,会在redis启动的时候就被创建,然后被共享。
为什么其他值不共享,因为如果要共享其他值,每次创建的时候,都有查看内存中有没有这个值的对象,这个查询的效率是很低的。
每个对象都有个属性 unsigned lru:22;,用于记录对象最后一次被访问的时间
通过object idletime
命令,可以查看一个key的空转时长,其实就是当前时间戳减去lru。
在redis回收内存的时候,而且当回收算法是volatile-lru或者allkeys-lru时,会优先回收空转时长较长的key。
原文:https://www.cnblogs.com/Xjng/p/12085100.html