Redis读书笔记 - 原理篇

高性能原理

线程IO模型

redis保证服务高效的原因是因为它是单线程程序,因此在对内存数据处理时候不需要锁以及线程间来回切换的浪费。

为了处理客户端的并发连接,redis使用基于事件驱动的io多路复用来管理多个socket。
使用select系统调用处理要响应的事件(read,write,accept),循环调用select,并附带超时时间timeout阻塞在这里。

  • 如果有事件来,则处理事件
  • 如果没有事件来,则阻塞到超时间
    然后在每一次循环中都会去查询是否有定时任务要处理,如果有则处理没有则进入下一次循环。

这里也解释一下上面的加粗文字,对于定时任务,使用时间戳为key的小顶堆,这样的话可以根据当前时间戳判断是否有需要处理的任务,即使没有也可以知道距离下一次需要处理定时事件的间隔,则可以用此间隔作为select系统调用的timeout。
而对于redis来说,即使没有人为制定任何即使任务,内部也是有定时任务一直在循环的(渐进rehash就可以在长时间没有请求的情况下在此处偷cpu)。

通信协议

redis使用了非常简单的序列化协议RESP,只划分了五种形式,单行字符串,多行字符串,整数,错误,数组。其中数组可以进行嵌套,因此可以序列化一些略微复杂的结构。虽然序列化后会有很多冗余换行符,但是它十分简单易理解易实现。具体的规则可以看这个。

持久化

对于redis的持久化,算是面试中的常见考点了,这里还有不少坑要填

  • rdb持久化
    因为redis是所有数据都运行在内存的程序,因此在断电的时候会丢失所有数据,这也是使用内存的高效率的弊端。容易想到的办法就是定期将数据保存到磁盘。但是涉及到磁盘io是非常慢的操作,尤其还不能阻塞单线程的redis主程序,因此这里使用的子进程进行保存的巧妙方式。
    • fork写数据
      当redis主进程调用fork产生一个新的子进程时,子进程的内存空间是要完全复制父进程的,因此把子进程的内存中的数据保存进磁盘即可。而且因为内核对与fork时的内存复制的处理方式是“copy-on-write”(COW写时复制)。
      意思是在fork复制内存的时候并不会真正的复制内存,而是将父进程的内存块标记为“只读”,并且给两个进程所共享。当要进行写操作时,将要写的内存快复制一份出来,这样父子进程的这一块内存就分离开了,然后再进行写操作。
      这样的话由子进程进行数据的保存,保存的数据即为fork那一刻的所有数据的快照(snapshot)。

因为保存全量数据的开销很大,所以不能很频繁的保存,如果一小时保存一次,那么在出现故障的时候最多会丢失一小时内的数据,所以看起来并不是那么可靠。

  • aof持久化
    另一种的持久化方式是按顺序通过记录所有的操作指令进磁盘,然后再重启的时候重新走一遍所有的操作。而记录指令进磁盘也不是每一次都会走磁盘io,而是记录在内核空间为aof文件描述符分配的一块内存上,然后定时写入磁盘。
  • fsync写数据
    因为指令的数据量不大,则可以“频繁”的调用并将其写入磁盘,相比较快照的全量,增量备份会提供更轻便而且断电损失更小的方式。而这里写入磁盘时使用的fsync系统调用,这是一个阻塞的系统调用,要数据写入了磁盘才会返回。因此,这里的频繁加了引号,如果每秒调用很多次则会对redis主线程的性能影响很大,因此需要一个平衡点一般1s一次左右就可以,这样的话即使断电也只会丢失1s内的操作。
    PS:考虑到fsync是很简单一个系统调用,cpu阻塞在等待磁盘io,而磁盘io应该是由DMA实现,而这一步仅仅是为了确保将这一秒的数据完全存入了磁盘。这样的话如果采用蕾丝tcp的滑动窗口的方式来确保写入磁盘,会减少cpu阻塞的消耗会更好吧。(个人见解,也希望有大神解答)
  • aof重写
    这种保存方式还有一个问题就是随着时间的增长aof文件会越来越大,因此需要对整个aof文件进行瘦身。这里也是使用fork一个子进程的方式,将整个内存数据转化为操作指令生成新的aof文件,然后全部生成后,再将fork后主进程新增的操作指令追加到新aof文件,这样瘦身就完成了。

aof持久化的方式看似更加可用,但是在重启的时候,要走一遍整个aof的操作耗费时间很长。

  • 两种方式的混用 4.0
    在4.0中出现了两种方式的混用,在进行rdb备份后将后续新增的操作指令以aof格式接在rdb后面,这样的话aof的长度也不会过长。

管道

管道对于性能的提升是在网络层进行的。可以理解为“多次操作打包成一次网络交互”,从而减少了多次网络交互的耗时。这里看到过一篇讲解,有三个图非常明显,这里就不搬运了。

事务

redis已经是一个成熟的数据库了,应该有属于自己的事务。但是这里的事物只有原子性而没有同一性,因为redis不支持回滚操作。
在事务这里新增的命令有4个

  • multi 表示开始记录事务内的操作。
  • exec 提交事务并开始处理
  • discard 不提交当前事务,用于multi后exec前笔误或者类似情况
  • watch 表示开始记录事务那的操作,并盯住一个或多个变量。在exec执行前,检查变量,若被动过则不执行。用于实现较为复杂的事务逻辑,作乐观锁。
    事务常跟管道一起使用,在python里面就是。而redis对于事物的实现也只是在执行时按顺序执行这些指令,因为redis单线程所以原子性不会被别的地方干扰到。而这些指令的成功与失败的结果也是按顺序返回,并不会因为某一条失败就退出事务。

你可能感兴趣的:(Redis读书笔记 - 原理篇)