Redis运行机制与单线程
前言
当研究一个新组件之前我一般都会问几个问题,然后带着问题去研究。
当然,Redis is what,where to use,when to use,how to use这wwwh问题我默认你已经知道答案了。
我要问的问题是:
总听人说Redis是单线程的,是这么回事吗?为什么?
Redis内部都用到了哪些数据结构?存储这些数据结构的底层空间是怎么划分的?
Redis利用内存,它的垃圾回收机制是什么样的?
Redis支持事务和持久化吗?如果支持那么是怎么实现的?
Redis的主从,哨兵系统,集群如果有节点出了问题怎么处理?
别急,咱们一步一步来,本篇就先介绍一下Redis的单线程。
一、IO多路复用与Redis单线程
为什么要先抛出这个概念?因为这是海量客户端与redis进行命令交互的关口!
IO多路复用技术有很多种,包括select、epoll、kqueue、evport。redis底层会自动选择这四种中性能最高的技术作为与客户端IO交互的关口。
以select技术为例,系统底层维护了几个集合,这几个集合分别保存可读套接字、可写套接字、异常套接字等,内核会启动一个工作线程接收客户端的连接并把几种套接字分类放到对应的集合里。比如当前套接字为可读套接字,那么就把该套接字放到可读套接字集合。redis线程调用select函数的时候就会从上面的几个集合中获取套接字,获取到了就对套接字进行读写,获取不到可以阻塞等待也可以直接返回(一般是阻塞等待)。本质上是内核线程作为生产者往集合里生产套接字,redis用户线程作为消费者从集合里拿数据。
问题1、单线程体现在哪?
答:上面了解了redis线程从相关套接字集合中获取套接字,这个redis线程就是我们所谓的单线程。
问题2、这个单线程职责是啥?
答:redis单线程取到了相关套接字之后对其进行读写,这里以客户端发来set key value命令为例子,单线程读取到这个命令之后,对命令进行解析、查找命令表、调用命令对应的函数、写入数据库、返回写入结果给客户端。也就是说redis单线程除了负责读写套接字还负责执行命令的业务逻辑。除此之外redis单线程还负责调用垃圾回收函数进行垃圾回收。
问题3、单线程为啥还能这么快?
首先:多线程运行时需要上下文环境切换的,这些需要cpu的调度,线程多的时候浪费时间
然后:redis是纯内存操作,对内存的写入和读出都是非常迅速的,这也变相要求了我们在选择使用redis的时候尽量不要向redis里放大数据
然后:redis底层使用了一些特殊的数据结构如跳跃表等,通过这些数据结构的优化可以让对象更快的存入内存
最后:就是我们这里的IO多路复用技术了,底层封装了四种,选性能最优的执行
二、Redis数据库结构
redis底层实现实际上也是大量的对象、函数。只不过是用C而不是用Java。
redis的数据库就是一个对象redisDb,redis服务器对象redisServer内部会持有一个redisDb数组,初始的时候数组大小为16,即redis数据库最开始有16个,客户端存放的数据就在这16个中的一个。
我们知道redis是以键值对存储数据的,实际上redisDb内部保存了一字典dict,字典又保存了客户端的多个键值对,这个字典又被称为键空间。
image.png
读到这里你应该明白了,所有客户端存储在redis的数据都在redisServer对象的redisDb对象数组的某一个元素里面的dict下面
redisDb还有一个expires属性,这个属性也是一个字典,用与保存对象的过期时间
问题1、怎么进行数据增删改查?
了解hashmap的你一定知道,直接操作dict键空间就可以了。
三、Redis的垃圾回收机制
Redis回收过期对象的策略:定期删除+惰性删除
问题1、什么是定期删除?
答:定期删除就是每隔一定时间就进行一次删除,但与其他定期删除不同,redis定期删除并不会删除所有数据库中的所有过期对象。redis会检查某一些数据库(redisDb数组中的一些)中的某一些键,如果过期就删除,redis还会保存已经检查到了第几个数据库了,下次直接在该数据库开始检查。redis默认情况下每隔100ms执行一次定期删除,默认扫描16个数据库,每隔库检查20个键。
问题2、什么是惰性删除?
答:当客户端调用读写数据库的命令的时候,redis会判断这些命令涉及到的键是否过期,如果过期就删除。
惰性删除
问题3、为什么要两个组合删除?单独用一个不好吗?
答:只用定期删除全扫描会浪费cpu,惰性删除容易导致某些键扫描不到导致内存泄漏。两个组合起来刚好能覆盖整个内存。
问题4、Redis单线程是如何协调业务与垃圾回收的?
首先要了解redis服务器里两个事件
文件事件:redis处理客户端命令,套接字读写,键值对写入读取等
时间事件:redis内部垃圾回收事件,垃圾回收调用serverCron函数,需要定期执行
答:Redis单线程逻辑上先处理文件事件,后处理时间事件,实际执行的时间事件的时间可能略晚于时间事件所设定的时间,而且如果文件事件执行时间过长会break留到下次继续执行,把控制权交给时间事件。反之时间事件会调用子线程执行,让出控制权给文件事件。不会出现抢占等事件。
看下图,100ms执行一次时间事件,在85到130毫秒之间处理的是文件事件,时间事件被推迟到了131毫秒。从这个设计上可以看出redis对文件事件处理的优先级最高。
所以单线程只是对业务逻辑来说的,调用子线程证明用了其他线程了。
一次完整的事件处理
四、Redis持久化
redis持久化有两种
一种是内部调用SAVE/BGSAVE把redis数据库中的数据保存成RDB文件
一种是内部调用append sync等命令把客户端执行的命令保存成AOF文件
问题1、两种持久化有什么区别?
答:首先是保存的文件不同;然后是一个保存数据,一个保存产生数据的算法;最后是保存时机不同,RDB文件的时机用户可以通过save xxx时间内 yyy次修改 就调用BGSAVE保存,而AOF是在写入命令执行完成,返回之前把aof_buf缓冲区中的命令写入进入AOF文件的。
问题2、持久化操作不会浪费资源吗?
答:实际上BGSAVE命令执行的时候是Redis单线程又启动了一个线程,因此这里来看Redis也不是纯粹的单线程的。
总结
本篇博客我们介绍了redis单线程是怎么回事、redis的数据库结构、垃圾回收机制、持久化机制等。希望了解了这些概念的你能大体知道redis长什么样,由哪些东西组成的,这样以后再使用的时候就能够做到心中有数了。