自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(48)
  • 收藏
  • 关注

原创 流式响应的三种超时:连接、响应头与块间空闲

LLM Gateway 通过三段式超时机制保障流式请求稳定性:① 连接超时(3s)防网络不可达;② 响应头超时(30s)应对上游排队;③ 逐块空闲超时(20s)防止模型卡死。采用 WebFlux + WebClient,利用 timeout() 的重置特性实现块间间隔监控,自定义 UpstreamIdleTimeoutException 精准定位问题,确保超时后安全终止流并保留计费与日志完整性。三者协同,避免网关被僵尸连接拖垮,实现资源回收与可观测性统一。

2026-10-22 15:15:06 441

原创 用户点了停止生成,上游还在烧 token 吗?取消传播链路拆解

LLM Gateway 通过 Spring WebFlux 实现大模型流式响应,关键在于正确传递取消信号。若 Controller 自行 subscribe() 而非返回 Flux,将导致客户端断开后上游仍持续生成,引发无效计费。项目通过在 bodyToFlux 后置 doFinally(CANCEL),验证取消信号穿透算子链并关闭上游连接,确保计费准确。结合日志、指标与退款逻辑,实现“断流即停费”,避免账单虚高。

2026-10-22 15:14:59 534

原创 WebFlux 里写了一个阻塞调用会发生什么?事件循环被占用的代价

LLM Gateway 采用 WebFlux 架构,通过异步非阻塞模型应对大模型请求的高延迟与高并发。传统线程模型因线程被 I/O 阻塞导致资源浪费,而事件驱动机制仅用少量线程处理海量连接,显著提升吞吐。限流链路基于 Redis + Lua 实现原子操作,确保精准控流。关键在于杜绝任何阻塞行为——尤其是阻塞式 Redis 客户端(如 Jedis)在 event loop 中调用会引发雪崩。正确做法是使用响应式客户端(如 Lettuce),保障整个链路不阻塞、不卡死,实现“能跑”到“不炸邻居”的安全演进。

2026-10-21 11:16:17 471

原创 返回 Flux 却不是流式?collectList 与 flatMap 导致的静默退化

本文剖析LLM网关中流式响应的“静默退化”陷阱:尽管接口返回Flux,但collectList()、异步flatMap、cache()等操作会隐性破坏流式传输,导致首字节延迟飙升、顺序错乱或内存暴涨。通过对比三种典型错误模式,强调流式本质是运行时行为而非类型特征。最终给出正确实现:使用concatMap保序、避免中间缓存、确保逐块透传,并在Controller层固定produces = TEXT_EVENT_STREAM,保障用户3秒内可见首字,真正实现“感知上的极速”。

2026-10-21 11:16:03 423

原创 OpenAI 兼容网关如何做到客户端零改造?协议归一到底归了什么

OpenAI兼容网关实现客户端零改造的核心在于协议归一:统一入口路径、忽略未知字段、重建响应结构。关键动作包括:钉死object字段、改写model名、重构delta分块格式、过滤上游[DONE]并补全,错误响应统一为OpenAI标准格式。通过@JsonIgnoreProperties(ignoreUnknown = true)确保兼容扩展参数,但仅转发model、messages、stream等基础字段,其余如tools等暂不透传。归一本质是网关承诺对外一致,内部可灵活调度上游,实现透明切换厂商而不影响客

2026-10-19 15:33:41 642

原创 新增一家大模型厂商要改几行代码?配置化渠道接入的边界

新增一家大模型厂商,是否只需改配置?答案取决于架构设计。本文以开源LLM Gateway为例,揭示“配置化接入”的真实边界:当厂商接口兼容OpenAI标准(如路径、请求/响应结构一致)时,仅需配置base-url、api-key、model三要素,即可通过统一实现类OpenAiCompatibleProvider动态适配;但若协议差异大,则必须新增实现类。核心原则是:将可变数据(配置)与不变逻辑(代码)分离,避免“一家一类”导致的重复与维护成本。配置化并非万能,其边界在于接口一致性。

2026-10-19 15:33:24 407

原创 为什么所有业务都该通过一个网关调用大模型?多厂商接入的五个重复成本

当多套系统直连大模型时,重复投入在协议适配、密钥管理、限流、厂商切换与成本追踪上。本文通过开源项目 LLM Gateway 证明:网关的核心价值在于将厂商差异、密钥、限流等复杂性收敛至统一入口。真实数据流显示,请求经限流过滤后,由 Provider 统一适配并转发,实现“一次配置,全链路生效”。项目已实现限流、多厂商接入与统一接口,但计量结算仍为规划中。

2026-10-17 17:30:44 564

原创 一个实现类服务 N 家厂商:Provider SPI 与配置驱动路由

本文提出一种基于配置驱动的LLM网关架构,通过“一个OpenAI兼容实现类 + 多份YAML配置”模式,将厂商差异从代码移至配置。新增厂商仅需添加4行配置,无需修改任何Java代码或Controller。核心设计包括:统一的Provider接口、注册表路由与配置装配机制,实现零代码扩展。该方案避免了if-else堆和重复实现,适用于多数OpenAI兼容厂商接入场景,提升可维护性与扩展性。项目地址:https://github.com/maixiaowen111/llm-gateway

2026-10-17 17:30:35 604

原创 为什么单元测试全绿,联调仍可能失败?订单 Agent 的三层测试与 CI

本文剖析了“测试全绿但功能不通”的典型问题,揭示单元测试的结构性盲区:Mock掩盖真实契约。通过三层测试架构(单元测试、端到端冒烟、集成边界)实现分层覆盖——第一层快而可移植,第二层真环境验证关键链路,第三层聚焦跨服务一致性逻辑。强调“是否被Mock掩盖”决定测试层级,最终以权限闸门为例,展示如何通过精准分层保障系统可靠性。

2026-10-16 17:09:18 209

原创 Java 对象写入 Redis 后类型变了?Jackson 多态序列化问题复盘

在订单取消Agent中,会话记忆需持久化至Redis以支持多轮对话。因泛型擦除与Jackson反序列化机制,直接存储List<ChatMessage>会导致读取时类型丢失,出现ClassCastException或toolCalls为null的隐蔽问题。项目采用“扁平DTO+显式type字段”方案,将多态对象转为结构化存储格式,避免类名固化带来的版本兼容风险,确保重构不影响历史数据,实现稳定、可演进的会话记忆系统。

2026-10-16 15:16:00 353

原创 LLM 选错工具怎么办?提示词之外还需要确定性规则兜底

AI订单助手因LLM工具选择的随机性,常在“取消订单”指令下先调用查询接口,导致权限闸门未触发,用户无法批准。根源在于模型将“先查后取消”视为合理路径。解决方案为双保险:后端设temperature=0并强约束提示词,压制概率漂移;前端增加确定性校验,若检测到“取消意图+无批准按钮”,自动补发明确指令,强制回归正确流程。

2026-10-15 13:26:29 304

原创 容器里为什么不能写 localhost?一次 Redis 连接失败的排查过程

摘要(148字): 项目部署时order-system容器启动即崩,因代码中Redis地址写死为localhost:6379。容器内localhost指向自身,无法连接外部Redis服务。根因是未使用Docker Compose的服务名redis进行跨容器通信。修复方案:配置外置,通过环境变量覆盖默认地址,确保本地与容器环境一致。同时发现Spring Boot 2.x/3.x属性前缀不同,手写Bean不识别标准环境变量,凸显“配置被两套体系接管”的深层问题。

2026-10-15 13:26:17 583

原创 如何让 Claude Desktop 与 Cursor 调用订单工具?MCP Streamable HTTP 接入思路

本文提出将AI Agent的工具能力从封闭的聊天框中解放,通过MCP协议实现跨客户端统一调用。作者将订单Agent改造为标准MCP服务端,支持Claude Desktop、Cursor等多入口接入,核心创新在于“写操作必须人工批准”——模型仅具提议权,审批强制在浏览器完成,保障安全可控。架构上采用轻量级JSON-RPC实现,严格遵循协议规范,会话独立、权限隔离,业务逻辑集中于后端系统,确保一致性与可维护性。

2026-10-14 16:39:39 466

原创 从一句\“取消订单\“到退款通知:AI 写操作完整业务闭环

本文提出AI Agent在企业中实现写操作的安全闭环方案:通过“模型提议、人工批准、业务执行”三步机制,确保取消订单、退款等高风险操作可控。核心设计包括基于参数指纹的原子化权限闸门、幂等状态流转、事务内落库退款事件及调度器兜底重试,实现从自然语言指令到真实业务变更的可信闭环。项目虽模拟支付流程,但构建了完整可靠的通知链路,为AI Agent落地提供可复用的安全范式。

2026-10-14 16:39:28 530

原创 隐私保护不能只靠日志打码:从数据源头最小化敏感信息

本文指出,仅靠日志打码无法真正保障隐私,核心问题在于敏感数据已提前暴露。作者以订单AI Agent为例,揭示数据在Agent进程内通过日志、大模型、会话记忆、异常回显等多渠道泄露的风险。提出“源头最小化”方案:通过双视图DTO(用户/内部视图分离)、脱敏字段返回、内部密钥校验,确保Agent从不获取完整敏感信息。结合线程安全的脱敏工具与测试验证,实现“数据不出源”的纵深防御,从根本上杜绝隐私泄露。

2026-10-13 11:59:13 573

原创 AI 会话记忆怎么做?Redis 多轮上下文与 TTL 设计

本文详解AI Agent会话记忆设计,基于开源项目order-agent-assistant,采用Redis实现多轮对话上下文管理。核心思路:会话记忆仅存于单次会话(TTL 30分钟),不缓存业务数据,避免状态不一致;通过扁平化ChatMessage类解决JSON序列化问题;在AgentLoop外层统一读写,支持自动裁剪(最多40条)与续期。架构上分离记忆与业务操作,确保模型仅决策、业务系统唯一执行,保障一致性与可维护性。

2026-10-13 11:59:01 319

原创 工具调用失败时不要把堆栈喂给模型:统一错误信封设计

摘要(148字): 在AI Agent中,工具调用失败时直接返回堆栈会引发模型误解、信息泄露与提示注入风险。本文提出“统一错误信封”设计:将异常映射为结构化JSON信封,仅传递机器可读的errorCode与人类可懂的message,堆栈仅记录于日志。通过分类errorCode(如CLIENT_ERROR不可重试),指导模型正确决策,提升可用性与安全性。代码实现确保信封序列化兜底,避免处理路径崩溃,是低成本高收益的落地实践。

2026-10-12 10:00:40 447

原创 如何防止 Agent 无限循环?最大步数、工具超时与安全失败机制

— 为订单 Agent 设计并落地四层防护:最大 8 步熔断(模型调用与工具执行双计数)、工具调用独立超时(连接 2s / 读取 5s)、五类异常统一收敛为结构化错误信封、写操作人工批准闸门。结果是模型在两个工具之间来回横跳(A 的结果让它觉得该调 B,B 的结果又让它觉得该调 A),工具调用次数一直在涨之前就已经烧掉大量 Token。可以量化、面试官会追问的点:8 步的依据是什么、为什么工具执行也要计数、4xx 和 5xx 为什么要分开、超时后怎么避免重复副作用、异常信息为什么不能直接给模型。

2026-10-12 10:00:26 622

原创 模型负责决策,代码负责执行:业务 Agent 最重要的职责边界

模型在不确定的世界里做决策,代码在确定的规则里做执行。凡是"错了会真出事"的部分,都不能交给概率模型,必须落成可测试、可审计、可幂等的 Java 代码。推荐标签:AI Agent、大模型应用开发、Java、Spring Boot、架构设计。

2026-10-11 10:55:23 477

原创 AI 想取消订单,为什么必须先让用户确认?Human-in-the-loop 权限闸门设计

AI取消订单必须先经用户确认,因写操作不可逆且错误代价高。通过“Human-in-the-loop”权限闸门机制,将写操作拦截并挂起,待用户手动批准后才执行。系统以一次性令牌与参数指纹确保批准安全,防止重放与篡改。该设计在保障自动化效率的同时,将关键决策权交还人类,实现安全与体验的平衡。

2026-10-11 10:55:01 525

原创 AI Agent 如何接入真实业务系统?订单助手三层架构完整拆解

前端、决策层、执行层三层齐全,Docker 一键起,clone 下来就能跑。⚠️:支付/退款是,不接真实支付通道。这一点在项目 README 里也是这么写的——面试时主动讲清楚边界,比被追问出来强。

2026-10-10 09:58:51 496

原创 Mock 单测还不够:用 Testcontainers 验证库存幂等与真实事务行为

本文基于真实项目实践,揭示Mockito单测无法验证库存服务在分布式场景下的幂等与事务正确性。通过Testcontainers启动真实MySQL,结合集成测试验证:原子UPDATE防超卖、幂等表唯一键拦截重复请求、回放时价格与数量一致性保护等核心机制。测试覆盖Feign重试、调度补发、MQ重投等真实异常场景,证明“幂等+事务”组合的可靠性依赖真实数据库行为,仅靠Mock无法触及行锁、唯一约束、事务边界等关键层面。

2026-10-10 09:58:33 526

原创 Docker Compose 启动中间件的真实坑:端口冲突、启动顺序与数据持久化

Docker Compose 的坑大多不是"docker 不会用",而是。

2026-10-09 12:08:34 323

原创 JVM 问题怎么排查?Prometheus、JDK 命令与 Arthas 三层诊断体系

本文构建了JVM问题排查的三层诊断体系:事前监控(Prometheus+Grafana) 实现堆内存、GC频率等指标预警;事后分析(jstat/jmap/jstack) 通过dump文件定位内存泄漏类与线程问题;在线诊断(Arthas) 支持不停服实时观测运行时状态。三者协同,形成“现象→嫌疑→根因”完整证据链,实现从被动救火到主动防控的转变。

2026-10-09 12:08:20 534

原创 本地事务管不到远程服务怎么办?扣库存补偿与取消订单 Outbox 方案

拆分微服务后,远程扣库存脱离本地事务管控,引发超卖、重复扣减与事务阻塞问题。本文基于 OrderSystem Cloud 实践,提出三策:原子化库存扣减(UPDATE + 行锁防超卖)、幂等表+补偿机制(订单失败时反向还库存)、Transactional Outbox 模式(取消订单不同步还库,通过事件队列异步补发)。结合 RocketMQ 与本地事件表,实现分布式事务一致性,解耦远程调用与本地事务,保障数据最终一致。

2026-10-08 14:57:56 522

原创 一条慢 SQL 是怎样被定位的?Druid、EXPLAIN 与联合索引实战

本文通过真实案例剖析一条高频执行的慢 SQL 如何被定位与优化。项目中 t_event_record 表每 5 秒扫描一次,日均执行超 1.7 万次,随数据量增长导致性能瓶颈。借助 Druid 监控面板,精准定位该查询为“执行次数×耗时”最高的罪魁祸首。通过 EXPLAIN 发现其为全表扫描(type=ALL),经分析确认缺失联合索引。最终创建 (status, next_retry_time) 联合索引,实现从全表扫描到索引范围扫描的跃迁,扫描行数减少 67%,并启用 ICP 优化,显著提升性能。核心经验

2026-10-08 14:48:02 550

原创 网络重试导致重复下单怎么办?clientRequestId 与数据库唯一键双保险

为防止网络重试导致重复下单,采用clientRequestId与数据库唯一键双保险机制:用户点击提交时生成唯一幂等键,后续重试复用该键;服务端通过client_request_id唯一索引实现幂等,避免重复落单。结合预检查、订单号生成与库存扣减幂等,确保同一意图仅生成一张订单,兼顾兼容性与高可用。

2026-10-07 13:41:24 489

原创 Feign 超时重试如何避免重复扣库存?业务幂等键与唯一索引方案

在微服务架构中,Feign超时重试可能导致库存重复扣减。核心问题在于:远程调用失败后重试可能引发副作用(如已扣库存但响应丢失)。解决方案采用三道闸门机制:业务幂等键(orderNo + productId + operation)+ 唯一索引实现首次调用抢占,防止重复执行;原子UPDATE确保并发下不超卖;事务一致性保障幂等记录与库存变更同提交/回滚。通过数据库唯一约束实现高并发下的幂等性,从根本上避免重复扣减,兼顾正确性与可用性。

2026-10-07 13:41:07 517

原创 防超卖为什么改用 MySQL 原子 UPDATE,而不是继续依赖分布式锁?

本文对比了防超卖的三代方案,指出分布式锁(如Redisson)因锁与事务边界不一致、快照读漏洞及性能瓶颈,存在隐蔽竞态风险。作者在微服务订单系统中改用MySQL原子UPDATE:UPDATE ... SET stock = stock - ? WHERE stock >= ?,通过行锁实现并发串行化,并以影响行数判断成败。该方案将“检查+扣减”合并为原子操作,避免快照读穿透,确保数据一致性,且无需额外锁机制,兼具高可用与高性能,是数据库层面防超卖的更优解。

2026-10-06 11:02:20 569

原创 雪花算法还能携带分片信息?订单号「雪花 ID + 基因法」设计

本文提出“雪花算法+基因法”设计,解决订单号在分库分表场景下的路由难题。通过将8位基因位嵌入雪花ID最低位(userId % 256),实现订单号自带分片信息,支持orderNo & 0xFF直接定位分片,避免广播查询。改造后位分配为:1(符号)+41(时间戳)+5(机器)+9(序列号)+8(基因),兼顾递增性与扩展性,确保未来分表无需修改生成逻辑,提升查询性能与系统可演进性。

2026-10-06 11:00:41 582

原创 为什么我没有立刻做分库分表?订单系统扩容的正确优化顺序

在订单系统初期,面对几百行数据时,作者果断放弃分库分表,转而遵循“从低到高”的优化顺序:先通过慢SQL排查与索引优化解决性能瓶颈;再引入缓存应对热点读;随后实施读写分离缓解主库压力。只有当上述手段均无法支撑时,才考虑分库分表。文章强调,分表是高成本、不可逆的决策,应作为最后手段。真正的技术能力不在于“用上”分表,而在于“判断何时不用”。

2026-10-05 14:00:28 437

原创 Dynamic-Datasource 与 ShardingSphere 怎么选?不要为暂时用不到的能力买单

在订单系统中,读写分离应优先于分库分表。当前数据量小,仅需“读走从、写走主”,选用 dynamic-datasource 仅需3行配置+1个注解,轻量高效。而 ShardingSphere 功能过重,引入额外维护成本。正确落地需三层:主从复制(地基)、多数据源管理(容器)、路由注解(开关)。避免为未来不用的能力买单,坚持“先轻后重”演进策略。

2026-10-05 13:59:51 470

原创 主从延迟会造成什么问题?订单系统\“读走从、判断走主\“的一致性策略

主从延迟会导致“读己之写失效”“判断依据过期”等严重问题,尤其在订单系统中可能引发资损。本文提出“读走从、写走主、判断走主”的一致性策略:纯展示接口可走从库,但涉及状态判断或写入的操作必须读主库,确保逻辑正确性。通过显式标注@DS("master")强化一致性,避免依赖默认值。同时指出从库读压力过大、大事务、单线程重放等五大延迟放大因素,强调容量规划需警惕读写分离带来的正反馈循环。

2026-10-04 12:41:23 495

原创 MySQL 读写分离完整链路:主从复制、多数据源与 @DS 路由

本文基于订单系统实战,详解MySQL读写分离完整链路:以主从复制(binlog+三线程异步同步)为地基,动态数据源(Dynamic-Datasource)实现多库管理,@DS注解完成SQL路由。强调主从配置需手动建立复制连接(CHANGE MASTER TO),否则静默失效;推荐轻量级方案,避免过度引入ShardingSphere。核心原则:读多写少时,用一致性换性能,三层缺一不可。

2026-10-04 12:40:28 305

原创 MQ 重复投递不可怕:前置检查、数据库事务与唯一索引三层幂等

语义——识别为"已处理"而非"处理失败",避免 MQ 无限重投进死信;:每一次重复投递都要走完"开事务 → 插 process_record → 抛异常 → 事务回滚 → 异常捕获"这一整套,:进程在事务提交前被 kill(业务没做成,但外部已经产生了副作用,比如短信网关已经发出去了),这类只能靠告警 + 人工。只代表"有一次投递开了个头",不代表业务做完了。是**"这条消息已经处理过"的信号,不是"处理失败"的信号**。,那又是另一个量级的复杂度——是否值得,取决于业务对"绝对不丢"的定价。

2026-10-03 13:21:15 506

原创 多实例同时扫描任务如何避免重复发送?条件抢占、租约与僵尸任务回收

的——实例随时可以重启、扩容、被杀,正确的那个实例总能抢到。这就是"无锁化"的思路:把并发控制下沉到数据库的行锁,让架构更简单、更能水平扩展。所以引入 RocketMQ 做异步解耦:订单落库成功之后,把积分、短信、推送这些事丢给消息队列,由消费者慢慢处理。电商下单成功之后有一串"不那么核心但必须做"的收尾动作:给用户加积分、发短信、发 App 推送。这样一来,"订单成功但事件丢失"这个窗口就被彻底堵死了——订单和事件要么一起成功,要么一起回滚。同一条 WAIT 事件会被 A、B、C 同时捞到。

2026-10-03 13:15:11 718

原创 RocketMQ 如何做到消息不轻易丢失?Transactional Outbox 发送链路拆解

用户下单后积分未到账,根源在于异步操作(积分、短信、推送)与订单事务分离,导致消息丢失。传统写法“先发MQ再提交事务”易多发,“先提交事务再发MQ”则可能漏发且无报错。解决方案采用Transactional Outbox模式:将事件记录与订单同事务写入本地消息表,事务成功即事件持久化;再由独立发布器异步重试发送至RocketMQ,确保“事件必落库、消息必发出”。通过状态机管理重试、抢占机制防并发、僵尸任务回收,实现高可靠异步解耦,彻底解决消息丢失问题。

2026-09-30 12:19:16 732

原创 微服务内部接口如何鉴权?X-Internal-Key、X-User-Id 与 TraceId 的职责边界

在微服务架构中,order-service 调用 stock-service 时返回 401,根源在于错误地将用户 JWT 透传为服务间调用凭证。正确做法是分离“服务身份”与“用户身份”:通过 X-Internal-Key 鉴权服务合法性,X-User-Id 传递业务归属,X-Trace-Id 支持链路追踪。关键设计是将内部接口路径设为 /internal/**,由 InternalKeyInterceptor 单独校验服务凭证,实现鉴权与业务上下文的解耦,避免因 Token 过期或无用户上下文导致服务不可

2026-09-30 12:18:06 567

原创 一次微服务调用是如何发生的?Nacos、OpenFeign 与 LoadBalancer 调用链

本文详解微服务间调用链路:order-service 通过 OpenFeign 调用 stock-service 扣库存,全过程涉及服务发现(Nacos)、客户端负载均衡(LoadBalancer)、请求拦截(RequestInterceptor)、HTTP 编解码、服务端鉴权与幂等扣减六大环节。每环节均可能引发 401、超时、重试导致重复扣减等线上问题。文章以时序图拆解调用流程,强调理解链路各阶段“正常路径”与“异常处理”的必要性,揭示配置错误、重试机制、服务注册不一致等根因,助力排查与设计高可用系统。

2026-09-29 16:07:02 398

原创 从单体到微服务:订单系统拆成 5 个进程后,解决了什么又失去了什么?

(单体版)→(微服务版,本文主角)很多人讲微服务,讲的是「微服务有什么好处」的 PPT。但真到了生产上,比「好处」更重要的是——拆开之后你失去了什么,怎么把它补回来。这篇文章用一个真实落地的订单交易系统,把两边的账都算清楚。

2026-09-29 16:05:49 394

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除