Redis是一个键值对数据库,它默认有16个db,默认选中的是第0个数据库

定义

typedef struct redisServer{
//其他属性

//数据库,一个数组,默认是16个
redisDb *db;

//其他属性
}
typedef struct redisDb{
//其他属性

//字典----键空间
dict * dict;
//字典----过期时间,保存所有带有过期时间的键
dict * expires;

//其他属性
}

从数据结构定义可以看出每一个数据库底层都是用字典实现的

键空间维护操作

当执行命令时,Redis会维护一些数据来支撑一些高级特性,比如键过期、AOF持久化、发送通知等,具体如下:

读取操作会根据读取是否命中,更新redisServer的keyspace_hits属性和keyspace_missed属性,即命中和不命中衍生:如果程序支持这个命令,是否可以通过判断这个属性来避免缓存穿透的问题,需要验证

读取操作会更新对象LRU属性,此属性可以通过OBJECT IDLETIME查看,但是此命令本身不会更新对象的LRU属性。这里的对象是值对象。

读取时发现已过期,则会先删除这个过期键

如果客户端使用watch命令监视某一个键,那么服务器修改它之后会将这个键标记为脏(dirty),从而让事务程序知道这个键被修改过

每次修改键,都会讲dirty计数器+1

如果服务器开启了通知功能,那么修改之后将按配置发送相应的通知

通过info stats 命令可以看到更多的信息

total_connections_received:1
total_commands_processed:30
instantaneous_ops_per_sec:0
total_net_input_bytes:1011
total_net_output_bytes:13707
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
expired_stale_perc:0.00
expired_time_cap_reached_count:0
evicted_keys:0
keyspace_hits:2
keyspace_misses:1
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:10075
migrate_cached_sockets:0
slave_expires_tracked_keys:0
active_defrag_hits:0
active_defrag_misses:0
active_defrag_key_hits:0
active_defrag_key_misses:0

过期时间

设置过期时间有秒\毫秒以及秒\毫秒的时间戳,它们分别对应四种命令,然后四种命令的另外三种最终都是通过转换为pexpireat实现的

expire:秒

pexpre:毫秒

expireat:秒-时间戳

pexpireat毫秒-时间戳

setex在设置值的时候同时可以设置过期时间,最终也还是通过pexpireat来设置过期时间

命令ttl和pttl可以得带过期时间键的剩余时间

persist 命令可以移除过期时间

删除策略

redis通过三种不同策略检查带有过期时间的键,将过期时间和当前时间进行比较如果小于0,则过期,否则未过期,即可执行相关的操作。。过期操作依然是一种修改,即可通过watch命令来检测是否被删除,实时的发现键被删除而做出相应的处理。

定时删除

在设置键的同时创建一个定时器,当一个键的过期时间来临的时候就会立马删除

优点

立即删除

内存友好,因为会立即删除

缺点

对CPU不友好,这么多定时器需要耗费相当一部分的CPU资源

因为创建定时器需要通过时间事件来触发,而时间事件的实现方式是一种无须列表,查找键的时间复杂度是O(N),所以效率也不高

惰性删除

每次从键空间获取键时,都检查键的过期时间,如果过期了就删除

优点

CPU友好

一次删除只会删除某一个键

缺点

对内存不友好,极端情况下甚至相当于内存泄露,一直不会释放内存

定期删除

每隔一段时间执行一次删除过期键操作,是批量删除。具体逻辑是:

从一定数据量的数据库中随机取出一定量的键,如果过期,则删除。在整个过程中有一个current_db记录当前被操作的数据库下标,直到所有的db都被执行一遍删除操作,current_db被标记为0

优点

通过算法平衡CPU和内存友好性

小结:

Redis实际采用的删除策略是:惰性删除+定期删除

这样做的好处是,能够通过主动方式及时删除那些过期键,然后使用定期删除的算法尽量避免内存的不友好和CPU的不友好。

RDB、AOF、复制对过期键处理

因为Redis会将数据持久化到磁盘上,为了保证数据一致性和正确性,Redis需要提供不同的处理方式对三种场景下过期键的处理

RDB

如果服务器以Master身份运行,从RDB文件中load的时候会判断键是否过期,然后删除

如果服务器以salve身份运行,从RDB文件中load的时候不会进行判断,但是下一次同步会将过期的干掉

执行save和bgsave命令时会判断键是否过期,然后删除

AOF

任何一种删除策略在执行删除的时候都会往AOF文件中APPEND删除命令

重写AOF的时候会将过期键删除掉

复制模式

当服务器运行在复制模式下时,从服务器不会像主服务器一样有主动的过期键删除策略,而是被动接收主服务器发送过来的删除del操作,这样可以保证主从的数据一致性

通知

使用如下参数配置notify-keyspace-events选项,可以让Redis发送不同的通知来达到检测键的命令执行情况,然后再做出相应的逻辑处理

配置可选项

# 以下有三组,组之间是与的关系

# 这是一组,互斥
K Keyspace events, published with __keyspace@__ prefix.
E Keyevent events, published with __keyevent@__ prefix.


# 这是一组,可以指定多个
g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
# 这是一组,可以指定多个
$ String commands
l List commands
s Set commands
h Hash commands
z Sorted set commands
x Expired events (events generated every time a key expires)
e Evicted events (events generated when a key is evicted for maxmemory)


# 是"g$lshzxe"别名
A Alias for g$lshzxe, so that the "AKE" string means all the events

配置方式就是举例:

发送所有键空间和键时间通知,即所有一切通知,选项=AKE

发送所有键空间通知,选项=AE

发送所有键事件通知,选项=KE

发送字符串相关的键空间和键事件通知,选项=$KE