共计 3322 个字符,预计需要花费 9 分钟才能阅读完成。
大家都晓得 redis 默认是 16 个 db,然而这些 db 底层的设计构造是什么样的呢?
咱们来简略的看一下源码, 重要的字段都有所正文
typedef struct redisDb {
dict *dict; /* The keyspace for this DB 字典数据结构,十分重要 */
dict *expires; /* Timeout of keys with a timeout set 过期工夫 */
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP) list 一些数据结构中用到的阻塞 api*/
dict *ready_keys; /* Blocked keys that received a PUSH */
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS 事务相干解决 */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
unsigned long expires_cursor; /* Cursor of the active expire cycle. */
list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
redis 中的所有 kv 都是寄存在 dict 中的,dict 类型在 redis 中十分重要。
字典 disc 的数据结构如下
typedef struct dict {
dictType *type; //
void *privdata;
dictht ht[2]; //hashtable, 每个 dict 都有两个这样的数据结构,次要用于 hash 扩容
long rehashidx; /* rehashing not in progress if rehashidx == -1 rehash 的作用 避免链表有限增长 */
unsigned long iterators; /* number of iterators currently running 遍历记录的一些字段 */
} dict;
redis 中当呈现 hash 抵触的时候,咱们会采纳头插法 (链表) 的形式来解决,然而链表有限增常的话 hashtable 会进化,进化成一个链表,影响查问效率,这个时候咱们就须要对之前的数组进行扩容,把老的数据搬到新数组下面,这个过程就是 rehash
接下来咱们来看看 dictType 的类型
typedef struct dictType {uint64_t (*hashFunction)(const void *key);
void *(*keyDup)(void *privdata, const void *key); //key 用于数据类型的复制
void *(*valDup)(void *privdata, const void *obj); //value 用于数据类型的复制
int (*keyCompare)(void *privdata, const void *key1, const void *key2); //hash 抵触的时候须要在抵触的值外面一个一个的比照
void (*keyDestructor)(void *privdata, void *key);
void (*valDestructor)(void *privdata, void *obj);
} dictType;
typedef struct dictht {
dictEntry **table; // 指向数组的首地址 是健值对的外围构造
unsigned long size;// 数组的长度
unsigned long sizemask; // 恒等于 size-1
unsigned long used;
} dictht;
typedef struct {
void *key; // 指向 SDS 的数据结构
union { // 联合体示意 value 类型, 只会用到一个字段
void *val; // 指向 redis 对象 redisObject
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 头插法解决 hash 抵触
} dictEntry;
接下来咱们看一下内存关系的对应图
typedef struct redisObject {
unsigned type:4; // 以后对象类型 list string hash set zset 等
unsigned encoding:4; //redis 做的底层优化(编码)
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;
最初咱们有一张总的图来表是 redis 的内存关系
encoding 存储的优化策略
1: 整型编码的解决
咱们先来看一个例子
127.0.0.1:6379> set type-int 12345
OK
127.0.0.1:6379> object encoding type-int
"int"
// 返回的 encoding 类型是 int
127.0.0.1:6379> set type-int-long 12345678901234567890
OK
127.0.0.1:6379> object encoding type-int-long
"embstr"
// 返回的 encoding 类型是 embstr
咱们能够发现,在都是数字的时候,如果长度小于 20,就会主动转换为 int 类型,这是 redis 中专门做的解决
if (len <= 20 && string2l(s, len, &value))
在一个 redisObject 中,就能够间接用 ptr 去存储整型值,而不必从新去开拓一块 sds 的空间
2:redis 对象字符串存储相干优化
127.0.0.1:6379> set type-str-short xxx
OK
127.0.0.1:6379> object encoding type-str-short
"embstr"
127.0.0.1:6379> set type-str-long xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-x
// 字符串长度 45
127.0.0.1:6379> object encoding type-str-long
"raw"
127.0.0.1:6379> set type-str-long2 xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-xxxxxxxxxx-
// 字符串长度 44
127.0.0.1:6379> object encoding type-str-long2
"embstr"
一个 redisobject 是存在内存中的,cpu 在实现一个 io 的时候,它是怎么来读数据的呢,其实 cup 的 io 中有一个缓冲行的概念,在 linux 零碎中,一个缓冲行个别是 64 个字节
接下来咱们看看一个 redis 对象大略占多大的内存空间,其实咱们能够大略算进去。
typedef struct redisObject {
unsigned type:4; //4bit
unsigned encoding:4; //4bit
unsigned lru:LRU_BITS; //24bit
int refcount; //4byte
void *ptr; //8byte
} robj;
一个 redis 对象自身就须要占(4bit+4bit+24bit = 4byte)+ 4byte + 8byte = 16byte 的大小
这样的话一个缓冲行还残余 48 个 byte 的大小,有点节约,
48 个 byte,依照 sds 的调配策略应该在 sdshdr8 那个区间中,而 sdshdr8 自身就须要占 3 个字节,sds 须要兼容 c 语言的函数库,都会在结尾加上 0,所以 sdshdr8 自身是占用 4 个字节,所以一个缓冲行中还残余 44 个字节,来存储残余的数据,所以在 redis 字符串对象中,当长度小于 44 的时候,encoding 的类型是 embstr,没有新开拓一块 sds 空间
关注我的技术公众号,每周都有优质技术文章推送。
微信扫一扫下方二维码即可关注: