Rdis持久化机制
RDB快照
RDB是将某一时刻的内存中所有数据以快照的形式写入磁盘(生成dump.rdb文件),恢复时直接将快照文件读回内存。 相比起AOF文件,RDB文件较小,恢复速度快,但是生成快照的频率不好控制,容易丢失较多数据。
RDB的触发机制
- 自动触发:按照时间和数据修改次数双重限制触发执行BGSAVE,防止时间间隔太大丢失太多数据或时间间隔太小影响redis运行。
- 手动触发:使用SAVE(阻塞)或BGSAVE(非阻塞)命令手动触发。
bgsave与写时复制
- Redis执行BGSAVE时,会调用fork()创建一个子进程,fork()复制父进程的地址空间(包括内存数据)给子进程,此时父子进程共享相同的内存数据。
- 如果其中一个进程尝试修改数据,操作系统会复制该内存页,确保修改不会影响另一方。
- 写时复制避免了在fork()时立即复制大量内存数据,减少了内存和CPU的开销。
AOF日志
AOF以追加方式在日志(appendonly.aof)中记录每个写操作,恢复时重新执行命令。 相比RDB快照,AOF能更好地保证数据不丢失,但是AOF文件远大于RDB文件,且恢复速度慢于RDB。 如果同时开启了RDB和AOF,redis在恢复数据时会先使用AOF,因为数据较完整。
执行写命令与追加到AOF文件的顺序
- redis是先执行写命令再将数据写入AOF缓冲区,AOF缓冲区再刷盘到AOF文件中,这样只有正确执行的命令会被记录,不会阻塞当前写命令的执行,但是可能造成数据丢失。
AOF缓冲区有三种不同落盘策略
- Always:每个写命令执行完立刻写回。
- everysec:每秒写回(常用)。
- no:由操作系统控制。
AOF文件重写机制
- AOF文件会不断增大,Redis提供了重写机制来去除冗余命令,减小文件体积。
- 重写是将当前内存中的数据转化为写命令,保存到新的AOF文件中。
- 手动重写命令为bgrewriteaof,也可以设置自动重写规则。
- 重写同样由写时复制完成,并且Redis会将重写期间执行的新命令写入到AOF重写缓冲区,当子进程完成重写,主进程再将AOF重写缓冲区的数据追加到新的AOF文件中。
混合持久化
混合持久化发生在AOF重写时,子进程会先将与主进程共享的内存数据以RDB格式写入AOF文件,同时主进程执行的命令缓存在AOF重写缓冲区,以追加的方式写入AOF文件。此时AOF文件前半部分是RDB格式的数据,后半部分是AOF格式的数据,恢复时加载会变快且丢失数据变少,但是文件可读性变差。
Redis过期机制
redis的过期键删除策略是惰性删除+定期删除。
- 惰性删除:不主动删除数据,直到访问数据时才检查当前键值是否过期,若过期则返回null。可能会存储很多过期的键,对内存不友好。
- 定期删除:周期性地随机抽取一批key,若已过期会将其删除,若过期key占抽查总数的25%以上,就继续抽查(有时间上限)。
注意:在主从架构中,从库不会自动删除过期键,而是要等主库删除时发同步命令才会删除,这会导致客户端访问从库可以得到过期的键。
注意:RDB/AOF持久化时会对过期键进行检查,过期键不会保存到RDB/AOF文件中(恢复时也会检查)。
Redis缓存淘汰策略
当redis内存使用达到上限,此时需要内存淘汰策略,删除内容中不常用的数据。共有8种策略,即以下四种淘汰策略和两种执行方式的组合:
- 4种淘汰策略:
- 不删除。
- 随机删除。
- LRU,淘汰最长时间未被使用的页面。
- LFU,淘汰一定时期内被访问次数最少的页面。
- 2种执行方法:
- 对所有数据执行。
- 对设置了过期时间的key执行。
- 另外对于不删除策略,2种执行方式没有区别,故额外多出来的一种策略为“删除马上要过期的key”。
- 推荐使用对所有数据执行LRU算法的策略。
redis的LRU和常见的LRU实现有什么区别
常见的LRU使用hashmap和双向链表需要额外存放各种指针,牺牲比较大的存储空间,所以redis使用一种近似的做法,即随机选出若干个key,淘汰掉最久不用的。实现上,redis提供一个保存待淘汰元素的候选池,按照空闲时间排序,更新时redis从键空间随机采样key计算空闲时间,去更新候选池。
LRU存在什么问题
LRU存在缓存污染问题,例如一次读取了大量数据,但是这些数据只读取一次,那么这些数据会被缓存很长一段时间,所以引入LFU。LFU相比于LRU,实际就多记录了数据的被访问次数。
Redis线程模型
Redis采用单线程模型来处理客户端请求,同时结合多线程技术处理某些后台任务。 其中单线程是指接收客户端请求到执行请求再到返回数据给客户端这个过程是单线程的,由主线程实现。 多线程指Redis在做其他任务时可能会开启子线程,如持久化或非阻塞命令。
redis6之前的单线程模型
单线程可以避免线程切换,避免锁竞争,且即使单线程也可以依赖IO多路复用并发处理多客户端请求。Redis6之前的单线程模型如下图所示:
- 单线程模型基于事件驱动,首先使用IO多路复用同时监听多个Socket连接,将事件放到队列中交给文件事件分派器(单线程)。
- 文件事件分派器把不同的socket事件(读事件,写事件和连接事件)分派给不同事件处理器(连接事件处理器,写事件处理器,读事件处理器)。
redis6之后的多线程模型
随着Redis的网络IO瓶颈越来越明显,Redis6之后采用多线程来处理网络IO,提高并行度。redis6之后的多线程模型如下图所示:
- 主线程接收建立连接请求,并把Socket放到全局等待队列中,通过轮询方法把Socket连接分配给IO线程。
- 主线程等待IO线程读取Socket完毕,然后以单线程的方式执行命令,最后将结果写入缓冲区。
- 主线程等待IO线程把结果写回到Socket返回给客户端。