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相关推荐

  1. 《Redis设计与实现》学习笔记

    Redis 本文会有一些Redis和Java容器对象的对比,一个是分布式数据库,一个是JVM内部数据容器,应用场景不同,仅仅是为了加深对Redis"数据库"的认识,加深对Redis ...

  2. 结合redis设计与实现的redis源码学习-2-SDS(简单动态字符串)

    上一次我们学习了redis的内存分配方式,今天我们来学习redis最基本的数据结构SDS,在redis的数据库里,包含字符产值的简直对在底层都是由SDS实现的. SDS的基本数据结构是sdshdr结构 ...

  3. 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.常用命令 参考 ...

  4. 深入学习Redis(3):主从复制

    原味链接:https://www.cnblogs.com/kismetv/p/9236731.html 前言 在前面的两篇文章中,分别介绍了Redis的内存模型和Redis的持久化. 在Redis的持 ...

  5. Redis数据库教程——系统详解学习Redis全过程

    Redis数据库教程--系统详解学习Redis全过程 Redis快速入门:Key-Value存储系统简介 Key-Value存储系统:     Key-Value Store是当下比较流行的话题,尤其 ...

  6. 深入学习Redis(1):Redis内存模型

    前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分. 我们使用Redis时,会接触Redis的5种对象类型(字符串 ...

  7. 【Redis笔记】一起学习Redis | 如何利用Redis实现一个分布式锁?

    一起学习Redis | 如何利用Redis实现一个分布式锁? 前提知识 什么是分布式锁? 为什么需要分布式锁? 分布式锁的5要素和三种实现方式 实现分布式锁 思考思考 基础方案 改进方案 保证setn ...

  8. Redis 设计与实现 读书笔记(菜鸟版)

    Redis 设计与实现 读书笔记(简略版) 写在前面 第一章(内部数据结构) SDS List Dictionary Rehash Rehash 与 COW 渐进式Rehash 字典收缩 Skipli ...

  9. 学习Redis的基本命令

    文章目录 学习Redis的基本命令 1.和key相关的 2.和String相关的 3.和Hash相关的 4.和List相关的 5.和Set相关的 6.和Sorted Set相关的 学习Redis的基本 ...

  10. 【程序厨】学习 Redis ,可以看看这个

    哈喽,大家好,我是厨子. 昨天收到了一位学弟的私信,想让我写一下 Redis 的学习路线,因为他之前从来没有接触过 Redis ,甚至都没有听过.但是 Redis 是秋招面试重点,想问一下应该如何学习 ...

最新文章

  1. 程序员四大焦虑瞬间:拿什么拯救你,我日益后退的发际线?
  2. 别人的Linux私房菜(17)进程管理与SELinux初探
  3. BCP timeout prevention - 每秒刷新一次 Fiori
  4. linux可疑程序,linux可疑程序追踪
  5. 驾驶证损毁、驾驶人信息变更的如何换证
  6. SpringBoot项目修改html后不即时编译
  7. linux 管道文件上机总结,[转载]LINUX 管道 fifo 等总结
  8. oracle exp不生成dumpfile,预估出实际导出文件的大小。
  9. Linux IO控制命令生成
  10. 46.Android 自己定义Dialog
  11. 网站项目常用JS,CSS等控件插件
  12. dns服务器迁移方法简单说明
  13. win32com模块
  14. 用letax写毕业论文-- 原创性声明、承诺书、授权书
  15. 中国科学院大学计算机研究所2019,中科院计算所2019年夏令营名单
  16. 湿气重怎么办?湿气有哪些危害?祛湿建议首选云植祛湿颗粒
  17. 跳马周游c++_C++——跳马问题(广搜)
  18. 用数组+链表实现哈希表
  19. OpenGL进阶(十九) - 多光源
  20. 我读Saliency Filters cvpr 2012

热门文章

  1. 区分浏览器,判断浏览器版本
  2. TS报错Error: xxx doesn‘t exist on type ‘xxx’
  3. DTX-1800精度恢复,调教之路?
  4. 鸿蒙测试机型微博,华为多款机型开启鸿蒙尝鲜:微博已适配小尾巴
  5. 手机如何扫码连接wifi
  6. Java手机游戏开发简明教程 (SunJava开发者认证程序员 郎锐)
  7. 全球及中国标签印刷行业十四五发展形势与需求规模预测报告2022版
  8. Java面试:基础概念
  9. Scratch 3.0 版本比较
  10. Scratch3.0 二次开发(3)修改菜单栏