redis笔记(二)

Scroll Down

1、线程IO模型

1.1、非阻塞IO

当我们调用套接字的读写方法,默认它们是阻塞的,比如read方法要传递进去一个参数n,表示最多读取n个字节后再返回,如果一个字节都没有,线程就会卡在那里,直到新的数据到来或者连接关闭,read方法才可以返回,线程才能继续处理。write方法一般不会阻塞,除非内核为套接字分配的写缓冲区已经满了,write方法就会阻塞,直到缓存区中有空闲空间出来。
非阻塞IO在套接字对象上提供了一个选项Non_Blocking,当这个选项打开时,读写方法不会阻塞,而是能读多少读多少,能写多少写多少。能读多少取决于内核为套接字分配的读缓冲区内部的数据字节数,能写多少取决于内核为套接字分配的写缓冲区的空闲空间字节数。读方法和写方法都会通过返回值来告知程序实际读写了多少字节。

1.2、事件轮询(多路复用)

事件轮询API是用来解决线程要读数据,结果读了一部分就返回了,那么线程如何知道何时才应该继续读。当数据到来时,线程如何得到通知的问题。最简单的事件轮询API是select函数,它是操作系统提供给用户程序的API。输入是读写描述符列表read_fds&write_fds,输出是与之对应的可读可写事件。同时还提供了一个timeout参数,如果没有任何事件的到来,就可以立即返回。时间过了之后还是没有任何事件到来,也会立即返回。拿到事件后,线程就可以继续挨个处理相应的事件。处理完了继续过来轮询。于是线程就进入了一个死循环。这个死循环成为事件循环。

2、持久化

Redis的数据全部在内存里,如果突然宕机,数据就会全部消失,因此必须有一种机制来保证Redis的数据不会因为故障而丢失,这种机制就是Redis的持久化机制。
Redis的持久化机制有两种,第一种是快照,第二种是AOF日志。快照是一次全量备份,AOF日志是连续的增量备份。快照是内存数据的二进制序列化形式,在存储上非常紧凑,而AOF日志记录的是内存数据修改的指令记录文本。AOF日志在长期的运行过程中会变得无比庞大,数据库重启时需要加载AOF日志进行指令重放,这个时间就会无比漫长,所以需要定期进行AOF重写,给AOF日志进行瘦身。
redisinsist1.png

2.1、快照原理

Redis是单线程程序,这个线程要同时负责多个客户端套接字的并发读写操作和内存数据结构的逻辑读写。
在服务线上请求的同时,Redis还需要进行内存快照,内存快照要求Redis必须进行IO操作,可文件IO操作不能使用多路复用API。
这意味着单线程在服务线上请求的同时,还要进行文件的IO操作,而文件IO操作会严重拖累服务器性能。
还有个重要的问题,为了不阻塞线上的业务,Redis还要一边持久化,一边响应客户端的请求。持久化的同时,内存数据结构还在改变。
Redis使用操作系统的多进程COW机制来实现快照持久化。

2.2、fork(多进程)

Redis在持久化时会调用glibc的函数fork产生一个子进程,快照持久化完全交给子进程来处理,父进程继续处理客户端请求。子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段。
子进程做数据持久化,不会修改现有的内存数据结构,它只是对数据结构进行遍历读取,然后序列化写到磁盘中。但是父进程不一样,它必须持续服务客户端请求,然后对内存数据结构进行不间断的修改。
这个时候就会使用操作系统的COW机制来进行数据段页面的分离。数据段是由很多操作系统的页面组合而成,当父进程对其中一个页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对这个复制的页面进行修改。这时子进程相应的页面是没有变化的,还是进程产生时那一瞬间的数据。
随着父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长,但是也不会超过原有数据内存的2倍大小。另外,Redis实例里冷数据占的比例往往是比较高的,所以很少会出现所有的页面都被分离的情况,被分离的往往只有其中一部分页面。每个页面的大小只有4Kb,一个Redis实例里面一般都会有成千上万个页面。
子进程因为数据没有变化,它能看到的内存里的数据在进程产生的一瞬间就被凝固了,再也不会被改变,接下来子进程就可以遍历数据,进行序列化磁盘了。

AOF原理

AOF日志存储的是Redis服务器的顺序指令序列,AOF日志只记录对内存进行修改的指令记录。
假设AOF日志记录了自Redis实例创建以来所有的修改性指令序列,那么就可以通过对一个空的Redis实例顺序执行所有的指令也就是重放,来恢复Redis当前实例的内存数据结构的状态。
Redis会在收到客户端修改指令后,进行参数校验、逻辑处理、如果没问题,就立即将该指令文本存储到AOF日志中,也就是说,先执行指令才将日志存盘。
Redis在长期运行过程中,AOF的日志会越来越长。如果实例宕机重启,重放整个AOF日志会非常耗时,导致Redis长时间无法对外提供服务,所以需要对AOF日志瘦身。

AOF重写

Redis提供了bgrewriteaof指令用于对AOF日志进行瘦身,其原理就是开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中。序列化完毕后再将操作期间发生的增量AOF日志追加到这个新的AOF日志文件中,追加完毕后就立即替代旧的AOF日志文件了,瘦身工作就完成了。

fsync

AOF日志是以文件的形式存在的,当程序对AOF日志文件进行写操作时,实际上是将内容写到了内核为文件描述符分配的一个内存缓存中,然后内核会异步将脏数据刷回到磁盘的。
这就意味着如果机器宕机,AOF日志内容就可能还没有来得及完全刷到磁盘中,这个时候就会出现日志丢失。
Linux的glibc提供了fsync(int fd)函数可以将指定文件的内容强制从内核缓存刷到磁盘。只要Redis进程实时调用fsync函数就可以保证AOF日志不丢失。但是fsync是一个磁盘IO操作,很慢。所以在生产环境的服务器中,Redis通常是每隔1秒左右执行一次fsync操作,这个1s的周期是可以配置的。这是在数据安全性和性能之间做的一个折中,在保持高性能的同时,尽可能使数据少丢失。
Redis同样提供了另外两种策略,一个是永不调用fsync让操作系统来决定何时同步磁盘,这样很不安全,另一个是来一个指令就调用fsync一次结果导致会非常慢。