redis是用C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库。
redis的对象系统
Redis使用数据结构来实现对象,然后由对象系统实现健值数据库。
创建k-v对象
每当我们在redis创建一个键值对时,就会创建两个对象,一个键对象(为sds对象),另一个对象类型将会根据存储类型自动选择,值对象可以是字符串对象,列表对象,哈希对象,集合对象,有序集合对象。
redisObject对象
struct redisObject{
type类型
encoding实现的数据结构
ptr指向数据结构
refcount引用计数
lru//最后一次被访问的时间
}
对象的引用计数refcount
创建一个对象,时候refcount=1
当refcount=0时候,对象占用的内存会被删除
当对象被另一个个数据库的键值对的对象指向时,refcount+1
redis会在启动时候创建1000个整数对象,用于对象共享。
不共享字符串对象,因为对比比较花时间。只用于整数。
在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构
127.0.0.1:6379> exists book
(integer) 0
127.0.0.1:6379> hset book title "Mastering Redis" // 当键不存在时,会新建一个哈希表并设置域的值
(integer) 1
127.0.0.1:6379> hget book title
"Mastering Redis"
127.0.0.1:6379> hset book title "Thinking In Java" // 当域已经存在于哈希表时,会用新值覆盖旧值
(integer) 0
127.0.0.1:6379> hget book title
"Thinking In Java"
Redis 的 Set 集合数据 , 与 List 列表功能相似 , 唯一的区别是 Set 集合中的元素是不允许重复的 ;
127.0.0.1:6379> sadd name Tom Jerry Jack
(integer) 3
127.0.0.1:6379> smembers name
1) "Jerry"
2) "Tom"
3) "Jack"
redis的数据结构
Redis有以下这五种基本类型:
- String(字符串)
- Hash(哈希)
- List(列表)
- Set(集合)
- zset(有序集合)
对象的value的type:
查询一个对象的type方法:type 对象的key
对象 | 对象type属性的值 | TYPE命令的输出 |
---|---|---|
字符串对象 | REDIS STRING | “string” |
列表对象 | REDIS LIST | “list” |
哈希对象 | REDIS HASH | “hash” |
集合对象 | REDIS SET | “set” |
有序集合对象 | REDIS ZSET | “zset” |
对象的值的encoding:
查询方法:object encoding 对象的key
编码 | 底层数据结构 |
---|---|
REDIS_ENCODING_INT | long 类型的整数 |
REDIS_ENCODING_EMBSTR | embstr 编码的简单动态字符串 |
REDIS_ENCODING_RAW | 简单动态字符串 |
REDIS_ENCODING_HT | 字典(一对哈希表,方便rehash) |
REDIS_ENCODING_LINKEDLIST | 双端链表 |
REDIS_ENCODING_ZIPLIST | 压缩列表 |
REDIS_ENCODING_INTSET | 整数集合 |
REDIS_ENCODING_SKIPLIST | 跳跃表和字典 |
- String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
embstr和raw都是sds的编码实现,embstr是专门用来保存短字符串的一种优化编码方式,他和raw一样都是使用了sds和redisObject结构来表示字符串对象,但是raw会调用两次内存分配函数来分别创建redisObject和sds对象,而embstr则只使用一次内存分配函数来分配一个连续的空间来依次存储redisObject和sds结构。因为是连续空间所以需要保证放置的是小字符串。执行命令时两者效果相同。embstr的优点包含:一次内存分配释放,连续存储方便读取。
- List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),使用ziplist编码,否则使用linkedlist编码
- Hash:哈希类型元素个数小于512个,所有值小于64字节的话,使用ziplist编码,否则使用字典编码。
ziplist存储hash的key-value,采用key-value交替存储的方式,k-v-k-v-k-v-eof
- Set:如果集合中的元素都是整数且元素个数小于512个,使用intset编码,否则使用字典编码。
- Zset:当有序集合的元素个数小于128个,每个元素的值小于64字节时,使用ziplist编码,否则使用skiplist(跳跃表)编码
type的encoding实现:
数据结构 | 编码方式 | 描述 |
---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 使用整数值实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR (DS) | 使用embstr 编码的简单动态字符串实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用简单动态字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用双端链表实现的列表对象 |
REDIS_HASH | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的哈希对象 |
REDIS_HASH | REDIS_ENCODING_HT | 使用字典实现的哈希对象 |
REDIS_SET | REDIS_ENCODING_INTSET | 使用整数集合实现的集合对象 |
REDIS_SET | REDIS_ENCODING_HT | 使用字典实现的集合对象 |
REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的有序集合对象 |
REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 使用跳跃表和字典实现的有序集合对象 |
redis数据库
redis有服务器和客户端,我们使用redis-cli启动就是客户端。
struct redisServer{
redisDb *db;//一个数组,存储着服务器中所以的数据库
int dbnum;//服务器中数据库的数量,初始值为16,默认创建16个数据库
};
默认使用0号数据库,可以使用select切换.
struct redisDb{
dict *dict;
}
在数据库中有一个字典,保存数据库所以的键值对,我们称这个字典为键空间。
字典的键一定是sds对象。
读写键空间时的维护操作
(1)更新服务器的键空间命中次数。
(2)读取一个键后,会更新键的LRU(最后一次使用时间)。
(3)watch
(4)键过期删除
(5)持久化
过期时间如何实现:每个数据库都有过期字典
过期时间是一个long值,是一个unix时间戳
struct redisDb{
dict * expires;//存储着过期时间的字典
}
字典,键是字符串key,值是过期时间。
过期键删除策略
redis使用惰性删除和定期删除的结合。
惰性删除:在每一次读写数据库都会先检查这个键是否过期,如果过期就删除。
定期删除:(1)在每一次取出都会捎带一些键检查(2)会顺序检查
RDB文件对过期时间的处理
保存:在执行save或者bgsave命令创建一个新RDB文件时,不会保存过期的键。
载入:服务器以主服务器运行,会检查键的过期时间,如果过期不会载入。如果是从服务器,过期也会载入,但是主从同步后就没了。
AOF文件对于过期时间的处理
AOF文件的写入:当键过期后,会在AOF中写入一条delet命令。
AOF的重写:会忽略过期键。
分布式的过期时间的实现
主服务器过期后删除,会向从服务器发送delet命令。
在没有主服务器发送的delet命令,从服务器不会处理过期键。
数据库通知:获取键的操作,以及命令的执行情况
(1)键空间通知:查看这个0号数据库和宗的键message执行过什么操作:subscribe__keyspace@0__:message
(2)键事件通知:查看0号数据库中del执行情况:subscribe keyevent@0:del
redis的RDB的持久化:RDB文件包含了数据库在某个时间点上的快照
RDB文件的二进制格式包含了数据库中的几乎所有内容
Redis是一个内存数据库,如果断电就会丢失,通过存储在磁盘上一个二进制文件RDB文件实现持久化。
有两个命令实现持久化:save(阻塞Redis服务进程实现持久化)和bgsave(派生子进程实现持久化)
Redis在启动时候自动执行载入RDB文件。
因为AOF文件的更新频率高于RDB,所以启动时候先检测AOF是否启动,如果AOF启动,则会载入AOF文件,而不用RDB。只有AOF持久化功能关闭才会使用RDB。
当服务器save时候会阻塞,不执行命令。
自动间隔性保存
struct redisServer{
saveparams* ;
}//redis服务器数据结构包含save参数
Redis允许用户设置save选项,让服务器每隔一段时间自动执行一次bgsave命令。
eg. save 900 1
如果在900秒内执行了至少一次修改,那么就保存。
save设置的默认项:
save 900 1
save 300 10
save 60 1000
redis服务器的数据结构记录了上一次save操作的记录
struct redisServer{
long dirty;
time_t lastsave;
}
dirty表示在上一次成功执行save或者bgsave命令之后,服务器对数据库进行了多少次修改。
lastsave是一个UNIX时间戳,记录了服务器上一次执行save或者bgsave的时间。
周期性检查save条件是否满足
每隔100毫秒就会检查一次是否满足saveparams的条件
RDB文件结构
大写是常量,小写是变量和数据,通过固定长度的字符串和常量实现字段的划分
REDIS–db_version—SELECTDB—0–pairs—SELECTED—3—pairs—EOF----check_sum
pairs存什么就是什么
redis的AOF的持久化
AOF(Append Only File)通过保存Redis服务器执行的写命令来记录数据库的。
AOF的实现步骤可以分为:命令追加(append)到缓存区,写入到AOF磁盘文件。
redisServer{
sds aof_buf;追加到这个缓存区
};
每次用户的命令都会写入到aof的缓存区中
AOF文件的写入
Redis服务器的进程就是一个事件循环,在这个循环中文件事件负责接受客户端的命令请求,以及向客户端发送回复,而时间事件负责定时执行的任务(比如RDB),然后执行AOF写入。
loop:
while True:
processFileEvents();
processTimeEvents();
flushAppendOnlyFile();//根据配置是否需要写入AOF文件
写入AOF文件的策略:每次循环都会将命令写入到aof缓存中,但是写入AOF文件依据同步策略来决定
(1)always:每个命令都在写入aof缓存后都同步到AOF文件
(2)everysec:每过1秒,就同步
(3)no:没有同步命令,每次都需要执行flushAppendOnlyFile()从而同步。
AOF重写
AOF重写文件和原AOF文件无关,是通过从数据库中读取数据,忽略过期键值对,然后生成AOF命令。
是通过创建子进程来进行重写,服务器还可以处理命令,但是这也会导致数据不一致。
Redis服务器设置了一个AOF重写缓存区,AOF执行后会将AOF重写缓存中的命令添加到AOF文件中。
Redis是单线程的,但为了高效持久化与复制,通常会创建子进程:
(1)RDB持久化
(2)AOF持久化
(3)主从复制
redis事件
Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:
(1)文件事件:Redis客户端(或者其他服务器)通过套接字和Redis服务器通信的抽象。
(2)时间事件:循环事件(或者在指定时间)执行的操作的抽象。
网络事件处理器(文件事件处理器)
文件事件处理器使用IO复用程序来监听多个信道,如果有事件,就派送到相应的事件处理器,执行完毕后,IO复用程序会切换到其他套接字。
当客户端准备好执行连接,读取,写入,关闭等操作时,对应的文件事件就会产生,这时服务器的处理器就会调用相应的处理器来进行处理。
文件事件处理器的构成:文件事件就是对套接字操作的抽象
套接字s1,s2,s3--------------IO多路复用程序---------文件事件分配器--------------事件处理器
事件处理器:命令请求处理器,命令回复处理器,连接应答处理器
事件处理器的类型
服务器相对于套接字:writable事件(客户端读时候,服务器写时候),readable事件(从客户端写时候,服务器读时候)
io复用程序同时监听两种事务,同时发生时候,会先执行readable事件
连接应答处理器----服务器对于套接字的连接监听,实现与客户端的连接
命令请求处理器-----读套接字传入的命令
命令回复处理器----服务器通过套接字给用户回复
一次完整的事件处理过程:
客户端------》连接请求--------》服务器 连接请求应答处理器
客户端------》命令--------------》服务器 命令请求处理器
客户端-------《回复-------------《服务器 命令回复处理器
事件事件
两类:定时事件,周期性事件
区别在于事件的返回值,如果为0是定时事件,如果为30,则会重新启动事件,并且将启动事件设置为30毫秒之后。
这个版本Redis只有周期性事件,而没有定时任务。
时间事件的实现:时间事件处理器
服务器将所有的时间事件都放在一个链表中,当时间事件处理器执行时,会遍历整个链表,查找已到达的时间事件,并且调用相应的处理器。
时间事件的构成
time_event
id:3
when:12234932423(UNIX)
handle:handle_
代码实现:processTimeEvents()
for()遍历
if(到点了)执行处理器
时间事件应用实例:serverCron函数
redis服务器需要定期对自身资源和状态进行检查:serverCron函数
心跳检测,服务器同步,AOF或RDB,清理过期键,更新服务器统计状态,关闭失效服务器连接
默认,serverCron每100ms运行一次,可以修改。
事件的调度:服务器如何调度文件事件和时间事件
aeProcessEvents():
计算最近的时间事件到达时间
如果已到,则取出来
aeApiPoll(取出的事件);阻塞并等待文件事件的产生,如果到了时间事件的时间,则立刻跳出
processFileEvents();
processTimeEvents();
main():
init_server();
while(not_shutdown)
aeProcessEvents()
close_server();
客户端
每个客户端连接Redis服务器,都会建立一个客户端的数据结构。
客户端向服务器发送请求,服务端返回答复。
Redis服务器使用单线程,多个客户端使用IO复用来实现。
struct redisServe{
list *clients;
}
struct redisClient{
int fd;//套接字描述符
name;
int flags;//客户端状态标志符
sds querybuf;//输入缓存区
robj *argv;//客户端传入的命令数组
int argc;//命令的数量
struct redisCommand cmd;//命令表,装有redis能执行的命令,将会将传入的命令的参数放到命令结构中
char buf【】;//输出缓存区
int authenticated;//身份状态
time_t ctime; //创建客户端过了多久
time_t lastinteraction;//上次执行过了多久
time_t obuf_soft_limit_reached_time;//空转了多久
}
服务器在载入AOF文件时候,会创建一个伪造的Redis服务器来执行命令,执行完后删除。
服务器
用户----命令----> 客户端 ------将命令转换成协议格式然后发送-------> 服务器------回复转换为协议格式------>客户端—将回复显示在界面。
1.
当客户端与服务器之间的连接套接字因为客户端的写入而可读时:
(1)读取套接字请求,并将其保存到redisClient输入缓存区;
(2)分析buf,提取关键字到client的argv;
(3)调用执行器;
2.命令执行器的执行过程
(1)根据client查找相应的命令实现。收集执行函数,执行参数,参数个数。
(2)执行预备操作。
(3)调用命令实现函数。
(4)执行后续操作。
3.serverCron函数
服务器的serverCron函数默认每隔100ms执行一次,这个函数负责管理服务器资源,并且保证服务器的运转。
它的功能有:
(1)更新服务器事件缓存。
(2)更新LRU时钟。最后一次被访问的时间
(3)更新服务器每秒执行命令次数
(4)更新内存峰值
(5)处理SIGTERM信号(关闭服务器的)
(6)管理客户端连接状态以及资源。
(7)接受客户端的bgrewriteaof(aof延迟重写)持久化操作
(8)检查执行持久化
服务器没有在执行如何持久化—》有aof重写----》延迟持久化
服务器没有在执行如何持久化—》没有aof重写—》自动保存条件满足-----》bgsave,rdb持久化
服务器没有在执行如何持久化—》没有aof重写—》自动保存条件没满足----》aof重写满足—》aof重写
(9)aof缓存区写入AOF文件
4.初始化服务器
在redis服务器启动时候,需要初始化服务器
(1)初始化服务器状态,创建与一个redsiServer,设置端口,运行频率,文件路径,rdb和aof条件
(2)载入配置,改为用户配置
(3)初始化数据结构
(4)还原数据库状态,如果开启了AOF,使用AOF,如果没有,就使用RDB。
(5)执行loop,开始工作