文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Redis线程模型的原理是什么

2023-06-21 22:01

关注

这篇文章主要介绍“Redis线程模型的原理是什么”,在日常操作中,相信很多人在Redis线程模型的原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Redis线程模型的原理是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

一、概述

众所周知,Redis是一个高性能的数据存储框架,在高并发的系统设计中,Redis也是一个比较关键的组件,是我们提升系统性能的一大利器。深入去理解Redis高性能的原理显得越发重要,当然Redis的高性能设计是一个系统性的工程,涉及到很多内容,本文重点关注Redis的IO模型,以及基于IO模型的线程模型。

我们从IO的起源开始,讲述了阻塞IO、非阻塞IO、多路复用IO。基于多路复用IO,我们也梳理了几种不同的Reactor模型,并分析了几种Reactor模型的优缺点。基于Reactor模型我们开始了Redis的IO模型和线程模型的分析,并总结出Redis线程模型的优点、缺点,以及后续的Redis多线程模型方案。本文的重点是对Redis线程模型设计思想的梳理,捋顺了设计思想,就是一通百通的事了。

注:本文的代码都是伪代码,主要是为了示意,不可用于生产环境。

二、网络IO模型发展史

我们常说的网络IO模型,主要包含阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO,本文重点关注跟Redis相关的内容,所以我们重点分析阻塞IO、非阻塞IO、多路复用IO,帮助大家后续更好的理解Redis网络模型。

我们先看下面这张图;

Redis线程模型的原理是什么

2.1 阻塞IO

我们经常说的阻塞IO其实分为两种,一种是单线程阻塞,一种是多线程阻塞。这里面其实有两个概念,阻塞和线程。

像建立连接、读、写都涉及到系统调用,本身是一个阻塞的操作。

1.1 单线程阻塞

服务端单线程来处理,当客户端请求来临时,服务端用主线程来处理连接、读取、写入等操作。

以下用代码模拟了单线程的阻塞模式;

import java.net.Socket; public class BioTest {     public static void main(String[] args) throws IOException {        ServerSocket server=new ServerSocket(8081);        while(true) {            Socket socket=server.accept();            System.out.println("accept port:"+socket.getPort());            BufferedReader  in=new BufferedReader(new InputStreamReader(socket.getInputStream()));            String inData=null;            try {                while ((inData = in.readLine()) != null) {                    System.out.println("client port:"+socket.getPort());                    System.out.println("input data:"+inData);                    if("close".equals(inData)) {                        socket.close();                    }                }            } catch (IOException e) {                e.printStackTrace();            } finally {                try {                    socket.close();                } catch (IOException e) {                    e.printStackTrace();                }            }              }    }}

我们准备用两个客户端同时发起连接请求、来模拟单线程阻塞模式的现象。同时发起连接,通过服务端日志,我们发现此时服务端只接受了其中一个连接,主线程被阻塞在上一个连接的read方法上。

Redis线程模型的原理是什么

Redis线程模型的原理是什么

我们尝试关闭第一个连接,看第二个连接的情况,我们希望看到的现象是,主线程返回,新的客户端连接被接受。

Redis线程模型的原理是什么

从日志中发现,在第一个连接被关闭后,第二个连接的请求被处理了,也就是说第二个连接请求在排队,直到主线程被唤醒,才能接收下一个请求,符合我们的预期。

此时不仅要问,为什么呢?

主要原因在于accept、read、write三个函数都是阻塞的,主线程在系统调用的时候,线程是被阻塞的,其他客户端的连接无法被响应。

通过以上流程,我们很容易发现这个过程的缺陷,服务器每次只能处理一个连接请求,CPU没有得到充分利用,性能比较低。如何充分利用CPU的多核特性呢?自然而然的想到了——多线程逻辑。

1.2 多线程阻塞

对工程师而言,代码解释一切,直接上代码。

BIO多线程

package net.io.bio; import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket; public class BioTest {     public static void main(String[] args) throws IOException {        final ServerSocket server=new ServerSocket(8081);        while(true) {            new Thread(new Runnable() {                public void run() {                    Socket socket=null;                    try {                        socket = server.accept();                        System.out.println("accept port:"+socket.getPort());                        BufferedReader  in=new BufferedReader(new InputStreamReader(socket.getInputStream()));                        String inData=null;                        while ((inData = in.readLine()) != null) {                            System.out.println("client port:"+socket.getPort());                            System.out.println("input data:"+inData);                            if("close".equals(inData)) {                                socket.close();                            }                        }                    } catch (IOException e) {                        e.printStackTrace();                    } finally {                                             }                }            }).start();        }    } }

同样,我们并行发起两个请求;

Redis线程模型的原理是什么

两个请求,都被接受,服务端新增两个线程来处理客户端的连接和后续请求。

Redis线程模型的原理是什么

Redis线程模型的原理是什么

我们用多线程解决了,服务器同时只能处理一个请求的问题,但同时又带来了一个问题,如果客户端连接比较多时,服务端会创建大量的线程来处理请求,但线程本身是比较耗资源的,创建、上下文切换都比较耗资源,又如何去解决呢?

2.2 非阻塞

如果我们把所有的Socket(文件句柄,后续用Socket来代替fd的概念,尽量减少概念,减轻阅读负担)都放到队列里,只用一个线程来轮训所有的Socket的状态,如果准备好了就把它拿出来,是不是就减少了服务端的线程数呢?

一起看下代码,单纯非阻塞模式,我们基本上不用,为了演示逻辑,我们模拟了相关代码如下;

package net.io.bio; import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.net.ServerSocket;import java.net.Socket;import java.net.SocketTimeoutException;import java.util.ArrayList;import java.util.List; import org.apache.commons.collections4.CollectionUtils;  public class NioTest {     public static void main(String[] args) throws IOException {        final ServerSocket server=new ServerSocket(8082);        server.setSoTimeout(1000);        List<Socket> sockets=new ArrayList<Socket>();        while (true) {            Socket socket = null;            try {                socket = server.accept();                socket.setSoTimeout(500);                sockets.add(socket);                System.out.println("accept client port:"+socket.getPort());            } catch (SocketTimeoutException e) {                System.out.println("accept timeout");            }            //模拟非阻塞:轮询已连接的socket,每个socket等待10MS,有数据就处理,无数据就返回,继续轮询            if(CollectionUtils.isNotEmpty(sockets)) {                for(Socket socketTemp:sockets ) {                    try {                        BufferedReader  in=new BufferedReader(new InputStreamReader(socketTemp.getInputStream()));                        String inData=null;                        while ((inData = in.readLine()) != null) {                            System.out.println("input data client port:"+socketTemp.getPort());                            System.out.println("input data client port:"+socketTemp.getPort() +"data:"+inData);                            if("close".equals(inData)) {                                socketTemp.close();                            }                        }                    } catch (SocketTimeoutException e) {                        System.out.println("input client loop"+socketTemp.getPort());                    }                }            }        }     }}

系统初始化,等待连接;

Redis线程模型的原理是什么

发起两个客户端连接,线程开始轮询两个连接中是否有数据。

Redis线程模型的原理是什么

两个连接分别输入数据后,轮询线程发现有数据准备好了,开始相关的逻辑处理(单线程、多线程都可)。

Redis线程模型的原理是什么

再用一张流程图辅助解释下(系统实际采用文件句柄,此时用Socket来代替,方便大家理解)。

Redis线程模型的原理是什么

服务端专门有一个线程来负责轮询所有的Socket,来确认操作系统是否完成了相关事件,如果有则返回处理,如果无继续轮询,大家一起来思考下?此时又带来了什么问题呢。

CPU的空转、系统调用(每次轮询到涉及到一次系统调用,通过内核命令来确认数据是否准备好),造成资源的浪费,那有没有一种机制,来解决这个问题呢?

2.3 IO多路复用

server端有没专门的线程来做轮询操作(应用程序端非内核),而是由事件来触发,当有相关读、写、连接事件到来时,主动唤起服务端线程来进行相关逻辑处理。模拟了相关代码如下;

IO多路复用

import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.nio.charset.Charset;import java.util.Iterator;import java.util.Set; public class NioServer {     private static  Charset charset = Charset.forName("UTF-8");    public static void main(String[] args) {        try {            Selector selector = Selector.open();            ServerSocketChannel chanel = ServerSocketChannel.open();            chanel.bind(new InetSocketAddress(8083));            chanel.configureBlocking(false);            chanel.register(selector, SelectionKey.OP_ACCEPT);             while (true){                int select = selector.select();                if(select == 0){                    System.out.println("select loop");                    continue;                }                System.out.println("os data ok");                                 Set<SelectionKey> selectionKeys = selector.selectedKeys();                Iterator<SelectionKey> iterator = selectionKeys.iterator();                while (iterator.hasNext()){                    SelectionKey selectionKey = iterator.next();                                         if(selectionKey.isAcceptable()){                        ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();                        SocketChannel client = server.accept();                        client.configureBlocking(false);                        client.register(selector, SelectionKey.OP_READ);                        //继续可以接收连接事件                        selectionKey.interestOps(SelectionKey.OP_ACCEPT);                    }else if(selectionKey.isReadable()){                        //得到SocketChannel                        SocketChannel client = (SocketChannel)selectionKey.channel();                        //定义缓冲区                        ByteBuffer buffer = ByteBuffer.allocate(1024);                        StringBuilder content = new StringBuilder();                        while (client.read(buffer) > 0){                            buffer.flip();                            content.append(charset.decode(buffer));                        }                        System.out.println("client port:"+client.getRemoteAddress().toString()+",input data: "+content.toString());                        //清空缓冲区                        buffer.clear();                    }                    iterator.remove();                }            }         } catch (Exception e) {            e.printStackTrace();        }    }}

同时创建两个连接;

Redis线程模型的原理是什么

两个连接无阻塞的被创建;

Redis线程模型的原理是什么

无阻塞的接收读写;

Redis线程模型的原理是什么

再用一张流程图辅助解释下(系统实际采用文件句柄,此时用Socket来代替,方便大家理解)。

Redis线程模型的原理是什么

当然操作系统的多路复用有好几种实现方式,我们经常使用的select(),epoll模式这里不做过多的解释,有兴趣的可以查看相关文档,IO的发展后面还有异步、事件等模式,我们在这里不过多的赘述,我们更多的是为了解释Redis线程模式的发展。

三、NIO线程模型解释

我们一起来聊了阻塞、非阻塞、IO多路复用模式,那Redis采用的是哪种呢?

Redis采用的是IO多路复用模式,所以我们重点来了解下多路复用这种模式,如何在更好的落地到我们系统中,不可避免的我们要聊下Reactor模式。

首先我们做下相关的名词解释;

Reactor:类似NIO编程中的Selector,负责I/O事件的派发;

Acceptor:NIO中接收到事件后,处理连接的那个分支逻辑;

Handler:消息读写处理等操作类。

3.1 单Reactor单线程模型

Redis线程模型的原理是什么

处理流程

优点

缺点

怎么去解决上述问题呢?既然业务处理逻辑可能会影响系统瓶颈,那我们是不是可以把业务处理逻辑单拎出来,交给线程池来处理,一方面减小对主线程的影响,另一方面利用CPU多核的优势。这一点希望大家要理解透彻,方便我们后续理解Redis由单线程模型到多线程模型的设计的思路。

3.2 单Reactor多线程模型

Redis线程模型的原理是什么

这种模型相对单Reactor单线程模型,只是将业务逻辑的处理逻辑交给了一个线程池来处理。

处理流程

优点

缺点

有没有什么好的办法来解决上述问题呢?通过以上的分析,大家有没有发现一个现象,当某一个点成为系统瓶颈点时,想办法把他拿出来,交个其他线程来处理,那这种场景是否适用呢?

3.3 多Reactor多线程模型

Redis线程模型的原理是什么

这种模型相对单Reactor多线程模型,只是将Scoket的读写处理从mainReactor中拎出来,交给subReactor线程来处理。

处理流程

Handler完成读事件后,包装成一个任务对象,交给线程池来处理,把业务处理逻辑交给其他线程来处理。

优点

缺点

四、Redis的线程模型

4.1 概述

以上我们聊了,IO网路模型的发展历史,也聊了IO多路复用的reactor模式。那Redis采用的是哪种reactor模式呢?在回答这个问题前,我们先梳理几个概念性的问题。

Redis服务器中有两类事件,文件事件和时间事件。

本文重点聊下Socket相关的事件。

4.2 模型图

首先我们来看下Redis服务的线程模型图;

Redis线程模型的原理是什么

IO多路复用负责各事件的监听(连接、读、写等),当有事件发生时,将对应事件放入队列中,由事件分发器根据事件类型来进行分发;

如果是连接事件,则分发至连接应答处理器;GET、SET等redis命令分发至命令请求处理器。

命令处理完后产生命令回复事件,再由事件队列,到事件分发器,到命令回复处理器,回复客户端响应。

4.3 一次客户端和服务端的交互流程

3.1 连接流程

Redis线程模型的原理是什么

连接过程

3.2 命令执行流程

Redis线程模型的原理是什么

SET命令执行过程

4.4 模型优缺点

以上流程分析我们可以看出Redis采用的是单线程Reactor模型,我们也分析了这种模式的优缺点,那Redis为什么还要采用这种模式呢?

Redis本身的特性

命令执行基于内存操作,业务处理逻辑比较快,所以命令处理这一块单线程来做也能维持一个很高的性能。

优点

缺点

Redis又是如何去解的呢?

哈哈~将耗时的点从主线程拎出来呗?那Redis的新版本是这么做的吗?我们一起来看下。

4.5 Redis多线程模式

Redis线程模型的原理是什么

Redis的多线程模型跟”多Reactor多线程模型“、“单Reactor多线程模型有点区别”,但同时用了两种Reactor模型的思想,具体如下;

命令执行大致流程

到此,关于“Redis线程模型的原理是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯