在各类分布式框架中,还有一类是和高性能息息相关的,那就是分布式缓存。目前生产环境常用的开源分布式缓存框架就是Redis和Memcached,它们之间的主要区别,我这里简单比较下:
- Redis拥有更多的数据结构,支持更丰富的数据操作。
- Memcached没有原生的集群模式,而Redis原生支持cluster模式。
目前Redis在工业环境中用得比较多,所以本系列只介绍Redis。如果读者对Memcached感兴趣,可以参考其官方文档。
一、Redis线程模型
Redis是典型的单线程架构,所有的读写操作都是在服务端的一条主线程中完成的。Redis客户端与服务端的模型如下图,每次客户端调用都经历了 发送命令 、 执行命令 、 返回结果 三个过程:
既然是单线程,为什么Redis能实现这么高的读写性能呢?
因为Redis基于 Reactor 模式开发了自己的网络事件处理器—— 文件事件处理器(file
event handler) 。
文件事件处理器,是单线程模式运行的,但是使用了I/O多路复用技术,同时监听多个套接字,并为套接字关联不同的事件处理器,当套接字上产生文件事件时,就会调用相应的事件处理器进行处理,这样就可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了Redis内部线程模型的简单性。
文件事件处理器一共包含四个部分,它们分别是 套接字(Socket) 、 I/O多路复用程序 、 文件事件分派器 以及 事件处理器 ,如下图:
可以看到,I/O多路复用程序负责监听多个socket,并向文件事件派发器传递那些产生了事件的socket。
尽管多个文件事件可能会并发地出现,但I/O多路复用程序总是会将所有产生的socket都放到同一个队列里边,然后文件事件处理器会以有序、同步的方式处理该队列中的每个socket:根据每个socket当前产生的事件,来选择对应的事件处理器来处理。
1.1 套接字(socket)
Redis服务端通过Socket与客户端连接。每当一个套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,就会产生一个文件事件。 因为一个服务器通常会连接多个套接字, 所以多个文件事件有可能会并发地出现。
比如,当socket变得可读时,socket就会产生一个AE_READABLE
事件;当socket变得可写的时候,socket会产生一个AE_WRITABLE
事件。
1.2 I/O 多路复用程序
I/O 多路复用程序主要负责监听多个套接字,并向文件事件分派器传送那些产生了事件的套接字。
I/O多路复用程序会将所有产生事件的套接字都入队到一个队列里, 然后通过这个队列,以有序(sequentially)、同步(synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。 当上一个套接字产生的事件被处理完毕之后, I/O多路复用程序才会继续向文件事件分派器传送下一个套接字。
Redis 使用的IO多路复用技术主要有:select
、epoll
、evport
和kqueue
等。每个IO多路复用函数库在 Redis 源码中都对应一个单独的文件,比如ae_select.c,ae_epoll.c, ae_kqueue.c等。Redis 会根据不同的操作系统,按照不同的优先级选择多路复用技术。事件响应框架一般都采用该架构,比如 netty 和 libevent:
1.3 文件事件分派器
文件事件分派器,负责监听多个套接字,接收 IO 多路复用程序传来的套接字,并根据套接字产生的事件类型, 调用相应的事件处理器,比如:
如果是客户端要连接redis,那么会为socket关联连接应答处理器;
如果是客户端要写数据到redis,那么会为socket关联命令请求处理器;
如果是客户端要从redis读数据,那么会为socket关联命令回复处理器。
1.4 事件处理器
服务器会为执行不同任务的套接字关联不同的事件处理器,这些处理器是一个个函数, 它们定义了某个事件发生时, 服务器应该执行的动作。
二、示例
我们以一次Redis客户端的请求,来看下上述的整个流程:
- Redis服务启动初始化的时候,Redis会将【连接应答处理器】跟
AE_READABLE
事件关联起来; - 如果一个Redis客户端发起连接请求,此时会产生一个
AE_READABLE
事件,然后由【连接应答处理器】来处理跟客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE
事件跟【命令请求处理器】关联起来; - 当客户端向Redis发起命令请求时,首先就会在socket产生一个
AE_READABLE
事件,然后由对应的【命令请求处理器】来处理。这个【命令请求处理器】就会从socket中读取请求相关数据,然后进行执行和处理; - 当Redis准备好了给客户端的响应数据后,就会将socket的
AE_WRITABLE
事件跟【命令回复处理器】关联起来,当客户端准备好读取响应数据时,就会在socket上产生一个AE_WRITABLE
事件,对应的【命令回复处理器】会将准备好的响应数据写入socket,供客户端来读取; - 【命令回复处理器】写完之后,就会删除这个socket的
AE_WRITABLE
事件和【命令回复处理器】的关联关系。
三、总结
Redis属于单线程模型,但是效率非常高,每秒能够读写上万条数据,其主要原因如下:
- 纯内存操作;
- 核心是基于非阻塞的IO多路复用机制;
- 单线程反而避免了多线程的频繁上下文切换问题。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。