Redis设计与实现

Redis设计与实现
各种数据结构的用途以及优点:
(1)sds:数据库键值对的健一定就是一个sds对象。
(2)双向链表(长,多数据):当一个列表健包含了数量多的元素,或者其中包含的元素都是较长的字符串时。有序的,高效的插入和删除。
(3)字典:数据库的对象一一对应就是使用字典实现的。哈希健也是用字典实现的。高效插入和删除,快速查找。
(4)跳跃表(长,多):有序集合健的底层之一,元素多,元素长,就会使用。有序,范围查找,链表的优点。
(5)整数集合(整数,少):所有元素只包含整数,元素不多。有序,无重复
(6)压缩列表(元素少,小):之包含少数列表项,项是小整数或者短字符,那么就会使用ziplist来实现列表。空间优化。

第一部分—数据结构

第二章 数据结构-----sds简单动态字符串
Redis没有使用c语言的字符串(以空字符结尾的字符数组)。
Redis只在不需要修改的地方用到了c字符串(打印日志),其余都是用的SDS(简单动态字符串)。
struct sds{
int len;已使用的字节数量
int free;未使用的字节数量
char buf[]; 存储的字符串,以空字符结尾
}
sds的长度=(sds->len)-(sds->free);
使用了len和free能够杜绝缓冲区溢出问题。
sds相对于c字符串的优化:
1.sds减少修改字符串带来的内存重分配次数:
(1)空间预分配
当对sds操作,需要进行空间扩展时候,会为sds额外分配一些空间。
当len<1MB,free=len;
len>1MB,free=1MB;
因为预分配的实现,下次可以直接使用预先分配的空间。
(2)惰性空间释放
字符串缩短后,不会释放空间,而是加入到free中。
2.二进制安全
buf中存什么,读出来就是什么(因为不用‘/0’来判断结尾,而是使用len)
3.兼容部分c字符串函数。
第三章----链表
struct listNode{
struct listNode* prev;
struct listNode* next;
void * value;
}
第四章-----字典
字典:就像使用汉语字典一样,一一映射关系的抽象概念。
Redis使用哈希表来实现字典。
使用两个链表哈希表来实现字典。
struct dict{
dictType* type;指向dict使用的函数,代表类型
dictht ht[2];两个哈系表,可以用来rehash
int rehashidx;记录rehash的进度,-1代表不在rehash,3代表rehash到了第三个节点。
};
struct dicttht{
dictEntry** table;哈系表数组
long size;哈希表大小
long used;哈系表已有节点数量
}
struct dictEntry{
void* key;
void* value;
struct dictEntry; 指向下一个节点,形成链表,链表解决冲突的哈希表
}
rehash操作原理:
(1)rehash之后的长度问题:
如果是扩大,那么就是ht[0].used*2之后的第一个2的幂次
如果是缩小,那么就是ht[0].used之后的第一个2的幂次。
(2)如何rehash,利用新的size算出新的hash放上去。
(3)渐近式rehash:crud过程中捎带rehash,每次rehash完成一个就将rehashidx加一,当rehashidx值为used时,表示rehash完成,将其设置为-1。
完成rehash后,会将ht[1]设置为ht[0],ht[1]为null。
(4)在渐近式rehash期间,查找会同时找ht[0]和ht[1],但是添加只会添加到ht[1];

第五章-----跳跃表

第六章----整数集合
可以保存的类型有int16_t,int32_t,int64_t整数,并且集合中不会出现重复的整数。
strct intset{
uint32_t encoding;整数的编码类型。int16_t,int32_t,int64_t,即在数组contens中保存的item类型。
uint32_t length;数组长度。
contents[];保存元素的数组。大小按照从小到大排序,并且不包含重复项,没有结尾的‘/0‘,因为不需要重用字符串函数。
}
1.升级
新添加的元素要比encoding长度长,需要进行升级。
分配新空间,将原有元素放到新空间上,将新元素添加。
2.降级
不会降级

第七章----压缩列表
连续内存的串行节点构成的数据结构。为节约内存而生。
struct ziplist{
zlbytes------zltail----zllen—(entry1)----(entry2)-------…------zlend
}
//每一个压缩列表节点可以保存一个字节数组或者一个整数。
struct entry{
previous_entry_length------encoding-------content
}
previous_entry_length:前一节点的长度字节。
encoding:content编码方式
content:内容,一个整数,或者一个字符串
1.添加节点
添加到列表头或列表尾。
2.连锁更新
从表头添加新节点,导致原先的首节点的previous_entry_ength长度不够(可以是1字节或者5字节),那么就需要扩张原节点的previous_entry_length长度为5字节,那么从前到后的节点都需要更新位置和长度(或者遇到一个节点原先就是5字节的,那么后面就都不用动了)。
3.删除节点
通过标记,实现删除。

第八章-----对象系统
Redis使用数据结构来实现对象,然后由对象系统实现健值数据库。
包含:字符串对象string,列表对象list,哈希对象hash,集合对象set,有序集合对象zset。
每当我们在redis创建一个键值对时,就会创建两个对象,一个键对象(为sds对象),另一个对象类型将会根据存储类型自动选择,值对象可以是字符串对象,列表对象,哈希对象,集合对象,有序集合对象。
type为string,它的编码实现有:int,embstr(sds),raw(sds)
//embstr存储短字符,是raw的优化,将数据存在一起。raw是长字符,int是整数。
type为list,它的编码实现有:ziplist,linkedlist,listpack
//ziplist保存短的少的元素,linked存储长的
type为hash,它的编码实现有:ziplist,ht(字典)
//ziplist小,少,ht多长
type为set,它的编码实现有:ht和intset
//如果是整数,字典元素的值的编码是intset整数集合,如果是字符或者较长,那么就是ht
type为zset,它的编码实现有:ziplist,skiplist
(1)type为string下,int,embstr,raw的转换
embstr只读,修改就变raw(如果是set就会创建一个新的对象)。
int修改带有字母后变为raw
(2)type为list下,ziplist和linkedlist和plistpack进行转换
当存在元素长度大于64字节,元素数量大于512个,变为linkedlist
(3)type为hash时候,ziplist和字典(hashtable)
当任意键和值字符值和长度大于64,或者元素数量大于512个,需要使用hashtable。
(4)在ziplist如何存储键值对
zlbytes----zltail----zllen----“name”-----“Tom”-----“age”------- 25----zlend
(5)type为zset,编码可以是ziplist或者skiplist
相连两个元素共同表示一个键值对,第一个为键,第二个为值。
按照值从小到大排序
(6)对象的引用计数refcount
创建一个对象,时候refcount=1
当refcount=0时候,对象占用的内存会被删除
当对象被另一个个数据库的键值对的对象指向时,refcount+1
redis会在启动时候创建1000个整数对象,用于对象共享。
不共享字符串对象,因为对比比较花时间。只用于整数。
(7)redisObject对象
struct redisObject{
type类型
encoding实现的数据结构
ptr指向数据结构
refcount引用计数
lru//最后一次被访问的时间
}

第二部分:单机数据库
redis有服务器和客户端,我们使用redis-cli启动就是客户端。
struct redisServer{
redisDb *db;//一个数组,存储着服务器中所以的数据库
int dbnum;//服务器中数据库的数量,初始值为16,默认创建16个数据库
};
默认使用0号数据库,可以使用select切换.
struct redisDb{
dict *dict;
}在数据库中有一个字典,保存数据库所以的键值对,我们称这个字典为键空间。
字典的键一定是字符串对象。
1.读写键空间时的维护操作
(1)更新服务器的键空间命中次数。
(2)读取一个键后,会更新键的LRU(最后一次使用时间)。
(3)watch
(4)键过期删除
(5)持久化
2.过期时间如何实现:每个数据库都有过期字典
过期时间是一个long值,是一个unix时间戳
struct redisDb{
dict * expires;//存储着过期时间的字典
}
字典,键是字符串,值是过期时间。
3.过期键删除策略
redis使用惰性删除和定期删除的结合。
惰性删除:在每一次读写数据库都会先检查这个键是否过期,如果过期就删除。
定期删除:(1)在每一次取出都会捎带一些键检查(2)会顺序检查
4.RDB文件对过期时间的处理
保存:在执行save或者bgsave命令创建一个新RDB文件时,不会保存过期的键。
载入:服务器以主服务器运行,会检查键的过期时间,如果过期不会载入。如果是从服务器,过期也会载入,但是主从同步后就没了。
5.AOF文件对于过期时间的处理
AOF文件的写入:当键过期后,会在AOF中写入一条delet命令。
AOF的重写:会忽略过期键。
6.分布式的过期时间的实现
主服务器过期后删除,会向从服务器发送delet命令。
在没有主服务器发送的delet命令,从服务器不会处理过期键。
7.数据库通知:获取键的操作,以及命令的执行情况
(1)键空间通知:查看这个0号数据库和宗的键message执行过什么操作:subscribe__keyspace@0__:message
(2)键事件通知:查看0号数据库中del执行情况:subscribe keyevent@0:del

第十章。RDB持久
Redis是一个内存数据库,如果断电就会丢失,通过存储在磁盘上一个二进制文件RDB文件实现持久化。
有两个命令实现持久化:save(阻塞Redis服务进程实现持久化)和bgsave(派生子进程实现持久化)
Redis在启动时候自动执行载入RDB文件。
因为AOF文件的更新频率高于RDB,所以启动时候先检测AOF是否启动,如果AOF启动,则会载入AOF文件,而不用RDB。只有AOF持久化功能关闭才会使用RDB。
当服务器save时候会阻塞,不执行命令。
1.自动间隔性保存
struct redisServer{
saveparams*;
}
Redis允许用户设置save选项,让服务器每隔一段时间自动执行一次bgsave命令。
eg. save 900 1
如果在900秒内执行了至少一次修改,那么就保存。
save设置的默认项:
save 900 1
save 300 10
save 60 1000
2.
struct redisServer{
long dirty;
time_t lastsave;
}
dirty表示在上一次成功执行save或者bgsave命令之后,服务器对数据库进行了多少次修改。
lastsave是一个UNIX时间戳,记录了服务器上一次执行save或者bgsave的时间。
3.周期性检查save条件是否满足
每隔100毫秒就会检查一次是否满足saveparams的条件
4.RDB文件结构
大写是常量,小写是变量和数据,通过固定长度的字符串和常量实现字段的划分
REDIS–db_version—SELECTDB—0–pairs—SELECTED—3—pairs—EOF----check_sum
pairs存什么就是什么

第11章----AOF持久化
AOF(Append Only File)通过保存Redis服务器执行的写命令来记录数据库的。
AOF的实现可以分为:命令追加(append)到缓存区,文件写入,文件同步(sync)。
redisServer{
sds aof_buf;追加到这个缓存区
};
1.AOF文件的写入和同步
Redis服务器的进程就是一个事件循环,在这个循环中文件事件负责接受客户端的命令请求,以及向客户端发送回复,而时间事件负责定时执行的任务(比如RDB),然后执行AOF写入。
loop:
while True:
processFileEvents();
processTimeEvents();
flushAppendOnlyFile();//根据配置是否需要写入AOF文件
2.写入AOF文件的策略:每次循环都会将缓存中写入AOF文件,但是同步依据策略来决定
(1)always:写入并且同步到AOF
(2)everysec:写入AOF,并且每过1秒,就进行同步
(3)no:将缓存中内容写入AOF,并且不同步
3.AOF重写
AOF重写文件和原AOF文件无关,是通过从数据库中读取数据,忽略过期键值对,然后生成AOF命令。
是通过创建子进程来进行重写,服务器还可以处理命令,但是这也会导致数据不一致。
Redis服务器设置了一个AOF重写缓存区,AOF执行后会将AOF重写缓存中的命令添加到AOF文件中。
4.Redis是单线程的,但为了高效持久化与复制,通常会创建子进程:
(1)RDB持久化
(2)AOF持久化
(3)主从复制

第十二章 事件
Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:
(1)文件事件:Redis客户端(或者其他服务器)通过套接字和Redis服务器通信的抽象。
(2)时间事件:循环事件(或者在指定时间)执行的操作的抽象。
1.网络事件处理器(文件事件处理器)
文件事件处理器使用IO复用程序来监听多个信道,如果有事件,就派送到相应的事件处理器,执行完毕后,IO复用程序会切换到其他套接字。
当客户端准备好执行连接,读取,写入,关闭等操作时,对应的文件事件就会产生,这时服务器的处理器就会调用相应的处理器来进行处理。
2.文件事件处理器的构成:文件事件就是对套接字操作的抽象
套接字s1,s2,s3--------------IO多路复用程序---------文件事件分配器--------------事件处理器
事件处理器:命令请求处理器,命令回复处理器,连接应答处理器
3.事件处理器的类型
服务器相对于套接字:writable事件(客户端读时候,服务器写时候),readable事件(从客户端写时候,服务器读时候)
io复用程序同时监听两种事务,同时发生时候,会先执行readable事件
4.连接应答处理器----服务器对于套接字的连接监听,实现与客户端的连接
5.命令请求处理器-----读套接字传入的命令
6.命令回复处理器----服务器通过套接字给用户回复
7.一次完整的事件处理过程:
客户端------》连接请求--------》服务器 连接请求应答处理器
客户端------》命令--------------》服务器 命令请求处理器
客户端-------《回复-------------《服务器 命令回复处理器

二.事件事件
两类:定时事件,周期性事件
区别在于事件的返回值,如果为0是定时事件,如果为30,则会重新启动事件,并且将启动事件设置为30毫秒之后。
这个版本Redis只有周期性事件,而没有定时任务。
1.时间事件的实现:时间事件处理器
服务器将所有的时间事件都放在一个链表中,当时间事件处理器执行时,会遍历整个链表,查找已到达的时间事件,并且调用相应的处理器。
2.时间事件的构成
time_event
id:3
when:12234932423(UNIX)
handle:handle_
3.代码实现:processTimeEvents()
for()遍历
if(到点了)执行处理器
4.时间事件应用实例:serverCron函数
redis服务器需要定期对自身资源和状态进行检查:serverCron函数
心跳检测,服务器同步,AOF或RDB,清理过期键,更新服务器统计状态,关闭失效服务器连接
默认,serverCron每100ms运行一次,可以修改。
5.事件的调度:服务器如何调度文件事件和时间事件
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,开始工作

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值