当前位置: 首页 > news >正文

关于多线程的Redis模型

目录

1、Redis 6.0 的线程模型

2、单线程模型

3、I/O模型和网络操作

3.1、I/O 线程的职责

3.2、Redis 主线程的角色

4、非阻塞I/O

4.1. 事件驱动模型

4.2. 多路复用

4.3. 非阻塞套接字

4.4. 处理流程

4.5. 优势

4.6. 适用场景

5、配置和使用注意点


        Redis 在版本 6.0 中引入了多线程模型,以提升在某些场景下的性能,特别是在处理网络请求的阶段。

        之前的版本,Redis 是单线程的,即所有的请求处理、I/O 操作、命令执行等都是在一个线程中串行进行的。虽然这种模型简化了开发并避免了锁带来的复杂性,但在一些 I/O 绑定的工作负载下,可能导致性能瓶颈。

如下图所示:


1、Redis 6.0 的线程模型

使用了非阻塞的 I/O 模型(基于事件驱动的方式)。

如下图所示:

1.多线程 I/O

            Redis 6.0 引入了多线程模型来处理网络 I/O,包括接收请求和发送响应。这部分工作由多个线程并行处理,以减轻主线程的负担。

            多线程仅用于网络 I/O 的读写,而核心的命令请求解析、命令执行、数据修改等仍在主线程中进行。

    2.线程安全

            因为 Redis 的核心数据结构操作仍由单线程管理,所以这减少了对数据一致性和线程安全的复杂性。

            多线程处理的网络 I/O 是线程安全的,因为它主要涉及数据传输,而不直接改变 Redis 内部状态。

    3.线程数量配置

            可以通过配置选项 io-threads 设置 I/O 线程的数量。例如在 redis.conf 中:

    # redis.conf
    io-threads-do-reads yes
    io-threads 4
    
    • 注意,线程数(不包括主线程)的设置需要与工作负载匹配,过多的线程可能导致线程争用,并可能增加上下文切换的开销。
    • io-threads-do-reads yes:启用多线程对输入请求的读取。
    • io-threads 4:设置四个 I/O 线程。这不包括主线程。

    4.性能增强

              在高并发连接情况下,特别是对于连接数众多但单个请求处理时间较短的应用场景,多线程模型能够提升 Redis 的吞吐性能。

              是一种优化 I/O 性能的手段,而对于计算密集型的任务,单线程模型的优势在于避免了锁开销。

      5.I/O 模型细节

              在多线程环境下,每个 I/O 线程负责处理其分配的若干连接的 I/O 操作。主线程在 I/O 完成后负责将命令请求入队。

              当命令入队且准备好处理时,主线程依次从队列中取出命令进行执行。

      了解更多的redis模型,可参考:对Redis组件的深入探讨-CSDN博客


      2、单线程模型

      Redis 5.0及以前的版本,执行流程:

      1、请求接收

                Redis 使用 epoll 或类似的异步 I/O 多路复用技术监听客户端连接。主线程循环检测所有连接,接收和解析请求。

        2、命令执行

                接收到请求后,主线程立即执行所需的命令逻辑。包括对内存数据结构的操作(如 GET、SET 等)。内部没有任何锁,因为所有操作都是在同一个线程中进行的。

        3、响应发送

                使用同样的线程将响应数据发送回客户端。因为请求的处理和响应的发送都是同步进行的,这使得 Redis 能高效地处理短小请求。

        如下图所示:

        Client -----> [Redis Main Thread] -----> Execute Command -----> Response^                      ||                      |   (All in a single loop)'----------------------'
        
        • 单线程模型

          • 极简设计,避免锁竞争。
          • 操作直接通过单线程处理,减小上下文切换开销。
          • 极高的性能在处理简单和快速的请求时。
        • 多线程模型

          • 通过异步 I/O线程处理连接、读写,减轻主线程负担,特别是高并发下。
          • 支持多个连接同时进行网络 I/O,改善了吞吐量。
          • 保持命令执行的单线程保证,提高了多连接场合的响应能力。

        3、I/O模型和网络操作

                Redis 的多线程 I/O 模型主要涉及网络操作,而 Redis 主线程负责执行数据命令。

        这涉及到不同线程的角色和任务分配。

        3.1、I/O 线程的职责

        1、连接管理

                  I/O 线程负责管理客户端连接的打开和关闭。这意味着它处理在客户端与服务器之间建立和断开连接的过程。

                  多个客户端连接可以同时通过 I/O 线程进行处理,这是为支持高并发和大量连接而设计的。

          2、读操作

                  I/O 线程负责从客户端读取数据,即接收客户端发送的请求。这些请求通常包括 Redis 命令和相关参数。

          3、写操作

                  I/O 线程负责向客户端发送数据,也就是向客户端返回请求结果。响应通常包括命令执行结果,如查询返回的值,或操作状态(成功、失败等)。

                  这些 I/O 线程的主要目标是提高 Redis 的网络吞吐量,特别是在高并发场景。

          3.2、Redis 主线程的角色

          1.命令解析

                    主线程负责从 I/O 线程接收到的数据中解析具体的 Redis 命令。包括识别需要执行的类型(GET、SET、DEL等)和与命令相关的参数(如键名、值等)。

            2.命令执行

                    根据解析得到的命令,主线程在 Redis 数据库上执行相应的操作。

            这些操作包括:

                    读操作:从 Redis 数据结构中读取数据,例如,把一个键的值返回给客户端。

                    写操作:更新或插入数据,例如,设置一个键的值,添加新的键值对。

                    其他管理操作:如缓存设置、数据持久化、事务执行等。

            3.结果处理

                    处理完命令后,主线程生成结果数据(成功、失败、数据值等)并传回给 I/O 线程。I/O 线程最终向客户端发送该结果。

            总结

                    这种设计分离了网络 I/O 和命令执行的职责,保持了 Redis 在命令执行时的简洁性和一致性。同时,I/O 线程则专注于处理网络传输,这种并行化提高了服务器对大量并发连接的处理能力。

                    Redis 主线程保持命令执行单线程的模式,避免需要并发锁机制来处理复杂的竞争条件,这使得 Redis 在处理内存数据操作时很高效。对于开发者和运维者,这意味着既能享有较高的网络吞吐量,又能确保数据操作的简洁和一致。

            4、非阻塞I/O

                    Redis 的非阻塞 I/O 是其高性能和高并发处理请求的核心之一。

            下面将深入探讨 Redis 如何实现非阻塞 I/O,以及它的工作原理和优势。

            4.1. 事件驱动模型

                    Redis 使用了事件驱动的 I/O 模型,具体来说是基于「事件循环」的架构。这种模型允许 Redis 在单个线程中处理多个并发连接,而不需要为每个连接创建单独的线程。

            事件循环的关键组成部分如下:

            • 事件循环:这是一个持续运行的循环,负责等待和调度不同的事件。在每个循环迭代中,事件循环会检查是否有新的 I/O 事件(例如客户端请求到达)。

            • 事件源:可以是网络连接的读写事件或定时器等。可以通过调用系统调用(如 epollkqueue 或 select)来监控多个文件描述符。

            • 事件处理:一旦有事件触发,事件循环就会调用相应的回调函数来处理这些事件。处理过程是非阻塞的,意味着即便某个事件的处理需要时间,也不会阻塞其他事件的处理。

            4.2. 多路复用

                    Redis 通过多路复用来实现非阻塞 I/O。多路复用是指在单个线程中同时监控多个输入输出源(文件描述符和事件描述符)的能力。

            以下是一些常用的多路复用机制:

            • select:较早的多路复用机制,但存在文件描述符数量限制。

            • poll:克服了 select 的一些限制,但在高负载时性能仍然较低。

            • epoll:Linux 特有的高效多路复用机制,能够处理大量的并发连接,因此 Redis 在 Linux 上通常使用 epoll

            • kqueue:在 BSD 系统上使用的高效多路复用机制。

            4.3. 非阻塞套接字

                    Redis 使用非阻塞套接字进行网络通信。当 Redis 创建套接字时,会将其设置为非阻塞模式。这意味着,当对套接字进行读写操作时,如果操作不能立即完成,系统会立即返回而不会阻塞线程。这使得 Redis 可以迅速地响应多个客户端请求。

                    Java 提供了 NIO(New I/O)库,这个库支持非阻塞 I/O 操作,是处理大规模 I/O 任务的有效方式。NIO 的核心是 java.nio.channels 包,其中包含用于非阻塞操作的关键类。

            使用 Java NIO 设置非阻塞套接字

            下面是使用 Java NIO 设置非阻塞式 TCP 套接字的基本步骤:

            1. 创建 Selector: 用于监视多个通道的 I/O 事件。
            2. 创建 ServerSocketChannel: 用于接受连接。
            3. 设置为非阻塞模式: 将通道设置为非阻塞。
            4. 注册通道到 Selector: 以便 Selector 监视它的 I/O 事件。
            5. 循环处理事件: 在一个循环中调用 Selector,处理客户端连接和 I/O 操作。

            代码示例如下:

            import java.io.IOException;
            import java.net.InetSocketAddress;
            import java.nio.ByteBuffer;
            import java.nio.channels.Selector;
            import java.nio.channels.SelectionKey;
            import java.nio.channels.ServerSocketChannel;
            import java.nio.channels.SocketChannel;
            import java.nio.channels.Selector;
            import java.util.Iterator;public class NonBlockingServer {public static void main(String[] args) {try {// 创建 SelectorSelector selector = Selector.open();// 创建 ServerSocketChannelServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false); // 设置为非阻塞模式// 绑定到端口serverSocketChannel.bind(new InetSocketAddress(8080));// 注册到 Selector,并监视 ACCEPT 事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("Server started on port 8080...");while (true) {// 等待事件发生selector.select();// 获取所有已选择的键Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();// 如果是新连接事件if (key.isAcceptable()) {SocketChannel socketChannel = serverSocketChannel.accept();socketChannel.configureBlocking(false); // 也设置客户端通道为非阻塞socketChannel.register(selector, SelectionKey.OP_READ); // 注册读取事件System.out.println("Accepted new connection from: " + socketChannel.getRemoteAddress());}// 如果有可读事件if (key.isReadable()) {SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(256);int bytesRead = socketChannel.read(buffer);if (bytesRead == -1) {socketChannel.close(); // 关闭通道} else {String message = new String(buffer.array()).trim();System.out.println("Received message: " + message);// 可以在这里对接收到的数据进行处理...}}iterator.remove(); // 移除已处理的键}}} catch (IOException e) {e.printStackTrace();}}
            }
            
            1. Selector:用于监视通道的 I/O 事件。
            2. ServerSocketChannel:创建并配置一个非阻塞的服务器套接字通道,能接受客户端连接。
            3. 注册事件:将通道注册到 Selector 并指定要监视的事件(在这里是 OP_ACCEPT 和 OP_READ)。
            4. 事件循环:使用无限循环来监听 Selector 的事件,处理接受的新连接和读取的数据。

                    虽然 Java 的 Selector 不允许直接使用 epoll,但在 Linux 环境下,当你在 Java 中使用 Selector 时,JVM 会自动选择 epoll 作为底层的实现(前提是你的 JDK 为 Linux 提供了这个支持)。这意味着,使用 Selector 可以得到 epoll 的所有优势,无需直接与 epoll 交互。

            4.4. 处理流程

            以下是 Redis 处理客户端请求的基本流程:

            1. 接受连接:当客户端连接到 Redis 时,事件循环会通过非阻塞 I/O 接受连接请求,将其添加到监控的事件源中。

            2. 读取请求:当一个客户端发送请求时,事件循环会触发对应的读事件,Redis 会读取请求数据。这一过程是非阻塞的。

            3. 执行命令:Redis 解析请求后,将命令传递给内部处理逻辑进行执行。执行命令通常是在主线程中完成的。

            4. 写回响应:命令执行完成后,Redis 会将结果写回客户端,并通过事件循环监控写事件。

            4.5. 优势

            • 高并发:通过非阻塞 I/O 和事件驱动机制,Redis 可以处理成千上万的并发客户端连接。

            • 低延迟:由于没有线程间的上下文切换开销,Redis 在响应请求时具有较低的延迟。

            • 资源利用效率高:避免了大量线程和进程带来的资源消耗,提高了内存和 CPU 的使用效率。

            4.6. 适用场景

            这种非阻塞 I/O 的设计使得 Redis 特别适合用于以下场景:

            • 实时数据处理:快速响应和处理请求,比如实时分析和推荐系统。
            • 高并发场景:例如游戏服务器、聊天应用等需要同时处理大量并发连接的应用。

            5、配置和使用注意点

            • 开启条件:

            默认情况下,Redis 6.0 仍然运行在单线程模式。要启用多线程 I/O,必须修改配置文件。

            • 适用场景:

                    多线程 I/O 适合于 CPU 利用率较低,网络 I/O 瓶颈的场景。对于计算密集型任务或本身不易产生 I/O 瓶颈的场景,效果可能不显著。

            • 多线程适用:

            这项功能特别适合于那些频繁利用 Redis 进行小数据操作并需要极高网络吞吐量的应用。


            总结

                    通过引入多线程 I/O,Redis 6.0 提升了对高并发和高负载场景的处理能力,同时保持简洁的基本运行模型,提高了在很多新型应用中的性能表现。


            参考文章:

            1、对Redis组件的深入探讨-CSDN博客

            http://www.xdnf.cn/news/464599.html

            相关文章:

          1. 数据科学和机器学习的“看家兵器”——pandas模块 之二
          2. c++从入门到精通(四)--动态内存,模板与泛型编程
          3. python克洛伊婚纱摄影预约管理系统
          4. P2679 [NOIP 2015 提高组] 子串
          5. Linux之Yum源与Nginx服务篇
          6. Node.js 安装 + React Flow 快速入门:环境安装与项目搭建
          7. Spring Boot 和 Jedis版本搭配的建议
          8. 【言语】刷题5
          9. win11平台下的docker-desktop中的volume位置问题
          10. Newtonsoft.Json.JsonSerializationException
          11. 安卓A15系统实现修改锁屏界面默认壁纸功能
          12. 多个docker-compose-xx 停止并完全卸载 删除
          13. C++:字符数组与字符串指针变量的大小
          14. 鸿蒙OSUniApp制作多选框与单选框组件#三方框架 #Uniapp
          15. 协作赋能-1-制造业生产流程重构
          16. Midjourney 最佳创作思路与实战技巧深度解析【附提示词与学习资料包下载】
          17. 一些问题杂记
          18. NY244NY249美光闪存颗粒NY252NY256
          19. unity terrain 在生成草,树,石头等地形障碍的时候,无法触发碰撞导致人物穿过模型
          20. 全链路压测实战指南:从理论到高可用架构的终极验证
          21. Transformer的理解
          22. Elasticsearch 分片机制高频面试题(含参考答案)
          23. 【备忘踩坑】Android单元测试中读取测试assets的方法
          24. 一套基于 Bootstrap 和 .NET Blazor 的开源企业级组件库
          25. C#中Action的用法
          26. Milvus Docker 部署教程
          27. 【FFmpeg+SDL】使用FFmpeg捕获屏幕,SDL显示
          28. 套路化编程:C# winform ListView 自定义排序
          29. 5款AI驱动的MySQL数据库管理工具:提升运维效率的智能之选
          30. 数智化招标采购系统如何实现分散评标?