浅谈Java的网络编程,带你领略Java的世界

第31篇 · P2·一:RAG 知识库:架构与检索链路(切分入库/混合检索 RRF/Rerank 精排) 实战篇第二个项目「RAG 知识库」共两篇(第 31–32 篇),本篇是开篇,做架构与检索链路。做完你会得到文档摄入(递归切分 + 元数据入库)、向量 + BM25 混合检索(RRF 融合)、CrossEncoder Rerank 精排的完整可跑代码。它与 P1 的关系:P1(第 26–30 篇,数据分析 Agent)攻结构化数仓(Text-to-SQL),本项目攻非结构化知识(RAG),两者是数据工程师最常见的两类"让数据可问答"场景,简历上两个并存覆盖面才够。 阅读详情

昨天在坐车回家,打卡了一道力扣,完事之后闲的无聊,看到自己之前收藏了Akka,依稀记得这是一个比线程小的执行单元(当时是这么理解的),加上之前学Golang,看到了那种无脑开Goroutine处理请求的方式,自己又刚好苦于WebFlux+Reactor写出来的那一堆狗屎代码。于是想:为什么Java没有这种东西,可以无脑开轻量级线程处理?

我对于NIO似乎也就仅限于使用了,写一写NIO的单线程,多线程,主从多线程的Echo Server。用用Netty写写HelloWorld之类的。一直没有深究一些实现,想起来挂在宿舍床上的“治学严谨”的牌子(这牌子也有来历的),未免心生惭愧,于是利用坐车时间好好Google了一番,加上自己的理解,决定写下这篇文章。

何为I/O?

既然是NIO/BIO/AIO,我们就必须先要搞懂I/O是什么。我之前以为是单纯的读写操作,后来发现没有这么简单。

I/O单按照字面翻译来说的话,是输入/输出。比如从磁盘读/向磁盘写,从网卡接收数据/向网卡写入数据,数据库查询/增删改数据库。但凡 涉及到磁盘和网络的操作,我们统称为I/O操作 。我先给出一个这么笼统的定义。

何为阻塞?

在了解阻塞之前,我们先了解 各个设备的速度 :

CPU:1ns,寄存器:1ns,高速缓存 10ns,内存:10us,磁盘10ms,网络:100ms

其中:1s=1000ms,1ms=1000us,1us=1000ns,1ns=1000ps。

I/O是操作磁盘或网络的,而I/O操作与内存操作的速度差了1000倍不止,与CPU差了10^6不止,所以在CPU眼中,任何I/O操作都是很漫长的。

而所谓的阻塞指的是CPU等待I/O操作完成的过程。当某个线程想要读取来自网络的数据,需要先等待网卡准备就绪,然后数据传输,然后由内核把数据拷贝到用户内存,这一系列。写入需要等到网卡准备就绪,且前面排队的写请求全部完成,然后数据传输完成,这是一个很漫长的过程。

而 网络编程时说的阻塞指的就是这个漫长的网络过程 ,CPU啥也不干,干等数据到来,干等数据被写出。程序就被停在这里了,也不会往下面执行。

我们来看一张图好了。

蓝色部分即为阻塞,网络操作时的阻塞过程。

BIO/NIO/AIO

写过Socket通信,不论你是什么语言,基本都是这样的流程:

新建一个ServerSocket=>设置监听地址=>accept一个连接并返回一个Socket=>继续监听,新的线程处理刚刚返回的Socket。

这没什么不好的,一个很普通的Socket/ServerSocket服务器是吧!早期的Tomcat就是这样的。

现在我们来看看这整个过程中涉及到线程的部分,就是新建线程处理Socket的部分,为什么要这么做呢?

因为 每一个Socket的网络的读/写都是一个耗时的过程,如果我们不开辟新的线程,就会阻塞后面的连接 ,这样整个系统的连接数,吞吐量就会大幅下降。

每个连接一个线程,似乎是一种不错的方案,也很好地解决了无法多连接的问题。但是每个连接一个线程,会不会在系统连接数很高的情况下崩溃呢?毕竟Java线程就是操作系统线程,而且Linux下一个线程接近于一个进程,创建销毁调度的开销一点都不小。即使我们有线程池这种东西,那也毕竟是有限的,况且线程池维护也是一种负担,有没有一种解决方案呢?

到目前为止BIO结束了,接下来就是引入NIO/AIO的时候了,这里需要说明一下,NIO指的是非阻塞IO,即每次读操作时不像BIO那样等待有数据可读,而是直接返回,返回值代表可读数据大小,为-1表示不可读,所以NIO的非阻塞是在这里实现的, NIO仅仅是read操作立刻返回,而不会等待读到了数据再返回。写操作同理 。

所以现在说NIO一般是NIO+I/O多路复用产生的NIO,AIO同理。请读者知悉,下文的NIO都是NIO+I/O多路复用技术的处理方式。

好了,来看看I/O多路复用。

到目前为止我们知道 Socket处理之所以这么慢,是因为网络读/写太慢了 (这里我们暂时忽略业务逻辑中的耗时操作,比如查询数据库等), 线程在干等,所以产生了阻塞 。此外,我们知道,在程序被阻塞时,CPU是不干活的,这个期间我们为什么不能让另一个线程去执行呢?但这又引入一个新的问题,如果我调度了另一个线程,那当数据准备好时,或者可以写时,我该怎么得知呢?

嗯...嗅到了一丝异步+回调的味道。既然程序么得办法,那我们去看看操作系统能给我们提供什么解决方案吗?

Linux提供了Epoll(select和poll现在基本不用,我就不说了),macOS提供了Kqueue,Windows提供了IOCP。它们是 I/O多路复用技术 。

什么是I/O多路复用呢?首先需要知道在Linux中,万物皆文件,所以引入了文件描述符的概念,即FD。对于某一个远程进程的读写就是对于一个抽象文件的读写,所以它也有一个FD,而每一个Socket对应一个远程进程,所以每个Socket变成了一个FD。

通过对中断程序注册一个处理函数,可以实现每次网卡中断时,把这个中断对应的FD添加到就绪列表中,Epoll的select操作就是遍历就绪列表,找到每个FD对应的Socket,返回。如果为空就阻塞。当有新的中断产生时被唤醒。

现在我们把远程进程可读/可写这件事丢给了操作系统,操作系统使用网卡中断实现异步通知。就可以解放我们的程序了,而不必让它干等了。

所以就有了Selector+SelectionKey+Channel+Buffer那一套。来看一个典型的Echo服务器,基于NIO+I/O多路复用实现的。

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.channels.spi.AbstractSelectableChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @author CodeWithBuff
 */
public class NioTcpSingleThread {

    public static void main(String[] args) {
        NioTcpSingleThread.Server.builder().build().run();
    }

    private static final HashMap<SocketChannel, List<DataLoad>> dataLoads = new LinkedHashMap<>();

    private static final ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class DataLoad {
        private int intValue;

        private long longValue;

        private double doubleValue;

        private String stringValue;

        private int[] intArray;

        private long[] longArray;

        private double[] doubleArray;

        private String[] stringArray;
    }

    /**
     * Java NIO处理网络的核心组件只有四个:{@link Channel},{@link Selector},{@link SelectionKey}和{@link java.nio.Buffer}
     * <br/>
     * 说一下{@link ServerSocketChannel},{@link SocketChannel},{@link Selector}和{@link SelectionKey}之间的关系。
     * <br/>
     * {@link ServerSocketChannel}和{@link SocketChannel}不说了,无非就是一个用来在服务端建立连接,一个处理连接(实际I/O交互)的区别,在这里统称为{@link AbstractSelectableChannel},也就是它俩都继承的类。
     * <br/>
     * {@link Selector#select()}调用系统调用,轮询端口,记录已注册的{@link AbstractSelectableChannel}感兴趣的事件,如果发生了所有已注册的{@link AbstractSelectableChannel}感兴趣的事件之一的话,就返回。否则阻塞。
     * <br/>
     * 对于{@link AbstractSelectableChannel}来说,怎么让{@link Selector}帮自己记录并轮询自己感兴趣的事件呢?答案是:注册到{@link Selector}上即可,同时设置感兴趣的事件类型。
     * <br/>
     * 在注册成功后,会返回一个{@link SelectionKey}类型的变量,通过它,可以操作{@link AbstractSelectableChannel}和{@link Selector}。{@link SelectionKey}本身就是{@link AbstractSelectableChannel}和它注册到的{@link Selector}的凭证。
     * 就像是订单一样,记录着它们俩的关系,所以在注册成功的后续操作里,一般都是用{@link SelectionKey}来实现的。同时,{@link SelectionKey}还有一个attachment()方法,可以获取附加到它上面的对象。
     * 一般我们用这个附属对象来处理当前{@link SelectionKey}所包含的{@link AbstractSelectableChannel}和{@link Selector}的实际业务。
     * <br/>
     * 刚才说到了{@link Selector#select()},它会一直阻塞直到发生了感兴趣的事件,但是有时候我们这边可以确定某一事件马上或已经发生,就可以调用{@link Selector#wakeup()}方法,让{@link Selector#select()}立即返回,然后获取
     * {@link SelectionKey}集合也好,重新{@link Selector#select()}(这已经是下一次循环了)也罢。
     * <br/>
     * <br/>
     * 注意!!!如果某一个{@link AbstractSelectableChannel}在同一个{@link Selector}上注册了两个不同的感兴趣的事件类型,那么返回的两个{@link SelectionKey}是没有任何关系的。虽然可以通过{@link SelectionKey}再次修改
     * {@link AbstractSelectableChannel}感兴趣的事件类型。{@link SelectionKey}只在注册时生成返回,所以有(Channel + Selector) = SelectionKey。但是吧,啧,注册多个时会卡死,所以千万不要同一个Channel和同一个Selector注册多个!!!
     */
    @Builder
    private static class Server implements Runnable {

        @Override
        public void run() {
            System.out.println("Server开始运行...");
            Selector globalSelector;
            ServerSocketChannel serverSocketChannel;
            SelectionKey serverSelectionKey;
            try {
                globalSelector = Selector.open();
                serverSocketChannel = ServerSocketChannel.open();
                serverSocketChannel.bind(new InetSocketAddress(8190));
                serverSocketChannel.configureBlocking(false);
                serverSelectionKey = serverSocketChannel.register(globalSelector, SelectionKey.OP_ACCEPT);
                serverSelectionKey.attach(Acceptor.builder()
                        .globalSelector(globalSelector)
                        .serverSocketChannel(serverSocketChannel)
                        .build()
                );
                while (true) {
                    // select()是正儿八经的阻塞方法,它会一直阻塞直到发生了任何注册过的(Server)SocketChannel感兴趣的事件之一。比如有新的连接建立,Channel可以读了,或者Channel可以写了
                    // 它的返回值指出了有几个感兴趣事件,实际没啥用,所以在此直接忽略
                    globalSelector.select();
                    Set<SelectionKey> selectionKeySet = globalSelector.selectedKeys();
                    for (SelectionKey selectionKey : selectionKeySet) {
                        dispatch(selectionKey);
                        selectionKeySet.remove(selectionKey);
                    }
                }
            } catch (IOException ignored) {
            }
        }

        private void dispatch(SelectionKey selectionKey) {
            Runnable runnable = (Runnable) selectionKey.attachment();
            runnable.run();
        }
    }

    @Data
    @Builder
    private static class Acceptor implements Runnable {

        private final Selector globalSelector;

        private final ServerSocketChannel serverSocketChannel;

        @Override
        public void run() {
            try {
                SocketChannel socketChannel = serverSocketChannel.accept();
                System.out.println("已建立连接...");
                socketChannel.configureBlocking(false);
                SelectionKey socketSelectionKey = socketChannel.register(globalSelector, SelectionKey.OP_READ);
                socketSelectionKey.attach(Handler.builder()
                        .socketSelectionKey(socketSelectionKey)
                        .build()
                );
                // 此时注册了读感兴趣Channel,所以为了快速开启读,直接唤醒selector。其实就是让它别等了,我这边准备好了,你那边应该已经有数据了,直接返回吧。
                globalSelector.wakeup();
            } catch (IOException ignored) {
            }
        }
    }

    /**
     * "写"操作依赖于"读"操作读取到的数据,所以"写"之后不能再次"写",必须"读"或"关闭"。
     * <br/>
     * "读"操作之后可以继续"读"而无需等待"写完成",所以"写完"可以把感兴趣类型设置为"读"|"写"而不是单单的"写"。
     */
    @Data
    @Builder
    private static class Handler implements Runnable {

        private final SelectionKey socketSelectionKey;

        @Override
        public void run() {
            SocketChannel socketChannel = (SocketChannel) socketSelectionKey.channel();
            if (!socketChannel.isOpen()) {
                System.out.println("连接已关闭");
                try {
                    socketChannel.shutdownInput();
                    socketChannel.shutdownOutput();
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return ;
            }
            if (socketSelectionKey.isReadable()) {
                System.out.println("读事件发生,准备读...");
                Reader.builder()
                        .socketChannel(socketChannel)
                        .build()
                        .run();
                // 说明即对读感兴趣,也对写感兴趣(因为客户端可能是长连接,还要再次发送消息),但是同一个SelectionKey只能是读或写之一
                socketSelectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                // 读完了,就要准备写
                socketSelectionKey.selector().wakeup();
            }
            if (socketSelectionKey.isWritable()) {
                System.out.println("写事件发生,准备写...");
                Writer.builder()
                        .socketChannel(socketChannel)
                        .build()
                        .run();
                socketSelectionKey.interestOps(SelectionKey.OP_READ);
                // 写完了,立即返回就免了
                // socketSelectionKey.selector().wakeup();
            }
        }
    }

    @Data
    @Builder
    private static class Reader implements Runnable {

        private final SocketChannel socketChannel;

        @Override
        public void run() {
            try {
                byteBuffer.clear();
                int readable = socketChannel.read(byteBuffer);
                byte[] bytes = byteBuffer.array();
                String value = new String(bytes, 0, readable);
                System.out.println("读到了: " + value);
                DataLoad dataLoad = DataLoad.builder()
                        .stringValue(value)
                        .build();
                List<DataLoad> tmp = dataLoads.computeIfAbsent(socketChannel, k -> new LinkedList<>());
                tmp.add(dataLoad);
            } catch (IOException ignored) {
            }
        }
    }

    @Data
    @Builder
    private static class Writer implements Runnable {

        private final SocketChannel socketChannel;

        @Override
        public void run() {
            try {
                String value = "Server get: " + dataLoads.get(socketChannel).get(0).getStringValue();
                dataLoads.get(socketChannel).remove(0);
                socketChannel.write(ByteBuffer.wrap(value.getBytes(StandardCharsets.UTF_8)));
            } catch (IOException ignored) {
            }
        }
    }
}
复制代码

NIO做到了数据可读/可写时的通知机制,然后去读,去写,而AIO则是直接做到了数据传输到指定区域,说白了就是不需要自己去读,自己去写,当它被调用时数据已经全部准备好了,更加地异步。但是Linux和实际生产使用不多,我们就不提了,只给出一个示例代码:

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author CodeWithBuff
 */
public class AioTcpSingleThread {

    public static void main(String[] args) {
        Server.builder().build().run();
        // 防止主线程退出
        LockSupport.park(Long.MAX_VALUE);
    }

    private static final ConcurrentHashMap<AsynchronousSocketChannel, LinkedBlockingQueue<DataLoad>> dataLoads = new ConcurrentHashMap<>();

    private static final ReentrantLock READ_LOCK = new ReentrantLock();

    private static final ReentrantLock WRITE_LOCK = new ReentrantLock();

    private static final ByteBuffer READ_BUFFER = ByteBuffer.allocate(1024 * 4);

    private static final ByteBuffer WRITE_BUFFER = ByteBuffer.allocate(1024 * 4);

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class DataLoad {
        private int intValue;

        private long longValue;

        private double doubleValue;

        private String stringValue;

        private int[] intArray;

        private long[] longArray;

        private double[] doubleArray;

        private String[] stringArray;
    }

    @Builder
    private static class Server implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println("服务器启动...");
                asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
                asynchronousServerSocketChannel.bind(new InetSocketAddress(8190));
                asynchronousServerSocketChannel.accept(null, ACCEPTOR);
            } catch (IOException ignored) {
            }
        }
    }

    private static AsynchronousServerSocketChannel asynchronousServerSocketChannel = null;

    private static final Acceptor ACCEPTOR = new Acceptor();

    private static class Acceptor implements CompletionHandler<AsynchronousSocketChannel, Object> {
        // 这个方法是异步调用的,所以不用担心阻塞会阻塞到主线程
        @Override
        public void completed(AsynchronousSocketChannel result, Object attachment) {
            System.out.println("连接建立: " + Thread.currentThread().getName());
            System.out.println("连接建立");
            dataLoads.computeIfAbsent(result, k -> new LinkedBlockingQueue<>());
            // 使用循环来进行多次读取,写入
            while (result.isOpen()) {
                READ_LOCK.lock();
                // 这个方法也是异步的
                result.read(READ_BUFFER, attachment, new Reader(result, READ_BUFFER.array()));
                READ_BUFFER.clear();
                READ_LOCK.unlock();
                WRITE_LOCK.lock();
                String ans = "";
                try {
                    ans = "Server get: " + dataLoads.get(result).take().getStringValue();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 异步的
                result.write(ByteBuffer.wrap(ans.getBytes(StandardCharsets.UTF_8)), attachment, new Writer(result));
                WRITE_LOCK.unlock();
            }
            System.out.println("结束通信一次");
            // 尝试建立第二波通信
            asynchronousServerSocketChannel.accept(attachment, ACCEPTOR);
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            System.out.println("建立连接失败");
        }
    }

    private static class Reader implements CompletionHandler<Integer, Object> {

        private final AsynchronousSocketChannel asynchronousSocketChannel;

        private final byte[] bytes;

        public Reader(AsynchronousSocketChannel asynchronousSocketChannel, byte[] bytes) {
            this.asynchronousSocketChannel = asynchronousSocketChannel;
            this.bytes = bytes;
        }

        @Override
        public void completed(Integer result, Object attachment) {
            System.out.println("读取数据: " + Thread.currentThread().getName());
            if (result == 0 || !asynchronousSocketChannel.isOpen()) {
                return ;
            } else if (result < 0) {
                shutdown(asynchronousSocketChannel);
                return ;
            }
            System.out.println("读取数据: " + result);
            String value = new String(bytes, 0, result);
            System.out.println("读到了: " + value);
            LinkedBlockingQueue<DataLoad> tmp = dataLoads.get(asynchronousSocketChannel);
            DataLoad dataLoad = DataLoad.builder()
                    .stringValue(value)
                    .build();
            tmp.add(dataLoad);
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            System.out.println("读取失败");
        }
    }

    private static class Writer implements CompletionHandler<Integer, Object> {

        private final AsynchronousSocketChannel asynchronousSocketChannel;

        public Writer(AsynchronousSocketChannel asynchronousSocketChannel) {
            this.asynchronousSocketChannel = asynchronousSocketChannel;
        }

        @Override
        public void completed(Integer result, Object attachment) {
            System.out.println("写入数据: " + Thread.currentThread().getName());
            if (!asynchronousSocketChannel.isOpen()) {
                return ;
            }
            System.out.println("写入数据: " + result);
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            System.out.println("写入失败");
        }
    }

    private static void shutdown(AsynchronousSocketChannel asynchronousSocketChannel) {
        try {
            asynchronousSocketChannel.shutdownInput();
            asynchronousSocketChannel.shutdownOutput();
            asynchronousSocketChannel.close();
        } catch (IOException ignore) {
        }
    }
}
复制代码

NIO解决了什么?未解决什么?

NIO实现了一个线程管理多个连接的I/O操作,而不用像BIO每个连接一个线程,本质是通过中断机制+系统调用实现的。这样就可以在一个线程处理所有可用的I/O事件。

注意,如果一个Selector(一般来说一个Selector对应一个线程,一个线程对应多个Selector会降低Selector效率)仅注册一个连接,那NIO和BIO就没什么区别了。

还记得我们为什么从BIO走到了NIO吗?是因为BIO无法抗住大量的连接,所以 NIO解决了大连接的问题 。

但是 NIO并不会带来每个请求的速度的提升 ,这点请记住,甚至在连接数不多时,处理速度还不如BIO。

NIO使用注意事项

前面我们假设NIO中不要出现耗时业务,但是如果必须有,比如数据库操作,那怎么办呢?在这里我们参考NIO框架Netty的实现。

Netty建议对于耗时操作,应该通过传入的自定义线程池处理,即把这个任务提交到线程池,然后添加异步调用,当任务处理完毕,继续下一步处理。总之耗时任务线程池处理,普通任务直接在NIO线程处理就好。

2026年软考系统架构师案例分析:ATAM 架构评估怎么答? 8 月已过,下半年架构师报名月底就要启动了。上半年 5 月的真题复盘咱们已经收官,从这篇开始,开启新的系列——,专挑案例题里分值最高、最容易丢分的那几类硬骨头。第一块硬骨头,就是。先还原这类真题怎么考(这里是演练)的:案例题给你一段架构场景描述——比如某电商系统"为了提升性能,将商品数据全量放入分布式缓存""为了便于扩展,将下单服务拆分为 20 个微服务"——然后连问三刀:请指出该架构中的并说明理由;请指出该架构中的;请指出该架构中的,并分析其中涉及的质量属性。 阅读详情

相关推荐

架构专栏|论无服务器架构(serverless)

无服务器架构(serverless)是一种以函数即服务与后端即服务为核心的云计算范式,其核心思想是开发者无需关注服务器管理、资源调度与运维工作,仅需聚焦业务逻辑代码编写,平台自动完成资源分配、弹性扩缩容及故障恢复。该架构通过抽象基础设施层,将应用运行环境与计算资源交由云服务商管理,实现"按需使用、按量计费"的开发与运行模式,显著降低了软件开发与运维的复杂度。

科技互联网领域深度探索者,乐于分享与写作。 29

Java软件开发优质文章整理

文章目录1. 算法2. 操作系统3. 网络4. 面向对象 设计模式5. 数据库6. Java1. Java基础3. Java容器4. Java MVC5. java并发6. Java Web7. Java IO7. 系统设计8. 工具9. 框架10. 分布式 缓存 集群11. 面经 整理一些个人感觉还不错的文章,以便大家学习。 1. 算法 1234 2. 操作系统 List item 3...

ArcherLu的博客 877

如何在不牺牲精度的前提下,把 YOLOv11 压缩到 Jetson 能跑的程度

YOLOv11 在目标检测任务中具备较强的准确性与多尺度鲁棒性,但其原始模型参数规模与计算量,对边缘设备来说并不友好,特别是面向 Jetson 这类资源受限的推理平台。本文基于实际部署场景,逐步拆解从结构层级改造、剪枝策略制定、量化导出流程,到 Jetson TensorRT 推理落地全过程。在不牺牲主干精度的前提下,最终实现 YOLOv11 推理延迟缩短 3 倍、模型体积压缩 4 倍、mAP 损失控制在 1% 以内的轻量化优化结果,并总结出一条可复用的工程路径,供工业界与边缘侧模型部署开发者参考。

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 2391

从 GLM-5.3-Flash 看“智能平权”:低成本模型背后的架构协同与业务评测方法

我是安徽最忧郁程序员无隅本文是我学习《这一次,智谱用 GLM-5.3-Flash 实现了真正的智能平权》以及智谱官方技术资料后整理的研究笔记。原文中的近 1000 条资讯回测、精选率与垃圾拦截率、前端生成案例,以及官方披露的国产芯片集群部署和性能提升,。我在本文中做的事情,是梳理这些材料背后的技术链路、分析评测结论的适用边界,并设计一套未来可以实际复现的评测方案。凡是来自原文作者或官方的结果,文中都会明确标注来源。

2301_80956187的博客 211

Redis 与 MySQL 数据一致性:从原理到架构实战

如果写操作是"更新缓存",在并发场景下,可能出现:线程 A 更新 DB 为 100 → 线程 B 更新 DB 为 200 → 线程 B 更新缓存为 200 → 线程 A 更新缓存为 100。最终缓存值 100 与 DB 值 200 不一致。而"删除缓存"后,下次读请求会重新从 DB 加载最新值,天然避免了上述并发写入导致的脏数据问题。

m0_58600248的博客 366

架构专栏】2.7 多媒体 知识点

本文主要是对架构师考试大纲中的 第2章 计算机胸痛基础知识之 《2.7多媒体》知识点进行整理,主要包括多媒体的定义、分类、特征、多媒体系统的基本组成、多媒体技术应用、多媒体系统的关键技术。多媒体系统的关键技术包括4大分类:视音频技术、通信技术、数据压缩技术、虚拟现实 VR和增强现实AR。本文对计算机相关知识进行详细归纳,对相关同学有一定帮助。

weixin_42081167的博客 387

具身智能架构变局:本能驱动兴起,技术路线博弈仍在持续

本能驱动的核心思路:不依靠海量任务样本做全任务预训练,在底层内置基础物理交互本能单元,高层负责语义任务理解,当传感器采集到力、触觉、视觉实时信号,底层本能单元直接完成毫秒级响应调节,减少对大模型高频推理的依赖。从CLIPort开启视觉‑语言联合表征,再到RT‑1、RT‑2把机器人动作当做Token处理,π₀进一步把端到端VLA推向灵巧操作,加上OpenVLA等开源项目降低研究门槛,该路线实现极强的语义泛化。当下云‑边‑端协同的价值,已经从“算力不够只能上云”,转向成本优化、多机协同调度、行业知识持续迭代。

2601_96727407的博客 168

【手搓 Agent 第2.3关】搭建 Agent 进阶能力:工具注册中心架构重构

本篇优化了之前编写 Agent 时的杂乱硬编码,使用工具注册中心统一调度,促使后期增添工具更加容易。顺便优化了 RAG 知识库的懒加载,使 Agent 启动更快。

2502_92964924的博客 336

实战解析:LiteLLM 路由熔断、多阶重试与高可用降级(Fallback)架构机制

在大模型网关的建设中,高可用不能只寄希望于单点的稳定,而必须通过工程化的流量分摊、极速熔断与多级容灾:提供充足的重试预算,确保多级 Fallback 链条能被 100% 完整遍历;:单次失败即刻隔离,杜绝在已知故障节点上盲目重试;:匹配供应商的分钟级配额窗口,确保节点冷却后满血归队;主备分层调度:日常使用主力免费池,高并发或故障时自动升舱与兜底,兼顾了成本控制与系统可用性。

nvd11的专栏 532

线上Web异常发现为什么总是滞后?一套自研Web实时异常分析监控系统整体架构拆解

<think>我们根据要求,需要生成≤150字的文章摘要。文章内容是关于Web端实时异常分析监控系统设计,包括痛点、架构、能力等。摘要需要概括核心内容。注意字数限制。</think>本文围绕Web端实时异常分析监控系统,剖析线上异常排查滞后、复现难、数据割裂等痛点,对比现有方案局限,提出自研分层架构:客户端SDK采集、IndexedDB缓存、双通道上报,后端分析、告警与闭环。系统支持多维度分析、自定义告警、问题追踪,并明确适用边界。

weixin_42268042的博客 202

项目集成避坑:重上位机护照阅读器适配难?轻上位机架构解决方案

跨平台兼容性极强,全面适配Windows、Android、鸿蒙、Linux及各类国产信创操作系统,兼容X86、ARM等主流架构,台式机、工控机、嵌入式设备、移动终端均可无缝对接,真正实现一次开发、全端复用。但现阶段政企项目全面信创改造,统信UOS、中标麒麟等国产系统常态化落地,搭配龙芯、兆芯等国产处理器,同时大量业务下沉至嵌入式终端、微型工控机、移动核验平板,老旧架构的适配短板彻底暴露。而ER护照阅读器采用的轻上位机架构,从底层重构算力逻辑,解决了多平台适配难、资源抢占、调试成本高的行业开发痛点。

OCR_13371621275的博客 173

LLM Architecture Gallery 是什么:新开源模型出来后,先对照架构再决定要不要部署

LLM Architecture Gallery 的价值,不是又提供了一个“模型很全”的页面,而是把现代 LLM 的结构差,收成开发者能反复打开的对照表。这个模型是 Dense、MoE,还是 hybrid它主要改了注意力,还是改了专家层长上下文时,KV 看起来贵不贵和上一代比,换的是骨架还是训练配方下一步该去读,还是直接安排压测这个模型综合排名第几我这张 4090 / L20 一定能跑业务里中文效果、工具调用、Agent 稳不稳站点本身也还在更新。

weixin_41338279的博客 327

网络编程 Day2】TCP 通信核心:三次握手、核心 API、C/S 架构与粘包问题全梳理

面向连接:通信前必须通过三次握手建立连接,通信结束通过四次挥手断开可靠传输:有确认机制、超时重传、排序、流量控制、拥塞控制,保证数据不丢、不乱、不重复面向字节流:数据被看作连续的字节流,没有消息边界,会出现粘包全双工:同一个连接建立后,双方可以同时发送和接收数据,两条数据流独立TCP 连接有两条独立的数据流(A→B 和 B→A),可以同时收发,互不干扰。同一个 fd 既能调用 send 也能调用 recv,这就是全双工。TCP 是面向字节流的,没有消息边界。

2401_89475491的博客 553

亿级用户IM系统 之 接入网关负载均衡架构(一、网络结构与DPVS部署)

本文讨论了亿级用户的IM系统在网络结构上设计以及入口4层负载均衡DPVS使用模式的相关内容。

robinfoxnan的专栏 325

共享电动车AI认知中台架构续篇

共享电动车认知中台不是在数据中台旁边增加一个聊天机器人,也不是把车辆、订单、网格和任务画成知识图谱后接入大模型。一套真正可运行的认知中台,需要把自然语言理解、业务语义、本体与知识、实时指标、预测算法、确定性规则、任务执行和效果复盘连接成闭环。大模型位于认知接口和编排层,负责理解问题、选择诊断流程、组织证据和生成解释;数据中台、规则引擎和算法服务负责提供可验证的事实与判断;调度、换电、维修和客服系统负责执行动作。

weixin_41026747的博客 356

婚恋平台AI智能匹配风控架构实战:基于天远双人婚姻评估查询构建自动化准入网关

破解婚恋社交匹配痛点:从传统人工核查到数据穿透 在现代数字婚恋平台与严肃交友社交场景中,确保参与者身份与婚姻状态的真实性是构建信任基石的核心。当前诸多婚恋平台的“AI智能匹配推荐与风控引擎”在处理海量用户注册与深度互动时,面临着信息不对称的巨大挑战。传统的婚姻状况核查往往依赖用户主动上传离婚证、单身证明等静态截图,辅以庞大的人工审核团队进行比对。这种方式不仅

2501_94042197的博客 238

构建高可用和高防御力的云服务架构第五部分:PolarDB(55)

存储计算分离【参会入口】高性能。

2601_96220351的博客 230

企业级医药 AI 智能客服项目实战(一):从前端到大模型,完整拆解系统整体架构

先说结论:这个项目不是“Vue 页面调用一个大模型接口”这么简单,而是一个以对话为统一入口,把医药知识、客服规则、真实业务系统、AI 工作流和安全控制组织在一起的企业级智能客服平台。三个代码仓库分别负责什么;普通聊天、知识库问答、固定安全回复和工作流为什么走不同链路;方案书里规划的功能、仓库中已经存在的代码和当前环境真正能用的能力,到底有什么区别。用户:我吃药后呼吸困难,还能继续吃吗?医疗紧急风险识别;客服场景路由;固定安全回复;SSE 流式事件;用户消息与助手消息保存;

yurenpai的博客 557

双轨大模型路由实战:从端侧Ollama边缘推理到云端大模型容灾降级架构深度拆解

端云双轨架构、心跳探活/熔断降级机制、JSON提示词工程防幻觉,以及实际项目背景和解决的问题。 字数上限150字,所以需要高度精炼。摘要需要涵盖文章的主要内容和价值,可以用一段话概括。先梳理关键点:企业级三大难题(成本、网络抖动、隐私隔离)→ 双轨路由引擎方案(本地优先、云端兜底、熔断降级)→ 实战要素(心跳探活、状态机熔断、JSON清洗)。 把这些信息压缩成一段

艺杯羹的博客 393

基于ZYNQ PL的AD7606数据采集与FFT实时频谱分析:从硬件到创新实现

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kKnmpgJm-1750295287594)(https://example.com/fft_plot.png)]本文涉及代码已在Xilinx ZCU102开发板与AD7606评估板上验证通过,采样率支持200KSPS@8通道,FFT计算延迟<1ms。传统MCU+DSP方案难以兼顾速度与功耗,而ZYNQ的PL+PS架构为这一问题提供了完美解决方案。本文实现的ZYNQ PL方案突破了传统处理器的性能瓶颈,通过。

QQ_778132974的博客 312

基于msp430f169和3.2寸彩屏ssd1289的贪吃蛇和俄罗斯方块

基于msp430f169和3.2寸彩屏ssd1289的贪吃蛇和俄罗斯方块,“锦电杯”电子设计大赛车载预警防撞系统,附的两个游戏,通过手机蓝牙控制。或者用电脑串口也可以.zip

上一篇: 可以放进收藏夹的Java进阶笔记,你还在犹豫不决嘛??
下一篇: Java进阶笔记 线程池(类比银行业务来理解)
Fightevery
博客等级 码龄5年 20粉丝 223原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值