原文地址:www.xilidou.com/2018/03/20/…java
以前的文章讲解了 Redis 的数据结构,这回就能够看看做为内存数据库,Redis 是怎么存储数据的。以及键是怎么过时的。程序员
阅读这篇文章你将会了解到:redis
咱们先看代码 server.h/redisServer
数据库
struct redisServer{
...
//保存 db 的数组
redisDb *db;
//db 的数量
int dbnum;
...
}
复制代码
再看redisDb的代码:数组
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)*/
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 */
} redisDb;
复制代码
整体来讲redis的 server 包含若干个(默认16个) redisDb 数据库。缓存
Redis 是一个 k-v 存储的键值对数据库。其中字典 dict 保存了数据库中的全部键值对,这个地方叫作 keyspace
直译过来就是“键空间”。bash
因此咱们就能够这么认为,在 redisDb 中咱们使用 dict(字典)来维护键空间。服务器
keyspace 的 kay 是数据库的 key,每个key 是一个字符串对象。注意不是字符串,而是字符串对象。微信
keyspace 的 value 是数据库的 value,这个 value 能够是 redis 的,字符串对象,列表对象,哈希表对象,集合对象或者有序对象中的一种。数据结构
因此对于数据的增删改查,就是对 keyspace 这个大 map 的增删改查。
当咱们执行:
>redis SET mobile "13800000000"
复制代码
实际上就是为 keyspace 增长了一个 key 是包含字符串“mobile”的字符串对象,value 为包含字符“13800000000”的字符串对象。
看图:
对于删改查,没啥好说的。相似java 的 map 操做,大多数程序员应该都能理解。
须要特别注意的是,再执行对键的读写操做的时候,Redis 还要作一些额外的维护动做:
Redis 做为缓存使用最主要的一个特性就是能够为键值对设置过时时间。就看看 Redis 是若是实现这一个最重要的特性的?
在 Redis 中与过时时间有关的命令
其实这些命令,底层的命令都是由 REXPIREAT 实现的。
在 redisDb 中使用了 dict *expires,来存储过时时间的。其中 key 指向了 keyspace 中的 key(c 语言中的指针), value 是一个 long long 类型的时间戳,标定这个 key 过时的时间点,单位是毫秒。
若是咱们为上文的 mobile 增长一个过时时间。
>redis PEXPIREAT mobile 1521469812000
复制代码
这个时候就会在过时的 字典中增长一个键值对。以下图:
对于过时的判断逻辑就很简单:
接下来就须要讨论一下过时的键的删除策略。
key的删除有三种策略:
这三种策略就是对时间和空间有不一样的倾向。Redis为了平衡时间和空间,采用了后两种策略 惰性删除和定时部分删除。
惰性删除比较简单,不作过多介绍。主要讨论一下定时部分删除。
过时键的定时删除的策略由 expire.c/activeExpireCycle() 函数实现,server.c/serverCron() 定时的调用 activieExpireCycle()
。
activeExpireCycle 的大的操做原则是,若是过时的key比较少,则删除key的数量也比较保守,若是,过时的键多,删除key的策略就会很激进。
static unsigned int current_db = 0; /* Last DB tested. */
static int timelimit_exit = 0; /* Time limit hit in previous call? */
static long long last_fast_cycle = 0; /* When last fast cycle ran. */
复制代码
首先三个 static
全局参数分别记录目前遍历的 db下标,上一次删除是不是超时退出的,上一次快速操做是何时进行的。
计算 timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
能够理解为 25% 的 cpu 时间。
若是 db 中 expire 的大小为0 不操做
expire 占总 key 小于 1% 不操做
num = dictSize(db->expires);num 是 expire 使用的key的数量。
slots = dictSlots(db->expires); slots 是 expire 字典的尺寸大小。
已使用的key(num) 大于 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 则设置为 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP。也就是说每次只检查 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 个键。
随机获取带过时的 key。计算是否过时,若是过时就删除。
而后各类统计,包括删除键的次数,平均过时时间。
每遍历十六次,计算操做时间,若是超过 timelimit 结束返回。
若是删除的过时键大于 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 的 1\4 就跳出循环,结束。
步骤比较复杂,总结一下:(这里都是以默认配置描述)
这篇文章主要解释了 Redis 的数据库是怎么实现的,同时介绍了 Redis 处理过时键的逻辑。看 Redis 的代码越多愈加现,实际上 Redis 一直在作的一件事情就是平衡,一直在平衡程序的空间和时间。其实平时的业务设计,就是在宏观上平衡,平衡系统的时间和空间。因此我想说的是,看源码是让咱们从微观学习系统架构,是每一位架构师的必经之路。你们加油。
我以前的三篇关于 Redis 的基础数据结构连接地址,欢迎你们阅读。
Redis 的基础数据结构(二) 整数集合、跳跃表、压缩列表
欢迎关注个人微信公众号: