Redis专题--数据库

《redis设计与实现》--第九章

redisServer

redis服务器将所有数据库都保存在redis.h/redisServer结构的db数组中,db数组的每个项都是一个redis.h/redisDb结构,每个redisDb代表一个数据库。Redis使用一个类型为“redisServer”的数据结构来保存整个Redis服务器的状态.

https://www.cnblogs.com/wujuntian/p/9218961.html

struct redisServer {
int dbnum;//服务器的数据库数量,值由服务器配置的“databases”选项决定,默认为16
redisDb *db;//数组,保存着服务器中的所有数据库
 
list *clients;//一个链表,保存了所有客户端状态,每个链表元素都是“redisClient”结构
 
time_t unixtime;//保存秒级精度的系统当前UNIX时间戳,减少获取系统当前时间的系统调用次数,100毫秒更新一次
long long mstime;//保存毫秒级精度的系统当前UNIX时间戳
unsigned lruclock;//默认每10秒更新一次,用于计算数据库键的空转时长,数据库键的空转时长 = 服务器的“lruclock”属性值 - 数据库键值对象的“lru”属性值
 
long long ops_sec_last_sample_time;//上一次进行服务器每秒执行命令数量抽样的时间
long long ops_sec_last_sample_ops;//上一次进行服务器每秒执行命令数量抽样时,服务器已执行命令的数量
long long ops_sec_samples[REDIS_OPS_SEC_SAMPLE];//环形数组,每个元素记录一次服务器每秒执行命令数量抽样结果,估算服务器在最近一秒钟处理的命令请求数量(数组长度默认为16,100毫秒更新一次)
int ops_sec_idx;//ops_sec_samples数组的索引值,每次抽样后值增1,等于16时重置为0
 
size_t stat_peak_memory;//已使用内存峰值
 
int shutdown_asap;//关闭服务器的标识,1表示关闭,0不关闭
 
pid_t rdb_child_pid;//记录执行BGSAVE命令的子进程的ID,-1表示服务器没有正在执行BGSAVE
pid_t aof_child_pid;//记录执行BGREWRITEAOF命令的子进程的ID,-1表示服务器没有正在执行BGREWRITEAOF
int aof_rewrite_scheduled;//1表示有BGREWRITEAOF命令被延迟了(服务器执行BGSAVE期间收到的BGREWRITEAOF会被延迟到BGSAVE执行完成之后执行)
struct saveparam *saveparams;//记录了自动保存条件的数组(执行BGSAVE的条件)
long long dirty;//修改计数器(上一次执行BGSAVE之后已经产生了多少修改)
time_t lastsave;//上一次执行自动保存操作(BGSAVE)的时间
sds aof_buf;//AOF缓冲区
 
int cronloops;//serverCron函数的运行次数计数器
 
lua;//用于执行Lua脚本的Lua环境
redisClient *lua_client;//Lua脚本的伪客户端,在服务器运行的整个生命周期一直存在,直至服务器关闭才会关闭
dict *lua_scripts;//字典,记录所有载入的Lua脚本,键为某个Lua脚本的SHA1校验和,值为对应的Lua脚本
dict *repl_scriptcache_dict;//字典,记录已经传播给所有从服务器的所有Lua脚本,键为脚本的SHA1校验和,值为NULL,用于EVALSHA1命令的复制
 
long long slowlog_entry_id;//下一条慢查询日志的ID
list *slowlog;//保存了所有慢查询日志的链表
long long slowlog_log_slower_than;//服务器配置“slowlog-log-slower-than”选项的值,表示查询慢于多少微秒便记录慢查询日志
unsigned long slowlog_max_len;//服务器配置“slowlog-max-len”选项的值,表示服务器最多保存多少条慢查询日志记录,若超出,最久的记录会被覆盖
 
monitors;//链表,监视器客户端列表
 
dict *pubsub_channels;//字典,保存所有频道的订阅关系,键为某个被订阅的频道,值为链表,记录了所有订阅这个频道的客户端
list *pubsub_patterns;//链表,保存所有模式的订阅关系,每个链表节点都包含了订阅的客户端和被订阅的模式
};

在Redis服务器状态结构中,“dbnum”属性记录了服务器的数据库数量,它的值可以通过服务器配置的“databases”选项决定,默认为16。“db”属性是一个数组,保存保存着服务器中的所有数据库。

通过select num命令可以切换数据库。

select 2后,目标数据库改为2号数据库,变成

通过修改redisClient db指针,让它指向服务器中不同的数据库,从而实现切换目标数据库的功能。这就是select命令的实现原理。

redisDb

其中每一个数据库都对应一个“redisDb”数据结构:

struct redisDb {
//数据库键空间字典,保存数据库中所有的键值对
//键空间的key,就是数据库的key,每个键都是一个String对象
//键空间的value,就是数据库的value,每个值可以是String,list,hash,set,zset中任意一种redis对象
dict *dict;
dict *expires;//过期字典,保存数据库中所有键的过期时间
dict *watched_keys;//字典,正在被WATCH命令监视的键
};

键操作

添加一个新键值对到数据库,实际上就是添加一个新键值对到键空间字典里面。增删改查等操作同理,例如在9-4的基础上执行

set date "2013.12.1"

 

读写键空间的维护操作

  • 当使用redis命令对数据库进行读写时,服务器不仅会对键空间执行指定的读写操作,还会执行一些额外的维护操作,包括:
  • 在读取一个键之后(读写操作都要对键进行读取),服务器会根据键是否存在来更新服务器的键命中(hit)次数或键空间不命中(miss)次数,这两个值可以在INFO stats命令的keyspace_hits和keyspace_misses属性中查看
  • 在读取一个键之后,服务器会更新键的LRU时间,这个值可以用于计算键的闲置时间,使用OBJECT idletime<key>可以查看key的闲置时间
  • 如果服务器在读取一个键时发现该键已经过期,先删除这个过期键,然后才执行余下的其他操作
  • 如果客户端使用watch命令监视了某个键,那么服务器在对被监视的键进行修改后,会将这个键标记为脏(dirty),从而让事务程序注意到这个键已经被修改过
  • 服务器每次修改一个键之后,都会对脏键计数器+1,这个计数器会触发服务器的持久化以及复制操作
  • 如果服务器开启了数据库通知功能,那么在堆键进行修改之后,服务器将按配置发送相应的数据库通知

键的过期机制/生存时间

Redis数据库结构redisDb的“expires”属性保存了Redis数据库所有拥有过期时间的键以及它们对应的过期时间,底层同样由字典实现。数据库键过期时间的设置和删除,实际上都是对过期字典的操作。其中,字典的键是一个个指针,分别指向键空间字典中的一个个键对象(共享对象,节省内存空间);字典的值则是一个个long long类型的整数表示的毫秒精度的UNIX时间戳,保存数据库键的过期时间。
1. 键过期时间设置
expire:以秒为单位,设置Redis键的生存时间为ttl秒。
pexpire:以毫秒为单位,设置Redis键的生存时间。
expireat:以秒为单位,设置Redis键的过期时间为timestamp时间戳。
pexpireat:以毫秒为单位,设置Redis键的过期时间。
注:实际上'expire'、'pexpire'、'expireat'命令最后都会转换为'pexpireat'命令来执行。


2. 键过期时间查看
ttl:以秒为单位,返回键的剩余生存时间。
pttl:以毫秒为单位,返回键的剩余生存时间。
3. 键过期判定
  检查当前Unix时间戳是否大于键的过期时间,是则过期,否则不过期。
4. 过期键的理论删除策略
(1)定时删除:
  设置一个键过期时间的同时,创建一个定时器。每个带有过期时间的键都对应着一个定时器。
  这种策略对内存是最友好的,但对CPU时间是最不友好的。创建一个定时器需要用到Redis服务器中的时间事件,而当前时间事件的实现方式为无序链表,查找一个事件的时间复杂度为O(N),并不能高效地处理大量时间事件。
(2)惰性删除:
  访问一个键的时候再检测该键是否过期,是则删除之。
  这种策略对CPU时间是最友好的,但对内存是最不友好的。没被访问到的过期键永远不会被删除,可以看做内存泄露。对于运行状态非常依赖于内存的Redis来说,这种策略显然会影响到Redis的性能。
(3)定期删除:
  这种策略是对前两种策略的整合与折中方案。使用这种策略需要控制好删除操作每次执行的时长和执行的频率,否则会退化为前两种策略的其中一种。
5. Redis采用的过期键删除策略
  Redis服务器实际使用的是惰性删除和定期删除两种策略配合使用的方案。
(1)惰性删除策略的实现:
  所有读写数据库的Redis命令在执行之前都会先检查输入键是否已过期,过期则删除之。
(2)定期删除策略的实现:
  在规定时间内,分多次遍历服务器中的各个数据库,从数据库的过期字典中随机检查一部分键的过期时间,并删除其中的过期键。

  • <1>定期删除程序每次运行时,都会从一定数量的数据库中取出一定数量的随机键进行检查,并删除其中的过期键。
  • <2>使用一个全局变量记录当前删除程序检查的是第几个数据库,下一次运行都会接着上一次的进度进行处理。
  • <3>随着删除程序的不断执行,服务器中所有的数据库都会被检查一遍,然后这个全局变量被重置为0,开始新一轮的检查工作。

AOF、RDB和复制功能对过期键的处理
(1)生成RDB文件:
  在执行save或bgsave命令创建一个新的RDB文件时,程序会对数据库中的键进行检查,已过期的键不会被保存到新创建的RDB文件中。
(2)载入RDB文件:

  • <1>主服务器模式:载入RDB文件时,程序会对文件中保存的键进行检查,只有未过期的键会被载入到数据库中。
  • <2>从服务器模式:文件中保存的所有键都会被载入到数据库中。不过因为主从服务器在进行数据同步的时候,从服务器的数据库会被清空,所以过期键对载入RDB文件的从服务器也不会造成影响。

(3)AOF文件写入:
  当过期键被惰性删除或定期删除之后,程序会向AOF文件追加一条del命令,来显式地记录该键已被删除。
(4)AOF重写:
  程序会对数据库中的键进行检查,已过期的键不会被保存到重写后的AOF文件中。
(5)复制:
  当服务器运行在复制模式下时,从服务器的过期键删除动作由主服务器控制:

  • <1>主服务器在删除一个过期键之后,会显式地向所有从服务器发送一个del命令,告知从服务器删除这个过期键。
  • <2>从服务器在执行客户端发送的读命令时,即使碰到过期键也不会将其删除,而是将过期键的值继续返回给客户端。
  • <3>从服务器只有在接到主服务器发送来的del命令之后,才会删除过期键。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值