Redis高级篇之I/O多路复用的引入解析

news2025/4/23 13:39:25

文章目录

    • 一、问题背景
      • 1. 高并发连接的管理
      • 2. 避免阻塞和延迟
      • 3. 减少上下文切换开销
      • 4. 高效的事件通知机制
      • 5. 简化编程模型
      • 6. 低延迟响应
      • 本章小节
    • 二、I/O多路复用高性能的本质
      • 1. 避免无意义的轮询:O(1) 事件检测
      • 2. 非阻塞 I/O + 零拷贝:最大化 CPU 利用率
      • 3. 单线程事件循环:无锁、无上下文切换
      • 4. 高效的系统调用:内核级优化
      • 5. Reactor 模式:事件分发与业务逻辑解耦
      • 6. 边缘触发(ET) vs 水平触发(LT)
      • 性能对比:传统模型 vs 多路复用
      • 为什么 Redis 能单线程扛住 10 万 QPS?
      • 本章小节:I/O 多路复用快的本质
    • 三、基于I/O多路复用的Redis高性能设计源码分析
      • 1. 事件循环核心结构:`aeEventLoop`
      • 2. 多路复用 API 的抽象层
        • `aeApiCreate`:初始化多路复用实例
        • `aeApiAddEvent`:注册事件到epoll
      • 3. 事件循环主流程:`aeMain`
        • 关键函数 `aeProcessEvents`
      • 4. 文件事件处理:从连接建立到命令执行
        • 步骤1:监听客户端连接(`acceptTcpHandler`)
        • 步骤2:注册客户端读事件(`readQueryFromClient`)
        • 步骤3:读取并解析命令(`readQueryFromClient`)
        • 步骤4:执行命令并写回结果
      • 5. 多路复用 API 的性能优化
        • `epoll` 的边缘触发(ET) vs 水平触发(LT)
        • `aeApiPoll` 的实现(以epoll为例)
      • 6. 单线程模型与 I/O 多路复用的协同
      • 本章小结:Redis I/O 多路复用的设计精髓
    • 四、使用java实现I/O多路复用模型
      • 关键设计解析
        • 1. Selector 核心机制
        • 2. Channel 注册与事件类型
        • 3. ByteBuffer 状态管理
        • 4. 事件处理流程
      • 性能优化点
        • 1. 零拷贝优化
        • 2. 批量处理事件
        • 3. 对象池化
      • 对比传统BIO模型
      • 运行测试
      • 扩展方向

一、问题背景

Redis采用I/O多路复用技术(如epollkqueueselect)作为其高性能设计的核心机制,主要解决了以下关键问题:


1. 高并发连接的管理

  • 问题:传统多线程/多进程模型中,每个连接需分配独立线程或进程,资源消耗大(内存、CPU上下文切换)。
  • 解决:I/O多路复用允许单线程同时监听和管理成千上万的网络连接,通过事件驱动的方式处理请求,避免为每个连接创建独立线程,显著降低资源占用。
  • 场景:适用于高并发场景(如10万+并发连接),如实时消息队列、高频访问的缓存服务。

2. 避免阻塞和延迟

  • 问题:传统阻塞I/O中,线程在等待数据时会被挂起,导致吞吐量下降。
  • 解决:结合非阻塞I/O,I/O多路复用仅在有数据到达或可写时通知线程处理,线程无需阻塞等待,最大化CPU利用率。
  • 示例:客户端发送请求后,Redis线程无需阻塞等待数据,转而处理其他连接的请求,直到数据就绪。

3. 减少上下文切换开销

  • 问题:多线程/进程模型中,频繁的上下文切换(Context Switching)会消耗大量CPU时间。
  • 解决:单线程配合I/O多路复用,无需线程间切换,减少CPU浪费,提升整体吞吐量。
  • 对比:多线程模型在并发1万连接时可能因切换开销导致性能骤降,而Redis仍能保持低延迟。

4. 高效的事件通知机制

  • 问题:传统轮询(如select)需遍历所有连接检查状态,时间复杂度为O(n),效率低下。
  • 解决:采用epoll(Linux)或kqueue(BSD)等高效多路复用器,仅关注活跃连接,时间复杂度为O(1)。
    • epoll优势:通过事件回调机制直接获取就绪事件列表,避免无意义的遍历。
    • 性能提升:连接数越多,相比select/poll的性能优势越明显。

5. 简化编程模型

  • 问题:多线程同步(如锁、信号量)增加代码复杂度和调试难度。
  • 解决:单线程事件循环模型避免了锁竞争,代码逻辑更简洁,降低并发编程的复杂度。
  • Redis设计:单线程处理命令执行和网络I/O,通过异步机制(如后台线程处理持久化)平衡性能与功能。

6. 低延迟响应

  • 问题:传统多线程模型中,线程调度和锁竞争可能导致请求处理延迟波动。
  • 解决:单线程按事件顺序处理请求,无锁竞争,确保每个请求的响应时间更可预测。
  • 适用场景:对延迟敏感的应用(如实时排行榜、会话存储)。

本章小节

通过I/O多路复用,Redis在单线程中实现了:

  • 高并发连接管理
  • 非阻塞I/O操作
  • 低资源消耗与上下文切换
  • 高效事件驱动处理
  • 稳定低延迟响应

二、I/O多路复用高性能的本质

I/O 多路复用之所以能实现高性能,核心在于它通过一种高效的事件驱动机制,解决了传统阻塞 I/O 和多线程模型的根本性缺陷。以下是其速度快的本质原因:


1. 避免无意义的轮询:O(1) 事件检测

  • 传统模型(如 select/poll:需要遍历所有文件描述符(FD)检查状态,时间复杂度为 O(n),连接数越大效率越低。
  • 多路复用(如 epoll/kqueue
    • 事件回调机制:内核直接维护一个“就绪队列”,仅返回已就绪的事件列表,时间复杂度 O(1)
    • 示例:10 万个连接中只有 100 个活跃时,epoll 直接返回这 100 个事件,而 select 需遍历全部 10 万个。

2. 非阻塞 I/O + 零拷贝:最大化 CPU 利用率

  • 非阻塞 I/O:线程无需等待数据就绪,立即返回处理其他任务,避免 CPU 空转。
  • 零拷贝技术:通过 sendfile 或内存映射(mmap)减少数据在内核态和用户态之间的复制次数,降低 CPU 和内存开销。
  • 对比:传统阻塞 I/O 下,线程在等待数据时完全挂起,浪费 CPU 周期。

3. 单线程事件循环:无锁、无上下文切换

  • 单线程模型:所有 I/O 事件由单线程顺序处理,避免了多线程的锁竞争上下文切换开销。
  • 资源消耗极低:单线程管理数万连接,内存占用仅为多线程模型的 1/100 甚至更低。
  • 适用场景:Redis 的单线程设计正是利用这一点,在 CPU 不是瓶颈时实现超高吞吐量。

4. 高效的系统调用:内核级优化

  • epoll 的优势(Linux)
    • 红黑树管理 FD:快速插入、删除、查找,时间复杂度 O(log n)。
    • 事件驱动回调:通过 epoll_ctl 注册事件,内核直接通知就绪的 FD。
  • kqueue(BSD/MacOS):类似原理,支持更复杂的事件类型(如文件变化、信号)。

5. Reactor 模式:事件分发与业务逻辑解耦

  • 核心思想:将 I/O 事件监听(Reactor)与事件处理(Handler)分离。
  • 工作流程
    1. Reactor 监听所有 I/O 事件(如可读、可写)。
    2. 事件就绪后,分发给对应的 Handler(如 Redis 的命令处理器)。
    3. Handler 处理完成后,将结果写回网络缓冲区。
  • 优势:逻辑清晰,避免阻塞,适合高并发。

6. 边缘触发(ET) vs 水平触发(LT)

  • 水平触发(LT):只要 FD 处于就绪状态,每次调用 epoll_wait 都会返回该事件。
  • 边缘触发(ET):仅在 FD 状态变化时(如从不可读变为可读)触发一次事件。
  • ET 的优势:减少重复事件通知,强制开发者一次性处理完所有数据,避免饥饿问题,性能更高。

性能对比:传统模型 vs 多路复用

场景多线程阻塞 I/OI/O 多路复用
10 万并发空闲连接10 万线程,内存爆炸单线程,内存占用极低
CPU 利用率高(上下文切换)高(无阻塞)
延迟稳定性波动大(线程调度)稳定(单线程顺序处理)
代码复杂度高(锁、同步)低(事件驱动)

为什么 Redis 能单线程扛住 10 万 QPS?

  • 纯内存操作:数据在内存中处理,速度极快(纳秒级)。
  • I/O 多路复用:单线程高效管理所有网络事件。
  • 无锁设计:避免线程竞争,保证原子性。
  • 批量写入优化:通过缓冲区合并小数据包,减少系统调用次数。

本章小节:I/O 多路复用快的本质

  1. 事件驱动:只处理实际发生的 I/O 事件,避免无效轮询。
  2. 非阻塞 + 零拷贝:最大化 CPU 和内存效率。
  3. 单线程无锁:消除多线程开销,简化编程模型。
  4. 内核级优化epoll/kqueue 等机制的高效实现。

这种设计在高并发、低延迟场景(如 Redis、Nginx)中表现尤为突出,成为现代高性能服务器的基石。

三、基于I/O多路复用的Redis高性能设计源码分析

Redis 的 I/O 多路复用实现是其高性能的核心设计之一,源码中通过 事件驱动模型(Event Loop)结合操作系统提供的多路复用 API(如 epollkqueueselect)来实现。以下是关键源码模块的分析,结合 Redis 6.0 源码(代码片段已简化)。


1. 事件循环核心结构:aeEventLoop

Redis 通过 aeEventLoop 结构体管理所有事件(文件事件和时间事件),定义在 ae.h 中:

typedef struct aeEventLoop {
    int maxfd;                   // 当前注册的最大文件描述符
    int setsize;                 // 最大监听的文件描述符数量
    long long timeEventNextId;   // 下一个时间事件的ID
    aeFileEvent *events;         // 注册的文件事件数组(每个fd对应一个事件)
    aeFiredEvent *fired;         // 已触发的文件事件数组
    aeTimeEvent *timeEventHead;  // 时间事件链表头
    void *apidata;               // 多路复用API的私有数据(如epoll实例)
    // ...
} aeEventLoop;
  • events 数组:记录每个文件描述符(如客户端 Socket)的读写事件及回调函数。
  • fired 数组:存储每次事件循环中触发的就绪事件。
  • apidata:指向底层多路复用 API 的私有数据结构(如 epollepoll_event 列表)。

2. 多路复用 API 的抽象层

Redis 对不同操作系统的多路复用 API 进行了统一封装,代码在 ae_epoll.cae_kqueue.cae_select.c 中。以 epoll 为例:

aeApiCreate:初始化多路复用实例
static int aeApiCreate(aeEventLoop *eventLoop) {
    aeApiState *state = zmalloc(sizeof(aeApiState));
    state->epfd = epoll_create(1024); // 创建epoll实例
    state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize);
    eventLoop->apidata = state; // 绑定到aeEventLoop
    return 0;
}
aeApiAddEvent:注册事件到epoll
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
    aeApiState *state = eventLoop->apidata;
    struct epoll_event ee;
    ee.events = 0;
    if (mask & AE_READABLE) ee.events |= EPOLLIN;  // 读事件
    if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; // 写事件
    epoll_ctl(state->epfd, EPOLL_CTL_ADD, fd, &ee); // 注册到epoll
    return 0;
}

3. 事件循环主流程:aeMain

事件循环的核心逻辑在 aeMain 函数中,代码在 ae.c

void aeMain(aeEventLoop *eventLoop) {
    eventLoop->stop = 0;
    while (!eventLoop->stop) {
        // 1. 处理时间事件(如过期键清理)
        // 2. 处理文件事件(网络I/O)
        aeProcessEvents(eventLoop, AE_ALL_EVENTS | AE_CALL_BEFORE_SLEEP);
    }
}
关键函数 aeProcessEvents
int aeProcessEvents(aeEventLoop *eventLoop, int flags) {
    // 1. 计算最近的时间事件触发时间(决定epoll_wait的超时时间)
    long long maxWait = calculateMaxWaitTime(eventLoop);

    // 2. 调用多路复用API等待事件(如epoll_wait)
    int numevents = aeApiPoll(eventLoop, maxWait);

    // 3. 处理触发的文件事件
    for (int j = 0; j < numevents; j++) {
        aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd];
        if (fe->mask & AE_READABLE) {
            fe->rfileProc(eventLoop, eventLoop->fired[j].fd, fe->clientData, mask);
        }
        if (fe->mask & AE_WRITABLE) {
            fe->wfileProc(eventLoop, eventLoop->fired[j].fd, fe->clientData, mask);
        }
    }

    // 4. 处理时间事件(如定时任务)
    processTimeEvents(eventLoop);
    return numevents;
}
  • aeApiPoll:调用底层多路复用 API(如 epoll_wait)等待事件,返回就绪事件数量。
  • 事件回调:根据事件类型(读/写)执行预先注册的回调函数(如 rfileProcwfileProc)。

4. 文件事件处理:从连接建立到命令执行

Redis 的网络事件处理流程如下:

步骤1:监听客户端连接(acceptTcpHandler

当监听 Socket(如 6379 端口)有新的连接到达时,触发读事件,执行 acceptTcpHandler

void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
    int cfd, cport;
    char cip[NET_IP_STR_LEN];
    // 接受客户端连接
    cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
    // 创建客户端对象
    redisClient *client = createClient(cfd);
}
步骤2:注册客户端读事件(readQueryFromClient

为新客户端 Socket 注册读事件,回调函数为 readQueryFromClient

client *createClient(int fd) {
    client *c = zmalloc(sizeof(client));
    // 注册读事件到事件循环
    aeCreateFileEvent(server.el, fd, AE_READABLE, readQueryFromClient, c);
    // ...
}
步骤3:读取并解析命令(readQueryFromClient

当客户端发送数据时,触发读事件回调:

void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
    client *c = privdata;
    // 从Socket读取数据到客户端缓冲区
    nread = read(fd, c->querybuf + qblen, readlen);
    // 解析命令(如SET/GET)
    processInputBuffer(c);
}
步骤4:执行命令并写回结果

命令解析完成后,执行命令并将结果写入客户端输出缓冲区,注册写事件:

void processCommand(client *c) {
    // 查找命令并执行(如dictFind(server.commands, c->cmd->name))
    call(c, CMD_CALL_FULL);
    // 将响应写入客户端缓冲区
    if (clientHasPendingReplies(c)) {
        // 注册写事件,回调函数sendReplyToClient
        aeCreateFileEvent(server.el, c->fd, AE_WRITABLE, sendReplyToClient, c);
    }
}

5. 多路复用 API 的性能优化

epoll 的边缘触发(ET) vs 水平触发(LT)

Redis 默认使用 水平触发(LT) 模式:

  • 水平触发:只要 Socket 可读/可写,事件会持续触发,直到数据被处理完。
  • 边缘触发(ET):仅在 Socket 状态变化时触发一次,需一次性读取所有数据(可能需循环读取)。

Redis 选择 LT 的原因:

  1. 代码简洁性:避免处理 ET 模式下的“饥饿”问题(需循环读取直到 EAGAIN)。
  2. 兼容性:LT 模式在所有多路复用 API(如 selectpoll)中行为一致。
aeApiPoll 的实现(以epoll为例)
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
    aeApiState *state = eventLoop->apidata;
    int retval;
    // 调用epoll_wait,等待事件(最大阻塞时间由tvp决定)
    retval = epoll_wait(state->epfd, state->events, eventLoop->setsize,
                        tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
    // 将就绪事件填充到eventLoop->fired数组中
    for (int j = 0; j < retval; j++) {
        struct epoll_event *e = state->events + j;
        eventLoop->fired[j].fd = e->data.fd;
        eventLoop->fired[j].mask = e->events;
    }
    return retval; // 返回就绪事件数量
}

6. 单线程模型与 I/O 多路复用的协同

Redis 的单线程模型通过以下方式与 I/O 多路复用协同工作:

  1. 事件顺序处理:所有网络事件由单线程按顺序处理,避免锁竞争。
  2. 非阻塞 I/O:Socket 设置为非阻塞模式,确保 read/write 不会阻塞线程。
  3. 批量处理:通过一次 epoll_wait 获取所有就绪事件,批量处理。

本章小结:Redis I/O 多路复用的设计精髓

  1. 统一抽象层:封装不同操作系统的多路复用 API,保证跨平台兼容性。
  2. 事件驱动模型:通过 aeEventLoop 管理所有事件,实现高效调度。
  3. 非阻塞 + 回调:最大化 CPU 利用率,避免线程阻塞。
  4. 单线程无锁:消除多线程上下文切换和锁竞争的开销。

通过这种设计,Redis 在单线程中轻松支持数万甚至数十万的并发连接,成为高性能内存数据库的标杆。

四、使用java实现I/O多路复用模型

以下是一个基于 Java NIO 的 I/O 多路复用模型的完整实现示例。该示例将创建一个简单的 Echo 服务器,使用 Selector 实现单线程管理多个客户端连接。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NIOMultiplexingServer {

    private static final int PORT = 8080;
    private static final int BUFFER_SIZE = 1024;

    public static void main(String[] args) throws IOException {
        // 1. 创建Selector(多路复用器)
        Selector selector = Selector.open();

        // 2. 创建ServerSocketChannel并配置为非阻塞模式
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(PORT));
        serverSocketChannel.configureBlocking(false);

        // 3. 将ServerSocketChannel注册到Selector,监听ACCEPT事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server started on port " + PORT);

        // 4. 事件循环
        while (true) {
            // 阻塞等待就绪的事件(支持超时参数)
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;

            // 获取所有就绪的SelectionKey集合
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove(); // 必须手动移除已处理的key

                try {
                    if (key.isAcceptable()) {
                        handleAccept(key, selector);
                    } else if (key.isReadable()) {
                        handleRead(key);
                    } else if (key.isWritable()) {
                        handleWrite(key);
                    }
                } catch (IOException e) {
                    // 处理客户端异常断开
                    key.cancel();
                    key.channel().close();
                    System.out.println("Client disconnected abnormally");
                }
            }
        }
    }

    // 处理新连接
    private static void handleAccept(SelectionKey key, Selector selector) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        
        // 注册读事件,并附加一个Buffer用于数据读写
        clientChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(BUFFER_SIZE));
        System.out.println("New client connected: " + clientChannel.getRemoteAddress());
    }

    // 处理读事件
    private static void handleRead(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();

        int bytesRead = channel.read(buffer);
        if (bytesRead == -1) { // 客户端正常关闭
            System.out.println("Client closed connection: " + channel.getRemoteAddress());
            channel.close();
            return;
        }

        // 切换为读模式
        buffer.flip();
        byte[] data = new byte[buffer.remaining()];
        buffer.get(data);
        String message = new String(data);
        System.out.println("Received: " + message);

        // 注册写事件(准备回写数据)
        key.interestOps(SelectionKey.OP_WRITE);
        buffer.rewind(); // 重置position以便重新读取数据
    }

    // 处理写事件
    private static void handleWrite(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = (ByteBuffer) key.attachment();

        channel.write(buffer);
        if (!buffer.hasRemaining()) { // 数据已全部写入
            // 重新注册读事件
            key.interestOps(SelectionKey.OP_READ);
            buffer.clear(); // 重置Buffer
        }
    }
}

关键设计解析

1. Selector 核心机制
  • Selector.open():创建多路复用器(底层使用操作系统提供的 epoll/kqueue
  • select():阻塞等待就绪事件(可设置超时时间)
  • selectedKeys():获取所有就绪的事件集合
2. Channel 注册与事件类型
  • 注册事件类型
    • SelectionKey.OP_ACCEPT:新连接事件
    • SelectionKey.OP_READ:数据可读事件
    • SelectionKey.OP_WRITE:数据可写事件
  • 非阻塞模式configureBlocking(false) 是必须的
3. ByteBuffer 状态管理
  • flip():切换为读模式(position=0, limit=原position
  • clear():重置Buffer(position=0, limit=capacity
  • rewind():重置position为0(用于重复读取数据)
4. 事件处理流程
Client ServerSocketChannel Selector SocketChannel Server 发起连接 触发ACCEPT事件 调用handleAccept 注册READ事件 发送数据 触发READ事件 调用handleRead 注册WRITE事件 回写数据 调用handleWrite Client ServerSocketChannel Selector SocketChannel Server

性能优化点

1. 零拷贝优化
// 使用FileChannel直接传输文件(无需用户态内存拷贝)
FileChannel fileChannel = new FileInputStream("largefile.txt").getChannel();
fileChannel.transferTo(0, fileChannel.size(), socketChannel);
2. 批量处理事件
// 使用selectedKeys迭代器快速处理所有事件
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
    // ...处理每个key...
}
3. 对象池化
// 复用ByteBuffer对象(避免频繁GC)
private static final ThreadLocal<ByteBuffer> bufferCache = ThreadLocal.withInitial(
    () -> ByteBuffer.allocateDirect(1024) // 直接内存更高效
);

对比传统BIO模型

特性BIO(阻塞I/O)NIO(多路复用)
线程模型1连接1线程单线程管理所有连接
资源消耗高(线程内存、上下文切换)低(单线程+事件驱动)
吞吐量低(受限于线程数)高(万级并发)
编程复杂度简单较高(需处理事件状态机)

运行测试

  1. 编译运行服务端:

    javac NIOMultiplexingServer.java
    java NIOMultiplexingServer
    
  2. 使用 telnetnc 测试:

    telnet localhost 8080
    > Hello  # 输入任意内容,服务器会原样返回
    

扩展方向

  1. 多线程优化:将业务处理与I/O线程分离(如使用线程池处理复杂逻辑)
  2. 协议解析:实现HTTP等复杂协议(需处理半包/粘包问题)
  3. 心跳机制:添加空闲连接检测(通过 IdleStateHandler 类似机制)

通过这种方式,你可以用 Java 原生 NIO 实现一个高性能的 I/O 多路复用服务端,支撑高并发网络请求。

注意:本文章不适合初级人员使用,建议先了解NIO、BIO和Netty的前提之下进行学习

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动驾驶与机器人算法学习

自动驾驶与机器人算法学习 直播与学习途径 欢迎你的点赞关注~

【网络编程】TCP数据流套接字编程

目录 一. TCP API 二. TCP回显服务器-客户端 1. 服务器 2. 客户端 3. 服务端-客户端工作流程 4. 服务器优化 TCP数据流套接字编程是一种基于有连接协议的网络通信方式 一. TCP API 在TCP编程中&#xff0c;主要使用两个核心类ServerSocket 和 Socket ServerSocket Ser…

从零开始配置 Zabbix 数据库监控:MySQL 实战指南

Zabbix作为一款开源的分布式监控工具&#xff0c;在监控MySQL数据库方面具有显著优势&#xff0c;能够为数据库的稳定运行、性能优化和故障排查提供全面支持。以下是使用Zabbix监控MySQL数据库的配置。 一、安装 Zabbix Agent 和 MySQL 1. 安装 Zabbix Agent services:zabbix…

Java学习手册:RESTful API 设计原则

一、RESTful API 概述 REST&#xff08;Representational State Transfer&#xff09;即表述性状态转移&#xff0c;是一种软件架构风格&#xff0c;用于设计网络应用程序。RESTful API 是符合 REST 原则的 Web API&#xff0c;通过使用 HTTP 协议和标准方法&#xff08;GET、…

读一篇AI论文并理解——通过幻觉诱导优化缓解大型视觉语言模型中的幻觉

目录 论文介绍 标题 作者 Publish Date Time PDF文章下载地址 文章理解分析 &#x1f4c4; 中文摘要&#xff1a;《通过幻觉诱导优化缓解大型视觉语言模型中的幻觉》 &#x1f9e0; 论文核心动机 &#x1f680; 创新方法&#xff1a;HIO&#xff08;Hallucination-In…

IOT项目——物联网 GPS

GeoLinker - 物联网 GPS 可视化工具 项目来源制作引导 项目来源 [视频链接] https://youtu.be/vi_cIuxDpcA?sigMaOKv681bAirQF8 想要在任何地方追踪任何东西吗&#xff1f;在本视频中&#xff0c;我们将向您展示如何使用 ESP32 和 Neo-6M GPS 模块构建 GPS 跟踪器——这是一…

Java学习手册:HTTP 协议基础知识

一、HTTP 协议概述 HTTP&#xff08;HyperText Transfer Protocol&#xff09;即超文本传输协议&#xff0c;是用于从万维网&#xff08;WWW&#xff1a;World Wide Web &#xff09;服务器传输超文本到本地浏览器的传输协议。它是一个应用层协议&#xff0c;基于请求-响应模型…

【含文档+PPT+源码】基于微信小程序的健康饮食食谱推荐平台的设计与实现

课程目标&#xff1a; 教你从零开始部署运行项目&#xff0c;学习环境搭建、项目导入及部署&#xff0c;含项目源码、文档、数据库、软件等资料 课程简介&#xff1a; 本课程演示的是一款基于微信小程序的健康饮食食谱推荐平台的设计与实现&#xff0c;主要针对计算机相关专…

Redis 慢查询分析与优化

Redis 慢查询分析与优化 参考书籍 &#xff1a; https://weread.qq.com/web/reader/d5432be0813ab98b6g0133f5kd8232f00235d82c8d161fb2 以下从配置参数、耗时细分、分析工具、优化策略四个维度深入解析 Redis 慢查询问题&#xff0c;结合实战调优建议&#xff0c;帮助开发者…

使用达梦官方管理工具SQLark快速生成数据库ER图并导出

在数据库设计与开发中&#xff0c;实体-关系图&#xff08;ER 图&#xff09;作为数据建模的核心工具&#xff0c;能够直观呈现表结构、字段属性及表间关系&#xff0c;是团队沟通和文档维护的重要工具。然而&#xff0c;许多开发者在实际工作中常面临一个痛点&#xff1a;手动…

模型 替罪羊效应

系列文章分享模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。转嫁罪责于无辜&#xff0c;维系群体控制与稳定 1 替罪羊效应的应用 1.1 多品牌危机中的行业“背锅侠” 行业背景&#xff1a;食品行业爆发大规模安全危机&#xff0c;多家企业卷入某类食品重金属超标…

TapData × 梦加速计划 | 与 AI 共舞,TapData 携 AI Ready 实时数据平台亮相加速营,企业数据基础设施现代化

在实时跃动的数据节拍中&#xff0c;TapData 与 AI 共舞&#xff0c;踏出智能未来的新一步。 4月10日&#xff0c;由前海产业发展集团、深圳市前海梦工场、斑马星球科创加速平台等联合发起的「梦加速计划下一位独角兽营」正式启航。 本次加速营以“打造下一位独角兽企业”为目…

15.电感特性在EMC设计中的运用

电感特性在EMC设计中的运用 1. 共模电感与差模电感的差异2. 电感的高频等效特性![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b4dc000672af4dd69a528450eb42cf10.png)3. 电感在EMC设计中的使用注意事项3.1 LC滤波计算3.2 并联型多级浪涌防护的电感退耦 1. 共模电感…

uniapp Vue2升级到Vue3,并发布到微信小程序的快捷方法

目录 前言&#xff1a;升级项目的两种方式步骤一、新建项目 【选择-默认模版】二、修改-pages.json三、补充-缺少的文件四、修改-Main.js按照 [官方文档-vue2升级vue3迁移指南](https://uniapp.dcloud.net.cn/tutorial/migration-to-vue3.html) 修改 五、升级-uni-ui扩展组件的…

数据重构如何兼顾效率与性能稳定?zStorage 全闪存分布式存储的技术实践与实测数据

点击蓝字 关注我们 zStorage 作为数据库场景下的全闪存分布式存储&#xff0c;除了性能要好&#xff0c;更重要的是要在各种情况下都能保持“稳定”的好。一个高并发的交易型业务数据库&#xff0c;如果出现轻微的IO抖动&#xff0c;就可能造成数据库并发事务提交的排队&#x…

A2A + MCP:构建实用人工智能系统的超强组合

构建真正有效的连接型人工智能系统的挑战 如果你正在构建人工智能应用&#xff0c;这种情况可能听起来很熟悉&#xff1a; 你需要特定的人工智能能力来解决业务问题。你找到了完成每个单独任务的出色工具。但把所有东西连接在一起却占据了大部分开发时间&#xff0c;还创建了…

力扣每日打卡17 49. 字母异位词分组 (中等)

力扣 49. 字母异位词分组 中等 前言一、题目内容二、解题方法1. 哈希函数2.官方题解2.1 前言2.2 方法一&#xff1a;排序2.2 方法二&#xff1a;计数 前言 这是刷算法题的第十七天&#xff0c;用到的语言是JS 题目&#xff1a;力扣 49. 字母异位词分组 (中等) 一、题目内容 给…

Word处理控件Spire.Doc系列教程:C# 为 Word 文档设置背景颜色或背景图片

在 Word 文档中&#xff0c;白色是默认的背景设置。一般情况下&#xff0c;简洁的白色背景足以满足绝大多数场景的使用需求。但是&#xff0c;如果您需要创建简历、宣传册或其他创意文档&#xff0c;设置独特的背景颜色或图片能够极大地增强文档的视觉冲击力。本文将演示如何使…

掌握 Altium Designer:轻松定制“交换器件”工具栏

在PCB设计过程中&#xff0c;快速交换器件&#xff08;如电阻、电容、IC等&#xff09;是提高效率的关键。Altium Designer提供了灵活的工具栏定制功能&#xff0c;让用户可以创建专属的"交换器件"工具栏&#xff0c;将常用操作集中管理&#xff0c;减少菜单切换时间…

【C++ Qt】信号和槽(内配思维导图 图文并茂 通俗易懂)

每日激励&#xff1a;“不设限和自我肯定的心态&#xff1a;I can do all things。 — Stephen Curry” 绪论​&#xff1a; 本章是Qt中的第三章&#xff0c;也是我们理解Qt中必备的点 信号槽&#xff0c;它本质由信号和槽两个来实现&#xff0c;其中将细致的讲述如何自定义信号…