Redis篇-2--原理篇1--I/O多路复用机制(5种I/O模型,I/O多路复用)

I/O多路复用机制:
Redis 是通过I/O多路复用机制来管理大量客户端连接。这使得redis可以实现通过单线程来处理多个客户端连接的请求,避免了为每个客户端创建独立的线程,从而减少了上下文切换的开销,提高了系统的并发性和性能。
理解:redis是通过一个线程来处理多个客户端的请求(通过 I/O 多路复用实现),意在减少CPU上下文的切换,提升系统的并发性和性能。

一、重点概念理解

1、什么是文件描述符?

文件描述符: 是操作系统为每个打开的文件、套接字、管道等 I/O 资源分配的一个整数标识符。它用于唯一标识进程与某个 I/O 资源之间的连接。
文件描述符是操作系统内核管理 I/O 资源的方式,应用程序通过文件描述符与这些资源进行交互。

简单理解下:
一个进程可以访问多个文件(或其他网络终端或通道连接等)资源,这些访问都需要进程与文件建立连接后才能进行数据传输。操作系统为了能清晰区分这些连接,不导致数据传输错乱,会给每一个的这种连接(进程和文件通信)都添加一个唯一整数标识,就是文件描述符。

如:一个快递站可以给人发快递(快递站如:操作系统),每个人通过快递站给别人寄快递(寄快递如:建立传输连接),快递站需要对这次寄件添加唯一的快递单号(快递单号如:添加文件描述符),这样才能保证寄件的安全对吧。

2、有哪些类型的文件描述符?

(1)、文件:普通文件(如文本文件、图片文件等)。
(2)、套接字:网络连接(如 TCP/UDP 连接)。
(3)、管道:进程间通信的通道。
(4)、设备:如终端、打印机等硬件设备。

上面介绍的I/O 资源,大概就是如上的4种类型。在简化下理解,I/O资源就是数据文件(你在电脑上看到的图片或文本等文件),这样就好理解吧?但实际上应该至少为上面的4种类型啊。
当你打开一个文件或创建一个网络连接时,操作系统会返回一个文件描述符,用于唯一区分下进程与文件之间的访问关系。

3、什么是“数据尚未准备好”?

“数据尚未准备好”是指当应用程序尝试从某个 I/O 资源(如文件、套接字等)读取或写入数据时,操作系统暂时无法提供所需的数据或无法立即处理写入请求。具体来说,这可以分为两种情况:
**(1)、读操作时数据尚未准备好:**当应用程序尝试从某个 I/O 资源读取数据时,操作系统还没有接收到任何数据,或者数据还没有完全到达。此时,操作系统无法立即提供数据,导致读操作暂时无法完成。
**(2)、写操作时数据尚未准备好:**当应用程序尝试向某个 I/O 资源写入数据时,操作系统可能暂时无法处理写入请求,例如网络连接中的缓冲区已满,或者磁盘写入速度较慢。此时,写操作也无法立即完成。
简单理解:
不考虑其他因素,简单理解为读和写实际都是耗时操作即可,这段读取和写入需要等待的时间就是数据尚未准备好的时间。

4、为什么会出现“数据尚未准备好”的情况?

出现这种情况的原因取决于 I/O 资源的类型和当前的状态:
(1)、网络套接字:在网络通信中,数据传输是有延迟的。当客户端发送数据到服务器时,服务器可能还没有接收到完整的数据包。同样,当服务器尝试向客户端发送数据时,网络可能暂时不可用,或者客户端的接收缓冲区已满,导致服务器无法立即发送数据。
(2)、文件 I/O:在读取文件时,如果文件位于磁盘上,操作系统需要从磁盘加载数据到内存中。如果文件较大或磁盘性能较差,数据加载可能需要一段时间,导致读操作无法立即完成。
(3)、管道或设备:在进程间通信或与硬件设备交互时,数据的产生和消费可能是异步的。例如,生产者进程可能还没有生成足够的数据(线程同步被阻塞等),或者消费者进程的缓冲区已满,导致读写操作无法立即完成。

二、5种I/O模型介绍

在计算机系统中,I/O 操作(如读取文件、网络通信等)通常是阻塞的,即当程序发起 I/O 请求(如:读取指定文件的内容)时,它会等待直到操作完成。
为了提高系统的并发性和性能,现代操作系统提供了多种 I/O 模型来处理 I/O 操作。
以下是五种常见的 I/O 模型:

1、阻塞IO(Blocking IO)

(1)、工作原理:

  • 在阻塞 I/O 模型中,当应用程序发起 I/O 请求(如 read 或 write)时,进程会被阻塞,直到 I/O 操作完成。
  • 如果数据尚未准备好(例如,网络连接中没有接收到数据),进程将一直处于等待状态,无法执行其他任务。
  • 一旦 I/O 操作完成,操作系统会唤醒进程,继续执行后续代码。
    即:当线程遇到阻塞任务(如InputStream.read())时,会一直卡在这里,直到处理完成后,才会继续向下执行代码

(2)、优点:

  • 实现简单,编程模型直观,易于理解和使用。

(3)、缺点:

  • 阻塞 I/O 会导致进程在等待 I/O 完成时无法做其他事情,浪费 CPU 资源,尤其是在高并发场景下,可能会导致大量进程处于等待状态,影响系统性能。

(4)、适用场景:

  • 适用于单线程或低并发的应用程序,或者 I/O 操作非常少的场景。

代码示例:
如: InputStream.read(), OutputStream.write()或 serverSocket.accept()等方法都会阻塞,直到处理完成或接收到响应后才会放行

import java.io.*;
import java.net.*;

public class BlockingIOServer {
    public static void main(String[] args) throws IOException {
        // 创建一个阻塞的 ServerSocket,监听 8080 端口
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("服务器启动,等待客户端连接...");

        while (true) {
            // 阻塞等待客户端连接
            Socket clientSocket = serverSocket.accept();    // serverSocket.accept()方法就是阻塞操作,当没有客户端连接,程序会一直卡在这里,直到有客户端连接后,才会放行并继续向下执行代码
            System.out.println("客户端已连接: " + clientSocket.getInetAddress());

            // 启动一个新线程处理每个客户端连接
            new Thread(new ClientHandler(clientSocket)).start();
        }
    }

    // 处理客户端连接的线程类
    static class ClientHandler implements Runnable {
        private final Socket clientSocket;

        public ClientHandler(Socket socket) {
            this.clientSocket = socket;
        }

        @Override
        public void run() {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                 PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {

                String inputLine;
                while ((inputLine = in.readLine()) != null) {   // InputStream.read()方法都是阻塞的,只有数据完全返回后才会继续向下执行代码
                    System.out.println("收到客户端消息: " + inputLine);
                    out.println("服务器已收到: " + inputLine);
                }

            } catch (IOException e) {
                System.err.println("客户端连接异常: " + e.getMessage());
            } finally {
                try {
                    clientSocket.close();
                } catch (IOException e) {
                    System.err.println("关闭客户端连接时出错: " + e.getMessage());
                }
            }
        }
    }
}

2、非阻塞IO(Nonblocking IO)

(1)、工作原理:

  • 在非阻塞 I/O 模型中,当应用程序发起 I/O 请求时,如果数据尚未准备好,操作系统不会阻塞进程,而是立即返回一个错误码(如 EAGAINEWOULDBLOCK)。
  • 应用程序可以继续执行其他任务,直到数据准备好后再重新发起 I/O 请求。
  • 这意味着应用程序需要不断地轮询(polling)检查 I/O 是否就绪,直到数据准备好为止。
    理解:当读/写或连接请求发起后,会立即返回结果,不阻塞当前线程,继续向下执行代码。对于一些需要返回值的方法通常返回null或false等,不会造成主线程阻塞等待。但一般为了防止数据丢失,都会使用循环机制优化重试,如果不重试的话,那么任务可能就真的不做了。

(2)、优点:

  • 进程不会被阻塞,可以在等待 I/O 的同时执行其他任务,避免了资源浪费。

(3)、缺点:

  • 轮询机制会导致 CPU 频繁地检查 I/O 状态,增加了 CPU 开销,尤其是在 I/O 未准备好时,频繁的轮询会浪费大量的 CPU 时间。
  • 编程复杂度增加,开发者需要手动管理 I/O 状态的检查和重试逻辑。

(4)、适用场景:

  • 适用于对实时性要求较高的场景,或者 I/O 操作非常频繁但每次 I/O 量较小的场景。

代码示例

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

public class NonblockingIOServer {
    public static void main(String[] args) throws IOException {
        // 创建一个非阻塞的 ServerSocketChannel,监听 8080 端口
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress(8080));
        serverSocket.configureBlocking(false);          // 设置为非阻塞模式
        System.out.println("服务器启动,等待客户端连接...");

        while (true) {
            // 尝试接受客户端连接,不会阻塞
            SocketChannel clientSocket = serverSocket.accept();     // 因为上面设置了非阻塞模式,这里不会阻塞,如果没有连接产生,这里会立即返回null并向下执行代码
            if (clientSocket != null) {
                System.out.println("客户端已连接: " + clientSocket.getRemoteAddress());

                // 处理客户端连接
                handleClient(clientSocket);
            } else {
                // 如果没有客户端连接,继续循环,不会阻塞
                System.out.println("没有新的客户端连接...");
            }

            // 模拟其他任务
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static void handleClient(SocketChannel clientSocket) throws IOException {
        // 非阻塞读取数据
        clientSocket.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        int bytesRead = clientSocket.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();  // 切换到读模式
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String message = new String(data).trim();
            System.out.println("收到客户端消息: " + message);

            // 回复客户端
            ByteBuffer response = ByteBuffer.wrap(("服务器已收到: " + message).getBytes());
            clientSocket.write(response);
        }
        clientSocket.close();
    }
}

3、IO多路复用(IO Multiplexing)

(1)、工作原理:

  • I/O 多路复用允许一个进程同时监视多个 I/O 文件描述符(如套接字),并等待其中任何一个文件描述符变为可读或可写。
  • 当某个文件描述符就绪时,操作系统会通知应用程序,应用程序可以针对该文件描述符进行相应的 I/O 操作。
  • 常见的 I/O 多路复用 API 包括 select、poll 和 epoll(Linux 特有)。
    理解:一个线程同时监听多个客户端的连接,并对每一个连接的请求都能做出正确的回应。

(2)、优点:

  • 单个线程可以同时处理多个 I/O 操作,避免了为每个 I/O 操作创建独立的线程或进程,减少了上下文切换的开销。
  • 相比于非阻塞 I/O 的轮询机制,I/O 多路复用只需要在 I/O 就绪时才进行处理,避免了频繁的 CPU 检查,提高了效率。

(3)、缺点:

  • 编程复杂度较高,开发者需要管理多个文件描述符的状态,并且需要处理 I/O 就绪的通知。
  • select 和 poll 的性能随着文件描述符数量的增加而下降,因为它们需要遍历所有文件描述符来检查状态。epoll 在这方面做了优化,适合处理大量文件描述符。

(4)、适用场景:

  • 适用于高并发场景,尤其是需要同时处理大量 I/O 操作的服务器应用程序,如 Web 服务器、数据库服务器等。

了解一下:
为了看懂之后的代码,这里需要先了解下几个概念。
Selector事件多路复用器:Selector 是 Java NIO 中的一个核心类,用于管理多个通道(Channel),并监听这些通道上的 I/O 事件(如连接、读、写等),当有事件发生时通知应用程序。它允许一个线程同时处理多个通道的 I/O 操作,而不需要为每个通道创建独立的线程。
ServerSocketChannel服务器端套接字通道:ServerSocketChannel是 Java NIO 中的一种特殊的通道,用于监听传入的客户端连接请求。它可以绑定到一个特定的端口,等待客户端发起连接请求。
Selector和ServerSocketChannel两者结合
可以将 ServerSocketChannel 注册到 Selector 上,监听 OP_ACCEPT 事件。当有新的客户端连接请求到达时,Selector 会通知应用程序,应用程序可以调用 ServerSocketChannel.accept() 来接受这个连接,并将其转换为 SocketChannel。
SocketChannel和ServerSocketChannel的区别
1、ServerSocketChannel:用于监听传入的客户端连接请求(OP_ACCEPT事件)。它绑定到一个特定的端口,等待客户端发起连接。当有新的客户端连接时,ServerSocketChannel会返回一个新的 SocketChannel,用于与该客户端进行数据通信。
2、SocketChannel:用于与客户端进行实际的数据传输。每个 SocketChannel 对应一个具体的客户端连接,负责读取和写入数据。你可以将 SocketChannel 注册到 Selector 上,监听 OP_READ或 OP_WRITE 事件,以便在数据准备好时进行处理。

具体使用流程如下:
1、创建一个 Selector实例。
2、创建一个 ServerSocketChannel,并将其绑定到某个端口。
3、将 ServerSocketChannel 注册到 Selector 上,监听 OP_ACCEPT事件。
4、使用 Selector.select() 阻塞线程,等待注册通道中发出 I/O 事件请求。
5、当有新的客户端连接请求时,Selector 会通知应用程序,应用程序可以通过 ServerSocketChannel.accept() 接受连接,并将新连接的 SocketChannel 注册到 Selector上,监听 OP_READ 或 OP_WRITE 事件。
6、继续处理其他 I/O 事件,直到服务器关闭。

如下图:
在这里插入图片描述
前半部分就描述了I/O多路复用的过程。
I/O多路复用程序就类似Selector的作用。
套接字S1,S2等就是发起请求的客户端。
S1,S2等客户端与I/O多路复用程序建立连接就相当于分别创建ServerSocketChannel且注册到Selector上。
建立完连接后,I/O多路复用程序会对每一个客户算S1,S2建立SocketChannel 通道,用于数据的传输。
I/O多路复用程序与客户端完成通道建立后,就会处于监听阻塞状态,等到管理的SocketChannel通道中存在I/O请求时,就会调用文件事务分派器去处理请求,并通过连接的通道将结果返回给客户端。

代码示例:

import java.io.IOException;
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.util.Iterator;
import java.util.Set;

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建一个 Selector,用于管理多个ServerSocketChannel 
        Selector selector = Selector.open();

        // 创建一个 ServerSocketChannel,监听 8080 端口
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.bind(new InetSocketAddress(8080));
        serverSocket.configureBlocking(false);  // 设置为非阻塞模式

        // 注册 ServerSocketChannel 到 Selector,监听 OP_ACCEPT 事件
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
    
       // 可以再次创建第2,3个ServerSocketChannel并注册到Selector,这里占时忽略

        System.out.println("服务器启动,等待客户端连接...");

        while (true) {
            // 阻塞等待 I/O 事件
            selector.select();  // 这里会阻塞当前线程,直到管理的通道中有 I/O 事件就绪
            // 获取所有就绪的 SelectionKey
            Set<SelectionKey> selectedKeys = selector.selectedKeys();          // 获取I/O请求的具体信息,如:是哪一个通道的请求,是什么类型请求(连接还是读还是写)等,然后在根据请求做出回应
            Iterator<SelectionKey> iterator = selectedKeys.iterator();

            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();  // 仅处理一个请求,避免重复处理

                if (key.isAcceptable()) {
                    // 处理新的客户端连接
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel clientSocket = server.accept();   // 获取数据通道
                    clientSocket.configureBlocking(false);

                    // 注册 SocketChannel 到 Selector,监听 OP_READ 事件
                    clientSocket.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端已连接: " + clientSocket.getRemoteAddress());
                } else if (key.isReadable()) {
                    // 处理客户端读事件
                    SocketChannel clientSocket = (SocketChannel) key.channel();
                    handleClient(clientSocket, key);
                }
            }
        }
    }

    private static void handleClient(SocketChannel clientSocket, SelectionKey key) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 读取客户端数据
        int bytesRead = clientSocket.read(buffer);
        if (bytesRead > 0) {
            buffer.flip();  // 切换到读模式
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            String message = new String(data).trim();
            System.out.println("收到客户端消息: " + message);

            // 回复客户端
            ByteBuffer response = ByteBuffer.wrap(("服务器已收到: " + message).getBytes());
            clientSocket.write(response);

            // 继续监听该客户端的读事件
            key.interestOps(SelectionKey.OP_READ);
        } else if (bytesRead == -1) {
            // 客户端关闭连接
            System.out.println("客户端断开连接");
            clientSocket.close();
            key.cancel();
        }
    }
}

4、信号驱动IO(Signal Driven IO)

(1)、工作原理:

  • 信号驱动 I/O 使用信号机制来通知应用程序 I/O 操作是否就绪。
  • 应用程序通过调用 sigaction 函数注册一个信号处理函数(如 SIGIO 信号),当 I/O 操作就绪时,操作系统会发送信号给应用程序,触发信号处理函数。
  • 信号处理函数可以在后台异步处理 I/O 操作,而主线程可以继续执行其他任务。

(2)、优点:

  • 信号驱动 I/O 允许应用程序在 I/O 就绪时立即得到通知,避免了阻塞和轮询,提高了响应速度。
  • 信号处理函数可以在后台异步执行,不会阻塞主线程。

(3)、缺点:

  • 信号处理函数的执行环境较为特殊,不能进行复杂的操作(如分配内存、锁操作等),限制了其应用场景。
  • 信号驱动 I/O 的实现较为复杂,调试和维护难度较大。
  • 信号的传递是异步的,可能会导致信号丢失或合并,影响可靠性。

Java 标准库中并没有直接支持信号驱动 I/O 的 API,一般在java中不推荐使用

5、异步IO(Asynchronous IO)

(1)、工作原理:

  • 异步 I/O 是一种真正的异步 I/O 模型,应用程序发起 I/O 请求后,操作系统会在后台异步执行 I/O 操作,而应用程序可以继续执行其他任务。
  • 当 I/O 操作完成后,操作系统会通知应用程序,应用程序可以选择在回调函数中处理结果,或者通过轮询的方式检查 I/O 是否完成。
  • 异步 I/O 的典型实现包括 POSIX AIO(Linux/Unix)和 Windows 的 I/O Completion Port(IOCP)。
    理解:当发起这种IO请求后,会在后台创建新的线程去执行任务,主线程不会阻塞。如果是需要返回值的场景调用Future.get()方法时会阻塞。(任务会在后台完成)

(2)、优点:

  • 应用程序可以在发起 I/O 请求后立即返回,继续执行其他任务,不会被阻塞,真正实现了 I/O 操作的异步化。
  • 异步 I/O 可以显著提高系统的并发性和响应速度,尤其是在高并发场景下,能够有效减少 I/O 等待时间。

(3)、缺点:

  • 异步 I/O 的实现较为复杂,编程模型不同于传统的同步 I/O,开发者需要处理异步回调和状态管理。
  • 不同操作系统对异步 I/O 的支持不同,跨平台兼容性较差。
  • 异步 I/O 的性能并不总是优于 I/O 多路复用,具体取决于操作系统的实现和应用场景。

(4)、适用场景:

  • 适用于高并发、高性能的网络应用,尤其是需要处理大量 I/O 操作的场景,如 Web 服务器、数据库服务器等。

6、5种I/O模型对比

(1)、阻塞 I/O:该任务会阻塞线程执行,直到任务完成放行,适合低并发场景。
(2)、非阻塞 I/O:该任务会立即返回结果,不会阻塞主线程。如果返回的是异常结果,如:false或null时,表示该任务并没有完成,主线程如果不采取合适机制去处理的话,就代表放弃了这个请求任务。一般可以轮询检查 I/O 状态,通过重试的机制去完成这样的请求处理,但这样可能会浪费 CPU 资源。
(3)、I/O 多路复用:通过 Selector监听多个文件描述符的 I/O 事件,避免了轮询,适合高并发场景。
(4)、信号驱动 I/O:通过信号通知 I/O 事件的发生,适合对实时性要求较高的场景,但在 Java 中不常用。
(5)、异步 I/O:真正的异步 I/O 模型,允许应用程序在发起 I/O 请求后立即返回,任务会在后台创建新的线程去执行,适合高并发、高性能的应用场景。

在这里插入图片描述

三、Redis实现I/O多路复用的原理

Redis 实现了不同的 I/O 多路复用机制,具体选择取决于操作系统的支持。
Redis 内部实现了一个抽象层,称为 ae(async event)库,它封装了不同操作系统的 I/O 多路复用 API,使得 Redis 可以在不同的平台上保持一致的行为。

1、支持的主要 I/O 多路复用机制

(1)、epoll机制(Linux 特有)

  • epoll 是 Linux 提供的一种高效的 I/O 多路复用机制,特别适合处理大量文件描述符。epoll的优势在于它只监听就绪的文件描述符,而不是像 select和poll 那样需要遍历所有文件描述符,因此性能更高。
  • Redis 在 Linux 系统上默认使用epoll,因为它提供了更好的性能和扩展性。

(2)、kqueue机制(FreeBSD、macOS)

  • kqueue 是 FreeBSD 和 macOS 提供的一种 I/O 多路复用机制,类似于 epoll,也支持高效的事件通知和文件描述符管理。
  • Redis 在 FreeBSD 和 macOS 系统上使用 kqueue 来处理 I/O 事件。

(3)、select 和 poll

  • select 和 poll 是较早的 I/O 多路复用机制,适用于大多数 Unix 系统。它们的性能随着文件描述符数量的增加而下降,因为它们需要遍历所有文件描述符来检查状态。
  • Redis 在不支持 epoll 或 kqueue 的系统上会退回到使用 select 或 poll,但这通常不是首选,因为它们的性能不如 epoll 和 kqueue。
2、I/O多路复用流程

(1)、初始化事件循环:Redis 启动时,会初始化一个事件循环(event loop),该循环负责监听所有客户端连接的 I/O 事件(如读、写、关闭等)。

(2)、注册文件描述符:每当有新的客户端连接时,Redis 会将该连接的文件描述符注册到 I/O 多路复用器(如 epoll 或 kqueue)中,以便监听该连接的 I/O 事件。

(3)、等待事件:事件循环会进入等待状态,等待 I/O 多路复用器通知有文件描述符就绪。在这个过程中,Redis 不会被阻塞,可以继续处理其他任务。

(4)、处理事件:当某个文件描述符就绪时,I/O 多路复用器会通知 Redis,Redis 会根据事件类型(如读、写、关闭等)进行相应的处理。例如,如果是一个读事件,Redis 会从该连接中读取数据;如果是一个写事件,Redis 会将数据写入该连接。

(5)、继续循环:处理完当前事件后,事件循环会继续等待下一个事件,重复上述过程。

3、为什么Redis选择I/O多路复用?

(1)、高并发处理能力:Redis 采用 I/O 多路复用可以同时处理大量的客户端连接,而不需要为每个连接创建独立的线程或进程。这大大减少了上下文切换的开销,提高了系统的并发性和性能。

(2)、高效利用 CPU:I/O 多路复用避免了阻塞和轮询,只有在 I/O 就绪时才会进行处理,因此可以更高效地利用 CPU 资源。

(3)、简化编程模型:相比多线程或进程模型,I/O 多路复用的编程模型更加简单,开发者不需要处理复杂的线程同步和锁问题。

(4)、跨平台支持:Redis 通过 ae 库封装了不同操作系统的 I/O 多路复用 API,确保了 Redis 在不同平台上的一致行为。

学海无涯苦作舟!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/936381.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕设-基于springboot的某学院兼职平台的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…

Promise详解-1:初识Promise

最近在回顾ES6的知识&#xff0c;想整理下跟Promise相关的内容。我准备整一个Promise解读的系列&#xff0c;看看能深入到什么程度吧。由浅入深&#xff0c;先认识下Promise。 痛苦的回忆&#xff1a;回调地狱 假如现在让你维护一个“古老”的项目&#xff0c;缺少脚手架的加…

【蓝桥杯备战】Day 1

1.基础题目 LCR 018.验证回文串 给定一个字符串 s &#xff0c;验证 s 是否是 回文串 &#xff0c;只考虑字母和数字字符&#xff0c;可以忽略字母的大小写。 本题中&#xff0c;将空字符串定义为有效的 回文串 。 示例 1: 输入: s "A man, a plan, a canal: Panama…

让文案生成更具灵活性/chatGPT新功能canvas画布编辑

​ ​ OpenAI最近在2024年12月发布了canvas画布编辑功能&#xff0c;这是一项用途广泛的创新工具&#xff0c;专为需要高效创作文案的用户设计。 无论是职场人士、学生还是创作者&#xff0c;这项功能都能帮助快速生成、优化和编辑文案&#xff0c;提升效率的同时提高内容质量…

使用秘钥登录服务器

在我们测试或生产环境中&#xff0c;为了服务器安全性&#xff0c;有时可能需要以 SSH 密钥的方式登录服务器&#xff0c;接下来&#xff0c;将演示如何通过 SSH 私钥的方式来远程服务器。 一、远程服务器生成密钥对 1、首先在目标远程服务器下生成 SSH 密钥对 ssh-keygen然…

linux-16 关于shell(十五)date,clock,hwclock,man,时间管理,命令帮助

想显示一下当前系统上的时间该怎么显示&#xff1f;有一个命令叫做date&#xff0c;来看date命令&#xff0c;如下图&#xff0c; 第一个星期几对吧&#xff1f;然后是月日小时分钟秒&#xff0c;最后一个是年对吧&#xff1f;CST指的是它的时间格式&#xff0c;我这个可以先姑…

番外篇 | Hyper-YOLO:超图计算与YOLO架构相结合成为目标检测新的SOTA !

前言:Hello大家好,我是小哥谈。Hyper-YOLO,该方法融合了超图计算以捕捉视觉特征之间复杂的高阶关联。传统的YOLO模型虽然功能强大,但其颈部设计存在局限性,限制了跨层特征的融合以及高阶特征关系的利用。Hyper-YOLO在骨干和颈部的联合增强下,成为一个突破性的架构。在COC…

在 Ubuntu 中 make 是否是系统自带的?怎么样查看Linux系统中是否有make?

make 命令 并不是所有 Ubuntu 系统都默认安装的&#xff0c;但它通常是开发工具链的一部分&#xff0c;许多开发者会在安装系统后配置它。make 是一个非常重要的构建工具&#xff0c;用于自动化编译和构建过程&#xff0c;特别是在编译软件或内核时。 make 的来源 make 是一个…

ubuntu+ros新手笔记(一)

系统ubuntu20.04 ros noetic humble(源码安装失败&#xff0c;放弃源码安装了) 1. ubuntu安装vcs 拉取autoware源码的时候需要用到命令 vcs import src < autoware.ai.repos但是ubuntu默认没有安装vcs工具&#xff08;zsh: command not found: vcs&#xff09; 应使用以…

蛋白研究新热点:AI 全方位剖析 DHA 与 Ferrostatin - 1 的作用密码

胰腺癌是一种非常棘手的癌症&#xff0c;传统化疗药物往往对它收效甚微&#xff0c;很难提高患者的生存率。不过&#xff0c;研究人员发现了一种可能的新治疗方向 —— 利用双氢青蒿素&#xff08;DHA&#xff09;诱导癌细胞发生铁死亡。 下面将以Dihydroartemisinin induces …

大数据挖掘建模平台案例分享

大数据挖掘建模平台是由泰迪自主研发&#xff0c;面向企业级用户的大数据挖掘建模平台。平台采用可视化操作方式&#xff0c;通过丰富内置算法&#xff0c;帮助用户快速、一站式地进行数据分析及挖掘建模&#xff0c;可应用于处理海量数据、高复杂性的数据挖掘任务&#xff0c;…

docker安装、升级、以及sudo dockerd --debug查看启动失败的问题

1、docker安装包tar下载地址 Index of linux/static/stable/x86_64/ 2、下载tgz文件并解压 tar -zxvf docker-24.0.8.tgz 解压后docker文件夹下位docker相关文件 3、将老版本docker相关文件&#xff0c;备份 将 /usr/bin/docker下docker相关的文件&#xff0c;mv到备份目录…

远程桌面防护的几种方式及优缺点分析

远程桌面登录是管理服务器最主要的方式&#xff0c;于是很多不法分子打起了远程桌面的歪心思。他们采用暴力破解或撞库的方式破解系统密码&#xff0c;悄悄潜入服务器而管理员不自知。 同时远程桌面服务中的远程代码执行漏洞也严重威胁着服务器的安全&#xff0c;攻击者可以利…

【电子通识】电流倒灌为什么需要注意?

电流倒灌是一个很常见的问题,以“IO电流倒灌”为关键词在百度上进行搜索,可以找到很多相关案例。 电流倒灌问题在5V电平的单片机时代几乎不会发生,主要是因为5V单片的IO耐压值高,单片机内部结构对IO保护设计很好。 到了3.3V单片机时代,这类问题有一定的偶发性,但…

Linux系统编程——进程间通信

目录 一、前言 二、进程间通信的目的 三、进程通信的方法 四、管道 通信 1、进程如何通信 2、管道概念 3、匿名管道 1&#xff09;理解 2&#xff09;匿名管道的创建 3&#xff09;匿名管道用途——控制进程 4&#xff09;匿名管道对多个进程的控制 5&#xff09;总…

【中工开发者】鸿蒙商城实战项目(启动页和引导页)

创建一个空项目 先创建一个新的项目选择第一个&#xff0c;然后点击finish 接下来为项目写一个名字&#xff0c;然后点击finish。 把index页面的代码改成下面代码块的代码&#xff0c;就能产生下面的效果 Entry Component struct Index {build() {Column(){Blank()Column(){…

Hadoop其一,介绍本地模式,伪分布模式和全分布搭建

目录 一、Hadoop介绍 二、HDFS的本地模式 三、伪分布模式 四、Hdfs中的shell命令 五、全分布搭建 六、使用Java代码操作HDFS 1、环境准备 2、单元测试&#xff08;Junit&#xff09;​编辑 一、Hadoop介绍 Hadoop 分为三部分 &#xff1a; Common、HDFS 、Yarn、MapRe…

【Linux-ubuntu通过USB传输程序点亮LED灯】

Linux-ubuntu通过USB传输程序点亮LED灯 一,初始化GPIO配置1.使能时钟2.其他寄存器配置 二&#xff0c;程序编译三&#xff0c;USB传输程序 一,初始化GPIO配置 1.使能时钟 使能就是一个控制信号&#xff0c;用于决定时钟信号是否能够有效的传递或者被使用&#xff0c;就像一个…

django——admin后台管理1

一、admin后台管理 访问url进入&#xff1a; http://127.0.0.1:8000/admin ​ 创建超级管理用户 终端输入以下命令&#xff1a; python manage.py createsuperuser (py36_pingping) E:\django学习\day03-django入门\demo>python manage.py createsuperuser Username: mo…

Jenkins与SonarQube持续集成搭建及坑位详解

Jenkins和SonarQube都是软件开发过程中常用的工具,它们在代码管理、构建、测试和质量管理方面发挥着重要作用。以下是关于Jenkins与SonarQube的作用及整合步骤环境搭建的详细解释: 一、Jenkins与SonarQube的作用 Jenkins: Jenkins是一个开源的持续集成和交付工具,它可以帮…