学习《Redis设计与实现》Chapter2
Redis服务器
redis服务器本质是一个事件驱动程序,处理文件事件和时间事件两种事件。从事件处理的角度看,redis服务器的运行流程可以理解为在一个while循环中,等到文件事件产生后先处理已产生的文件事件,然后处理已达到的时间事件,事件的处理都是同步、有序且原子的执行。由于时间事件的处理依赖于文件事件的产生,所以时间事件通常会比设定的到达时间晚一些执行。而且为了尽可能的减少服务器阻塞的时间,降低事件饥饿的可能性,如果文件事件的写操作要写入的字节数大于一个预设值,会先跳出循环将余下的数据留到下次再写;如果一个时间事件非常耗时,会放到子线程或者子进程中执行。
伪代码表示
while(true) {if (has file event) {handle file event;handle time events;}
}
redis被看作是单线程的原因就在于这段伪代码的核心逻辑,但是在一些地方仍是多线程。
3.0以前,主进程会fork子进程来执行RDB持久化和AOF重写,同时有一个两核线程池来执行异步关闭文件和异步刷aof_buf的内容到文件中。
4.0变成了三核线程池,增加了异步删除过期键的处理。
6.0在网络模型中引入了多线程。客户端发送命令不会立马读取解析,而是先加到一个队列中,由主线程去分配IO线程读取客户端命令。同时执行完的结果也不会立马写回响应,也是先加到队列中,再去计算开销来判断是启动子线程来异步写回还是用休眠的IO线程写回。
所以只要这段核心逻辑不变,redis引入的并行或并发操作都不需要担心并发安全问题。
文件事件
基于Reactor模式开发了自己的文件事件处理器。虽然文件事件处理器是单线程的,但是引入了IO多路复用程序来监听多个文件描述符,既实现了高性能的网络通信模型,也很好的对接了redis中其他单线程运行的模块。
文件事件处理的构成
· 套接字:socket
· IO多路复用程序:单线程。通过包装了select、epoll、evport、kqueue这些IO多路复用函数库实现的,程序会在编译时自动选择系统中性能最高的函数库,基本上就是取决于OS的类型。将所有产生事件的socket放在一个队列里,然后有序、逐个的向文件事件派发器传送套接字,待文件事件派发器处理完一个后才会发下一个。
· 文件事件分派器:单线程。接收IO多路复用程序传来的socket,并根据socket产生的事件类型,将事件分发给相应的事件处理器。
· 事件处理器:封装的函数,对应了服务器的行为。根据socket事件类型,分为连接应答处理器、命令请求处理器、命令回复处理器。
socket 连接应答处理器
socket ===> IO多路复用模型 ====> 文件事件派发器 ===select==> 命令请求处理器
socket 命令回复处理器
一次完整的客户端与服务器连接事件示例:
1.一个客户端向服务器发起连接,监听套接字产生AE_READABLE事件,触发连接应答处理器,处理完后创建客户端套接字,以及客户端状态,并将客户端套接字的AE_READABLE事件与命令请求处理器进行关联,使得客户端可以向主服务器发送命令请求。
2.之后,如果客户端向主服务器发送一个命令请求,那么客户端套接字将产生AE_READABLE事件,引发命令请求处理器读取命令内容并传给相关程序去执行。
3.执行命令后,为了将产生的命令回复传给客户端,服务器会将客户端套接字的AE_WRITEABLE事件与命令回复处理器关联,当客户端尝试读取命令回复的时候,客户端套接字将产生AE_WRITEABLE事件触发命令回复处理器。当命令回复处理器将命令回复全部写入到套接字后在解除关联。
时间事件
时间事件分为定时事件和周期性事件。
实现
服务器将所有的时间事件都放在一个无序链表中,这里说的无序,指的是在链表中的时间事件并不是按执行时间点来排序的。每当时间事件执行器运行时,它就遍历这个链表,找到所有可执行的时间事件,并调用相应的事件处理器。由于正常模式下服务器只使用serverCron一个时间事件,而且在benchmark模式下只使用两个时间事件,所以即使是一个无序链表,也不会影响执行的性能。
ServerCron函数
· 更新服务器的各类统计信息
· 清理数据库中的过期键值对
· 关闭和清理连接失效的客户端
· 尝试进行AOF和RDB持久化操作
· 如果服务器是主服务器,定期对从服务器同步
· 如果处于集群模式,对集群进行定期同步和连接测试
Redis持久化
因为redis是内存数据库,所以一旦服务器进程退出,在内存中的数据就全部都会丢失。所以为了解决这个问题,Redis提供了RDB和AOF的持久化功能,用于将内存中的数据库状态保存到磁盘里。
1.RDB
RDB持久化可以手动执行,也可以根据服务器配置周期性执行。该功能将某个时间点上的数据库状态保存到一个rdb文件里,当redis重启时,只要rdb文件还在,就可以用它来还原数据库状态,以此来保证数据的持久化。
rdb文件的创建
有两个命令用于生成rdb文件:SAVE和BGSAVE。SAVE命令会阻塞服务器进程,直到rdb文件创建完毕;BGSAVE命令会从主进程中fork出一个子进程,然后由子进程创建rdb文件,不影响服务器继续处理命令请求。
rdb文件的载入
rdb文件的载入是在服务器启动时自动执行的,所以没有专门的命令,只要服务器启动时检测到rdb文件存在,就会自动载入,载入期间服务器进程会处于阻塞状态。但是如果开启了AOF,那会优先使用AOF文件来还原。
自动间隔性保存
执行 save m n命令,如果服务器在m秒内进行了n次以上修改,就会触发BGSAVE命令。
过期键的处理
生成RDB文件时,过期键会被忽略,不保存到RDB文件中。
载入RDB文件时,对于一个已过期的键,如果服务器是以主服务器模式运行,则会忽略;以服务器模式运行,则会一并载入,但因为主从服务器同步时会清空从服务器的数据库,所以一般来讲也不会造成影响。
优点
· 保存的是某个时间点的数据集,且RDB文件是压缩的二进制文件,占用空间较小。
· 最大化redis的性能,主进程只需要fork出一个子进程。
· 数据量较大时,RDB文件的恢复速度快于AOF。
缺点
· 服务器故障时容易造成数据的丢失。因为RDB文件的生成是个重操作,所以频率注定不会很高。
· fork子进程采用的是copy-on-write方式。刚fork出子进程时,子进程和主进程共享内存,但随着主进程的不断写入,主进程需要将修改的页面拷贝一份出来再修改。所有页面都修改了的极端情况下,内存占用达到了2倍。
· RDB文件保存也是依赖fork的子进程执行的。如果数据量比较大,子进程可能会非常耗时,占用较多的内存和CPU,造成redis服务一秒内的暂停。
2.AOF
AOF持久化是通过记录Redis服务器的写命令来记录数据库状态的,可以类比MySQL的redo log来理解,不同的是redo log记录的只是单纯的某个物理位置的物理偏移量,而AOF记录的是整个命令语句。
AOF持久化的实现
AOF持久化的实现可以分为命令追加、文件写入、文件同步三个步骤。
· 命令追加:当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议的格式将命令追加到服务器的aof_buf缓冲区内。
· 文件写入与同步:写入和同步可以分别理解为write和flush,write是写入操作系统的缓冲区中,flush是将操作系统的缓冲区中的内容刷到磁盘上。因为redis在处理文件事件时很可能发生写命令,所以每次事件循环结束前都会根据配置决定是否需要将aof_buf中的内容写入和保存到AOF文件中。配置appendfsync的值来调整行为:always(总是会对aof_buf缓冲区执行write+flush)、everysec(总是会对aof_buf缓冲区执行write,但只有距离上次flush超过一秒才会再次flush到AOF文件中,而且flush操作是由一个子线程来执行的)、no(只执行write,什么时候执行flush交给操作系统决定)。默认是everysec,具体用哪个,取决于对业务的判断,以做效率和安全性的取舍,这一点和redo log是一样的。
AOF的载入和数据还原
因为AOF文件中包含了重建数据库状态所需的所有写命令,所以只要读取AOF文件并全量执行一次就可以还原数据库关闭前的状态。还原步骤也很简单:
· 先创建一个不带网络连接的伪客户端,因为命令只能在客户端中执行,而要执行的写命令来源于AOF文件而不是网络请求,所以就无所谓了。
· 从AOF文件中逐条读取解析交给伪客户端执行
· 循环执行到AOF文件中的命令读完即可
AOF重写
既然AOF文件是记录写命令的,如果服务器一直运行下去,不做特殊处理的话AOF文件会越来越大,很可能对服务器甚至宿主计算机造成影响,并且AOF文件越大还原时间就越长。重写功能就是为了解决这个问题的。通过该功能,redis服务器可以创建一新一旧两个AOF文件。新的AOF文件是通过直接读取数据库状态来重写的,比如在旧的AOF文件中某个集合记录了5条单个的写入命令,那么在新的AOF文件中,其实就可以合并成一条批量写入命令,这样就能减少该集合的所需命令数量。但是为了避免还原时写入缓冲区溢出,单条批量命令最大只会记录64个元素,所以对于允许带有多个元素的类型的键,可能是多条批量命令记录在AOF文件内。
原理是这样,随之而来的两个问题肯定要想办法解决。第一个是重写命令会带来大量的写入操作,而redis作为单线程进程,为了不阻塞服务器的主进程,就要fork出一个子进程来执行aof_rewrite命令。第二个是重写过程中主进程依旧在接收处理写命令,而AOF重写是基于数据库的一个副本,这就会导致数据库最新状态和AOF文件保存的数据库状态不一致,所以设置了一个AOF重写缓冲区来记录重写执行过程中传入的写命令,在重写执行完毕后将缓冲区内的写命令再次同步到AOF新文件中,最后覆盖旧的AOF文件。整个过程中,只有重写执行完成后,将缓冲区内的命令写入AOF的操作会对主进程有一定的阻塞妨碍,如果重写过程中执行的写命令非常多,可能会导致主进程阻塞较久,这是不能接受的。
redis做了两点对于重写缓冲区的优化,重写开始时会创建父子进程通信的管道和一个文件事件。
· 重写过程中,该文件事件会通过该管道将重写缓冲区的内容发到子进程。
· 重写结束后,子进程会通过该管道尽量从父进程中读取更多的数据,每次等待1ms。最多执行1000次,也就是1秒,但如果连续20次没读取到数据,则结束这个过程。
过期键的处理
AOF文件写入时,如果过期键没有被删除,AOF文件也不会因为这个受到什么影响。过期键被删除后,会在AOF文件中追加一条Del命令来显式地记录该键已被删除。
AOF文件重写时,忽略过期键。
复制模式下,过期键的删除动作由主服务器控制,并显示的向所有从服务器发送一个Del命令来通知删除。在主服务器发出Del命令前,如果通过从服务器访问该key,即使已经过期了,也会当作没过期处理。
优点
· 比RDB更可靠,根据不同的fsync策略做持久化,宕机可能损失的数据量都比RDB小的多。
· 纯追加的日志文件,内容易懂,可以手动修改。
缺点
· 对于相同的数据集,AOF文件一般都比RDB文件大
· everysec、always的fsync策略仍是通过性能的牺牲来保证数据的可靠性。
· AOF文件中储存的阻塞命令可能会导致数据集无法正常恢复。
3.混合持久化
对已有的持久化方式的优化,只发生于AOF重写过程。本质就是执行AOF重写时,fork出的子进程先将当前全量数据以RDB当时写入AOF文件,然后将aof_buf中的增量命令追加到文件中,最后将旧的AOF文件替换掉。通过aof-use-rdb-preamble配置。
载入
使用AOF文件重建数据库状态时,会通过文件开头是否为REDIS来判断是否为混合持久化。是的话先加载记录的RDB内容,再增量的执行AOF内容记录的命令。
优点
结合了RDB和AOF的优点,更快的重写和恢复。
缺点
失去了AOF的可读性。
持久化的选择
如果追求数据的安全性,应该同时开启RDB和AOF,同时也可以启用混合持久化。
如果数据允许数分钟内的丢失,开启RDB即可。
如果不在意数据的丢失,可以关闭持久化功能。
Redis的过期键删除策略
在redis中,是通过一个额外的字典来存储键和过期时间的对应关系的,称这个字典为过期字典。通过过期字典就可以检查到键的过期设定,以此来判断键是否过期了。
redis的过期键删除策略,就是通过惰性删除策略+定期删除策略实现的。
常见的三种过期删除策略:
1.定时删除
在设置键过期时间时,生成一个定时器,让定时器在过期时间来临时立即执行键的删除。
优点:
可以保证过期键的即使清理,对内存十分友好。
缺点:
· 对CPU的占用,尤其是同一时间内过期键较多时,可能会影响到服务区的吞吐量和响应时间
· 频繁创建定时器,而且定时器在redis中是用时间事件实现的,本质就是一个无序链表,查找一个事件的时间复杂度位O(n),并不能高效的大量处理时间事件。
2.惰性删除
放任键过期不管,只有在取出键时才对键进行过期检查,这个时候才会去做删除键的操作。
优点:
不会在任何和当前无关的键上消耗CPU资源,对CPU十分友好。
缺点:
不删除意味着内存不释放,很可能导致大量内存消耗在无用的垃圾数据中,几乎等同于内存泄漏。
3.定期删除
每隔一段时间执行一次删除过期键的操作,并限制操作执行的时长和频率来减少对CPU的影响。
这种做法其实就是定时删除和惰性删除的折中策略,优点和缺点完全取决于执行时长和执行间隔的设定。
Redis内存淘汰策略
当redis的内存不足时,为了保证命中率,就会选择数据淘汰策略。在配置文件或者命令行里设置memory_policy的值。
八种策略
1.noeviction:默认策略。不淘汰任何数据,写入报错。
2.allkeys-lru:在所有的 key 中,使用 LRU算法淘汰部分 key。
3.allkeys-lfu:在所有的 key 中,使用 LFU算法淘汰部分 key。
4.allkeys-random:在所有的 key 中,随机淘汰部分 key。
5.volatile-lru:在设置了过期时间的 key 中,使用 LRU 算法淘汰部分 key。
6.volatile-lfu:在设置了过期时间的 key 中,使用 LFU 算法淘汰部分 key。
7.volatile-random:在设置了过期时间的 key 中,随机淘汰部分 key。
8.volatile-ttl:在设置了过期时间的 key 中,挑选 TTL短的 key 淘汰。
可以看出其实就是key的选择和淘汰规则的选择的排列组合。volatile代表过期键,allkeys代表所有键。
Redis客户端
redis服务器是典型的一对多服务器程序,可以与多个客户端建立网络连接。服务器通过管理一个clients链表来保存所有与服务器连接的客户端的状态结构,对客户端的操作都是通过遍历该链表来完成。
客户端可以分为两大类:伪客户端和客户端。简单理解一下就是,通过用户层面去连接或向redis服务器发送命令的,就是客户端;而Redis自身的一些能够发送命令的功能,就是通过构建一个伪客户端来实现的。
学习《Redis设计与实现》Chapter2相关推荐
- 《Redis设计与实现》学习笔记
Redis 本文会有一些Redis和Java容器对象的对比,一个是分布式数据库,一个是JVM内部数据容器,应用场景不同,仅仅是为了加深对Redis"数据库"的认识,加深对Redis ...
- 结合redis设计与实现的redis源码学习-2-SDS(简单动态字符串)
上一次我们学习了redis的内存分配方式,今天我们来学习redis最基本的数据结构SDS,在redis的数据库里,包含字符产值的简直对在底层都是由SDS实现的. SDS的基本数据结构是sdshdr结构 ...
- redis设计与实现学习笔记1
文章目录 1.对象 1.1 类型 1.2 内存回收 1.3 对象共享 1.4 对象空转时长 2.单机数据库 2.1 RDB 2.2 AOF 2.3 事件 2.4客户端 2.5服务器 3.常用命令 参考 ...
- 深入学习Redis(3):主从复制
原味链接:https://www.cnblogs.com/kismetv/p/9236731.html 前言 在前面的两篇文章中,分别介绍了Redis的内存模型和Redis的持久化. 在Redis的持 ...
- Redis数据库教程——系统详解学习Redis全过程
Redis数据库教程--系统详解学习Redis全过程 Redis快速入门:Key-Value存储系统简介 Key-Value存储系统: Key-Value Store是当下比较流行的话题,尤其 ...
- 深入学习Redis(1):Redis内存模型
前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分. 我们使用Redis时,会接触Redis的5种对象类型(字符串 ...
- 【Redis笔记】一起学习Redis | 如何利用Redis实现一个分布式锁?
一起学习Redis | 如何利用Redis实现一个分布式锁? 前提知识 什么是分布式锁? 为什么需要分布式锁? 分布式锁的5要素和三种实现方式 实现分布式锁 思考思考 基础方案 改进方案 保证setn ...
- Redis 设计与实现 读书笔记(菜鸟版)
Redis 设计与实现 读书笔记(简略版) 写在前面 第一章(内部数据结构) SDS List Dictionary Rehash Rehash 与 COW 渐进式Rehash 字典收缩 Skipli ...
- 学习Redis的基本命令
文章目录 学习Redis的基本命令 1.和key相关的 2.和String相关的 3.和Hash相关的 4.和List相关的 5.和Set相关的 6.和Sorted Set相关的 学习Redis的基本 ...
- 【程序厨】学习 Redis ,可以看看这个
哈喽,大家好,我是厨子. 昨天收到了一位学弟的私信,想让我写一下 Redis 的学习路线,因为他之前从来没有接触过 Redis ,甚至都没有听过.但是 Redis 是秋招面试重点,想问一下应该如何学习 ...
最新文章
- 程序员四大焦虑瞬间:拿什么拯救你,我日益后退的发际线?
- 别人的Linux私房菜(17)进程管理与SELinux初探
- BCP timeout prevention - 每秒刷新一次 Fiori
- linux可疑程序,linux可疑程序追踪
- 驾驶证损毁、驾驶人信息变更的如何换证
- SpringBoot项目修改html后不即时编译
- linux 管道文件上机总结,[转载]LINUX 管道 fifo 等总结
- oracle exp不生成dumpfile,预估出实际导出文件的大小。
- Linux IO控制命令生成
- 46.Android 自己定义Dialog
- 网站项目常用JS,CSS等控件插件
- dns服务器迁移方法简单说明
- win32com模块
- 用letax写毕业论文-- 原创性声明、承诺书、授权书
- 中国科学院大学计算机研究所2019,中科院计算所2019年夏令营名单
- 湿气重怎么办?湿气有哪些危害?祛湿建议首选云植祛湿颗粒
- 跳马周游c++_C++——跳马问题(广搜)
- 用数组+链表实现哈希表
- OpenGL进阶(十九) - 多光源
- 我读Saliency Filters cvpr 2012
热门文章
- 区分浏览器,判断浏览器版本
- TS报错Error: xxx doesn‘t exist on type ‘xxx’
- DTX-1800精度恢复,调教之路?
- 鸿蒙测试机型微博,华为多款机型开启鸿蒙尝鲜:微博已适配小尾巴
- 手机如何扫码连接wifi
- Java手机游戏开发简明教程 (SunJava开发者认证程序员 郎锐)
- 全球及中国标签印刷行业十四五发展形势与需求规模预测报告2022版
- Java面试:基础概念
- Scratch 3.0 版本比较
- Scratch3.0 二次开发(3)修改菜单栏