本文的部分内容参考自《小林Coding》,部分地方根据源代码进行剖析。
Redis源码地址:https://github.com/redis/redis.git
今天继续
Redis存储是如何实现的?
键值对的实现
Redis是一个Key-Value模式的非关系型数据库,那么Key和Value的保存模式我们在这里说一说。
其实kv给大家的第一影响是啥?数组?哈希?
没错就是哈希,redis其实是用哈希表保存的键值对,键为str,值为数据类型,哈希表中的每一对元素是哈希桶
哈希桶存放的是啥?如果学过C++的STL,这里你可以理解为pair<string,T>
。
下面来西索一下。
下面来简单聊聊图上面的东西,具体的聊我会在后文说,真实的存储其实和这个图还是有些区别
Redis的数据库管理
-
redisDb 结构,表示 Redis 数据库的结构,结构体里存放了指向了 dict 结构的指针;
-
dict 结构,结构体里存放了 2 个哈希表,正常情况下都是用「哈希表1」,「哈希表2」只有在 rehash 的时候才用;
-
dictht 结构,表示哈希表的结构,结构里存放了哈希表数组,数组中的每个元素都是指向一个哈希表节点结构(dictEntry)的指针;
在最新版的dict中已经将dictht改为了数组,这样可能稍微难以理解,一会儿细说
-
dictEntry 结构,表示哈希表节点的结构,结构里存放了 void key 和 void value 指针, key 指向的是 String 对象,而 \value 则可以指向 String 对象,也可以指向集合类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象。
下面我们来说说源代码,我会在保留原注释的条件下进行解释
/* Redis database representation. There are multiple databases identified
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
//Redis数据库结构,通过ID来识别多个数据库
typedef struct redisDb {
// 当前数据口的所有键值对空间
dict *dict; /* The keyspace for this DB */
// 存放已设置exptime的key的集合
dict *expires; /* Timeout of keys with a timeout set */
// 客户端等待的阻塞中的key
dict *blocking_keys; /* Keys with clients waiting for data (BLPOP)*/
// 客户端等待的阻塞中的key,如果key被删除,那么应该取消阻塞,是blocking_keys的子集
dict *blocking_keys_unblock_on_nokey; /* Keys with clients waiting for data,
*and should be unblocked if key is deleted (XREADEDGROUP).
*This is a subset of blocking_keys*/
// 可以解除阻塞的键(客户端已经收到的key)
dict *ready_keys; /* Blocked keys that received a PUSH */
// 还在被watch命令监视中的键
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
// 数据库id
int id; /* Database ID */
// 数据库的平均TTL
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. */
clusterSlotToKeyMapping *slots_to_keys; /* Array of slots to keys. Only used in cluster mode (db 0). */
} redisDb;
//dictEntry的实现很简单,就是个链表
struct dictEntry {
void *key;
union {
void *val; //其他
uint64_t u64; //整数
int64_t s64; //整数
double d; //双精度数值
} v;
struct dictEntry *next; /* Next entry in the same hash bucket. 指向下一个hash值相同的entry*/
void *metadata[]; /* An arbitrary number of bytes (starting at a
* pointer-aligned address) of size as returned
* by dictType's dictEntryMetadataBytes(). */
};
struct dict {
dictType *type;
dictEntry **ht_table[2]; //第二个表用来进行rehash
/**
* dictEntry **ht_table[2]; <==>
* typedef struct dictht{
* dictEntry **table;
* }dictht;
* ditcht ht[2];
**/
unsigned long ht_used[2];
//用于保证rehash安全,如果值为1,那么不能执行rehash
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
//redis6之后新加入,如果值>0则暂停rehash,保证rehash安全
/* Keep small vars at end for optimal (minimal) struct padding */
int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
//哈希的内存指数
signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
//由dictType的dictEntryBytes定义的大小的任意字节数(从指针对齐的地址开始)。
void *metadata[]; /* An arbitrary number of bytes (starting at a
* pointer-aligned address) of size as defined
* by dictType's dictEntryBytes. */
};
/* If safe is set to 1 this is a safe iterator, that means, you can call
* dictAdd, dictFind, and other functions against the dictionary even while
* iterating. Otherwise it is a non safe iterator, and only dictNext()
* should be called while iterating. */
typedef struct dictIterator {
dict *d;
long index;
int table, safe;
dictEntry *entry, *nextEntry;
/* unsafe iterator fingerprint for misuse detection. */
unsigned long long fingerprint;
} dictIterator;
然后redisServer的内容有点多,我就截图了
从这个图我们看出来,不仅数据可以用dict来存储,命令也是。