news 2026/10/1 5:35:33

Agent平台核心运行时重构:调度、记忆与并发架构实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent平台核心运行时重构:调度、记忆与并发架构实践

去年年底,我在代码评审里看到Orkas调度器的第N次补丁时,心里那根弦终于崩了。Orkas是我们内部的Agent编排平台,每天要跑几十万个Agent任务,按说早该进入稳定维护期,可每次线上出问题,顺着调用链一路摸下去,最后总能定位到同一个根上——地基太旧了。这里说的“地基”,是指承载Agent调度、记忆和编排的那一层核心运行时。所谓底层重构,不是改几个接口、换几个依赖库的小打小闹,而是把整个执行骨架拆掉重搭。这篇文章就是这次重构的全记录,包含我对Agent框架的选型思考、架构取舍和踩坑实录,写给正在或者准备重写自家Agent平台的团队参考。

1. 为什么非动不可:旧架构的三座大山

先说清楚,旧架构不是一无是处。它在早期验证阶段帮我们跑通了从零到一,支撑起了第一波Agent业务。但业务量上来以后,三个结构性缺陷越来越明显,几乎每一次线上事故都能追溯到其中一座“大山”。这些缺陷不是靠打补丁能解决的,因为它们长在架构最底层,像地基里的裂缝一样,地面上的装修再精致也掩盖不住。

1.1 调度器像老式电话交换机

旧Orkas的调度模型是“单线程事件循环 + 全局任务队列”。所有Agent任务进来之后丢进一个大的阻塞队列,几个固定数量的Worker线程轮流消费。单看这个模型没什么问题,很多经典系统都是这么设计的,但放到Agent场景里就变了味。Agent任务不是单纯的短请求,而是一个长流程:一个任务可能要多次调用大模型、多次调用工具、中间还有人在环审批。一个长文档分析任务可以占住Worker长达好几分钟,背后几十个短问答任务全部排队干瞪眼。

更头疼的是没有优先级概念,线上告警触发的紧急诊断任务,可能排在几百个批处理任务后面。我印象最深的一次,某个客户反馈Agent“卡住不动”,排查下来发现它的任务在一个500多任务的队列里排了快二十分钟。这种调度器,本质上只能支撑“请求-响应”模式的轻量任务,撑不起Agent这种“长流程编排”形态。举个生活化的例子,这就像只有一个收银台的超市,前面一个大妈买了整整一车东西慢慢结账,后面所有只买一瓶水的顾客都得等着。早期用户量少,这种等待还能忍;当Agent任务变成高频生产工具,这种调度方式就直接成了第一瓶颈。

1.2 记忆系统是内存里的临时便签

第二个大问题是记忆。老版本的Orkas把Agent的会话状态直接放在进程的内存里,一个ConcurrentHashMap就搞定,节点一重启,所有对话上下文全部清零。后来我们加了Redis做持久化,但也只是把最近二十轮对话塞进去,超出的部分直接丢掉。这个设计在ChatBot时代够用,因为用户问一句答一句,不需要太长的上下文。但Agent场景下,用户会让Agent先读一份文件、再写一段分析、再根据分析结果生成报表,整个过程是依赖链条。如果Agent做到第三步时忘了第一步的文件内容,整个任务就废了。

更隐蔽的问题是记忆和执行的耦合:记忆的快照存在任务线程的局部变量里,子任务并行执行的时候根本拿不到共享上下文,导致很多编排逻辑写得极其别扭,只能通过全局静态变量偷偷传数据。你一定见过那种代码——一个类里有几个public static字段,注释写着“临时方案,后续优化”,然后这个“后续”就是一年。记忆系统不重构,Agent就没有真正的“长期记忆”能力,每次对话都是第一次见面,这跟Agent的核心价值是背道而驰的。

1.3 编排逻辑散落在业务代码里

第三座大山最让人崩溃:编排逻辑没有独立层,全部散落在业务代码里。旧核心有一个三千行的handle_task()函数,里面用大量if-else判断当前任务要调用哪个工具、要不要调大模型、出错后怎么重试。每接入一个新工具,就要在核心函数里加一个分支,顺带改一圈测试,还要担心影响其他任务的执行路径。团队里有个说法叫“动一次handle_task,心里抖三抖”,真不是夸张。这种代码结构带来的问题是:新工具接入周期长、回归风险高、而且编排流程完全不可观测。线上一个Agent任务执行到中间某一步失败,你想知道它为什么走这条路,日志里只有零散的打印,根本拼不出完整的决策链路。

这对一个Agent平台来说是致命的——Agent的核心价值在于复杂的自主决策流程,如果这个流程本身无法被追踪和复现,那后续所有的安全审计、效果优化、故障排查都无从谈起。打个比方,这就像公司的核心业务流程全写在一个巨大的Excel宏里,没人能说清楚每一步的逻辑,出了问题只能靠资深员工的经验去猜。三个问题叠加在一起,结论非常明确:旧地基已经撑不起Agent的编排形态。与其继续打补丁,不如把地基重做一遍。

2. 重构后的地基长什么样:四层架构与三个核心抽象

想清楚要动的地基之后,接下来最重要的问题是新地基怎么设计。我们不想搞一个“看起来很酷但落不了地”的架构,所以设计原则很简单:每一层解决一类明确的问题,每一个抽象都有清晰的边界。最终定下来的方案是四层架构加三个核心抽象,下面展开说。

2.1 四层架构:接入层、编排层、运行时层、基础设施层

我们先是把整个Orkas的边界重新划了一遍,按职责分成四层。接入层负责API网关、鉴权、限流,把所有外部请求转成内部统一的Task描述。编排层是这次重构的核心,负责解析Task、拆解步骤、决定调用哪个工具、如何处理失败重试。运行时层负责真正执行Step,管理线程模型、并发控制、超时熔断。基础设施层负责记忆存储、向量检索、消息队列、监控日志这些底座。

打个比方,接入层是大堂接待,编排层是领班排班,运行时层是灶台炒菜,基础设施层是仓库和水电。分层带来的直接好处是团队可以并行开发,几个小组互不干扰,边界清晰之后,测试也可以分模块写,而不是一个庞大的集成测试套件。对我们来说,还有一层隐含的价值:后续Agent技术栈升级时,只要接口不变,替换某一层的实现不会影响其他层。比如编排层今天用LangChain的思路,明天换成自研的Graph执行器,只要对外暴露的Task和Step语义不变,接入层和基础设施层完全可以不动。

2.2 三个核心抽象:Task、Step、Session

架构分完之后,紧接着要定义核心抽象。我们最终确定了三个:Task、Step、Session。Task代表一个Agent请求的目标,携带用户的原始意图和输入参数,对应“用户想让我干什么”。Step代表执行过程中的一个原子动作,可能是一次大模型调用、一次工具调用、一次条件判断、一次人工审批,对应“这一步具体做什么”。Session则是贯穿整个Task生命周期的持久化上下文,包括对话历史、中间结果、变量状态、工具执行记录,对应“Agent现在的处境和记忆”。

这三个抽象看起来简单,但把旧架构里最纠缠不清的三个概念彻底解开了。之前Task和上下文是混在一起的,任务执行完,上下文就没了。现在Session独立于Task存在,一个Session可以承载多个Task的连续执行,Agent就能真正实现跨请求的持续记忆。举个例子,用户今天上午让Agent分析一份销售数据,下午接着问“那上个月的数据呢”,新架构下Agent会检索Session里的长期记忆,关联到上午的分析上下文,给出有连续性的回答。而旧架构里,下午的问题就是一个全新的任务,Agent完全不记得上午聊过什么。

2.3 重写还是渐进式重构:一次没有回头路的取舍

做重构之前,团队内部其实吵过一轮:是推倒重来,还是渐进式替换?推倒重来的诱惑很大,因为旧代码的“坏味道”积累太重,大家都想甩掉包袱。但仔细评估后我们选择了渐进式替换,分模块拆边界,每拆一块就上线验证一块。原因很现实:Agent平台上的业务是线上在跑的,不可能容忍一个“停服三个月再回来”的重写。而且Agent生态变化太快,今天的主流框架明天可能就被替代,一次性投入全部资源去写一个完整的“终极架构”,风险太高。

渐进式的好处是每步都可回退、可验证、可积累经验,坏处是中间会有很长一段“新旧混合”的过渡期,需要很强的兼容层设计。我们在实际操作中把整个重构拆成了四个阶段:先重写调度器,这是承载一切的基础;再重写记忆系统,让Agent有真正的持续记忆;接着重写编排层,把散落的逻辑收敛进来;最后才是并发模型和基础设施的升级。每个阶段都有明确的验收标准和回滚方案。现在回头看,这个决策是对的,因为我们中间改了两次设计,都是在局部模块上调整,没有伤筋动骨。

3. 三块硬骨头:调度、记忆和并发

架构设计和核心抽象定了之后,就进入最硬的实战阶段。这三块分别是调度、记忆和并发,每块都独立成章,但又互相牵连。改调度器会影响延迟,改记忆会影响正确性,改并发会影响整体吞吐,任何一个模块出错,线上都会立刻有感知。所以这三块我们投入的精力最大,踩的坑也最多。

3.1 调度器:从“先来先服务”到多级反馈队列

调度器是我们最先动刀子的模块。新的调度方案选择了多级反馈队列,即多个不同优先级的队列,每个队列的时间片不同,高优先级队列时间片短、执行频次高,低优先级队列时间片长、执行频次低。Agent任务天然适合这种模型:大部分简单问答任务应该在几百毫秒内完成,属于高优先级快车道;少数重任务(长文档分析、批量数据处理)可以放到低优先级慢车道慢慢跑。同时设计了老化机制,防止长任务被饿死:只要某个任务在低优先级队列里等待超过一定时间,就被提升到高优先级队列重新参与调度。

下面是我们调度器核心逻辑的简化示意,用的是Java伪代码风格:

public class MLFQScheduler { // 三级队列:Q0时间片500ms,Q1时间片2s,Q2时间片8s private final Queue<Task>[] queues = new Queue[3]; private static final long[] TIME_SLICE = {500, 2000, 8000}; public void enqueue(Task task, int level) { if (task.waitingTime() > AGING_THRESHOLD) { level = Math.max(0, level - 1); } queues[level].offer(task); } public Task pickNext() { for (int i = 0; i < 3; i++) { if (!queues[i].isEmpty()) { Task task = queues[i].poll(); task.setRemainingSlice(TIME_SLICE[i]); return task; } } return null; } }

这段代码只是骨架,实际应用的时候还有两个关键点必须注意。第一,时间片耗尽不代表任务失败,而是降级:任务从Q0被抢占后重新进入Q1,它内部的执行状态要能挂起和恢复,这要求Step的执行模型具有可中断性。我们每个Step都实现了suspend和resume接口,状态序列化到工作记忆里,被抢占后随时可以恢复执行。第二,老化判断不能每次入队都全表扫描,我们用一个延迟队列维护“因老化需要升级”的任务列表,定时批量处理,否则任务量一上来,调度本身的CPU开销反而成了瓶颈。

上线后的效果,在同规模流量下,短任务的P50延迟从1.8秒降到400毫秒,长任务虽然偶尔被抢占,但整体完成时间没有恶化。这里有个经验可以分享:不要把时间片参数设得太激进。我们一开始把Q0的时间片设成100毫秒,结果发现大量简单任务因为时间片不足被迫降级,反而增加了调度开销。后来调到500毫秒才稳定下来。时间片的选择要结合任务的实际执行分布来定,最好先采样统计一下线上任务的平均执行时长,再反推参数。

3.2 记忆系统:从“内存便签”到三层记忆结构

记忆系统的重构,我们参考了认知科学里的工作记忆和长期记忆概念,做成三层结构。第一层是工作记忆,保存当前Task执行过程中产生的临时状态,存储在Redis里,TTL设为一个小时,保证节点重启也能快速恢复。第二层是长期记忆,保存Agent从用户过往交互中学到的偏好、重复使用的上下文、历史工具调用记录,用向量库存储,执行到需要参考时会做相似度检索。第三层是技能库,保存工具的使用范式,比如某个工具的参数模板、调用前的校验规则,按需加载到运行时,不占常驻内存。

工作记忆的序列化格式直接采用JSON,每个Step执行完都会原子更新,避免脏写。需要注意的是“记忆裁剪”这个细节——大模型的上下文窗口是有限的,工作记忆不能无限增长。我们给每个Session设了token预算,默认8000 token,超过预算就触发裁剪策略:优先保留最近的对话、关键中间结果,其次压缩早期对话为摘要,压缩后的摘要本身要有索引,能被长期记忆检索到。这个策略既要防止上下文爆炸,也要防止裁掉关键信息。在测试环境里,我们模拟过30轮连续对话,裁剪之后Agent对早期关键信息的召回率还能保持在85%以上,基本够用。

长期记忆的向量检索也有一个调参心得:相似度阈值不要拍脑袋定。我们最初用0.8,发现召回率太低,很多该出现的记忆检不出来,后来改成0.7并且加了一个时间衰减权重——同样的内容,一个月前的参考价值远低于昨天的。衰减系数用指数形式:score = rawScore * Math.exp(-ageDays / 30)。这样调完之后,用户问“上次那个方案的数据是多少”,Agent能准确地从长期记忆里捞出来,而不是答非所问。另外,向量库的索引要定期重建,我们用的Milvus,每周末跑一次全量索引优化,否则数据量大了之后检索延迟会明显上升。

3.3 并发模型:从固定线程池到虚拟线程

第三块硬骨头是并发模型。旧Orkas用的是固定大小的线程池,核心线程数开到100,最大200。听起来不少,但Agent任务是IO密集型的——大量时间花在等大模型返回、等外部API响应上,200个线程实际上只能同时支撑二三十个活跃任务。我们尝试过协程方案,但因为团队技术栈主要是Java/Kotlin,最后还是选了Java 21的虚拟线程。虚拟线程的好处是数量可以开得很大,几万个都没问题,因为它的栈空间是按需分配的,调度由JVM自己管理,不会为了等IO白白占住一个昂贵的平台线程。

切换并发模型后的调整集中在几个配置上。虚拟线程池直接用一个简单的ExecutorService,核心参数是最大虚拟线程数,我们设成5000,实际使用中从未触顶。数据库连接和外部HTTP客户端连接不能无限增加,必须做隔离:每个租户的Agent任务共享一个连接池,但池大小按租户配额动态分配,防止一个租户的任务激增把整个平台的连接吃光。下面是我们压测的对比数据,同样跑1000个并发Agent任务:

指标旧线程池方案虚拟线程方案
每分钟处理Step数320011000
P99延迟8.5秒2.9秒
活跃任务数上限约30个不受线程数限制

需要注意的是,虚拟线程不是银弹,它解决的是IO阻塞问题。如果你的Step里有大量CPU密集计算,比如本地跑模型推理、大量正则匹配,该用并行流或者单独的计算线程池还是要用。我们就把文件解析这类CPU密集型的操作单独扔到一个固定大小的计算线程池里,避免它占用虚拟线程而且拖慢JVM的整体调度。还有一个小细节:虚拟线程默认是后台线程,应用退出时可能不会等待未执行完的虚拟线程,所以我们自己维护了一个关闭钩子,在服务下线前优雅地排空任务队列。

4. 几十万行代码怎么安全落地:迁移策略与兼容方案

重构完成之后,最大的风险不是写不出代码,而是怎么把新代码安全地顶上线上。这个阶段我们花了跟写代码差不多的时间,因为Agent平台一旦出问题,影响的不是单个用户而是一整条业务线。迁移策略的核心就三个词:灰度、兼容、回放验证。

4.1 灰度发布:三层递进

我们的灰度策略分三层递进。第一层是按租户灰度,先让内部几个beta租户切到新引擎,观察基础功能和稳定性,周期一到两周。第二层是按任务类型灰度,把最轻量的简单问答任务逐步切过去,这类任务逻辑简单、出问题影响面小;重任务后切。第三层才是按流量比例灰度,从5%开始,观察P99延迟和任务失败率,稳定后升到20%、50%、100%。

每一步都有明确的回滚条件:只要任务失败率超过基线2个百分点,或者P99延迟超过旧系统1.5倍,立刻切回旧引擎,不纠结不硬扛。灰度期间我们盯的指标除了常规的延迟、错误率、吞吐之外,还有一个容易被忽略的:任务完成后的用户满意度反馈。Agent任务跟普通API请求不同,最终结果是给用户看的“答案”或“动作”,所以即使技术上全部成功,如果用户反馈变差,也要停下来排查是否新引擎在某个环节的行为跟旧版不一致。

4.2 兼容层:给旧API三个月的“过渡期”

新旧引擎共存期间,最考验人的是兼容层设计。我们做了一个API兼容层,前端业务方不需要改任何代码,还是调用原来的接口。兼容层内部做请求转发和结果转换,把旧接口的参数映射成新的Task描述,再把新引擎返回的Step执行结果包装回旧的响应结构。同时做了错误码映射表——旧系统返回的一些错误语义和新系统不一致,比如旧系统“任务超时”返回504,新系统有更细分的超时类型,兼容层统一翻译成旧码,避免业务方踩坑。

这个兼容层我们不打算长期保留,定了一个三个月过渡期,到期后强制业务方升级到新API。过渡期内同时维护两套逻辑确实累,但有这个缓冲,业务侧完全没有感受到底层变换的阵痛。有一点要提醒:兼容层容易变成“垃圾场”,各种临时补丁往里面堆。我们要求所有兼容逻辑必须是显式映射,不允许在兼容层里写业务判断,一旦发现,代码评审直接打回。这为了保证后期移除兼容层的时候,不会牵扯出隐性的业务依赖。

4.3 回归与压测:重放线上流量的两个教训

验证新引擎正确性的方法,我们选了录制回放:把线上真实的任务请求录制下来,在测试环境同时跑新旧两套引擎,逐一对比两边的执行结果和决策路径。这里有两个教训值得讲。第一个是录制流量必须脱敏,第一批录制数据里混着用户上传的业务文件,虽然只在测试环境跑,但考虑到合规要求,我们后来写了一套脱敏脚本,把文件内容替换成伪造但结构一致的数据。

第二个是回放对比不能只看最终结果,还要看中间路径。因为Agent有随机性,两次调用大模型可能给出不同的中间决策,最终结果一样并不代表流程一致。我们做了一个路径相似度指标,把每个Task经过的Step序列做归一化对比,相似度低于阈值的自动进人工复核队列。这个指标帮我们抓出了不少隐蔽问题,比如新调度器下某个工具的调用顺序被改变,虽然结果没出错,但用户体感会不一样。回放验证一共跑了三周,累计对比了上百万个任务,才敢放心地把所有流量切到新引擎。

5. 重构过程中踩过的坑:问题排查与技巧实录

这部分按理说应该放到最后,但其实是我们整个重构过程中最值得写的一章。每个坑都对应一个真实的线上事件,而且都不是靠查文档能解决的问题,必须把调度、记忆、并发、依赖的调用链串起来才能定位。我把最有代表性的三个坑写在这里,附上排查思路和最终的解决方案。

5.1 虚拟线程打满数据库连接池

上线第一周,我们就碰到一个诡异的问题:任务没有明显增加,数据库连接池却频繁告警。排查发现根因是虚拟线程太好用了,一个任务内部多个Step并行执行,每个Step都会去拿数据库连接,虚拟线程数量多了之后,数据库连接池被瞬间抢空,大量虚拟线程阻塞在“等待连接”上。从监控上看,任务延迟飙升,但CPU和内存都很空闲,典型的资源等待场景。

解决方法是三层:连接池从20扩到100;任务内部对Step的并行度做限制,同一任务的并发Step数上限设为8;获取连接时加一个明确的超时时间,超过5秒直接失败并触发重试,而不是无限阻塞下去。这个问题也给我们提了个醒:并发模型的升级必须连带配套资源池一起重估,虚拟线程释放了“线程”这个稀缺资源,但数据库连接、外部API连接这些资源依然是稀缺的。建议所有做类似升级的团队,先把资源池的容量模型重新算一遍,再上虚拟线程。

5.2 向量检索的“万金油记忆”陷阱

长期记忆功能上线后,我们发现一个很搞笑的现象:Agent在回答问题时,经常把一段无关紧要的旧对话当成重要参考。排查下来,原因是向量检索的相似度计算里,有些通用性很强的内容(比如“好的”“明白”“收到”这种寒暄)和任何问题都有一定的相似度,被当成“万金油记忆”反复捞出,反而盖过了真正相关的历史内容。用生活类比的话,就像你去图书馆查资料,管理员每次都先给你一本《新华字典》,而不是你要的专业书。

解决思路是两层:一是检索前做一次query重写,把寒暄词和无关的停用词过滤掉,再用重写后的向量去检索;二是结果集做多样性去重,相似度最高的两条如果内容重复度过高,只保留一条,给其他候选记忆留出位置。顺手也给检索结果加了时间衰减,旧内容除非相似度特别高,否则不占前排。这个调整上线后,长期记忆的相关性指标提升了将近一倍,Agent回答时引用记忆的准确率明显提高。

5.3 外部API超时引起的“假死任务”

第三个坑是Agent“假死”。现象是任务状态一直显示执行中,但实际没有任何进展。追下去发现,Agent在执行某个Step时调用了外部第三方API,这个API没有响应,而我们的HTTP客户端只设置了连接超时,没有设置读取超时和整体超时,任务就这么挂住了。更糟糕的是,这种“假死”任务还占着调度资源,时间长了会把整个队列堵住。

修复方案是三层保险。HTTP客户端设置连接超时3秒、读取超时15秒、整体请求超时20秒。对第三方API单独做断路器,连续10次失败即熔断,熔断后不再调用而是走备选方案。Agent编排层再做一次兜底,每个Step都要有整体执行时限,超时后由编排层标记失败并触发重试或降级。这套组合拳打完之后,“假死”任务基本清零,线上监控里也很少再出现长时间卡在“执行中”的任务。我把排查过程整理成了一个速查表,团队内部后来排查问题基本都靠它:

现象可能原因第一步排查
任务一直“执行中”外部API未返回查Step级日志的HTTP等待时间
大量任务排队调度队列设计不合理查队列长度和优先级分布
延迟突然飙升资源池被打满查连接池活跃数和线程池状态
记忆引用错误向量检索召回不准查query重写日志和召回阈值

最后分享一点个人体会。重构Orkas这半年,最大的感受是:地基类的改造见效慢,但做完之后的收益是全方位的。刚开始前两个月几乎每天都在跟旧代码搏斗,感觉进度像蜗牛爬,一度怀疑这个决定是不是错了。但等调度、记忆、并发这三块硬骨头啃下来,后面新功能开发的速度肉眼可见地快了,新同学上手看架构图也清晰很多。如果让我给同样要重构Agent平台的朋友一个建议,那就是先把可观测性做好——决策链路追踪、Step级日志、会话级回放,这三样东西在重构过程中救了我们太多次。地基打牢了,上面的楼才好盖,这个道理放在Agent平台开发上,真的再合适不过了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 5:35:26

AI视频切片质检全攻略:从生成到发布的审核流程

上周我刚处理完一批AI生成的短剧切片&#xff0c;42个候选片段最终只有11条能上线。这个通过率在我手里已经算不错的了——前两个月刚接手时&#xff0c;一批30条里能活下来5条都够我高兴半天。很多人以为AI视频切片最难的环节是让模型产出内容&#xff0c;真正干过这行的人都知…

作者头像 李华
网站建设 2026/10/1 5:35:23

高分辨率车道线语义分割实战:2800张精标数据集的训练与避坑指南

简介&#xff1a;面向自动驾驶与智能交通场景的高分辨率高速车道线图像语义分割数据集&#xff0c;提供约2800张已划分好的图像及对应标签&#xff0c;支持白实线、背景等6类分割任务&#xff0c;适合目标检测、语义分割模型训练与算法验证的初学者及研究人员使用。资源包共200…

作者头像 李华
网站建设 2026/10/1 5:35:23

广州餐饮老板必看:本地客单价提升的GEO推广方案与外卖平台排名技巧

广州餐饮行业本地推广的现状科普广州作为国内餐饮业态最丰富的一线城市之一&#xff0c;餐饮门店总量常年位居全国前列&#xff0c;从老牌早茶老店、网红商圈餐厅到社区巷弄的特色小吃店&#xff0c;各类餐饮商家共同撑起了广州万亿级的本地消费市场。对于餐饮行业来说&#xf…

作者头像 李华
网站建设 2026/10/1 5:35:10

DIV+CSS个人网站案例拆解:从布局到避坑全指南

简介&#xff1a;面向网页设计初学者的DIVCSS实战案例&#xff0c;以构建个人网站为完整项目&#xff0c;系统演示如何用div容器划分页面结构&#xff0c;并通过CSS实现头部、主体、侧边栏、页脚等常见模块的布局与视觉样式。资源内含一个可运行的HTML页面、配套的CSS样式表及多…

作者头像 李华
网站建设 2026/10/1 5:35:02

TeX Live 2023 全平台安装与中文排版避坑指南

我这两周前后帮三个师弟师妹把 TeX Live 重新装了一遍&#xff0c;分别在 Windows 11、Ubuntu 22.04 和 macOS 上各跑了一次完整流程。本来以为这活儿闭着眼都能干&#xff0c;结果每台机器都给我整出了新花样&#xff1a;有下载到一半断掉的&#xff0c;有装完之后xelatex找不…

作者头像 李华