求职意向:Java 后端开发、AI 应用工程化
caifengg123@163.com
在这个文档中,我基于工作项目梳理了核心亮点,包括链路梳理、架构设计、业务思考、技术细节和各种衍生,以及对应的问答思路。 基于这些材料,以及下面我将提供旧版的简历内容,结合我在括号中标注的所有优化要点,贴合真实工作场景打磨内容,修正夸大表述、强化运维产品思考、业务痛点拆解、关键技术选型、稳定性保障、客户交付及 AI 项目差异化亮点,量化成果、补齐技术细节,埋好问题“钩子”引导面试官发问,适配后端面试场景,生成完整版精准优化简历。 保留原始的层级、分点、句式结构、排版格式,只对每一条内容做精细化优化,不动任何结构、序号、标题、层级,直接在对话中输出。 以下是简历内容: xxx
cv-20260921
- 教育经历
华南理工大学 网络工程 2020.09 - 2024.07
在校经历:曾获学校与企业奖学金、”三好学生” 等荣誉,发表 EI 会议论文一篇;曾加入学院青马工程班学习,担任华工青年志愿者指导中心宣传部副部长,有丰富的活动组织和跨团队协作经验。 - 工作经历
Midea|后端开发工程师‑Java 2024.7~至今- MOPS 自动化运维平台
- MOPS 是集团核心自动化运维底座,覆盖主机、网络、存储等模块资源运维能力,通过自动化、流程化替代人工运维操作。
- 职责:负责网络与备份模块后端研发、线上稳定性及私有化交付,协同开发完成需求落地,并承担生产上线与问题闭环。
- F5 网络自动化建设:独立落地 DNS、负载均衡全流程自动化能力,覆盖资源申请、配置变更、回收及一键回退完整链路;围绕“配置可验证、状态可追踪、异常可恢复”设计事前校验、变更状态控制与配置快照机制,降低配置漂移及高风险变更带来的影响。全年累计承接 3k+ 生产工单,人工变更操作占比降低 **80%**,操作失误率降至 0。
- 防火墙下发策略优化:针对多节点定时调度下任务重复、串行执行效率低及策略重复下发问题,采用 Redis 分布式锁 + 数据库 CAS + EXECUTING 中间态分层保障任务互斥与策略执行正确性;通过专用线程池并行处理长耗时 IO 请求,并加入失败重试、超时控制及僵尸任务巡检机制,单批次下发效率提升 4 倍,策略重复下发率降至 0。
- NBU 备份自动化落地:基于 Ansible 与 NBU API,将备份客户端远程安装、策略下发等人工运维动作平台化;Java 侧通过异步任务解耦调度与执行,底层依托 SSH、Linux 权限控制完成目标主机自动化部署,部署周期由小时级缩短至分钟级。
- ToB 私有化交付:牵头网络、备份两大模块客户交付,完成需求对接、方案评审、定制开发、多环境 Bundle 适配、客户环境部署调试及上线验收;沉淀标准化交付文档与运维手册,完成从代码交付到客户环境运行的闭环。
- CMDB 基础配置数据库
- CMDB 是集团 IT 运维资产数据底座,承载上万台服务器及各类 IT 配置资产,为上层 AIOps、运维、监控等提供数据支撑。
- 职责:作为系统 Owner,负责业务方数据接入、功能支持与线上运维;深入梳理核心数据链路和线上运行机制,持续开展平台稳定性治理,并将常见运营答疑经验沉淀为 Skill,支持用户通过数字人入口自助答疑。
- 核心数据链路梳理:深入梳理数据接入、同步、存储等微服务链路,理解 MongoDB 元数据/业务数据分层、资产主键 upsert 幂等、Redis 缓存表头元数据等关键机制,形成对企业级数据接入、并发写入及数据一致性问题的系统认知。
- 防火墙采集故障排查:负责线上 Full GC / 内存压力故障闭环,按“GC 状态监控 → 线程栈定位阻塞点 → 堆内存分析根因”的流程排查,通过 JVM 参数调整与服务重启完成止血恢复;结合故障现场进一步定位表头元数据重复反序列化的核心诱因、梳理锁竞争与线程资源控制等潜在优化方向,形成稳定性排障与复盘闭环。
- 平台稳定性治理:长期负责线上 JVM 异常、中间件故障、接口卡顿等问题排查,形成“先止血恢复 → 现场取证 → 根因定位 → 验证复盘”的故障处置流程,并将常见排障路径沉淀为 Skill 并内部共享。
- CMDB‑Agent 智能问数
- 基于 CMDB 资产数据构建自然语言问数能力,使用户通过自然语言完成资产查询与统计,降低运维数据查询门槛;既服务平台用户,也作为子 Agent 为上层 AIOps 根因分析 Agent 提供资产查询能力。
- 职责:参与问数 1.0 全链路业务化开发、知识库构建与效果调优,并负责基于内部 Agent 平台的 2.0 重构。
- 问数 1.0:基于阿里开源 DataAgent 完成运维场景定制化落地,围绕真实业务问题进行查询增强、表召回、SQL 生成等节点的 Prompt 调优,并手工梳理运维业务知识并搭建分层知识库,完成基础多轮上下文与问答效果优化;重点积累 NL2SQL、上下文管理、Prompt 调优及 Agent 业务落地经验。
- Agent 2.0 重构:基于内部通用 Agent 平台完成问数 Agent 重构,将原先由人工预定义的 Graph/Workflow 转向 LLM 动态决策 + Harness 运行时治理;通过 Harness 承担会话/上下文管理,将原有业务能力抽象为可调用的 Skill/Tool,探索基于 ReAct 的自主工具调度,使模型能够根据当前上下文按需选择知识、Schema、SQL 等能力,同时通过运行时边界控制长链路执行的重试、超时、循环及异常恢复等问题。
- Agent 工程化方向:重点关注从“流程编排”向“Agent Runtime”演进过程中,如何拆分可调用能力、管理上下文、约束模型行为,并让 Agent 在真实生产环境中具备可控、可恢复、可持续运行的能力。
- MOPS 自动化运维平台
- 专业技能
- 后端开发:熟练掌握 Java、SpringBoot、SpringCloud、MyBatis,掌握企业级工程化规范,具备 AI Coding 实践经验。
- 数据库与中间件:熟悉 MySQL,具备 SQL 调优、索引优化、事务与锁机制等实践经验;熟悉 Redis、Kafka 中间件,具备缓存策略设计、消息队列治理与线上故障排查经验。
- 云原生与交付:熟悉 Docker、K8s 基础部署及 CI/CD 发布流程,具备中间件服务化开发与 ToB 私有化产品交付经验。
- AI 工程化能力:具备 Agent 应用实际落地经验,熟悉 Prompt、Tool/Skill、RAG 等技术;具备 Harness、ReAct、工具编排实践经验。
talks
自我介绍。
“面试官您好,我叫蔡枫,本科毕业于华南理工大学,目前有两年左右的后端开发经验。
我目前在美的是运维开发团队,主要参与三个方向的项目。
第一块是 MOPS 自动化运维平台,主要是把传统手工运维场景抽象成平台上的流程化、自动化能力,和代码实现。我主要负责网络和备份模块,核心落地了F5网络自动化,协同防火墙策略批量下发优化,以及 ToB 私有化交付。在这个过程中,我积累比较多的是运维自动化场景下的后端方案设计、典型IO密集型任务处理,以及结合 AI Coding 做迭代。
第二块是 CMDB 基础配置数据库,主要承接集团服务器和各类 IT 资产,围绕这些数据支持读、写、白屏化操作、自动化采集等能力。我目前是这个系统的负责人,主要负责业务方数据接入、功能支持和稳定性治理。我在接手之后处理了比较多线上问题,所以也积累了各类排障经验。
最近我重点投入的是 CMDB-Agent 智能问数。它主要基于 CMDB 资产数据,让用户通过自然语言进行资产查询,同时作为子 Agent 为上层运维 AIOps 入口提供资产查询能力。问数 1.0 主要基于开源 DataAgent,通过 Workflow 做场景定制化开发和效果调优;目前正在基于内部 Agent 平台推进 2.0 重构,利用平台提供的 Harness、Skill、MCP 等能力,从流程编排逐步向 ReAct 式 Agent Runtime 演进,实现工程化落地。
我的主要经历就是这三个方向。”为什么跳槽?
“过去两年我主要负责内部两套核心底座平台 CMDB、MOPS 的开发与稳定性保障,积累了完整的需求落地、业务迭代和故障运维经验。
但当前工作更多偏向存量系统的需求迭代与运维支撑,一方面,感觉个人兴趣不在运维平台上面,另一方面,虽然把稳定性做得很好,但在公司的评价体系下,偏维护类的工作可能很难形成可以用于晋升的业务产出,长期下来我感觉到个人成长触到了天花板。
希望能够找一个新环境,参与具备业务深度、能够产生技术沉淀的场景,或者新兴技术落地 如 AI 应用工程化这类有挑战的场景,产出更有重量的技术成果,进一步把自己的技术深度往上推。我也十分年轻,有信心尝试不同的领域;持续学习,保持好奇。”- 能力:点明自己实际做系统负责人,做过稳定性、平台建设、故障治理(摆实绩)
- 潜力:不满足只做维稳,渴望更有挑战的技术场景
- 隐性野心:想要产出有分量的技术成果,追求更深的技术成长(不说我要升职加薪,用 “产出技术成果” 表达进取心)
“天花板具体是什么,突破什么?在原来的岗位,就不能做这些深度改造吗?”
“客观看,我在现有团队已经可以独立扛下整套平台的全生命周期,从功能开发、迭代的负责,到线上故障排查。但目前业务形态以存量运维底座为主,大部分精力花在把业务方(系统运维)的需求转换为功能,并且要保障系统不出问题。(技术上已经整体掌握了;业务上,一直在做历史债务+需求堆积,而非技术导向)
这类工作的价值是稳,但很难产生新的业务成果。公司晋升评价更偏向新业务迭代类产出,所以在这套体系下我很难拿到向上的机会。
我不希望一直停留在‘保障旧系统稳定’这个层面。我希望能够接触真正有业务深度、或者大模型工程落地的场景,去做架构层面的思考与落地。我希望把我现有的后端、云原生、运维开发的综合能力,放到更大规模的业务场景里,沉淀真正有影响力的技术产出,这也是我选择出来看机会的主要原因。”
“所在的团队和系统,目前还在自动化的构建中,在智能化方面的投入较少。整体业务优先级偏向维稳,大规模架构重构、新形态业务落地的机会不多,资源和业务导向决定很难做大规模的技术产出。”你觉得你最大的优势是什么?(承接跳槽理由,顺势输出能力 + 潜力)
“一方面,我有完整平台从开发到线上运维的实战经验,能兼顾功能开发和系统运维视角,能够做需求落地和处理复杂线上问题;
另一方面我希望跳出存量系统维护,愿意去啃高并发、AI 工程化这类比较新的难题,希望在新业务场景把能力进一步放大。”看过源码吗?fastjson?claude code源码?私下会学什么?
实际没怎么看,但是会关注技术/AI咨询。。
AI
AI的使用,AI工作流(极海题库:https://bitfree.cn/question)
我的工作流就是将人类的战略思维(Strategy)与AI的战术执行(Tactics)分离。我负责定义‘做什么(What)’和‘为什么(Why)’,而AI负责‘怎么做(How)’,并通过 Hook 和 Review 机制确保结果的可控与高质量。
我做 AI coding 不是简单让 AI 生成代码,而是一套 Agent 化的工程开发流程。
首先我会前置统一 AI 编码规范(中文回答,生成注释,,)、梳理项目(先让 AI 读当前项目的技术架构、、异常处理逻辑)、注册好各类开发工具能力(Skills,MCP)。然后对于当前需求(readme.md),通过 Brainstorm 做需求发散、通过 Plan 做结构化拆解(拆分功能点P0P1P2,按模块拆分功能实现,各个模块各自开发??阶段性完成后进行测试,通过后再开发后续模块??)开发阶段,我采用主 Agent 调度 + 多专家子 Agent 并行开发的模式,主模型负责评审和把控质量,轻量化子模型负责具体代码编写,兼顾质量和效率(主模型用好的,贵的)。最后统一生成单元测试、做代码评审和联调,实现标准化、高质量、可落地的 AI 编码闭环。如何保证 AI Coding 代码质量?
- AI Coding 没有创造,而是放大了以前就存在的工程问题(程序员自身对业务背景不清楚/代码里可读性低)
- AI Coding 能力提升本质上从如何让程序员写的更快 → 如何让 AI 写的更符合预期并且可以证明之
- 对于新项目
- 创建事实库(背景介绍doc,渐进式披露,,)
- 代码/文档/测试同步(同时纳入版本管理)
- Agent Review
AI 写完必须过 AI 评审 Agent 专门检查(避免在写代码的 Agent 里评审时“糊弄”??) ⬇️
并发隐患
空指针风险
事务失效场景
SQL 全表扫描
参数未校验
重复代码、坏味道
- 对于旧项目
- 重新识别文档和代码,建立truth库(几周)
- 逐步添加agent编码约束(长期维护)
- 【AI编码的真正瓶颈是研发团队的验证能力!蚂蚁数科Harness工程实践分享】 https://www.bilibili.com/video/BV1UvbL6vEbM
AI 开发中对 SDD 的理解
“总而言之,SDD 不仅仅是一个开发流程,它更是一种工程思维的转变。它标志着我们从‘凭感觉编程’(Vibe Coding)进入了‘按规范交付’的工程化时代。对于后端开发者而言,掌握 SDD 意味着我们不再是 AI 生成代码的被动接收者,而是通过定义清晰的‘契约’,主动地、系统性地驾驭 AI,确保交付的软件既正确又符合业务意图。这正是在 AI 时代,后端工程师核心价值的体现。”AI coding 如何节省Token?
核心:少输入、少输出、少交互、精上下文
面试回答:原则 + 6个具体方法 + 总结价值
亮点:体现你会用 AI、懂成本、有工程规范AI CODING L1,L2,L3??
阿里数据库团队。。现在大模型能力越来越强,你觉得程序员会不会被替代?程序员核心竞争力是什么?
AI不会替代程序员,但是会淘汰只会机械编码、不会驾驭AI的人;未来竞争不是人对抗AI,而是会用好AI的程序员,和不会使用AI的程序员之间的竞争。
- AI的能力边界
✅ 优势:标准化代码生成、样板代码编写,高效完成确定性编码任务
❌ 短板:无法自主定义模糊问题、不理解业务隐性约束与存量技术债、缺少权衡决策能力、容易产生幻觉隐患
✅ 关键前提:AI产出的代码必须由人理解、校验、可控,否则无法上线落地 - 程序员三层核心竞争力
① 底层基础能力(底盘):计算机基础、并发、存储、网络等原理,用来识别AI代码隐患,具备评审、排障判断力
② 方案与业务能力(核心):需求拆解、架构取舍、风险评估、线上疑难问题排查。跨角色沟通、风险对齐、推动落地这类软能力,也是AI不具备的。
③ AI体系落地能力(高阶加分):Agent/RAG/Harness等大模型应用工程化、AI能力管控、搭建团队AI提效工具。 “我近期也在落地Text2SQL的Agent Demo,尝试用主Agent+专家子Agent+Harness编排的思路,本质就是探索如何规范化管控大模型能力,把AI可靠地融入研发流程。” - 个人发展策略
将AI作为编码工具,释放重复劳动;重心投入高价值的方案、决策、风险治理;持续夯实基础,同步积累AI工程落地经验,打造差异化优势。
- AI的能力边界
Agent
- Agent 是什么?Agent = LLM + Harness,我的理解是大模型的思考能力驱动智能体真正的完成一些人能做的事情。首先要能将手工操作拆分为可复现?的任务,更加适合完成重复性工作。
- Harness 是什么?Agent 中除了大模型,其余的影响到其稳定交付效果的内容,如会话管理、工具编排、失败重试、熔断机制。。从 AI 工程的发展来说,一开始对于一些短链路、单轮次的任务,做好提示词优化,“把任务讲清楚”,明显改变模型的输出效果;但随着 Agent 火起来,真正进入环境里做事,能够调 API、查数据,提示词工程(PE)达到了上限,转向上下文工程(CE),核心是 “要让模型在正确的时间拿到正确的信息”,包含所有会影响模型当前决策的信息总和,历史会话、背景上下文、用户输入、系统规则、过程中调用工具的返回、检索知识库结果等等,Prompt 只是 Context 的一部分,Context 是输入环境的工程化;随着 AI 更多的承担长链路任务,其工作的稳定性成为关键点,Harness Engineering(HE)解决的是 “让模型在真实执行中持续做对”,不仅要提供环境让 Agent 把任务做起来,还要持续盯着执行过程、检查最终结果、跑偏了要纠正、失败了能恢复。
- Langgraph → Agent?Graph/Chain 是节点和路由编排的 workflow,本质上是业务逻辑的 if-else 实现,但在 Agent 的场景下,原来复杂的“状态机”被模型接管了,变成一个基于 ReAct 的 loop(原来的流程图坍缩为一个model-execute),Agent 基于 Prompt+Skills+Reasoning-Acting 自行调度拆解任务+逐步执行+分析结果(for 循环),自由度更高,而 Langgraph 稳定性更高,生产仍然可用于安全约束、审计校验等,但也存在问题(业务复杂导致节点难以维护,固定走全流程可能导致效率低)
- Multi-Agent 架构?适用于:一个复杂任务里存在多个相对独立的认知职责,这些职责需要不同的Context、工具、权限、目标或评测标准,且它们之间还需要协作;否则,就算是繁杂但场景类似的长任务,也可以通过一个 Agent+workflow+tool 实现。这样,可以避免不同环节、差异很大的 Context 堆积在一起,影响其中单 Agent 生成;避免不同的 tools、权限堆积,影响单 Agent 判断使用;可以实现没有前后依赖的 Agent 的并行 work;可以做到不同 Agent 的独立评测和迭代。
- 通用 Agent / 个人工作 Agent 化?我在 Codex 里有一个 career-os 项目,能够评价当前简历,沉淀个人求职能力事实,根据 JD 适配微调 cv,定时拉取招聘信息。。
- Skills?个人/团队校验的抽象,帮助 AI 从通用型人才 → 专家型人才,披露式的 md文件,大模型自己判断需不需要加载。自己梳理的:纯知识类(CMDB平台答疑自助,内网 IT 排障指引),工具使用类(问数相关。。)
- MCP / Tools?支持 AI 调用工具与外部交互。
- LLM。预训练(数据集→文字token化→训练(调整模型参数使其”学会下一个token是什么”)→得到基础模型(输入词→神经网络→下一个词))→ 后训练→ 强化学习
- RAG?帮助 AI 补充内部知识到上下文。
- 智能问数。nl2sql 帮助用户通过对话快速查询企业数据;引入新技术,实现旧系统重构,简单;实际落地,系统性工程,难。不仅要把已跑通、可复用的人工查数流程(澄清数据关系,统一查数口径,元数据治理,,这些规则约束,与“智能”无关的脏活累活才是关键),拆分成 AI 可理解、可执行的任务步骤,还要考虑失败重试、结果评价、、

Agent如何节省Token?
架构上,用Skill封装替代链式Tool调用,从根源上减少交互轮数;并设计模型路由,让大模型只处理复杂任务。
上下文上,通过摘要压缩历史对话,使用‘山顶洞人’模式精简输出,并极致优化提示词。
交互上,利用KV Cache避免重复计算,在多Agent场景下采用Latent Briefing技术进行高效通信。
基建上,部署语义缓存来拦截高频重复问题。LLM相同,提词相同,输出结果一定一样吗?
结论先说:默认情况下一定不一样;只有严格锁死全部条件,才能做到每次输出完全相同Skills?
“简单来说,Agent Skills 就是AI的标准化岗位说明书。它通过 SKILL.md 文件将领域知识、操作步骤和脚本封装起来。
最巧妙的是它采用了渐进式披露机制,只在需要时才加载详细内容,既保证了效果又节省 Token。如果说 Tool 是 AI 的手脚,那 Skill 就是 AI 的‘肌肉记忆’和‘工作经验’。在2026年,构建高质量的 Skills 库是企业级 AI 应用落地的关键。”
cf:skills提供业务知识,让llm从普通员工→该业务的资深员工,区别于预设好场景提前设计好Prompt模板供模型使用,Skills让LLM能按需自行构造Prompt?!RAG?落地?
- 全称是 检索增强生成(Retrieval-Augmented Generation)。大模型只有预训练的知识,不具备企业内部知识,RAG 通过知识库建设,知识检索,提示词构建,在提供给LLM的Prompt里引入内部知识上下文,从而让模型生成更准确、更实时的回答。
RAG 本质上就是为了解决 LLM “知识滞后” 和 “一本正经胡说八道” 这两个核心痛点而诞生的架构。 - 为什么要用 RAG?(核心价值 vs 痛点,建议对比微调(Fine-tuning)来谈)
- 时效性 →知识更新快。只需更新外部知识库即可,无需重新训练昂贵的模型。
- 准确性 →减少幻觉。答案基于检索到的事实,有据可依,可追溯来源。
- 成本 →性价比高。比全量微调或预训练便宜得多,适合垂直领域落地。
- 隐私 →数据不出域。私有数据只在检索环节使用,不需要注入到模型参数中。
- 进阶补充(加分项,可以补充一点你对 RAG 当前挑战 的理解)
“虽然 RAG 很好,但在实际落地中也面临挑战。比如检索精度问题(如果检索不到相关内容,模型就答不对),以及上下文窗口限制(检索内容太多塞不进 Prompt)。所以现在的优化方向通常包括使用混合检索(关键词+向量)、重排序(Rerank)模型,以及更智能的切片策略。”
- 全称是 检索增强生成(Retrieval-Augmented Generation)。大模型只有预训练的知识,不具备企业内部知识,RAG 通过知识库建设,知识检索,提示词构建,在提供给LLM的Prompt里引入内部知识上下文,从而让模型生成更准确、更实时的回答。
如何基于知识库设计一个 AI问答系统?
- 画全景图(建立宏观认知)
要给面试官一个清晰的整体架构。你可以这样开场:“一个完整的RAG(检索增强生成)问答系统,本质上是一条数据处理和问答的链路。我们可以将其拆解为离线知识库构建和在线问答服务两个核心部分。” - 离线阶段
- 数据解析与清洗:提到企业数据往往是多模态的(PDF、PPT、图片)。你需要指出系统能处理非结构化数据,利用OCR技术提取图片中的文字,并进行版面分析(去除页眉页脚、乱码)。
- 分块 (Chunking):智能分块,这是关键点。不要只说“切分”,要提到“语义完整性”。
策略:可以提到按段落、章节切分,或者使用滑动窗口(例如500 token一个块,重叠50 token)来避免上下文丢失。
元数据:强调保留元数据(如来源文件名、页码、章节标题),这对后续溯源非常重要。 - 向量化(Embedding):说明使用Embedding模型将文本块转化为向量,并存入向量数据库(如Milvus、Faiss)。同时,建议提到混合索引的概念,即除了向量索引,还建立倒排索引(BM25),为混合检索做铺垫。
- 在线阶段
- 查询理解:用户的问题往往很简短或有歧义。你可以提到系统会先对Query进行改写或扩写(例如结合历史对话上下文),使其更适合检索。
- 混合检索:这是加分项。单纯依靠向量检索(语义匹配)可能会漏掉专有名词,单纯关键词检索(字面匹配)无法理解语义。方案:提出使用“向量检索 + 关键词检索”的双路召回策略,然后通过RRF(倒数排名融合)算法合并结果。
- 重排序 (Rerank):Cross-Encoder, BGE-Reranker。检索回来的Top 50个片段可能包含噪声。引入一个Rerank模型(如Cross-Encoder)对这50个片段进行精细打分,只取Top 5最相关的片段喂给大模型。这能显著提升准确率并减少Token消耗。
- 生成与优化:将“用户问题”+“精排后的知识片段” 组装Prompt、流式输出、防幻觉 LLM (GPT-4, Llama 3)。强调在Prompt中加入约束,例如“请仅根据提供的参考资料回答,如果资料中没有答案,请说明不知道”,以此来抑制幻觉。
- 谈工程挑战与优化(体现资深程度)补充生产环境中的挑战:
- 性能与延迟:
缓存机制:提到构建L1(精确匹配)和L2(语义匹配)双层缓存。对于高频问题(如“怎么重置密码”),直接从Redis返回答案,无需经过大模型,将延迟降至毫秒级。
流式输出:为了提升用户体验,答案应采用流式(Streaming)返回,首字延迟控制在200ms以内。 - 长文档与多模态:
针对超长文档(如几百页的手册),采用分层索引(文档级->章节级->片段级)或Map-Reduce策略。
针对图表,提到使用多模态模型进行解析,保留表格的结构化信息。 - 评估体系:
提到如何衡量系统好坏,例如使用RAGAS框架,评估检索的准确性(Recall@K)和生成的忠实度(Faithfulness)。
- 画全景图(建立宏观认知)
Harness?
- 广义Harness(行业规范/设计思想) ➡️ Agent标准化运行底座标准,定义必备能力:会话生命周期状态机、长短双层记忆、上下文自动裁剪、重试/熔断、统一Prompt管理、可插拔Skill调度、安全沙箱;
- Harness框架(规范落地实现)➡️ 遵循上述标准的现成代码底座,分技术栈:
Java:AgentScope、Agents-Flex(后端业务、求职首选)
Python:LangGraph??
TS:DeepSeek Harness(快速本地原型验证) - ??与旧框架层级差异:LangChain/LangChain4j仅LLM/RAG工具封装,无标准化生命周期管控,需手动拼接流程 ➡️ 新增完整运行管控层,开箱即用生命周期、记忆、容错。
??!手搓一个问数 Agent(不用框架)
- 不用 LangGraph 好处是啥?
- 方案:自己实现类Harness能力
- 验证:代码Test类?问数效果?
AI 技能体系
- 底层永久核心(不会过时,必学)
- LLM基础:Token、上下文窗口、流式输出、多模型调用、输入输出安全;
- RAG全链路:切片、向量化、多路召回、重排、幻觉优化;
- 原生提示词工程:CoT、结构化输出、模板设计;
- 原生Agent逻辑:任务拆解、工具调用、反思循环。
- Harness体系
- Harness底座使用与设计:生命周期、双层记忆、重试熔断、会话隔离;
- Skill模块化拆分、插件化开发;
- 多Agent协同基础;
- AI工程运维:链路日志、Token监控、性能优化。
- 个人技术体系搭建思路
- 底层打底:吃透LLM/RAG/原生Agent底层原理,不依赖框架;
- 标准化落地层:主攻Java Harness框架(AgentScope),掌握规范落地思路;
- 业务层:模块化Skill设计,把业务抽象为可插拔工具;
- 底层深挖:业余手写极简Harness(记忆/状态机/重试),面试深度作答;
- 边界区分:能清晰区分「规范、框架、业务Skill」三者关系。
- 最小可运行Demo:Nl2SQL智能问数(精简落地方案)
- 选型 ➡️ 主框架:AgentScope(Java,贴合后端求职) ➕️ 配套组件:LLM 通义千问/DeepSeek API ,数据库:SQLite(零部署测试业务表),向量库:Chroma本地轻量版(RAG存储表注释),基础依赖:SpringBoot、JDBC、Lombok
- 分层设计 ➡️ LLM层:模型推理大脑 ➕️ Harness运行时层(AgentScope内置):会话管理、长短记忆、上下文裁剪、3次重试熔断、Prompt模板中心、Skill调度器、SQL安全沙箱 ➕️ 可插拔Skill层(4个最小业务模块):MetaSkill:RAG检索数据表元数据,SqlGenSkill:自然语言生成SQL, SqlCheckSkill:语法校验、拦截高危DDL/DML,QuerySkill:执行查询、结果自然语言汇总 ➕️ 存储层:SQLite业务库 + Chroma向量知识库
- Demo标准工作流(完整Harness生命周期展示)
- 用户输入自然语言提问 → Harness创建独立会话,加载历史对话记忆
- Harness调度MetaSkill:RAG检索匹配数据表结构与字段注释
- Harness加载SQL生成Prompt模板,调度SqlGenSkill产出SQL
- Harness调度SqlCheckSkill校验语法、拦截危险语句(校验失败:触发Harness重试机制,最多3次,超限自动熔断终止流程,校验通过:进入下一步)
- Harness调度QuerySkill执行SQL,获取数据
- LLM汇总查询结果,生成自然语言回答
- Harness更新短期会话记忆、持久化关键元数据至长期向量记忆
- 输出答案,完整生命周期日志打印(状态、重试次数、Token消耗)
- 与旧Data-Agent区别
- 不再手写大量 if‑else 来硬编码工具调用分支。业务能力被封装成标准化 Skill 注册到 Harness;调用哪个 Skill 由大模型做动态决策,Harness 负责解析指令、安全执行、管控重试熔断和上下文记忆流转。开发者只需要新增 / 修改 Skill,不用改动 Agent 主循环。
- 举个反例:阿里开源 Data-Agent(非 Harness)
写死:用户提问第一步固定先调用 RAG,再生成 SQL,再校验,再执行。 这种叫固定流水线,不是 Agent。
Agent 的价值是:由大模型动态决定步骤顺序,不一定每次都完整走全部 Skill。 比如有些简单问题不需要 RAG,直接回答;有些报错需要多轮重试调用同一个 Skill。Harness 就是承接这种动态决策后的执行。 - Nl2SQL Demo(using Harness)举现实例子
- LLM 思考:需要 MetaSkill,参数 query=2026 销售总额
- Harness:解析调用指令,找到注册好的 MetaSkill,执行向量检索(纯代码 Skill)
- MetaSkill 返回订单表结构
- 上下文更新,再次交给 LLM
- LLM 思考:调用 SqlGenSkill,入参带上表结构和用户问题
- Harness 调度 SqlGenSkill(内部包含 Prompt 模板 + LLM 调用 + 清洗代码)
- 拿到 SQL,LLM 指令调用 SqlCheckSkill
- Harness 执行校验,发现没问题,继续调度 QuerySkill 执行 JDBC 查询
- 查询结果返回,LLM 不再调用任何 Skill,直接输出最终答案。
- 大模型驱动,Harness主控,Skills支持
- 大模型(驱动 / 决策)
是大脑,负责理解用户意图、规划任务步骤、选择调用哪个 Skill、决定什么时候结束任务。
⚠️ 它不直接执行任何外部操作:不能查数据库、不能读向量库、不能操作文件,只输出思考和工具调用指令。 - Harness(主控 / 运行管控)
是运行容器与调度中枢。接收大模型输出的工具指令,完成:Skill 查找、权限沙箱、执行调用、异常捕获、重试熔断、会话生命周期、记忆读写、上下文组装,再把 Skill 执行结果回传给大模型,驱动下一轮思考。
“主控” 不是说 Harness 自己决定业务逻辑,而是掌控整个 Agent 的执行流程与运行边界;业务走向依旧由大模型决策。 - Skills(支持 / 能力支撑)
是标准化可插拔能力单元。分为两类:
纯代码 Skill:RAG 检索、SQL 校验、DB 查询;
LLM 型 Skill:内部封装 Prompt 模板 + LLM 调用 + 前后处理逻辑;
对外统一接口,由 Harness 按需调度执行,提供大模型本身不具备的外部能力。
- 大模型(驱动 / 决策)
- 底层永久核心(不会过时,必学)
项目
- 项目介绍。
1 自我相信,要反复强调:这个项目非常重要
2 包装项目:为什么做,怎么做,亮点,结果
3 告诉面试官一件他不知道的事情,产生好奇
三大原则:强调项目业务价值、标准项目叙事结构、预埋知识点引导面试官提问,主动导向自身准备充分的技术栈。
如何把项目讲好?细节?
1 往岗位需求上倾向(包装)
2 具体怎么展开?为什么做(背景,不要说“老板要做的”),核心问题(一两个难点、如何解决),惊喜(额外思考、解决了什么)
3 面对质疑:您提出了一个稳定性/架构层面的问题,可能当时没考虑到,而是在聚焦别的问题;未来可以。。“挑一个你做得比较有代表性的项目讲一下”
“我比较有代表性的一个项目是 MOPS 里的防火墙策略批量下发优化。
这个场景本质上是一个多节点定时调度的批量 IO 任务:防火墙工单审批后生成待下发策略,由 XXL-JOB 定时触发,系统批量调用防火墙 API 执行下发。
核心挑战不是CPU计算,而是如何控制任务并发以及保证配置一致性。
原来的问题主要有两个,一个是单条策略下发比较慢,整批任务串行执行导致效率比较低;另一个是多节点定时调度下,如果上一轮还没执行完,下一轮可能再次扫描到相同的待执行策略,存在重复下发和配置漂移风险。
所以我们从三个层面做优化:执行层使用专用线程池,并行处理没有顺序依赖的策略;调度层通过 Redis 分布式锁保证同一时间只有一个节点执行这一轮任务(保证任务并发的可控);策略层通过数据库状态更新和 EXECUTING 中间态,进一步防止已经被领取执行的策略被下一轮重复扫描。
在异常处理上,单策略有最多 3 次重试,整轮任务有 CompletableFuture.allOf 的整体等待上限,同时还有针对异常 EXECUTING 状态的巡检兜底。
最终单批次下发效率提升大约 4 倍,策略重复下发率降到了 0。”1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20防火墙策略批量下发(技术树)
│
┌─────────────┼─────────────┐
↓ ↓ ↓
问题背景 核心方案 异常兜底
│ │ │
多节点 + 长IO 三层分级优化 单策略 3 次 Retry
串行效率低 │ Timeout: allOf 1780s < leaseTine
重复执行 │ Zombie Patrol 30min 巡检重试
│
┌───────────────┼────────────────┐
↓ ↓ ↓
执行层 调度层 状态层
解决“太慢” 解决“多节点同时跑” 解决“策略重复领取”
ThreadPool Redis Lock CAS + EXECUTING
│ │ │
IO并行 互斥 策略认领
8/100/20 TTL/Token 状态机
Reject Redisson TODO→EXECUTING
→DONE/FAIL
还存在需要澄清的问题:“超时后下一轮到底会不会共享同一个线程池,以及当时为什么允许 release lock”必须以真实代码为准。
你只要把下面三个地方确认一下,这个问题就彻底落地:
ThreadPoolExecutor 是在哪里创建的?每轮 new,还是 Spring 单例共享?
100 个策略是不是 CAS → submit 一个个进行?
allOf(…).get(1780s) 超时后的 finally 具体做了什么?只是 unlock,还是还有 executor/任务处理逻辑?
特别是第 1 个。
如果你确认是共享线程池,那么你刚才提出的“下一轮会不会受到上一轮影响”——答案就是:
会,而且这是非常值得你在面试时能够主动意识到的工程问题。
这反而会让你这个项目的技术理解再上一个层次。
1 | ## 项目浓缩成一条“脑内主线” |
一个挑战。/ 讲一个最复杂线上问题。
“比起 MOPS 的平稳迭代,更大的挑战是 CMDB 原来的负责人交接给我时,团队中的其他成员对其代码实现了解不高,而且故障多。。
今年,我逐渐接手了这个系统,后来主要负责业务方的数据接入、线上问题以及日常维护。因为它本身是一个比较复杂的微服务系统,所以我后面花了比较多时间把核心数据链路梳理清楚,包括数据接入、同步、MongoDB 存储、Redis 元数据缓存等。”
“CMDB 这块我的角色更多是系统 Owner 和线上维护,不是最初架构的设计者。我比较深入的是核心数据链路、问题定位和线上稳定性。比如业务接入出现问题时,我需要从接口一直追到数据同步、数据库以及缓存这一层,定位到底是哪一层出了问题。”CMDB 的 FullGC 当时是怎么处理的?
“你简历里 Agent 是怎么做的?”
“这个项目其实经历了两个阶段。1.0 更偏传统的 Workflow/Graph 编排,2.0 我们现在正在往 Agent Runtime 的方向重构。”
1.0 是基于阿里开源 DataAgent 做的运维场景定制,核心任务是让用户通过自然语言查询 CMDB 数据。我们主要做了表召回、SQL 生成、Prompt 调优、知识库以及基础多轮上下文。
2.0 的思路发生了比较明显的变化。原来是人工把 Graph/Workflow 定义好,让模型在预设流程里执行;现在希望把这些业务能力拆成 Skill/Tool,由 LLM 根据当前上下文动态决定调用哪个能力,Harness 负责会话、上下文以及运行时约束。”
“所以我现在比较关注的,是怎么让 Agent 在真实业务环境里稳定运行。”Agent 项目要同时准备“技术价值”和“业务价值”两条线
1
2
3
4
5
6
7
8
9技术线:Workflow → 意图 → 检索 → SQL → Agent 2.0
为什么要做 Agent?降低资产数据查询门槛。同时问数也是 AI 落地的经典场景。
为什么做 SQL?SQL 是查询能力的执行表达,模型负责把自然语言映射为结构化查询。
为什么自然语言而不是表单?复杂、多表、非固定查询更灵活;普通用户不需要理解系统结构
为什么 2.0?1.0 流程固定、能力绑定;2.0 抽象 Skill/MCP,让能力可复用、动态编排、后续扩展更容易。为什么你一个 Java 后端想做 Agent?
“我觉得 Agent 对我来说并不是完全跨领域转行。
我原来做 Java 后端,比较多解决的是确定性系统的问题,比如任务调度、并发控制、数据一致性、异常恢复和线上稳定性。
现在 Agent 引入了 LLM 以后,系统的决策变得不完全确定,但它依然需要解决很多工程问题,比如上下文管理、工具调用、超时、重试、状态管理和异常恢复。
所以我比较感兴趣的是把原来的后端工程能力应用到 Agent 上,让模型能力真正变成一个稳定可运行的业务系统。”一次排查问题的思路
先止血,后排查。。(快速定位影响面和故障类型,对应获取证据,定位根因)
cpu飙升,发现是一个接口有io操作,并发大了就卡住了。。
首先,停掉回写接口,把cpu降下来,恢复服务
分析,不是计算型的,所以多线程没用,要考虑优化io查询,cmdb加索引。。
其次,如何降低并发,加缓存?异步??处理偶现问题
技术亮点与难点
分布式,高并发,,,??不是重点
而是如何适配业务做场景的抽象,选用合适的技术栈来实现。。“你了解高并发 / 秒杀如何设计?”
在我们运维平台项目中,没有电商秒杀这种瞬时热点争抢资源的业务,但是项目中大量用到高并发相关基础组件。
比如 MOPS 大批量下发任务用到线程池异步、接口限流;CMDB 大批量资产入库用到 Redis 缓存优化、upsert 幂等、Kafka 异步事件。
针对秒杀这类场景,我学习过整套处理思路:
前端请求层做限流削峰,Redis 预减库存挡大部分流量,MQ 异步解耦下单流程;通过请求 ID 做下单幂等;利用分布式锁防止库存超卖;同时设计熔断降级保障核心链路;最后数据库做最终一致性校验。
我们运维场景没有资源抢占,更多是大批量平稳同步,不会出现热点 Key 争抢的情况。Q&A(doubao文档 https://www.doubao.com/docx/LBf3d5HhDo1GbBxiSpucIhmDnbb)
🛠️ MOPS 自动化运维平台
项目口述
MOPS 是集团核心自动化运维平台,主要承载集团主机、网络、存储等模块的日常运维任务执行工作,替代传统人工登录设备、配置策略的低效模式,实现运维工作标准化、自动化、闭环化。
我作为网络与备份模块核心负责人,全程负责需求对接、方案评审、功能落地闭环,平台稳定性治理,以及外部私有化定制交付全流程工作。我们的核心工作,就是把高频、重复、高危的手工运维动作,通过业务模型&流程设计、结合代码实现的方式固化为平台自动化能力。- F5 网络运维全流程自动化:我主导落地基于F5的负载均衡、域名解析自动化能力,覆盖资源申请、配置变更、资源回收全生命周期,同时创新性落地了一键回退能力。整体围绕事前风险拦截、事中数据一致性、事后故障兜底的思路设计。通过工单‑Record 快照模型解耦业务流程与设备真实配置,将高危手工设备变更转为平台可控、可审计、可回顾的标准化工单流程。
- 防火墙策略下发优化:核心业务,典型大批量IO密集任务场景,原来的方案是定时调度+串行下发,存在集群调度下策略重复下发、串行吞吐低的问题,我完成分层架构改造;使用隔离专用线程池 + CompletableFuture 实现并发下发;Redisson 分布式锁承担任务级性能防护;新增 EXECUTING 状态配合数据库 CAS 实现策略粒度正确性兜底;配套严格的超时时序约束、僵尸策略巡检任务闭环各类边界异常。
- NBU 备份自动化下发:复杂运维场景通过 ansible(ssh登录后用root权限执行shell命令)结合异步线程,实现客户端批量安装+备份策略下发,时间降低至分钟级。
- 私有化项目交付:承接客户定制化运维需求,全程对接客户沟通、需求确认、问题闭环、版本输出,积累了完整的 ToB 产品交付与客户闭环经验。
- 运维智能化建设:预想中。(完成运维助手与公司统一数字人平台的接入,实现办公端一键提报运维工单、自助查询运维进度;并且在团队根因定位 Agent 建设中,主动暴露平台工单标准化接口为外部 MCP 服务,支撑 AI Agent 自动拉取工单数据、辅助故障根因分析,完成传统运维平台向智能化运维的能力升级。)
- 整体而言,这个项目让我跳出单纯的功能开发,完整沉淀了业务抽象、需求治理、工程优化、团队协同、客户交付、AI 能力融合的综合落地能力,擅长把繁琐的业务流程转化为标准化、自动化、可兜底、可交付的工程能力。
Q0:你在 MOPS 平台主要负责什么?核心解决了什么业务问题?
我主要负责 MOPS 平台F5 网络自动化、NBU 备份自动化两大核心模块的全栈落地与运维交付,同时承担对内需求治理、团队方案推进、对外私有化定制交付工作。
核心解决两大业务痛点:第一,集团传统网络、备份运维高度依赖人工,高频工单重复操作多、效率低、人工误操作风险高;第二,人工变更无标准化流程、无记录、无回滚机制,一旦出错排查成本极高;
我通过将手工运维动作固化为平台自动化能力,实现了运维流程标准化、执行自动化、风险可兜底、能力可交付,大幅提升集团运维效率,降低线上变更风险,同时支撑外部客户定制化交付。Q1:讲一讲MOPS平台F5自动化策略下发的整体设计,有哪些核心思想?
- F5自动化下发是MOPS平台网络运维模块的核心能力,核心目标是把人工登录设备修改配置的高危操作,转化成平台可控、可审计、防冲突、可回滚的标准化工单流程。整体设计围绕防重复、防配置漂移、数据一致性、故障可兜底、架构可扩展这几个自动化运维核心思想落地。
- 在数据模型层面做了职责拆分:工单代表运维业务流程,Record记录映射F5设备上真实生效配置,工单走审批流转,记录留存设备侧变更快照,实现业务流程和设备配置解耦。
- 完整业务执行流程: LB负载均衡为例,用户提交工单 → 前置双重校验(防重复提交 + 配置漂移校验)→ 事务内保存工单主体与工单详情TicketDetail → 人工审批流转通过 → 将执行设备、执行策略等信息入库工单表 → 将工单状态更新为EXECUTING避免任务重复调度执行 → 自动化任务执行 → 生成变更Record记录 → 调用F5开放API完成设备配置下发。
- 其中两处关键前置校验,从源头规避线上风险:
- 防重复提交校验:新建LB工单提交时,校验是否存在同一发布IP+端口处于处理中/异常状态的工单,如果存在直接拦截,避免同一端口重复下发变更引发F5配置冲突。
- 配置漂移双向校验:提交变更、回收工单瞬间,实时调用F5 API拉取设备真实Pool Members配置,和MOPS系统内存储的变更前数据做集合差集双向比对;一旦两边数据不一致,直接阻止工单提交。解决运维人员直接登录F5手工改配置、平台记录与设备真实状态脱节的配置漂移问题,保证平台是配置的唯一可信来源。
- 在数据一致性保障上,submitTicket提交工单方法上加@Transaction数据库事务。工单保存、iflow流程提交、工单号更新等本地数据库操作全部包裹在事务内,任意一步异常全部回滚,避免出现“工单部分入库、数据残缺”的脏数据。
注意:调用F5设备的外部网络IO不会纳入该事务,外部调用放到事务之外异步执行,防止长事务、外部IO拖垮数据库;执行前先更新工单状态为EXECUTING,依靠数据库状态锁,防止定时调度重复执行同一个工单。 - 为解决变更风险,落地两套兜底能力:
- 蓝绿发布:通过新建全新ResourcePool,将业务节点加入新池子,原子修改VirtualServer对Pool的引用完成流量切换;旧Pool不删除,保留7天。整个切换只是VS引用变更,业务零中断;回退仅需要把VS切回旧Pool,秒级完成恢复。
- 一键快照回退:工单提交的同时,主动拉取F5完整配置,包含VirtualServer、ResourcePool、Profiles、SNAT、会话保持等,完整序列化存入工单表actual_meta_data快照字段。回退时直接读取快照还原设备配置,避免因为平台上的“变更前”记录与实际的“原值”不一致而出错。
- 其中两处关键前置校验,从源头规避线上风险:
- 架构层面使用策略模式实现多设备适配: 定义统一接口IDomainService抽象所有负载设备类型操作;F5实现类xxDomainServiceImpl标注@Service(“F5”)实现接口全部方法。Spring容器启动自动扫描所有实现类,构建iDomainServiceMap,以注解内字符串作为key。上层业务根据设备类型从Map获取对应实现执行逻辑。
- 拔高总结: 真正成熟的自动化运维,不只是把手工操作改成接口调用,更要做到:事前校验拦截风险、事中保证数据一致性、事后提供可回滚兜底、架构层面支持多设备扩展;同时约束直接登录设备的手工操作,和系统运维同事同步平台能力建设,推动平台成为配置变更唯一入口,从流程和代码双维度降低人为故障概率。
- 衍生追问1:为什么F5的API网络调用不能放到@Transaction事务里面?
外部API属于网络IO,耗时不可控,如果放在数据库事务中,会长时间占用数据库连接,造成长事务、锁等待、连接池耗尽问题。因此只把本地数据库操作放在事务,外部设备调用放到事务外异步执行,依靠工单状态字段做执行幂等控制。 - 衍生追问2:什么是配置漂移,除了提交工单时校验,还有什么长效治理手段?
配置漂移就是设备上手工修改配置,平台数据库没有同步更新,平台记录和真实设备状态不一致。 除了工单提交时实时比对拦截,长效方案:增加定时巡检任务,周期性拉取F5全量配置和平台记录做比对,发现不一致产生告警,推动运维人员对齐配置,做到事前预警。 - 衍生追问3:策略模式在这里,如果新增其他类型负载均衡设备,需要改动哪些代码?
只需要新增Nginx的接口实现类,实现IDomainService全部方法,打上@Service(“Nginx”)注解;上层业务调用逻辑完全不用修改,代码零改动,根据设备类型key直接从iDomainServiceMap拿到Bean执行,实现对扩展开放,对修改关闭。 - 衍生追问4:如何做到自动化流程可控、可审计、可回滚?
通过数据模型和下发流程的设计,将一个策略的自动化行为(包括流程+代码执行)约束在一个工单里:
事前:校验拦截风险、、
事中:①提交工单时,涉及流程平台接口调用+系统数据库操作,用事务保证数据一致性;②自动化执行中,对于外部接口的异常(F5报错了)直接抛出,终止自动化流程,报错回写工单,保留现场避免在已经发生异常的情况下继续执行后续的自动化代码,存在不可预见的变更风险,此时系统运维同事介入共同确定问题所在,同步优化
事后:提供可回滚兜底、、
Q2:项目中用到了线程池、异步优化,具体是什么场景?解决了什么问题?
- MOPS 整体业务并发压力不高,属于典型的多任务批量耗时型业务场景,核心痛点不是高并发击穿,而是串行执行效率极低、接口阻塞超时,我针对性做了两处工程优化:
- 批量域名解析场景:业务经常需要对几十上百个 IP 执行 nslookup 校验,串行逐个执行耗时极高。我通过自定义核心线程池,实现多 IP 并行解析,批量任务耗时缩短 60% 以上,同时通过线程池参数管控,避免无限制创建线程导致资源溢出。
- 防火墙策略下发:分布式锁解决任务重复执行,线程池解决串行执行效率低
- 备份策略下发场景:NBU 客户端批量安装+策略下发属于超长耗时任务,同步执行会导致接口阻塞、前端超时。我采用异步线程单独执行耗时任务,接口即时返回受理结果,后台异步完成执行、记录日志、同步结果入库,极大优化了平台使用体验与接口稳定性。
- 核心思路:运维场景关注稳定和可靠,没有大流量(但其实有密集计算,如防火墙策略生成算法),但是涉及外部交互要考虑io密集场景。针对批量、耗时、IO 密集型运维场景,通过并行+异步的工程思维,解决效率与接口稳定性问题,适配运维类业务的核心场景痛点。
- MOPS 整体业务并发压力不高,属于典型的多任务批量耗时型业务场景,核心痛点不是高并发击穿,而是串行执行效率极低、接口阻塞超时,我针对性做了两处工程优化:
Q3:介绍下 MOPS 防火墙策略定时下发的调度方案,线上遇到过什么核心问题?如何解决的?
- 防火墙策略定时下发是 MOPS 平台高优先级的运维场景,整体依托 XXL-Job 实现定时任务调度,扫描待执行防火墙策略,完成批量策略自动化下发、配置同步与状态校验。
业务本身属于典型 IO 密集场景:单条防火墙策略下发需要和防火墙设备交互,部分包含大量地址组的复杂策略,单条下发耗时能够达到数分钟;平台会同时堆积几十上百条待执行策略,对调度的防重复执行、任务隔离、并发吞吐、异常兜底要求很高,线上曾经出现过策略大量积压、重复下发、系统资源被打满的 P2 级线上问题,我针对故障根因完成了完整的架构改造,从任务调度、并发模型、数据库状态兜底多层做优化。 - 一、核心线上事故:定时任务重复执行问题
故障发生前原始实现逻辑:XXL‑Job 每 5 分钟触发调度,扫描数据库全部TODO待执行防火墙策略,拿到策略集合之后,串行逐条调用下发逻辑,执行完后更新状态为success/fail。整套实现只依赖 XXL‑Job 自带调度,没有额外的任务互斥、没有中间执行状态做数据层隔离。
导致严重线上问题:前一次调度的批量策略下发的任务尚未执行完毕(发现串行执行的特别慢,几分钟一条,又几分钟一条),下一轮定时调度触发后,新任务会再次扫描出未完成的策略,造成同一防火墙策略重复下发、重复配置覆盖,极易引发设备配置冲突、运维工单异常、设备会话震荡等高危问题。
拆解下来,原始代码一共存在三层核心缺陷,三个问题叠加放大故障影响:- 执行模型为串行下发:单条复杂策略耗时久,会阻塞整批任务所有后续策略,批量场景吞吐极低;
- 缺少任务级互斥保护:后端服务3节点部署,xxljob上一轮调度未跑完,下一轮调度周期到就会再次启动(轮询到另一个后端),多轮任务并发跑;(??!!不过如果有EXECUTING中间态,做了策略级的筛选,就没必要用分布式锁做任务级隔离了。。反而效率低了???)
- 缺少数据库中间执行状态兜底:没有EXECUTING执行中状态,任务在处理过程中数据库记录依旧停留在TODO,新一轮调度会重复捞取到正在处理的策略,仅靠应用内存状态无法做到跨实例、跨调度周期的防重复。
这里可以自然预埋引导:当时改造的时候我也意识到,单纯靠 Redis 分布式锁是无法做到 100% 正确性,一旦 Redis 发生故障,锁机制直接失效,必须要有数据库层面的兜底防护。
- 二、分层改造方案:任务互斥 + 并发执行 + 数据库 CAS 状态兜底的双重防护体系
- 第一层:专用线程池+CompletableFuture实现批量策略并发下发,解决串行阻塞、吞吐不足问题
摒弃串行for逐条下发逻辑,引入业务隔离的专用线程池firewallRuleDispatchExecutor:核心线程8、最大线程20、阻塞队列150,拒绝策略采用CallerRunsPolicy。把每一条防火墙策略下发封装为独立异步任务提交线程池,使用CompletableFuture批量收集任务结果。1
2
3
4
5List<CompletableFuture<FirewallSecurityRulePO>> futures = rules.stream()
.map(rule -> CompletableFuture.supplyAsync(
() -> dispatchSingleRule(rule, hasFailure),
firewallRuleDispatchExecutor))
.collect(Collectors.toList());
这里有一个关键设计考量:线程池最大线程数不需要等于批量任务总数。防火墙下发是典型IO密集场景,大量时间消耗在和防火墙设备API等待交互,CPU空闲;8‑20的线程数是权衡业务吞吐和防火墙设备侧限流的平衡点,如果线程数设置过大,瞬间大量请求打向防火墙设备,会触发设备接口限流,反而整体变慢。队列满时使用CallerRunsPolicy,由调度主线程自己执行任务,实现天然限流,避免任务直接丢弃丢失业务。
单批调度最多 100 条,队列 150 有必要吗?核心原因是这个线程池不是仅供这个定时任务使用,页面手动触发的防火墙下发也复用同一个线程池,会有额外任务涌入。 以及要应对调度任务重叠的边缘情况,极端场景:锁快要过期、任务还在执行,下一轮调度抢到锁启动;此时会出现两批任务短暂并行,总任务数会超过 100。队列留一点冗余,可以避免瞬时任务堆积,核心线程数达到20后触发拒绝策略 CallerRunsPolicy 阻塞 XXL‑Job 调度线程,影响框架调度,所以保守设置 150 做缓冲。 - 第二层:引入Redisson分布式锁,实现任务级互斥,避免多轮调度任务并发运行
为调度任务增加任务粒度的分布式锁,使用tryLock(waitTime=0)非阻塞抢锁逻辑。waitTime=0代表抢不到锁不会阻塞等待,直接返回结束本轮调度,交由下一个5分钟调度周期重试,相比任务排队堆积更加合理。1
2
3
4
5lockAcquired = lock.tryLock(0, LOCK_LEASE_TIME_SECONDS, TimeUnit.SECONDS);
if (!lockAcquired) {
// 上一轮调度任务还在执行,本轮直接跳过,等待下一个5分钟调度周期重试
return ReturnT.SUCCESS;
}
锁释放做双重安全校验,只有当前线程持有这把锁才执行unlock,防止不同节点、不同调度线程之间误释放别人持有的锁。同时设置锁自动过期时间leaseTime=1800秒,作为兜底,防止进程崩溃没有执行到finally,出现死锁。1
2
3
4
5finally {
if (lockAcquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}衍生预埋点:这里我做设计的时候特意区分了分布式锁的定位,它是性能优化手段,不是正确性的最终保障。分布式锁只能规避正常情况下多轮任务并发跑,一旦Redis宕机、网络抖动,锁机制会失效,不能依靠锁保证策略不重复下发,真正的正确性防线必须下沉到数据库层面。
- 第三层:新增EXECUTING执行中状态,CAS原子预占,策略粒度数据库兜底防重复下发
新增策略状态EXECUTING(4)执行中,完整状态机:TODO(1) –CAS预占–> EXECUTING(4) –下发成功–> DONE(2);下发失败–> FAIL(3)。
调度任务捞取出来一批TODO策略之后,真正执行下发之前,先执行数据库CAS原子更新:条件是当前记录状态必须为TODO,才允许更新为EXECUTING。依靠数据库行锁保证这条更新的原子性。如果两个线程同时争抢同一条防火墙策略,只有其中一条执行能够返回affected_rows=1预占成功;另外一个线程update返回0,直接跳过这条策略,不再执行下发。调度查询条件固定只筛选TODO的记录,已经被预占为EXECUTING的策略不会被后续调度扫描出来。1
2
3
4
5boolean preOccupied = firewallSecurityRuleService.updateToExecuting(rule);
// 等价SQL
// UPDATE firewall_security_rule
// SET exec_status = 4
// WHERE rule_name = #{ruleName} AND exec_status = 1
两层防护分工非常明确:
1、Redis分布式锁:任务粒度互斥,尽量避免多轮调度同时运行,减少无效数据库竞争、减少线程池资源浪费;属于性能层优化;
2、数据库CAS+EXECUTING中间状态:策略粒度的正确性兜底。就算Redis不可用、分布式锁完全失效,依靠数据库行锁也能保证单条策略不会被重复下发,是整套调度方案真正的正确性基石。
- 第一层:专用线程池+CompletableFuture实现批量策略并发下发,解决串行阻塞、吞吐不足问题
- 三、改造后防火墙策略下发核心设计
- 配套小批量滚动设计
单次调度任务限制只处理100条策略,分页每次取第一页100条;处理完成这批之后释放锁,下一轮调度再取下一批待执行策略。
①每批任务量可控,锁持有时间尽可能缩短,降低锁过期、死锁风险;
②内存占用可控,不会一次性加载成千上万条策略对象到JVM内存;
③分页漂移问题天然规避:已经预占为EXECUTING的策略不会再次被查询返回。 - 关键参数约束:、时、任务等待超时的时序设计
- 必须保证CompletableFuture#allOf的整体等待超时时间,要小于分布式锁leaseTime锁自动过期时间。避免锁提前过期引发并发风险。我们配置:
- 分布式锁自动过期leaseTime = 1800秒
- allOf批量任务整体等待超时 = 1780秒,比锁兜底过期时间提前20秒。
- 时序逻辑:
t=0,调度任务获取锁,开始执行批量下发;
t=1780,allOf等待超时,主调度流程捕获TimeoutException,进入finally主动释放分布式锁;
t=1800,是锁的兜底自动过期时间,用于服务进程崩溃,代码没走到finally释放锁的极端场景。 - 面试拔高点:如果反过来,任务等待超时大于锁leaseTime,会出现一个严重bug:批量任务还在线程池里面跑,但是锁已经自动过期释放;下一轮调度就可以抢到锁启动新任务,两套任务同时运行,破坏业务正确性。这个参数时序约束是这套调度方案里面非常关键的工程细节。
- 必须保证CompletableFuture#allOf的整体等待超时时间,要小于分布式锁leaseTime锁自动过期时间。避免锁提前过期引发并发风险。我们配置:
- 全量异常场景链路覆盖,把各类边界case全部闭环
- 场景A:单条策略下发网络抖动偶发超时
单条下发内置重试机制,最多重试3次,每次重试间隔sleep 3秒,用来解决瞬时网络抖动;3次重试全部失败,任务捕获异常,策略状态落地为FAIL,不会卡在EXECUTING。 - 场景B:allOf整体等待超时,部分异步任务还在线程池内执行
捕获TimeoutException,不会调用cancel去打断线程池里面正在执行的任务,子任务允许继续跑完,子任务执行结束之后自己把状态更新为DONE或者FAIL。
此时主流程已经释放分布式锁,下一轮调度可以正常启动;但下一轮查询条件只会捞取TODO,这些还在执行中的策略状态是EXECUTING,不会被重复扫描到,依靠数据库状态兜底保证不会重复下发。 - 场景C:服务进程突然崩溃,策略卡在EXECUTING永久僵死
这是整套改造唯一的正常路径覆盖不到的边界:服务在CAS预占为EXECUTING之后直接宕机,线程池任务没有机会跑完,这条策略永久停留在EXECUTING,普通调度任务不会扫描到该记录,策略永久积压,父工单状态永远无法闭环。
解决方案:额外补充独立的巡检兜底定时任务,扫描超过30分钟状态依旧是EXECUTING的策略,判定为僵尸任务,把状态回退重置为TODO,交由下一轮正常调度重新执行。 - 场景D:Redis整体不可用,tryLock直接抛出异常
捕获锁操作全部异常,本轮调度任务直接返回XXL‑Job执行失败。此时分布式锁完全失效,但数据库CAS+EXECUTING状态依旧生效,能够保障策略不会重复下发;只是会出现调度任务频繁失败,业务会产生积压,需要运维介入排查Redis故障,不会发生数据正确性问题。 - 场景E:单条策略业务逻辑执行抛出未知异常
统一异常捕获,异常分支强制把策略状态置为FAIL,避免策略遗留在EXECUTING,正常业务路径下EXECUTING不会永久滞留。
- 场景A:单条策略下发网络抖动偶发超时
- 配套小批量滚动设计
- 面试拔高总结
这套防火墙定时下发调度改造,和普通简单定时任务开发有很大区别。它不是简单写个定时扫描+调用接口,对于典型IO密集型任务,核心难点在于长耗时IO任务、多节点集群调度、中间件可能故障的现实前提下,如何分层构建正确性防护。
我从数据状态兜底、集群负载调度、单机并发提速三个维度,层层解决定时批量运维场景的核心痛点。
我没有把正确性全部押在Redis分布式锁上,而是采用“上层锁做性能优化,底层数据库CAS状态做最终正确性兜底”的分层思路;同时完整梳理了超时、进程崩溃、中间件故障等各类边界case,配套巡检兜底任务闭环极端场景。同时结合防火墙设备本身的限流特性做线程池参数权衡,兼顾吞吐和下游设备稳定性。整套改造上线之后,彻底解决了之前策略重复下发、任务大量积压的线上故障,后续长时间线上运行没有再出现同类问题。 - 衍生追问1:为什么不把线程池最大线程直接设置成每批100条,100个线程并发执行?
防火墙策略下发是IO密集型,但下游的防火墙设备本身API网关有并发限流阈值。如果直接开100个线程同时向防火墙发起变更请求,会直接触发设备侧限流,大量请求被设备拒绝,反而整体执行效率变差。8‑20是我们压测之后得到平衡点,在不触发设备限流前提下最大化吞吐;超过部分任务进入队列排队,线程执行完一条任务之后再取下一条,本质是可控的分批并发,而不是无限制的完全并行。 - 衍生追问2:allOf整体等待超时之后,为什么不去cancel线程池里面还没跑完的任务?
这里主要有两点考量。第一,下发防火墙策略是变更类高危运维操作,任务已经把请求发送到防火墙设备,有可能策略已经在设备侧生效,只是设备返回响应慢;如果直接cancel中断Java线程,Java侧任务终止,但防火墙设备上策略可能已经变更成功,会出现Java记录状态和真实设备配置不一致的配置漂移问题。第二,线程池里面的异步任务内部有完整try‑catch异常处理逻辑,任务执行结束会自己更新DONE/FAIL状态。所以选择不去中断子任务,允许子任务继续跑完,依靠EXECUTING状态防止重复调度,保证数据和设备配置尽量对齐。 - 衍生追问3:既然EXECUTING数据库CAS状态已经可以保证不重复下发,为什么还需要额外引入Redis分布式锁,直接去掉锁行不行?
技术上正确性可以保障,但是性能层面会有明显缺陷。如果没有任务级分布式锁,XXL‑Job每5分钟就会准时启动一轮调度任务;就算上一轮大批量任务还在跑,新一轮调度任务也会启动,会执行数据库查询、循环遍历策略列表、尝试执行CAS预占。虽然CAS大部分update返回0不会真正下发,但是会频繁发起数据库查询、大量无效CAS更新,消耗数据库连接、CPU、线程池资源,对数据库和应用服务造成不必要压力。
所以分布式锁的定位就是任务粒度的性能防护,尽量避免无效调度空跑,减少资源浪费;真正防重复下发的核心是数据库CAS状态。二者配合兼顾正确性和性能。 - 衍生追问4:讲讲EXECUTING僵死策略巡检任务的设计细节?
这个巡检任务属于兜底补偿逻辑,不做主流程。
设置定时任务,查询exec_status = EXECUTING AND now()‑update_time > 30分钟的策略。30分钟阈值是结合业务,单条复杂策略最大执行耗时数分钟,30分钟预留充足缓冲,避免把正在正常执行的任务误重置。
查询出来僵尸策略集合之后,执行数据库更新把状态从EXECUTING重置回TODO;(同步记录完整审计日志,方便事后排查复盘)重置之后下一轮正常调度就可以重新处理这条策略。(同时巡检任务自身也需要加独立的分布式锁,防止多节点并发巡检重复重置同一条僵尸策略。) - 衍生追问5:XXL‑Job本身支持分片、支持任务互斥,为什么不直接使用XXL‑Job原生互斥能力,自己还要再实现Redis锁+数据库CAS两套防护?
XXL‑Job的任务互斥锁是调度中心层面的锁。如果我们应用服务和XXL调度中心网络发生分区,调度中心认为任务已经执行完毕释放锁,但业务服务这边线程池任务还在继续跑;或者业务节点进程宕机,调度中心的锁释放逻辑无法感知业务侧真实执行状态。
而且XXL‑Job原生互斥只能做到任务粒度互斥,无法做到单条策略粒度的排他。极端场景下,就算同一个调度任务内部,多线程并发处理同一条策略记录的时候,XXL原生能力没有办法做行级的防重复保护。
企业运维变更类场景优先级最高是不能重复下发策略,不能把全部可靠性寄托在调度框架的能力上,所以我们选择业务层自建双重兜底:任务级Redis锁做优化,策略粒度数据库CAS做最终保障,把可靠性掌握在业务代码手里。
- 防火墙策略定时下发是 MOPS 平台高优先级的运维场景,整体依托 XXL-Job 实现定时任务调度,扫描待执行防火墙策略,完成批量策略自动化下发、配置同步与状态校验。
Q4:你说你负责需求把控和团队方案落地,具体做了什么工作?
MOPS 承载全集团运维工单,需求零散、场景琐碎、业务方诉求不统一,如果盲目迭代会导致功能冗余、逻辑混乱、难以维护。我在团队中主要承担需求收敛、方案标准化、落地推进的工作:
第一,日常对接网络运维团队,收集零散工单需求,梳理共性场景,过滤无效需求,将个性化诉求沉淀为标准化通用功能,避免重复造轮子;(如基于F5的负载均衡和域名解析能力建设,有许多共同点,协助外包同事,参考已落地的DNS一键回退能力,快速开发负载均衡一键回退功能)
第二,针对复杂运维场景,牵头拆解技术方案,统一团队开发规范,协助新人拆解任务、梳理执行逻辑,保障团队迭代节奏统一;(如DNS策略采集,涉及多项目协同,CMDB → MOPS-Job → ansible调度 → MongoDB写入)
第三,把控迭代优先级,区分紧急故障修复、日常功能迭代、长期能力建设,保障平台稳定优先、迭代有序。
这让我具备了从「纯开发编码」上升到「业务理解+需求治理+团队协同落地」的综合能力,能够站在业务视角做技术建设。
第四,,,讲经验型、重复类工作梳理skills,利用AI赋能。。。Q5:(todo)NBU 自动化下发怎么做的?
ssh+公秘钥,实现登录远程机器
ansible+api,亮点是。。效果是。。Q6:讲讲你私有化对外交付的工作内容,遇到过什么难点,怎么解决的?
我负责 MOPS 平台私有化项目的定制化需求交付、客户对接、问题全闭环工作。和对内迭代不同,私有化交付需要完全贴合客户现场环境、适配客户个性化运维流程,同时需要全程对接客户、答疑、处理适配问题,交付闭环要求极高。
除了系统的部署,一个功能要用起来,还要把用户环境的设备纳入进来。例如WindowsDNS策略下发,首先要把DNS设备托管(目标机器B上创建运维账号,特权机A免密登录:A发起ssh连接 → B取公钥加密随机数 → A用私钥解密后返回 → B校验解密正确通过认证 → A 在 B上 sudo -u root 执行任务),适配设备环境来改造脚本(先检查DNS角色是否安装,变更前检查记录是否已存在或不符合预期,变更后验证是否正确),设备信息录入CMDB&MOPS配置表。最终自动化全流程打通。
核心难点:客户现场运维流程、设备环境和集团内部不一致,存在大量定制化适配需求,且客户对平台稳定性、操作安全性要求极高。
我的落地做法:前期充分对齐客户流程,梳理定制化适配清单,区分通用能力和私有化专属能力;中期迭代过程中同步适配环境差异,完成功能定制、兼容性测试;后期全程跟进交付验收、问题答疑、故障兜底,保障项目顺利交付。
技术方面,熟悉了k8s云原生环境开发、出包、部署的全流程。
这份工作让我积累了成熟的 ToB 产品交付思维,懂得平衡通用化与定制化,保证产品可落地、可验收、可运维。Q7:产品私有化输出——运维手册
- 除了产品使用手册,作为开发人员输出了运维手册:
- 产品简介:自动化运维产品是企业 IT 运维体系的核心一部分,旨在通过流程化、集中化、自动化的方式,提高运维效率,降低人工运维风险,提升运维效率,并优化用户体验。
- 产品组件:k8s容器部署,端口、存储local、CPU / 内存 / 磁盘 2C/4G/100GB(什么时候 2C 会成为瓶颈?CPU 密集型 → 一个线程长时间霸占 CPU、高并发 + 几乎无 IO → 大量线程的上下文切换开销累积)、OS & 内核版本、网络要求 元集群内互通,高可用 2节点
- 安装部署:通过统一管控平台,工作流,拉起中间件(mongo,kafka,es,monstache)、各组件(service,sts,pod)、创建 ingress,异常看 k logs pod-xx
- 排障case
- 作业下发失败 error:ssh 22 fail,原因是 job-engine 的 pod 飘到了另一个宿主机IP上,而不是特权机,无法免密登录
- Zookeeper 集群拉起失败:版本化文件local存储,pod重启后IP变更,集群间无法通信,在集群健康状态下通过reconfig用k8s集群内部域名替换固定 IP 写入版本化文件,永久自愈
- 除了产品使用手册,作为开发人员输出了运维手册:
Q8:MOPS 并发不高,你觉得你的技术亮点在哪里?怎么体现你的工程能力?
- 虽然平台线上高并发场景少,但运维类平台的核心难点是场景复杂、流程琐碎、IO 密集、批量耗时、风险极高、交付要求高,我的工程能力主要体现在场景化落地和风险治理上:
- 场景化工程优化:针对批量 IO、超长耗时任务,落地线程池并行、异步解耦,解决效率与接口稳定性问题,具备典型的 IO 密集型业务优化思维;
- 风险工程化兜底:独创变更快照+一键回退机制,把人工不可控的运维风险,通过技术方案标准化兜底,体现风险前置、稳定优先的工程思维;
- 业务架构抽象:将零散、无序的人工运维操作,抽象为标准化、模块化、可复用的自动化流程,体现业务抽象与架构沉淀能力;
- 全链路交付工程化:实现对内标准化迭代、对外私有化可交付,具备完整的产品化、工程化落地思维。
- 高并发只是工程能力的一部分,而场景适配、风险兜底、业务沉淀、稳定交付、团队落地,是我在这个项目沉淀的核心工程素养。
- 虽然平台线上高并发场景少,但运维类平台的核心难点是场景复杂、流程琐碎、IO 密集、批量耗时、风险极高、交付要求高,我的工程能力主要体现在场景化落地和风险治理上:
Q9:如果让你优化现在的 MOPS 平台,你会从哪些方向入手?
- ??运维任务可配置化:以采集为例,很多都是定时任务,实现的逻辑写在代码里,可维护性不高;可以把采集逻辑、数据解析逻辑、做成可配置化的。
- 作业平台调度能力。。
- AI 能力深度落地:目前只是把工单接口做成MCP,提供给上层的AIOps Agent 做运维根因定位,本身MOPS可以做自助提单、异常工单自动排障等,简化团队工作。
Q10:AI、AIOps相关能力落地
- 我们整套运维平台体系是自动化底座MOPS+智能化AIOps Agent分层设计,AI相关能力分为已落地能力和中长期规划架构两部分。
- 已落地现有智能化入口与数据互通
- CMDB智能问数Agent入口,就做在MOPS导航栏,提供基础配置数据的自然语言查询能力。(CMDB和MOPS其实是一套运维产品,CMDB侧重资产盘点,MOPS侧重运维行为)
- MCP网关统一暴露MOPS资产、工单、变更记录查询接口,向上给运维Agent提供数据支撑故障根因分析。
- 现阶段团队资源倾斜重心偏向自动化基建迭代,对AI智能化模块的短期研发投入有限,但我们已经完成底层数据互通改造
- 中长期规划:MOPS-Agent开发与多层级父子Agent接入
- MOPS-Agent:独立Agent,智能提单,智能报错分析
- 接入集团数字人:面向全办公通用场景,在办公软件直接说“提一个防火墙工单”;
- 接入AIOps Agent:总运维Agent,整体采用主Agent调度多子Agent的分层协同架构,上层AIOPS Agent做意图识别、任务下发、结果分析,调度各类垂直子Agent获取运维信息,综合做故障定位分析。 如 ① CMDB数据查询子Agent:负责主机、集群、配置资产、资源台账检索; ② MOPS工单处理子Agent:负责防火墙开通、资源变更、故障工单的创建、查询、流转。③其他数据库、网络、主机Agent
- 业务场景举例:运维人员向AIOps主Agent提问 “帮我查下这台物理机为什么不可用”
- 主Agent识别需求,自动调度CMDB子Agent,拉取该机器配置、运行状态、监控指标;
- 自动联动MOPS子Agent,检索该机器近期所有变更工单、防火墙策略、停机记录;(已经通过MCP实现)
- 汇总两套子Agent返回的数据,由大模型整合线索,输出完整故障根因分析结论,无需人工跨系统查数据。
- 未来智能提单落地规划:后续会把智能提单能力封装进MOPS子Agent,对接集团统一数字人接口。员工在办公软件直接自然语言下发指令(如“帮我开通服务器防火墙白名单工单”),直接调用MOPS-Agent自动解析需求、生成标准运维工单,完成自动化提报。
- 追问1:父子Agent架构对比单一独立Agent优势在哪?
- 能力解耦:运维、监控、数据库相关能力通过独立子Agent实现,迭代互不影响,新增运维能力仅需开发新子Agent,不用改动主Agent;
- 统一交互入口:用户只需要和主Agent对话,不用记忆多个系统查询入口;
- 复杂问题联合分析:遇到跨资产、跨工单的复合型故障,主Agent可串联多个子Agent数据,自动整合线索,降低运维排查成本。
- 追问2:MCP网关在这里起到什么作用?
MCP是内部统一服务网关,用于AI可理解和调用的接口,相当于接口再做了一层封装。我们将MOPS所有查询、工单接口统一注册至网关,做鉴权、限流、路由管控。上层运维 Agent不会直接直连MOPS底层服务,全部走网关调用,保障平台权限安全、接口可控,同时统一日志记录,方便审计AI工具对运维数据的访问行为。
- 我们整套运维平台体系是自动化底座MOPS+智能化AIOps Agent分层设计,AI相关能力分为已落地能力和中长期规划架构两部分。
Q11:讲讲你在云原生部署、容器化交付方面的落地能力与思考?
- 我今年重点深耕平台私有化产品化交付体系建设,全程吃透了Java服务容器化打包、K8S云原生环境部署、私有化项目标准化交付全流程能力,完成了MOPS从“集团内部业务系统”到“可对外输出的标准化云原生产品”的能力沉淀,补齐了后端开发云原生交付、工程化落地的核心短板。
- 第一,熟练掌握Java服务容器化全流程打包规范。针对MOPS多模块架构特点,统一编写优化Dockerfile,基于JDK轻量基础镜像,完成服务分层打包,精简镜像体积、减少冗余依赖;同时规范打包流程、统一配置文件环境隔离,区分开发、测试、私有化生产环境配置,避免不同环境配置混淆导致的部署异常,实现服务一键构建、可移植、可快速部署。
- 第二,精通K8S云原生环境私有化部署与适配。熟练运用K8S核心资源对象,通过Deployment实现服务无状态弹性部署、保证副本高可用;配合Service实现服务内部负载均衡、端口统一暴露;通过ConfigMap、Secret挂载私有化定制配置和私密参数,适配不同客户现场的个性化环境需求,无需改动业务代码即可快速适配多场景交付。同时掌握容器日常运维能力,可快速排查容器启动异常、端口冲突、资源限制、配置挂载失效等私有化部署高频问题,保障交付稳定性。
- 第三,建立了完整的产品化、标准化私有化交付思维。内部迭代侧重业务功能落地,而私有化交付核心是标准化、通用性、可适配、可维护。我在跟进交付过程中,重点沉淀了适配外部客户的交付规范:一是环境标准化,统一私有化部署依赖、中间件版本、资源配额,降低现场适配成本;二是配置解耦,将所有环境差异化配置外置,实现一套镜像适配多客户现场;三是交付闭环,梳理私有化部署SOP、问题排查手册,实现快速交付、快速兜底、故障快速复盘。
- 核心能力亮点拔高:很多业务开发只专注业务CRUD,缺乏上线部署和产品化交付思维,而我通过私有化工作,打通了「代码开发→容器打包→云原生部署→客户交付闭环」的完整链路。不仅具备业务开发能力,更懂云原生工程化落地,能够适配企业级私有化产品交付场景,具备独立支撑项目对外商业化交付的综合能力。
- 衍生追问1:私有化容器化部署相比传统服务器部署,最大优势是什么?
- 环境一致性:容器打包屏蔽系统环境差异,解决“本地正常、线上报错”的环境不一致问题,适配不同客户现场复杂环境;
- 部署高效化:摒弃传统服务器繁琐的环境搭建、依赖安装流程,实现一键部署、快速迁移,大幅提升私有化交付效率;
- 高可用可扩缩:依托K8s实现服务副本冗余、故障自动重启、弹性扩缩容,相比单机部署稳定性更强;
- 产品化程度高:配置与代码解耦、环境隔离,真正实现一套产物多环境复用,符合商业化产品交付标准。
- 衍生追问2:如何实现高可用?
pod配置了多个副本,通过deployment,,
- 我今年重点深耕平台私有化产品化交付体系建设,全程吃透了Java服务容器化打包、K8S云原生环境部署、私有化项目标准化交付全流程能力,完成了MOPS从“集团内部业务系统”到“可对外输出的标准化云原生产品”的能力沉淀,补齐了后端开发云原生交付、工程化落地的核心短板。
Q12:你们 MOPS 是 ToB 吗?运维产品定位?技术上关注什么?
MOPS/CMDB 首先是集团内部运维中台工具,服务公司内部 IT、运维、开发人员;同时产品做了商业化改造,可以私有化部署交付给外部企业客户,属于 To‑B 私有化软件,没有面向 C 端个人用户。
MOPS + CMDB 整套运维体系是「资产数据底座 + 自动化操作平台」的双层闭环架构,覆盖企业运维「数据可信、操作可控、流程自动化、故障可追溯」的全链路能力,是标准化运维产品的核心基建。- MOPS 是标准化自动化运维操作平台,把零散、高危、重复的人工运维操作,转化为系统化、可管控、可回滚、可审计的线上自动化能力。技术上我核心关注「操作标准化、风险兜底、执行高效、流程闭环」:
- 核心本质是运维行为的工程化固化,将手工 SSH 操作、设备配置变更、批量策略下发等高危动作,通过 API + 脚本 + 任务调度实现系统代劳;
- 通过工单流程 + 配置快照记录,实现业务流程与设备真实配置的双向映射,解决人工操作无记录、无回溯、易漂移的问题;
- 针对批量 IO 密集场景,用线程池并行、异步解耦、分布式调度优化运维执行效率;
- 通过状态锁、事务管控、一键回退、前置校验,从技术层面兜底运维变更风险。
- CMDB 是全域 IT 资产唯一可信数据底座,核心不做运维操作,只负责资产数据的标准化存储、自动采集、精准检索、稳定分发,是所有上层运维、监控、变更、故障分析的数据源根基。技术上我核心关注「数据可信、服务稳定、高可用、链路可控」:
- 核心本质是高可靠的数据读写服务,不靠复杂高并发架构取胜,而是靠极致的稳定性治理;
- 通过标准化采集链路、Oplog/MongoShake 数据订阅、ES 全文检索,解决资产数据同步、查询、分发的工程问题;
- 通过写入校验、幂等控制、慢查询优化、集群高可用、故障 SOP 运维,长期保障数据不脏、服务不崩、链路不堵;
- 所有技术优化都是为了一个核心:给上层所有运维系统,持续输出稳定、准确、可溯源的资产数据。
- 我们这套运维体系没有花哨的高并发架构,但都是企业生产刚需的落地工程能力。 CMDB 解决「数据准不准、服务稳不稳」的底层根基问题,MOPS 解决「操作危不危、流程乱不乱、效率高不高」的运维执行问题; 一存一操、一数一行,形成企业运维数据与行为的完整闭环,真正用工程化技术解决了传统人工运维低效、高危、不可控的真实业务痛点。
- MOPS 是标准化自动化运维操作平台,把零散、高危、重复的人工运维操作,转化为系统化、可管控、可回滚、可审计的线上自动化能力。技术上我核心关注「操作标准化、风险兜底、执行高效、流程闭环」:
Q13:讲讲MOPS线程池满导致接口pending的故障排查、根因与优化方案
我处理过MOPS线上P2级线程池耗尽故障,核心是线程池设计不合理导致业务接口饿死,沉淀了Java线程池架构设计、故障排查、性能优化的工程实战能力。- 一、故障现象
validatePorts核心接口持续pending、客户端超时报错,影响用户正常使用,故障持续2小时,属于典型的线上线程池资源耗尽故障。 - 二、核心根因
- 架构设计缺陷:多个不同业务模块共享同一线程池queryCmdbValidIpExecutor,违反线程池隔离原则;
- 任务特性冲突:高峰期其他模块长耗时任务占满全部15个核心线程,快速的端口校验任务无线程可用,只能排队阻塞;
- 调用链路短板:上层接口使用Future.join()无超时阻塞等待,任务排队后直接导致API线程永久pending,无容错机制。
- 三、原有线程池参数问题复盘
原有线程池核心参数不合理:核心线程数15偏小、无任务超时机制、2000有界队列过大,导致慢任务长期占用线程、任务大量堆积、快速业务被饿死。 - 四、完整优化方案
- 业务线程池隔离:拆分共享线程池,为高优、快响应的端口校验业务独立配置线程池,与耗时后台任务物理隔离,避免相互干扰;
- 线程池参数调优:调高核心线程数、缩减队列长度,适配IO密集型运维业务场景,避免任务无限堆积;
- 增加超时容错:替换无超时的join()方法,配置任务超时时间,避免接口永久阻塞,提升服务容错性;
- 流量均衡优化:配合集群部署,规避大量请求集中单节点的问题,分散线程池压力。
- 面试拔高总结:这次故障让我深刻理解,线程池不仅是简单的多线程工具,更是服务资源隔离、SLA保障的核心架构手段,不同优先级、不同耗时的业务必须物理隔离,才能避免业务雪崩、接口饿死。
- 一、故障现象
Q14:为什么需要手动开启Tomcat MBean监控?有什么工程意义?
线上曾出现Tomcat线程池打满、接口阻塞但无监控告警的问题,核心原因是Spring Boot内嵌Tomcat默认关闭MBean注册,无法暴露线程池、连接数、请求耗时等核心指标,导致服务可观测性缺失。
一、问题根因
Spring Boot约定优于配置,内嵌Tomcat默认不向JMX注册MBean监控节点,监控探针无法抓取Tomcat运行指标,线上线程耗尽、连接溢出等故障无法提前预警,只能被动等待用户反馈故障。
二、解决方案与落地链路
在application.yml开启tomcat.mbeanregistry.enabled=true,触发自动装配机制,让TomcatServletWebServerFactory自动注册MBean到JVM,监控平台通过JMX Exporter定时抓取指标,实现线程活跃数、连接数、请求异常率、耗时等数据可视化。
三、工程核心价值
1 完善可观测体系:补齐Web容器层监控,实现应用层、容器层、业务层全维度监控;
2 故障前置预警:提前感知线程池满载、连接堆积、请求超时等隐患,实现从被动救火到主动预警;
3 性能调优依据:通过监控指标精准优化线程池参数、限流阈值,让调优有据可依。Q15:讲讲MOPS慢SQL排查与索引优化实战(35万数据量级)
我针对MOPS备份任务查询接口慢查询问题,完成全链路SQL优化,解决35万数据量级下的全表扫描性能瓶颈,沉淀了MySQL索引优化、查询语句重构的实战能力。- 一、故障核心问题
备份任务记录表累积35万数据,接口多条件模糊查询+大批量IN权限过滤,导致MySQL放弃索引、全表扫描,接口响应缓慢,用户体验极差。 - 二、核心性能瓶颈
- 双侧%模糊查询:所有业务字段使用LIKE ‘%值%’,完全失效B-Tree索引;
- 大IN列表叠加查询:权限过滤数千条系统名称IN列表,叠加模糊查询后,优化器直接放弃索引走全表扫;
- 索引覆盖不全:仅单字段索引,无针对性业务联合索引,无法适配多条件查询场景。
- 三、场景化优化方案(面试核心亮点)
- 查询语句精细化改造:
1 备份类型改为精确等值查询,前端下拉枚举选择,废弃模糊匹配,新增专属索引;
2 IP地址智能适配:完整IP走精确查询、IP段走前缀模糊查询,适配索引机制;
3 文件路径优化:备份集路径废弃左侧%,改为前缀匹配likeRight,可正常命中索引; - 索引精准新增:针对高频查询字段backup_type、ip_address、backup_set建立专属业务索引,缩小查询候选集;
- 查询逻辑优化:规避大IN列表低效查询,优化权限过滤逻辑,减少数据库查询压力。
- 查询语句精细化改造:
- 四、优化效果
彻底解决全表扫描问题,接口响应耗时从秒级降至毫秒级,35万数据量级下查询性能大幅提升,同时规避了后续数据增量带来的性能恶化风险。
- 一、故障核心问题
🔗 CMDB 基础配置数据库
项目介绍
CMDB 是美的集团 IT 基础设施统一资源底座平台,集中纳管全网服务器、网络设备、业务应用等全量 IT 资产,核心为 MOPS 运维平台等上层业务系统,提供可信、统一、稳定的资产元数据支撑。集团拥有上万台服务器,覆盖复杂制造业运维场景,CMDB 的稳定性和数据准确性,直接决定了上层运维业务的落地效果。
我作为 CMDB 项目核心业务 Owner,核心工作聚焦全链路架构吃透、保证核心功能稳定性、重大故障闭环,日常负责内外部用户答疑,对接各业务方的数据接入、系统运维和稳定性治理工作。- 数据读写能力:这是 CMDB 的存储底座。我深度吃透平台核心读写全链路体系,根据页面操作、对外接口和直接入库进行服务拆分,底层使用 MongoDB 存储各类资产实例元数据,Redis 做表头数据的热点缓存,梳理了核心读写特性和优化点(未落地)。日常负责数据对接,慢查询治理。
- 资产自动采集:使用配置驱动的通用采集框架,我重点掌握防火墙业务全链路治理工作,链路依靠 XXL‑Job 调度,Kafka 完成任务解耦,针对防火墙海量策略做分片投递,分片暂存 Redis,汇总完成后批量写入 MongoDB。线上发生过严重 FullGC 故障,我使用 jstat、jstack、MAT 这套标准流程完成故障定位。根因是字段配置对象重复反序列化,粗粒度全局锁进一步放大故障影响。我落地了配置对象复用、锁粒度优化、有界线程池背压等稳定性治理手段。
- 全文检索&数据订阅:为弥补 MongoDB 检索短板,用 Monstache 同步数据到 ES,流量走hidden节点,实现跨资产全局全文检索;我在私有化环境闭环了故障排查,恢复了全量 + 增量同步。订阅采用 MongoShake 解析 oplog 的变更事件推送 Kafka,实现下游系统异步订阅回调;核心是流量隔离与职责解耦。
- 系统运维:我大量精力投入平台稳定性运维与故障闭环,上半年接手时线上故障频发,经常遇到查询接口响应缓慢、视图查询超时、Java服务和中间件故障等问题;我常态化负责故障处理和闭环,整体沉淀了标准化故障排查 SOP,在内网环境下结合监控&不同指令,应对不同场景如何利用 AI 先止血,后找根因,再改进闭环。
- 私有化交付:全程对接外部客户,负责服务部署和日常答疑,独立定位并修复 ZooKeeper 集群启动异常问题。
【预埋引导话术,自然穿插】整套系统混合使用 MongoDB、ES、Kafka,在实践中能明显感受到文档数据库在强事务场景存在短板,这段时间我也系统深入学习 MySQL InnoDB 索引、事务、锁相关理论,对比两类数据库适用场景差异。
Q0:CMDB 核心读写链路设计
- 架构前提
- 两套Mongo集合:表头集合(存储CI模型表头Schema元数据)、资产实例集合(存储实际资产数据)
- 字段规范:理论上仅新增字段,不删除、不修改存量字段定义,杜绝历史数据字段丢失
- 表头元数据使用Redis缓存提升查询性能
- 资产写入 全部收敛到 Mongo upsert / $set 单文档原子指令,应用层无中间计算态
- 查询统一走可视化配置的视图接口,多模型联表使用Mongo聚合管道$lookup
- 微服务分层:ops-cmdb用户交互服务、data-access接口服务、synapplication数据底层CRUD服务
- 多节点分布式部署,存在跨实例并发问题
- 整体存储模型
- 表头元数据集合:维护每一类配置CI模型的字段定义、类型、约束,新增资产属性只在这里新增,不修改已有字段,规避存量数据字段丢失问题;表头数据会做Redis缓存,减少频繁查询元数据的压力。
- 资产实例集合:一条文档对应一条资产记录,依托Mongo灵活Schema特性,同集合内不同文档存在字段不一致
- 新增资产模型不需要改代码、不需要DDL,后台配置表头即可快速接入,适配运维繁多的资产类型
- 部署架构(读写服务分层解耦)
- ops-cmdb(页面交互的主服务,白屏化查询、编辑数据)
- data-access(对外暴露数据读写的接口,流量审计)
- ops-synapplication(实际与MongoDB交互,统一参数校验、来源标记、幂等控制,所有 CRUD 收敛于此)
- 完整的数据入库链路
- 写入请求经过data-access入口,到 syn服务,优先从Redis读取当前CI模型的表头Schema;缓存不存在则回源Mongo表头集合并回填Redis
- 根据表头和配置做入参校验,过滤非法字段,完成入库对象的构造。
- 基于逻辑主键执行Mongo upsert:主键不存在则新建文档;主键已存在则使用$set增量更新字段,整条指令为Mongo单文档原子操作,应用层不做“先查再改”逻辑。
- 写入成功后,推送资产变更事件,对外提供数据订阅能力
- 数据查询链路
- 所有查询统一走视图查询接口,视图预配置:单/联表字段、需要返回的字段Schema(取自表头元数据)
- 接口自动根据视图配置 组装 Mongo 聚合管道:单表普通查询,多表 $lookup 左外关联
- 执行聚合查询拿到数据后返回(如MOPS平台)。
- 读写系统的关键特性实现:
- 校验:syn服务 通过配置好的规则做校验,实际可以做成责任链(责任节点抽象类+具体节点实现类+构造链路递归校验)
- 溯源:syn服务 入库时文档里携带“来源”字段
- 幂等:syn服务 直接与数据库交互,完全依靠 Mongo upsert 原子幂等 相同主键重复请求只会覆盖 / 增量更新,不会产生脏数据、重复数据
- 并发:
- 普通资产:所有读写收敛到 syn服务 MongoDB upsert 原子操作,单机多线程、多服务多节点 都不存在并发读写问题;无业务层中间态
- 表头变更:多步非原子组合操作(更新 Mongo 表头集合 + 删除 Redis 缓存)需要 Redis 分布式锁(Redisson)串行化整个表头变更方法
- 限流:ops-cmdb 用户界面瞬时触发量不大;data-access 支持批量写入,查询限流 1000 和分页查询,,防止大查询打垮 Mongo
- 事务:Mongo 原生原子,等价事务安全
- 衍生1:这套写入方案下,还存在并发“写”一致性风险吗?
- 资产文档层面:所有写入收敛到Mongo upsert/$set原子指令(syn服务直接调Mongo API),应用层没有“查询→本地计算→更新”的逻辑,没有中间态,不存在经典的更新丢失问题。但多条请求并发更新同一条文档时,指令执行时序不可控,后执行的更新会覆盖前面的状态,如果业务有状态优先级、时序强要求,会增加version版本号实现乐观锁。
- 表头元数据层面:表头存在Redis缓存,会存在缓存一致性风险。解决方案:修改表头Schema成功后,直接删除该CI模型对应的Redis缓存,下次访问自动回源加载最新Schema。特殊情况:多人同时修改表头时,通过分布式锁串行管控变更流程:多个服务实例同时发起表头变更 → 竞争 Redis 分布式锁,拿到锁的才能执行表头更新 + 删缓存,其余等待,避免并发改 schema 造成元数据错乱。
- 并发“读写”风险?
- 资产单文档的写入为 Mongo 原子 upsert,Mongo 单文档查询指令本身也具备原子性,不会读取到文档半更新的中间脏数据,不存在数据损坏问题。 但 Mongo 默认快照读机制,读写并发场景下有可能短暂读取到更新前的旧数据,属于最终一致性;CMDB 资产查询场景可以接受该特性,无需额外加锁;如果业务需要强实时读取最新数据,可以调整为当前读。
- 有没有同时写表头和读redis的并发问题?可能存在短暂脏数据,但是没必要加锁。
- 衍生2:Mongo用$lookup做多资产联表查询,有什么短板,你们怎么管控?
- 衍生3:这套架构本身有什么短板?
- 实例文档字段不统一,复杂统计、大批量聚合查询能力弱,依赖Mongo聚合管道,没有独立检索引擎;
- 表头元数据需要维护缓存一致性,Schema变更必须严格走UAT验证流程;
- 衍生4:表头缓存为什么不采用更新缓存,而是失效删除?
表头属于低频变更数据,采用删缓存方案工程实现更简单,规避“DB更新成功、Redis更新失败”带来的脏数据问题,性价比更高。极端并发下(删缓存同时新请求回写旧缓存),配合分布式锁控制表头变更流程兜底。- Redis 分布式锁 基础实现➡️ 核心思路:利用 Redis SET key value NX EX 原子命令
- 加锁:执行下面命令,返回 ok = 拿到锁;失败 = 抢锁失败
1
2
3// NX:key 不存在才设置(互斥,保证只有一个拿到锁)
// EX:自动过期时间(防止服务宕机死锁,锁永远不释放)
SET lock_ci_model_123 uuid NX EX 30 - 执行业务逻辑(比如 CMDB 表头变更,串行执行,防止多人同时改 schema)
- 释放锁:Lua 脚本原子校验 + 删除(必须 Lua!不能先 get 再 del,会有并发 bug。Lua实现redis get+del操作的原子性,代码里用 Redisson 实现)
1
2
3
4
5
6-- 逻辑:只有锁的值等于当前自己的uuid,才删除,避免误删别人的锁
if redis.call('get',KEYS[1]) == ARGV[1] then
return redis.call('del',KEYS[1])
else
return 0
end
- 加锁:执行下面命令,返回 ok = 拿到锁;失败 = 抢锁失败
- Lua 脚本的作用:不负责业务互斥,只解决 分布式锁自身的释放安全问题
经典致命场景(只有 Lua 能解决)- 线程 A 执行业务超时,锁自动过期被 Redis 释放
- 线程 B 拿到锁正常执行
- 线程 A 跑完,执行「通过 uuid get 判断 + del 释放锁」
- 如果没有Lna,非原子情况下,A 会误删掉 B 的合法锁
- Redis 分布式锁 基础实现➡️ 核心思路:利用 Redis SET key value NX EX 原子命令
- 衍生5:真用到了这些技术?
“目前线上这套 CMDB,表头变更这块没有实现Redis 分布式锁、Lua 释放锁这套逻辑。因为内部平台表头行为只会由少数管理员操作,不会多人修改同一 CI 模型。但我梳理整个数据链路的时候,推演过这个场景潜在的并发一致性风险,也学习了业界标准的解决方案:也就是通过 Redis 分布式锁串行执行「更新 Mongo 表头 + 删除 Redis 缓存」,同时用 Lua 脚本安全释放分布式锁,规避锁超时误删的问题。该方案属于可后续迭代的稳定性优化。”
- 架构前提
Q1:CMDB 本质是一套资产数据读写服务,你在日常负责过程中,从架构稳定性角度,如何保障数据可信、接口稳定可靠?
CMDB 作为集团运维领域的根数据源,它的核心本质并不是简单的增删改查接口,而是持续稳定输出可信资产数据、服务长期稳定、链路全程可控。我作为平台管理员,不只是做功能使用与问题救火,更多会从架构稳定性视角,围绕写入管控、访问防护、故障兜底、长期治理四个维度去做持续性优化与思考。- 第一,在数据写入链路上。技术上,拆分服务,核心upsert接口收敛。天然实现幂等,没有并发风险。业务上,基于责任链模式搭建分层校验体系,从源头降低脏数据产生概率,并且通过传入字段标记溯源。一方面实现校验逻辑解耦,新增业务规则不需要改动主业务流程;另一方面统一全集团数据写入标准,避免各个业务接入方各自实现校验逻辑,口径不一致。
- 第二,在数据查询链路上。接口层面强制分页、设置单请求最大返回条数,实现限流;持续监控慢查询,针对高频视图查询定制复合索引;通过监控提前发现异常查询流量,提前介入治理,而不是等故障发生再处理。(这里我的思考是,单纯靠加索引无法永久解决查询压力,后续演进上需要做冷热资产区分,高频基础资产信息增加缓存层,把复杂视图查询流量与基础读写流量进行物理隔离,进一步提升整体可用性。)
- 第三,服务运行层面做好高可用与故障兜底设计。服务采用多实例容器部署,避免单点故障;沉淀完整的读写卡顿排查 SOP,从容器资源、外部依赖 RT、数据库慢查询、应用 GC 状态逐层定位瓶颈。在长期运维中意识到,高可用不能只依赖事后故障排查,需要前置建设:完善全链路埋点监控,对写入失败、查询超时、慢查询建立分级告警,由被动排障转向主动预警。
- 第四,长期的数据可信治理思考。功能层面校验只能拦截实时写入错误,无法避免日积月累产生逻辑脏数据。参考业界 CMDB 产品的建设思路,我认为必须配套离线巡检任务,定时扫描全量资产,按照业务规则校验数据一致性,对异常数据产生告警,形成 “写入拦截‑实时监控‑离线巡检” 三层的数据可信保障体系。
- 延伸总结(回答收尾拔高)
整体来看,保障 CMDB 稳定不是一次性架构改造,而是持续性的治理工作。功能代码只解决 “能不能读写”,而稳定性设计、限流防护、校验约束、持续巡检解决 “能不能长期可信、稳定地读写”。这套架构基于 MongoDB 文档库实现,更适合资产字段灵活多变的运维场景,牺牲了原生强事务能力;如果是交易类业务系统,基于 MySQL 事务机制会是更稳妥的选择,我也针对性补充学习了 MySQL 索引、事务与锁相关原理,完善据库层面的知识储备。 - 衍生问题 1:责任链模式在校验场景相比 if‑else 有什么优势?有没有弊端?
优势:规则解耦、易扩展、方便开关校验规则,适合持续迭代多条业务规范;
弊端:链条过长带来性能损耗,调用链路调试复杂;
落地取舍:核心强同步校验精简链路,次要校验后置异步执行。 - 衍生问题 2:如何防止业务方写出大量慢查询拖垮 CMDB 视图接口?
四层防护:接口强制分页 + 联表查询加索引 + 慢查询实时监控告警 + 业务访问限流 - 衍生问题 3:如果让你重构整套数据读写架构,优先增加哪些稳定性能力?
- 引入缓存承载高频简单查询,隔离复杂视图查询压力;常见联表查询加索引。。
- 增加脏数据定时巡检任务,主动发现不合规资产信息;
- 核心读写链路进行流量拆分??读从库??查询集群与写入集群物理隔离,互不影响。
Q2:谈谈 CMDB 整体微服务架构体系、流量链路与服务拆分的设计思想?
CMDB 整体基于 SpringCloud 微服务架构 搭建,是一套分层清晰、职责单一、能力解耦的企业级资产底座架构。我在日常运维、问题排查、功能支撑过程中,完整梳理了平台流量链路与各核心微服务的职责边界,也理解了团队架构拆分的核心设计思路。- 第一,完整的整体流量接入链路。
外部所有访问流量,经过 F5 负载均衡 做第一层流量分发与四层负载,再经过 Nginx/Apache 做静态资源处理与反向代理,统一前置流量收口,最终全部进入SpringCloud 网关。由网关实现统一鉴权、路由分发、限流拦截、请求日志埋点,再精准调度转发至后端各个微服务,实现了流量统一入口、统一管控、统一防护。 - 第二,核心微服务职责拆分与架构解耦设计(核心亮点)。
平台按照「用户交互、数据变更、数据同步、对外服务、采集执行、检索赋能」的职责维度做垂直拆分,避免大单体臃肿、职责混杂,我日常接触最多的四个核心服务,分工非常清晰:- 用户交互服务:专门承接前端页面所有用户操作、资产查询、表单提交、视图展示等交互类接口。只负责请求接收、参数校验、页面逻辑组装,不直接操作数据库,保证交互层轻量化、稳定、专注用户体验。
- 数据连接器服务:是 CMDB 对外暴露的标准化出入口,负责对外提供统一资产读写 API、资产视图查询、第三方系统数据对接、权限鉴权。所有下游系统、外部业务方调用 CMDB,全部统一走这一层,实现对内对外接口隔离,保护底层数据安全。
- 数据入库服务(核心中台服务):这是我理解最深的架构亮点。无论是前端用户交互产生的数据变更,还是外部连接器接收的第三方数据变更,所有写库、数据落地、数据校验、幂等控制,全部收敛统一由数据入库服务完成。
这个设计的优势非常关键:
✅ 实现读写分离、变更收口,所有脏数据拦截、校验规则、幂等逻辑只需要在这一层维护,不需要多服务重复开发;
✅ 彻底解耦「查询展示、对外接入、数据落地」能力,避免多服务直接写库导致的数据不一致、规则不统一、溯源困难;
✅ 所有变更单点可控、可审计、可兜底,从架构层面保障 CMDB 作为根数据源的权威性。 - 配套能力服务集群:平台将非核心链路能力完全拆分独立部署,包括:
• 数据同步服务:负责 Oplog 监听、增量数据推送下游;
• 采集执行/采集代理服务:负责全量资产任务调度、远程采集、数据清洗;
• 全文检索服务:负责对接 ES、模糊检索、资产搜索赋能。
各服务各司其职,任务互不干扰,单个子服务异常不会拖垮主核心链路,极大提升平台整体可用性。
- 第三,我对这套架构的个人设计思考与总结。
我在长期运维中总结,这套微服务拆分完全符合单一职责、收口可控、分层隔离、高内聚低耦合的架构思想:- 入口统一:网关+连接器双层收口,内外流量隔离;
- 变更唯一:全平台所有写操作统一收口入库服务,从架构根源保证数据可信;
- 能力解耦:交互、查询、写入、同步、采集完全拆分,迭代互不影响,适合长期大型底座迭代;
- 稳定性高:核心链路与辅助链路物理隔离,故障爆炸半径小。
- 面试一句话亮点总结:
CMDB 架构最大的精髓不在于微服务拆分本身,而在于所有数据变更的唯一收口设计,通过统一入库服务管控全平台写逻辑,从架构层面保证了集团资产数据源的唯一性、规范性与可信度,这也是企业级配置中心最核心的架构设计亮点。 - 衍生追问1:为什么不让交互服务、连接器服务直接写库?
如果多服务直接写库,会出现校验逻辑分散、幂等实现不统一、变更无统一日志、问题无法溯源,多人开发极易出现规则冲突、数据错乱。统一收口写入服务,可以做到规则统一、审计统一、兜底统一、治理统一,是底座类系统必备的架构设计。 - 衍生追问2:F5、Nginx、网关三者的区别你怎么理解?
三者是层层递进、各司其职的流量架构:- F5:四层负载,负责最前置的 TCP 流量分发、负载均衡、高可用接入,不处理业务逻辑;
- Nginx/Apache:七层反向代理,处理静态资源、简单路由、解压、缓存,减轻后端服务压力;
- SpringCloud网关:业务层入口,负责鉴权、限流、路由、灰度、业务拦截,是微服务真正的流量管家。
三者前置层层防护,让后端微服务只专注业务逻辑,是标准企业级流量架构。
- 第一,完整的整体流量接入链路。
Q3:讲讲CMDB基于Monstache实现MongoDB→ES全文检索,整体设计、核心亮点、故障排查思路
业务痛点:MongoDB文档库擅长字段灵活扩展,但做跨文档、多字段模糊/全局检索性能很差,无法满足运维侧全局IP、主机名快速检索需求,因此引入ES做全文检索,用Monstache完成MongoDB到ES的数据同步。- 整体实现思路
数据流:MongoDB副本集 → Monstache中间件 → Elasticsearch → qz‑elasticsearch Java封装服务 → 业务/前端。- MongoDB 副本集部署,所有写操作记录到 oplog(固定大小环形缓冲区)。
- Monstache 独立Go进程,兼顾全量初始化同步 + 基于 oplog 实现的 Change Stream 实时监听增量变更;
- 在MongoDB侧维护两套状态集合做同步管控:断点位点集合实现断点续传、全量标记集合避免重复全量扫描;
- 一个CIT资产类型对应一个ES索引,Java服务使用query_string做全字段检索,实现跨资产全局搜索,例如全局IP查询。
- 核心工程亮点(重点说,体现运维+架构思维)
- 职责解耦:同步逻辑完全剥离到独立Monstache进程,Java服务只做查询,不承担长连接监听、数据同步压力;并且消费副本集隐藏节点,同步流量不打主库。
- 状态可运维:MongoDB中持久化同步断点、全量完成标记。进程重启支持断点续传;数据不一致时,删除全量标记即可触发重新全量同步,运维操作简单。
- 全量+增量双模式:启动时未同步过就执行全量direct‑read补历史数据;完成后切换ChangeStream增量监听,正常同步延迟小于1s。
- 业务价值:实现运维场景跨资产类型全局IP、设备关键词检索,弥补MongoDB检索短板。
- 线上常见问题+排查思路(实战部分)
- ES与MongoDB数据不一致
- 先看Monstache进程和日志,确认ChangeStream长连接是否断开;
- 检查同步断点是否在推进,判断增量链路是否停滞;
- 增量故障:修复进程;存量不一致:删除全量标记,重启Monstache触发全量重同步。
- 部分文档同步失败:大多是MongoDB与ES字段类型不兼容、超大文档,日志定位异常文档,修正mapping或者业务数据。
- 同步延迟走高:比对oplog位点差距,判断是写入压力大还是消费跟不上,调大批量刷写参数或扩容ES。
- Oplog被覆盖(停服过久):旧oplog被环形缓冲区覆盖,增量断点失效;只能触发一次全量同步重建ES数据。
Java侧查询侧隐患:业务使用query_string+fields[“*”]全字段检索,性能开销大;优化:明确指定业务检索字段,不使用通配全部字段。
- 衍生1:为什么不用Java服务直接消费ChangeStream,要用Monstache?
- Java业务服务要承担业务流量,长连接+解析变更日志会增加服务压力;Monstache独立进程做同步,职责隔离。
- 自带位点持久化、断点续传、全量同步能力,不用自己编码实现位点管理。
- 支持读取MongoDB隐藏副本节点,同步流量隔离,保护主库性能。
- 衍生2:新增一个CIT资产类型,操作流程是什么?
ES预先建好对应索引与mapping;在Monstache配置加入namespace;按需删除全量标记重启,执行全量同步后自动进入增量监听。
- 整体实现思路
Q4:讲下 CMDB 数据订阅整体架构,为什么选用 MongoShake,而不是原生 ChangeStream 程序直接消费?数据订阅服务消费一条 Kafka 消息完整处理流程?
- 整体链路:MongoDB 副本集 → MongoShake(Go 独立进程,读取 hidden 隐藏节点)→ 输出 JSON 消息写入 Kafka mongo_sync 主题(8 个 partition)→ 数据订阅服务消费组ops‑subscribe消费,完成幂等、规则匹配、异步回调。
- 选择 MongoShake 而不是业务服务直连 ChangeStream 有几点考量:
- 隔离业务服务压力:ChangeStream 长轮询消费、解析变更日志消耗 CPU 与网络;独立 Go 进程 MongoShake 承担拉取、解析、分 partition 路由,业务 Java 服务只负责消费 Kafka,职责拆分。
- 读取 hidden 隐藏节点:不消耗主节点负载,避免订阅业务压垮业务主库。
- 内置位点持久化:MongoShake 自身记录同步位点,进程重启不会丢失变更事件;同时支持按 collection 做哈希路由,把不同集合变更打散到不同 Kafka partition,提升消费并行度。
原生 ChangeStream 直接消费也能实现,但 Java 服务重启、OOM、发布时位点管理需要自己编码;同时长连接压力落在业务应用,不利于稳定性隔离。
- 衍生追问:hidden 节点作用是什么?
hidden 节点属于副本集,不参与主节点选举,不承接业务读写,专门用来做备份、同步、离线分析,把订阅同步流量卸载到该节点,保护主库性能。 - 一次订阅回调,Kafka 消息完整处理流程:
- 幂等校验:构造 key topicname‑partition‑offset写入 Redis,TTL2 分钟;如果 key 已存在直接跳过这条消息,解决重复消费、重启重拉导致重复回调。
- 变更解析
- INSERT:直接解析消息体完整文档字段;
- UPDATE:ChangeStream 仅返回变更字段,需要根据_id回查 MongoDB 拿到最新完整文档;
- DELETE:只提取文档主键_id。
- 订阅规则匹配:从 MongoDB message_subscriber集合读取订阅配置,配置会缓存 Redis TTL60min;按集合名、操作类型、字段过滤条件做匹配。
- 异步 HTTP 回调:匹配成功把回调任务丢业务线程池异步执行;配置重试次数,连接超时 1s、读超时 5s;线程池满使用 AbortPolicy 丢弃任务并打印告警;回调结果写入审计 topic。
延伸引导:如果是 MySQL 环境,一般依靠 Binlog 实现同等能力,我近期也对比研究过 Binlog 与 Oplog 的设计思路异同。
Q5:详细描述防火墙自动采集整条链路,线上遇到过哪些典型问题?
- 链路流程:XXL‑Job 调度 → syn-task 采集代理下发采集任务 → 消息投递 Kafka → 采集执行服务消费任务 → 通过 API/Ansible/FTP 拉取设备配置 → 结果推送 Kafka → 采集代理消费数据,清洗入库 MongoDB。
- 问题1:简单介绍下CMDB防火墙自动采集的整条链路是怎么跑的?
- 记忆要点:总定位 → 三阶段流程 → 收尾点出通用性
- 面试回答: 我们的CMDB采集是一套配置驱动的通用入库框架,防火墙是其中典型的高数据量场景,完整复用这套架构。整条链路分三个阶段:
- 第一是采集下发:定时任务触发后,采集代理把设备信息和对应的采集脚本下发 kafka,采集执行服务中通过Ansible并发对多台防火墙执行采集。
- 第二是分片投递:因为单台防火墙的策略、路由等数据量很大,我们设计了分片机制,单台设备的采集结果拆成多个分片,每个分片独立写入Kafka;等所有设备都采集完成,再发一条汇总消息标记整个任务结束。
- 第三是合并入库:服务消费Kafka消息时分两种逻辑:分片消息先直接存入Redis暂存;等到消费到汇总消息时,才会把该任务下所有设备的分片从Redis拉出来,逐台拼接成完整数据,字段匹配后批量写入MongoDB。
- 简单说就是「并发采集→分片投递→汇总入库」,这套逻辑不止防火墙,服务器等其他设备也通用。
- 业务模型:任务 → 设备 → 分片(一个防火墙采集任务有一个instanceId,同时采集很多台防火墙,每台防火墙的采集数据可能分多个分片投递,组装后统一入库)
- 问题2:这套框架为什么这么设计?核心的设计考量是什么?
- 记忆要点:四个设计点,每个点对应解决一个痛点
- 面试回答: 核心是围绕「通用性」和「高数据量兼容性」两个目标做的设计,主要有四点考虑:
- 第一是配置驱动,在任务中配置好设备范围、采集脚本、结果映射(如何采集、采集数据如何转对象存Mongo)即可,核心采集入库逻辑都是通用的。采集代理服务中,对“每一台设备+采集脚本”下发一个任务,采集执行服务中,将采集结果分片存入kafka,采集代理服务后续组装好,映射后存Mongo。
- 第二是分片机制,专门适配防火墙这种单台设备上万条策略的场景,大体积数据拆分投递,避免单条消息过大压垮Kafka和消费端,也天然支持大规模设备并发采集。
- 第三是批量写入,单台设备的所有记录攒齐后一次性批量入库,而不是逐条写MongoDB,大幅降低数据库IO,对防火墙这种大数据量设备效果特别明显。
- 第四是加锁保护,汇总消息消费时加全局锁,防止同一条汇总消息重复消费导致数据重复入库,保证数据一致性。
- 问题3:线上这个服务出过最严重的故障是什么?你是怎么一步步排查的?
- 记忆要点:现象 → 三步排障法(看GC→抓线程→分析堆)→ 结论
- 面试回答: 最严重的是一次Full GC死循环故障,直接导致整个采集入库完全停掉。
我是按标准线上排障流程一步步定位的:
第一步,先确认故障失控程度:告警触发后,我先用jstat秒级监控GC状态,发现老年代占比99.97%,Full GC每秒2次,单次STW 600毫秒,12分钟里STW占了快80%,完全失控了,立刻决定保留现场不重启。
第二步,定位阻塞点:优先抓jstack,因为对进程影响最小。结果看到18个业务线程全阻塞了,都在等同一把全局锁;持锁的线程卡在入库逻辑里,一直不释放锁。这就把范围缩小到:单台设备入库过程卡住,导致锁不释放,所有任务都堵了。
第三步,定位内存根因:接着用jmap打堆转储,特意不加-live参数,避免触发GC破坏现场。用MAT分析后发现,持锁的那一个线程就占了757MB内存,接近堆的87%,内存主要来自字段配置表的反序列化对象。 到这里就基本定位了:内存溢出导致Full GC,根源在字段配置表的重复反序列化。
- 问题4:为什么只是反序列化一个配置,会打满整个堆,还形成死循环?
- 记忆要点:三类对象叠加 → 核心是重复创建 → GC回收不了 → 死循环放大
- 面试回答: 表面是反序列化,实际是三类对象叠加把1G的堆打满了,而且GC回收不了,形成无法自愈的死循环。
第一类是busMap,就是单台设备所有分片拼出来的完整采集数据,从处理开始到入库完成全程持有引用。
第二类是核心问题:citfieldsinfo字段配置对象。当时的代码是每条采集记录都从Redis拉一次配置JSON,重新反序列化一次,结果不复用。一台防火墙有上万条策略,就会创建上万个相同的配置对象,对象创建速度远远快于Young GC的回收速度,大量对象直接晋升到老年代。
第三类是待入库的记录列表,处理完的记录先攒着,等整台设备处理完才批量入库,越攒越大。
这三类加起来直接把1G堆占满了。 然后为什么回收不了?因为触发Full GC的时候,入库逻辑还在跑,这三类对象都被调用栈持有引用,GC根本回收不掉,老年代一直99.97%。 更糟的是形成了死循环:Full GC的STW会让Redis和MongoDB响应变慢,入库速度更慢,对象堆积时间更长,老年代更满,Full GC更频繁,完全无法自愈,卡死。
- 问题5:最后你是怎么修复的?做了哪些优化?
- 记忆要点:先止血 → 再根治 → 再架构优化(四个优先级)
- 面试回答: 我们分优先级处理,先临时止血,再根治问题,最后做架构层面的优化:
- P1 根治核心问题:把重复反序列化改掉,在单台设备入库入口处,只反序列化一次字段配置,后续所有记录都复用这个对象。改完之后,这部分内存直接从几百MB降到几MB,从根源消除了对象堆积。
- P2 优化锁粒度:原来的全局锁太粗,整个入库过程都在锁里,导致不同任务串行。我们改成按任务ID加细粒度锁,只保护「Redis读分片+删分片」这个毫秒级的原子操作,入库逻辑移到锁外面,不同任务可以真正并发,吞吐量提升很明显。
- P3 做流量保护:原来用的是裸线程,没有上限,流量突增容易打垮服务。我们换成有界线程池加调用者运行策略,线程池满了就阻塞Kafka消费,形成自然背压,相当于自带限流。
- P4 资源适配:临时把堆内存从1G调到4G先止血,后续就固化了这个配置,匹配服务器的可用资源。 改完之后服务恢复正常,老年代占比稳定,也没再出现过类似的GC问题。
- 问题6:从这个项目里,你有什么技术沉淀?或者说这套思路能复用到其他场景吗?
- 记忆要点:架构复用 → 排障方法论 → 稳定性闭环
- 面试回答: 主要有三方面的沉淀,都是可以跨场景复用的:
第一是通用采集架构的设计思路,配置驱动+分片+批量入库的组合,不仅适用于CMDB的防火墙、服务器采集,后续我们做其他配置数据的同步,也复用了这套框架,接入新数据源很快。
第二是标准化的JVM线上排障方法论,就是「先确认GC状态→线程栈定位阻塞点→堆内存分析定根因」的流程,还有抓堆时不加-live保留现场这些细节,也梳理到我的Skill了。
第三是稳定性优化的闭环思维:出问题不能只改bug,要从根因、架构、流量控制、资源配置多个维度补强。比如这次我们不止改了重复反序列化的bug,还从锁模型、线程模型、资源配置三个层面做了优化,让服务从「能跑」变成「稳定跑」,形成了完整的稳定性闭环。 另外我也对大数据量场景下的JVM内存调优有了更实的体感,比如元数据复用、对象创建速率对GC的影响这些,不是书本上的知识,是实际踩坑踩出来的。
- 问题7:入库方法为什么用全局锁?
讲完锁粒度优化后,可以顺势补一句:“这个优化做完之后,我们顺带也解决了之前大任务排队导致的消费延迟问题,相当于同时解决了稳定性和性能两个问题。” 自然引导面试官往性能优化、架构演进的方向继续追问,把话题牢牢握在你熟悉的范围内。- 当初入库方法为什么要加全局锁?
- 核心考点:你能不能说清楚加锁的业务背景,而不是“为了加锁而加锁”;考察对并发风险的识别能力。
- 记忆要点:业务场景 → 不加锁的风险 → 初代方案的合理性
- 面试回答: 加全局锁本质是为了解决汇总消息重复消费导致的数据重复入库问题。
我们的Kafka汇总消息(is_finish=true)标记一个采集任务所有设备都采集完成,触发全量合并入库。但Kafka本身有至少一次投递的特性,加上消费端offset提交异常、服务重启重试,都可能导致同一条汇总消息被消费多次。 如果不加锁,两条相同的汇总消息并发执行,就会两次从Redis读取分片、两次写入MongoDB,造成数据重复。(实际上upsert保证了没有幂等问题,只是会浪费资源,重复执行入库方法。。) 初代设计用synchronized全局锁,是因为实现最简单、能强保证一致性,而且前期采集设备少、数据量小,入库很快,锁的持有时间短,对性能影响不明显,属于典型的「业务初期用最简单方案保证正确性」的设计。
- 全局锁是导致这次Full GC的根本原因吗?
- 核心考点:区分「根因」和「故障放大器」,考察问题定位的准确性,避免主次不分。
- 记忆要点:明确结论 → 根因是什么 → 全局锁的角色是放大故障
- 面试回答: 不是根本原因,它是故障的放大器,真正的根因还是字段配置表重复反序列化导致的内存堆积。 我可以分得很清楚:
- 直接打满堆内存的,是busMap、重复创建的citfieldsinfo对象、待入库列表这三类对象的叠加,这是Full GC的触发源,和加不加锁没有关系。就算去掉全局锁,只要每条记录都重复反序列化,单台设备入库还是会把堆打满,触发Full GC。
- 全局锁的问题,是把「单任务故障」放大成了「全服务故障」。因为所有任务都抢同一把全局锁,持锁的那个线程因为Full GC卡住,迟迟不释放锁,后面18个消费线程就全部阻塞在锁上,整个服务的采集入库直接停摆。 简单说:内存问题是“病因”,全局锁是“并发症”,让病变得更重了。
- 如果要缩小锁粒度,应该怎么做?本质上是哪些地方要防止并发?
- 记忆要点:先讲本质(只保护「读+删分片」原子性)→ 原来锁错了什么 → 两步优化 & 效果
- 面试回答:
- 先讲本质:我们真正需要防止并发的只有一件事 → 防止同一个instanceId的汇总消息被并发/重复消费时,多次读取Redis中的分片数据,导致同一份数据重复入库。 除此之外,分片拼接、字段解析、批量写MongoDB这些处理逻辑,都是基于已经读出来的内存数据,完全不需要加锁。
- 原来的全局锁问题在哪:原来的synchronized直接锁了整个resolvePartitionMessage()方法,把「读Redis分片 → 拼接 → 逐行解析 → 批量入库」整个流程都包在锁里了,锁持有时间是秒级甚至几十秒级;而且所有任务抢一把锁,不同采集任务完全串行,既慢又容易因为一个任务卡住拖垮全部。
- 缩小粒度的两步做法
- 第一步:缩小锁的范围,只保护原子操作 把锁从「整个入库流程」缩小到只包裹「从Redis读取该instanceId下所有分片数据 + 原子删除这些分片」这一步。这一步是纯Redis内存操作,毫秒级就能完成。读完删完立刻释放锁,后面的分片拼接、字段解析、写MongoDB全部放到锁外面执行。 为什么必须把“读”和“删”绑成原子操作?因为如果读完不马上删,另一个线程又来读一次相同的分片,就会重复入库。只要保证“读-删”原子,同一份数据就只会被处理一次,后面的计算逻辑再慢也不会产生重复数据。
- 第二步:细化锁的维度,按instanceId加锁
不用全局一把锁,改成按任务维度加锁——每个instanceId对应一把锁。 比如用ConcurrentHashMap维护每个任务的锁对象,不同采集任务(不同instanceId)各自用自己的锁,互不干扰,可以完全并发。 如果是多实例部署,就换成Redis分布式锁,粒度同样是instanceId。
- 优化后的效果:锁持有时间从秒级直接降到毫秒级,不同采集任务并行入库,吞吐量提升非常明显;更重要的是,不会再出现「一个任务卡住,所有任务都堵死」的情况,故障隔离性好了很多。
- 当初入库方法为什么要加全局锁?
Q6:平台查询接口响应很慢,你的标准化排查 SOP 是什么?
- 检查容器资源指标:CPU、内存、网络负载,确认是否资源瓶颈;
- 观测依赖外部接口 RT,排除第三方调用耗时拖累整体链路;
- 抓取数据库慢查询,核查索引是否合理;
- 分析应用 GC 日志,判断是否存在内存压力、频繁 GC 拖慢服务。
实战案例:多次资产联合视图查询由于缺少复合索引导致超时,优化索引后查询耗时大幅下降。
???
看外部,qps升了?
看内部,检查容器资源指标:CPU、内存、网络负载,确认是否资源瓶颈;
看监控,tomcat,rt,,,
Q7:线上 Java 服务频繁 FullGC、出现 OOM,你的完整排查流程?
- 故障发生优先保留现场,导出堆快照;利用 jstack、jmap、MAT 工具分析;
- 定位内存占用大户:超大集合、长期无法释放的对象、线程长时间持有资源;
Q8:CPU 高怎么查?
我排查 Java 服务 CPU 高,遵循从机器→进程→线程→代码 / GC 的顺序:
第一步先用 top 看整机 CPU,区分用户态 us、内核态 sy、IO 等待 wa。us 高基本是应用侧问题;sy 高偏向系统调用、上下文切换;wa 高是 IO 瓶颈,不是 CPU 本身忙。
第二步找到 java 进程 PID(这里讨论 us 高来源 Java服务的情况),用top -H -p PID查看进程内线程,拿到高 CPU 线程的十进制 TID,转成 16 进制。
第三步,故障现场同时采集 jstack 线程栈 + jstat GC 指标,两个维度判断: 如果 GC 指标看到频繁 YGC/FGC,就是 GC 消耗 CPU,后续 dump 堆用 MAT 分析内存泄漏; 如果是业务线程占 CPU,拿 16 进制 TID 去 jstack 检索对应栈,定位代码,大概率是死循环、密集计算、正则回溯这类 RUNNABLE 的代码。
这里要区分:死锁不会导致 CPU 高,死锁线程阻塞不占用 CPU;只有持续 RUNNABLE 的循环代码、GC 线程才会持续占用 CPU。IO 阻塞线程会休眠,不耗 CPU。Q9:Kafka 消息堆积一般怎么分析、如何解决?
- 先监控 lag 指标,区分根因:消费处理能力不足、消费逻辑阻塞、消息重试形成死循环;
- 处理方案:优化消费业务逻辑、合理提升消费并发、阻塞消息隔离至死信队列;结合采集链路中真实堆积案例阐述。
Q10:讲讲Linux机器磁盘占用过高的排查SOP与长效优化方案
- 一、标准化排查SOP
- 告警确认:监控触发磁盘使用率>80%告警,通过跳板机SSH连接主机,逐层定位磁盘占用源头;
- 磁盘占用溯源:通过df -h查看分区使用率,结合du -sh逐层筛选Top5大占用目录,最终定位通常为快照、日志、发版时备份的jar文件等冗余文件;
- 问题验证:核查日志存量,确认快照日志长期累积、无自动清理,两个月未归档删除导致磁盘爆满;
- 临时止血:通过find命令精准删除60天以上过期快照日志,执行后重新校验磁盘使用率,快速解除告警。
- 二、核心技术亮点与长效优化(规避重复故障)
- 中间件原生自动清理优化:如 Zookeeper 修改zoo.cfg核心配置,开启自动清理机制,设置autopurge.purgeInterval=24(每日自动清理)、autopurge.snapRetainCount=7(仅保留7个最新快照),依托原生能力实现日志常态化清理;
- Java 服务 Logback/Log4j2 日志:最优是在日志框架内部配置时间 + 大小双维度滚动策略,限制单文件大小、归档份数、总磁盘上限,从源头避免单日志膨胀几十 G;如果无法修改应用配置,再用 logrotate 的 copytruncate 模式做兜底(操作系统层面做定时拷贝截断)。
- 定时任务兜底:不依赖原生能力,可以配置Linux定时任务(cp + truncate),每日凌晨低峰期自动清理30天以上过期日志;
面试拔高总结:这次问题让我意识到中间件运维不能只靠临时救火,必须结合组件原生特性+系统工具+定时运维机制,构建常态化稳定性防护体系,从根源杜绝磁盘溢出类故障。
- 三、Linux文件底层原理与零中断清理方案
- 核心底层原理(面试高频)
- 核心三要素:文件名仅为目录索引标签,inode是文件真实数据存储载体,文件句柄FD是进程读写文件的内核通道;
- Linux文件删除判定规则:文件真正释放的前提是「文件名解绑+所有进程FD关闭」,仅执行rm删除文件名,进程持有FD仍会持续写入inode,产生deleted幽灵文件,磁盘空间无法释放;
- 日志缓存特性:Java服务持有活跃日志文件句柄时,ll/ls读取的是目录缓存大小,无法实时同步真实文件大小,易造成操作误判。
- 线上致命踩坑复盘(避坑亮点)
此前处理60G超大活跃日志时,因ll缓存未刷新误判清理失败,错误执行rm删除活跃日志,导致产生大量幽灵文件,服务持续向废弃inode写入数据,磁盘空间无法释放,最终只能重启服务恢复,造成短暂服务不可用。通过这次故障,我总结出严禁直接rm清理Java活跃日志的运维准则。 - 两套零中断最优清理方案(适配不同场景)
- 轻量安全方案:cp备份+cat /dev/null > 清空,无需重启服务、无需发送信号,Java进程无感知,日志链路不中断,适配中小体积日志;缺点是cp过程短暂占用双倍磁盘空间;
- 超大日志最优方案:mv重命名+touch新建+kill -HUP重载日志,mv瞬间完成无磁盘翻倍风险,适配100G+超大日志文件,通过发送信号让Logback/Log4j2重新绑定新inode,实现无缝切换;
- 缓存刷新方案:掌握kill -USR1进程信号,不重启服务即可刷新目录缓存,让ll实时展示真实文件大小。
- 面试拔高总结:日志清理看似基础,实则涉及Linux内核底层机制与Java日志框架联动,我通过实战踩坑形成了「懂原理、避风险、分场景、零中断」的标准化运维能力,规避线上低级致命故障。
- 核心底层原理(面试高频)
- 一、标准化排查SOP
Q11:私有化交付遇到 ZooKeeper 集群无法启动,完整说下排查过程和解决方案
现象:容器环境重启后 ZooKeeper 集群启动失败,节点无法互相通信;
根因:集群配置文件硬编码节点物理 IP,容器重建之后 IP 发生变动;
手工处理:创建版本化配置文件
解决方案:选择集群健康运行窗口期执行 reconf 动态更新集群配置,用内部域名替换固定 IP;
亮点:方案无需停机重建集群,不存在业务中断;
拓展思考:云原生容器环境部署中间件,应当尽量规避写死 IP,优先使用域名、服务发现机制。Q12:重大故障——详细说说CMDB内存Swap抖动OOM的case
凌晨三点,我在生产环境错误操作,导致CMDB data-center服务P1级内存故障——核心是Linux Swap抖动引发的服务OOM崩溃,全程沉淀了系统内存调优、JVM参数配置、线上故障止血的高阶实战能力。- 一、故障核心现象
清空50G超大日志后,服务器Load飙升至15+,Swap使用率从14%暴涨至100%,持续si/so双向交换,系统陷入震荡,最终Java服务频繁FullGC、OOM崩溃,服务中断40分钟。 - 二、核心根因(分层拆解,面试亮点)
- 直接诱因:错误使用cat /dev/null > 清空大日志,内核触发文件截断、刷盘操作,临时占用大量PageCache,瞬间耗尽物理内存;
- 系统底层问题:内核LRU算法误判,将Java、ilogtail的4GB活跃热数据换出至Swap,而非仅清理冷数据;
- 稳态架构隐患:服务器物理内存配置临界,长期依赖Swap存放冷数据,无冗余缓冲空间,一旦出现临时内存占用,直接打破内存平衡;
- 恶性死循环:热数据常驻Swap,业务频繁访问触发持续Swap In/Out,GC暂停时间从毫秒级拉长至秒级,最终OOM Killer杀死进程。
- 三、紧急止血方案(零扩停最优操作)
- 精准止损:优先停止非核心日志采集服务ilogtail,快速释放5.8GB内存,满足Swap清空的物理条件;
- 强制恢复:执行swapoff -a && swapon -a,强制将Swap中8GB数据回迁物理内存,彻底终结内存震荡;
- 服务恢复:重启崩溃的data-center服务,校验日志写入、接口访问正常后恢复非核心服务。
- 四、长效整改优化
- 操作标准化:统一使用truncate -s 0命令清空大日志,杜绝cat截断引发的内存震荡问题;
- 日志架构优化:配置Logback按天、按大小双维度日志切割,避免单日志文件超大;部署自动备份清理定时脚本,实现日志常态化轮转;
- 定时任务清理:(CMDB维稳为主不发版)crontab 配置定时日志备份清理脚本
- JVM参数固化:统一配置4G堆内存、OOM自动dump、GC日志持久化、OOM自动退出参数,提升服务容错性与可观测性;
- 资源扩容优化:规划服务器内存扩容至32G,拆分非核心服务,降低核心服务内存占用压力。
- 面试拔高总结:这次故障让我跳出单纯的JVM调优,掌握了「应用层+JVM层+Linux系统层」三层联动的故障排查思维,理解了内存、PageCache、Swap的底层联动机制,具备处理服务器级重大稳定性故障的能力。
- 一、故障核心现象
Q13:讲讲CMDB MongoDB实例异常重启故障的排查思路与索引优化落地
我处理过MongoDB节点无故崩溃重启的线上故障,通过日志溯源、慢查询分析、索引优化,彻底解决数据库实例稳定性问题,沉淀了MongoDB性能调优与故障排查能力。- 一、故障排查标准化SOP
- 定性崩溃类型:通过日志关键词Got signal区分主动崩溃(Aborted)与外部kill(Terminated),锁定为服务内部异常崩溃;
- 定位故障时间窗口:截取崩溃前后日志,精准筛选异常时段的数据库操作记录;
- 挖掘核心元凶:过滤COLLSCAN全表扫描日志,发现单条查询扫描20万+数据、仅返回1条结果,无复合索引导致超高IO消耗,是实例崩溃核心诱因;
- 集群状态校验:通过MongoDB Compass确认副本集主从节点状态,区分仲裁节点与数据节点,保证优化操作在主节点执行;
- 索引落地验证:后台创建业务复合索引,通过Explain执行计划验证,实现全表扫描(COLLSCAN)转为索引扫描(IXSCAN),查询耗时从5s+降至毫秒级。
- 二、核心技术亮点
- 精准问题定位:通过日志关键词筛选、执行计划分析,定位「大扫描量、低返回量」的低效查询,解决MongoDB隐性性能瓶颈;
- 生产无损优化:采用background后台建索引,避免锁表影响线上读写业务;
- 稳定性治理:建立MongoDB慢查询巡检机制,常态化监控全表扫描、超耗时查询,提前规避实例过载、重启故障。
- 一、故障排查标准化SOP
Q14:CMDB Kafka偶发性消息堆积的根因分析与治理方案
线上出现每周定时Kafka消息堆积的规律性故障,我通过流量分析、消费能力校验,定位瓶颈并完成轻量化治理,保障采集链路稳定运行。- 一、故障现象与根因
每周日凌晨定时出现Topic消息堆积,1小时后自动恢复,核心根因是:定时采集任务集中触发,消息生产流量激增,但对应Topic 6个分区仅配置1个消费实例,消费能力无法匹配瞬时生产流量,形成生产>消费的堆积缺口。 - 二、治理方案与优化思路
- 风险评估:校验Broker磁盘容量、消息堆积自愈能力,确认堆积为瞬时流量峰值导致,无数据丢失、无业务阻塞,无需扩容消费实例;
- 告警阈值优化:适配业务流量规律,上调该时段消息堆积告警阈值,避免无效告警干扰运维;
- 长效优化规划:后续可根据流量峰值,扩容消费实例、匹配分区数量,提升峰值消费能力,彻底消除堆积现象。
- 一、故障现象与根因
Q15:CMDB微服务视图卡顿、Tomcat线程打满故障排查与治理
针对CMDB对外数据访问服务接口卡顿、500报错问题,我沉淀了Tomcat线程池监控、服务链路排查、上下游定位的标准化排查能力。- 一、故障核心现象
ops-data-access对外查询、写入接口大面积500报错,接口响应卡顿,核心原因为Tomcat工作线程被批量写入请求打满,查询请求无线程可用、直接被拒绝。 - 二、分层排查思路
- 线程监控定位:通过Tomcat线程指标判定,线程数打满100阈值、accept-count=0,新请求无排队队列直接拒绝;
- 链路分层校验:区分写入卡顿、查询卡顿不同场景,精准判断瓶颈在当前服务还是下游入库服务;
- 溯源流量源头:通过MongoDB审计表,按数据量排序筛选大批量写入请求,定位高频、大流量调用方,完成业务侧限流治理。
- 三、核心优化思路
- 流量隔离:拆分批量写入与普通查询流量,避免大批量任务占用核心线程池,拖垮查询业务;
- 阈值管控:优化Tomcat线程池参数、请求队列长度,适配业务批量场景;
- 业务治理:对接调用方,规范批量写入频次与单次数据量,从源头降低流量峰值压力。
- 一、故障核心现象
Q16:接口突然不可用?CMDB全文检索ES偶发500故障
我处理过ES检索接口偶发500的疑难故障,突破常规日志排查思路,精准定位依赖版本冲突的隐性问题,掌握中间件客户端适配、版本兼容治理能力。- 一、故障现象
CMDB queryUserResource接口偶发ES查询失败500报错,重启服务临时恢复,无固定复现规律,线上隐蔽性强。 - 二、核心根因
Spring Boot 1.5.10默认降级httpcore-nio低版本,与elasticsearch-rest-high-level-client7.17.23高版本不兼容;ES服务端主动断连时,客户端缺失核心构造方法,触发NoSuchMethodError,导致I/O reactor线程崩溃且不可逆,客户端彻底失效。 - 三、高阶排查思路(面试亮点)
- 跳出局部日志:搜索不到查询接口报错日志,可能是服务已经不可用,请求不到接口里➡️全局检索ERROR日志,发现I/O reactor STOPPED状态早于接口报错,锁定因果关系;
- 异常溯源定位:通过崩溃日志上下文,抓取ConnectionClosedException断连异常+版本不兼容报错,精准锁定JAR包冲突问题;
- 疑难问题复盘:明确故障隐蔽性——版本冲突代码路径仅在断连场景触发,服务长期稳定无异常,仅在中间件主动断连时爆发,属于典型隐性依赖BUG。
- 四、解决方案
- 临时止血:重启异常服务节点,重建ES客户端实例,快速恢复检索服务;
- 根治优化:统一管控Maven依赖版本,强制指定兼容的httpcore-nio高版本,解决版本冲突;
- 长效治理:梳理全服务中间件依赖版本,统一版本规范,规避依赖冲突类隐性故障。
- 一、故障现象
Q17:介绍下你的内网IT故障排查Skill?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222---
name: enterprise-it-troubleshooting
description: >
适配企业内网 IT 场景的通用线上问题排查技能。用于用户需要排查线上故障、
复盘告警、编写 SOP、定位 Java Full GC、CPU 高、线程池打满、Tomcat 拒绝请求、
Kafka 堆积、MongoDB 慢查询或异常重启、磁盘暴涨、ZooKeeper 异常、K8s Pod 异常、
配置或依赖冲突等场景。也适用于用户只给出一个现象,如“接口 500”“服务 pending”
“节点 CPU 高”“日志一直暴涨”“消息堆积”“Pod 起不来”,需要结合 Linux 命令、
应用日志、JVM 现象、监控平台、链路上下游关系做分层定位时使用。优先输出止血、
定界、根因、验证和改进建议。它通过工作流和对话引导帮助排查,但不直接与线上工具、
API 或运行环境交互。
---
# 企业 IT 线上故障排查
## 目标
你是一名面向企业内网场景的线上故障排查助手。你的任务不是直接执行线上操作,而是通过对话引导和排查工作流,帮助用户完成以下事情:
1. 快速定界故障层次
2. 明确先看哪些监控、日志和指标
3. 基于用户回传的信息继续收敛根因
4. 输出止血建议、根因分析和改进项
## 能力边界
你可以做的事:
1. 帮用户拆解故障现象、影响范围、时间线
2. 给出排查顺序和高概率分叉判断
3. 建议应该查看哪些监控、日志、命令输出和系统指标
4. 帮用户整理 SOP、事故报告、复盘结论和长期改进建议
你不能直接做的事:
1. 不能直接登录线上机器、容器、K8s、数据库
2. 不能直接调用企业内部 API、监控平台、堡垒机能力
3. 不能假设自己已经拿到了命令输出、日志文件或监控截图
4. 不能伪造排查证据,只能基于用户提供的信息继续判断
默认工作方式:
1. 先告诉用户应该去哪里取证
2. 再说明看到什么现象意味着什么
3. 最后基于用户回传的信息继续收敛根因
## 核心原则
1. 先看监控,再上机器
2. 先定影响面,再看单点异常
3. 先止血,再找根因
4. 先取证,再下结论
5. 优先构建“现象 -> 指标 -> 日志 -> 调用链 -> 根因”的闭环
## 通用排查框架
### 第一步:先定界
每次先回答这 4 个问题:
1. 故障现象是什么:慢、错、挂、堆积、起不来、资源高
2. 影响范围多大:单接口、单节点、单服务、整链路、整集群
3. 当前趋势如何:持续恶化、波动、已恢复、偶发
4. 问题在哪一层:应用、线程池、JVM、下游服务、数据库、网络、OS、K8s
如果信息不足,优先追问:
1. 从什么时候开始
2. 是报错还是变慢,错误码是什么
3. 单节点还是全部节点
4. 最近是否发版、改配置、重启、扩容、切流、执行过批量任务
5. 上下游依赖是否正常
### 第二步:先看监控
优先建议用户查看:
1. 主机层:CPU、Load、内存、Swap、磁盘、网络、IO wait
2. JVM 层:堆、Old 区、FGC、线程数
3. Tomcat 层:繁忙线程、最大线程、请求耗时
4. 接口层:RT、成功率、5xx、超时
5. 组件层:Kafka lag、Mongo RT、Pod 状态
常见判断:
1. 写入慢且查询也报错:优先看线程池/Tomcat 是否被打满
2. 只有查询慢:优先看下游 RT、数据库慢查询、索引
3. 只有单节点异常:优先怀疑节点局部问题
4. 多节点同时异常:优先怀疑共享依赖或公共配置变更
### 第三步:建议取证
根据故障类型,建议用户提供对应证据:
1. CPU 高:
- `top`
- `top -Hp <pid>`
- `jstack -l <pid>`
2. Full GC / OOM:
- `jstat -gcutil <pid> 1s 10`
- `jstack -l <pid>`
- `jmap -dump:live,format=b,file=heap.hprof <pid>`
- `free -h`
- `vmstat 1 5`
3. 线程池 / 接口 pending:
- Tomcat 繁忙线程
- 线程池活跃线程和队列积压
- 是否存在 `Future.get()` / `join()` 无超时等待
4. 磁盘暴涨 / 日志增长:
- `df -h`
- `du -sh`
- `lsof | grep deleted`
5. K8s / Pod 异常:
- `kubectl get pod -o wide`
- `kubectl describe pod`
- `kubectl logs --previous`
### 第四步:按调用链收敛
如果是微服务问题,优先画最短链路:
`入口 -> 网关/Ingress -> 当前服务 -> 下游服务 -> 中间件/数据库`
逐层判断:
1. 调用有没有发出去
2. 发出去后是超时、拒绝、异常还是空返回
3. 下游是否同时异常
4. 是请求量问题,还是单次请求太重
### 第五步:形成结论
输出时必须拆成四层:
1. 现象:用户看到了什么
2. 直接原因:这次为什么挂了
3. 根本原因:系统为什么允许它发生
4. 改进项:短期止血、中期修复、长期治理
## 常见故障优先判断
1. CPU 高:
- 业务热点线程
- 锁竞争
- Full GC 放大
2. Full GC / 内存故障:
- Old 区持续高位
- Full GC 后回不去
- 线程持有大量对象
- Swap 抖动
3. 线程池打满:
- 哪个线程池满了
- 谁在提交任务
- 任务为什么慢
- 是否多业务共享线程池
4. Kafka 堆积:
- 生产太快还是消费太慢
- 分区数和消费者实例数是否匹配
- 消费者是否卡在 GC、线程、锁、网络
5. MongoDB 慢查询 / 崩溃:
- 崩溃前最后几秒慢查询
- 是否 `COLLSCAN`
- `docsExamined` 是否远大于 `nreturned`
- 是否缺索引
6. ZooKeeper / K8s Pod 起不来:
- 是否绑定旧 IP
- 动态配置是否落在 PVC
- 当前读取的是新配置还是历史残留配置
7. 依赖 / 配置冲突:
- 是否断连后全部失败
- 是否 `NoSuchMethodError`
- 是否客户端状态不可逆损坏
## 输出格式
默认按以下结构回答:
### 故障判断
一句话说明当前最可能的问题层次。
### 先做什么
1. 立即止血动作
2. 立即补采的证据
3. 立即确认的监控项
### 排查路径
1. 第一步看什么
2. 第二步查什么日志或指标
3. 第三步如何判断分叉
### 高概率根因
列出 1 到 3 个最可能根因,并说明理由。
### 改进建议
1. 短期
2. 中期
3. 长期
## 示例触发
以下这类请求都适合使用本技能:
1. “线上一个 Java 服务 CPU 突然飙高,怎么查?”
2. “接口一直 pending,怀疑线程池满了,给个排查顺序”
3. “Mongo 节点异常重启了,怎么从日志反推慢查询”
4. “K8s Pod 一直 CrashLoopBackOff,想整理成 SOP”
5. “帮我把这次 Full GC 故障总结成事故报告和长期改进项”Q18:如果让你重新设计整套 CMDB 底座,你会做哪些优化?
- 对资产进行冷热分层存储,降低全量检索压力;
- 核心强一致性业务资产引入 MySQL 混合存储,弥补 MongoDB 事务能力短板;
- 在采集、数据同步全链路添加埋点监控,完善告警体系;
- 完善数据订阅能力,增加消息幂等、死信队列、自动补偿机制,提升下游同步可靠性。
Q19:CMDB 大量选用 MongoDB,为什么初期没有选择 MySQL?你如何对比两种数据库?
选型原因:平台资产类型繁杂,不同设备属性差异大,需要频繁新增扩展字段;MySQL 频繁执行 DDL 修改表成本很高,MongoDB 文档模型可以灵活扩展字段,适配业务场景。
客观短板:MongoDB 不适合强事务、多表复杂关联的交易场景。
主动过渡话术:如果是订单类核心业务系统,MySQL 会更加合适,事实上我对 Mysql的学习更加深入(有服务化数据库开发经验),我近期系统学习 InnoDB MVCC、锁机制、索引优化等内容,能够胜任关系型数据库相关的开发与调优工作。
🤖 CMDB-Agent 自然语言问数项目|AI工程化实战(核心加分项)
项目定位:基于阿里开源 Spring AI Alibaba DataAgent 二次开发,面向运维资产场景打造的轻量化智能问数 Agent。解决传统 CMDB 查数门槛高、需写SQL、依赖运维经验的痛点,支持用户通过自然语言直接查询、分析、统计运维资产数据,实现「自然语言→智能解析→精准查数→结果反馈」的端到端智能化能力,是传统运维平台向 AI 智能化升级的核心落地项目。我全程负责Java后端工程改造、Agent工作流架构优化、RAG检索定制、流式交互、模型热更新等核心AI工程化落地工作,重点深耕Java与AI结合的工程实战,而非单纯算法调优。
迭代经历
- 1.0:基于开源项目的 workflow 编排实现问数,基于 SpringAiAlibaba 的 Graph 实现问数过程的拆解实现;已上线。
- 2.0:基于内部 Agent 通用开发平台重构,平台集成会话管理、工具编排、模型配置等能力,快速“配置”实现问数Agent;开发中。
- 3.0:自己 vibe coding 实现一个问数demo,要包含 harness 的基本实现,“手搓”会话管理、工具编排、失败重试等能力;设计中。
3分钟标准项目口述文稿(以下内容来自问数 1.0,以及问答 Q1~Q11 都来自问数 1.0)
CMDB-Agent 智能问数项目,是我基于 Spring AI Alibaba 开源 DataAgent 二次迭代的运维领域智能化项目,核心目标是降低集团CMDB资产数据的查询门槛,让无SQL基础、无运维经验的员工,可通过自然语言直接查询、统计、分析上万台设备的资产数据,实现运维查数智能化、轻量化。
项目核心难点在于,大模型原生输出具有概率性、不可控性,直接端到端调用极易出现 SQL 错误、语义偏差、脱离运维业务场景的问题;同时通用RAG检索无法适配运维资产枚举类、术语类专属场景,存在漏召回、语义稀释的痛点。针对这些问题,我聚焦AI工程化落地、Java架构适配、场景化能力优化,完成了五大核心技术改造,全程规避纯算法短板,突出后端工程落地能力。
第一,我基于 Spring AI Graph 搭建DAG 原子化工作流架构,彻底解决大模型输出不可控问题。将原本端到端的问答流程,拆解为意图识别、证据召回、查询增强、SQL规划、SQL生成、语义校验、数据执行、报告生成等独立原子节点;每个节点通过结构化Prompt、固定输出格式、Java代码约束压缩模型自由度,同时配置Dispatcher动态路由,实现SQL失败重生成、规划不合理重试的容错闭环,极大提升问答准确率与系统可控性。同时支持分支、循环、故障重试,相比传统线性流程稳定性大幅提升。
第二,实现全链路流式交互架构,优化用户体验与实时反馈能力。基于 Spring WebFlux 响应式编程,贯通从LLM流式响应、Graph节点流式执行到SSE前端推送的完整链路,所有节点均以Flux为输出载体;同时自定义文本类型状态机,区分SQL、Markdown、推理日志、表格数据四类流式内容,前端差异化渲染展示。并且实现线程安全的主动断连、资源释放机制,避免流式请求堆积造成的内存泄漏。
第三,落地模型懒加载+无重启热切换工程能力,解决传统模型配置更新需重启服务的痛点。通过Spring AOP动态代理、自定义TargetSource实现Embedding模型动态委托,结合FactoryBean模式封装流式/阻塞式模型调用策略;通过比对数据库配置更新时间,结合ConcurrentHashMap缓存实现ChatClient懒加载、自动淘汰,支持DeepSeek、Qwen等多模型动态切换,无需重启服务,大幅提升运维迭代效率。
第四,定制运维场景专属混合RAG检索架构,解决通用检索适配性差、漏召回问题。采用向量检索+关键词检索并行执行、RRF算法融合的混合检索方案,同时针对运维业务特性,将知识库拆分为实体枚举类、规则映射类两类数据,差异化配置召回参数与检索策略;通过LLM提取实体关键词,联动数据库精准模糊匹配,解决向量语义稀释导致的枚举值漏召回、术语不匹配问题,大幅提升查询语义匹配度。
第五,设计轻量级多轮对话管理机制,实现会话无缝续接与低损耗交互。采用内存滑动窗口机制缓存核心对话摘要,不冗余存储完整上下文,降低Token消耗;冷启动时自动从数据库恢复历史会话状态,将上下文信息注入各节点Prompt,保障多轮问答的语义连贯性,同时规避大模型上下文过长导致的性能衰减与成本问题。
整体项目完全聚焦后端AI工程化落地,不依赖算法调优,通过Java架构优化、工作流约束、检索策略定制、流式能力改造,实现了大模型在企业运维场景的稳定、可控、可用落地,补齐了我Java高阶工程、AI应用开发、响应式编程、动态代理架构的实战经验,也是我区别于普通CRUD开发的核心技术亮点。Q1:你为什么要基于Graph图流程拆解问答链路?核心解决大模型什么问题?
核心思想:大模型的输出本质上是概率性的,在传统做法中直接让模型”端到端”回答问题风险极大。本项目用有向图(DAG)的方式将整个问答流程拆解为多个原子节点,每个节点只让模型做一件事,并通过精心设计的 Prompt 约束和上下游结构化输出将模型的自由度压缩到最小;同时搭配Dispatcher动态路由,形成完整容错闭环:SQL校验失败自动重生成、规划不合理触发重试、参数异常自动兜底,将大模型的不确定性,通过工程架构手段转化为可控、可观测、可修复的稳定能力,这也是企业级AI应用落地的核心关键。- 意图识别节点:模型只需输出”数据分析请求 or 闲聊”,输出空间极小
- 规划器节点:模型只能输出严格定义的
PlanJSON 结构(BeanOutputConverter约束) - SQL 生成节点:模型只负责生成 SQL,不负责其他任何判断
- 语义一致性节点:专门的 LLM 调用作为 SQL 的”审计员”,只输出”通过/不通过”
- 每个节点配有对应的
Dispatcher(边路由),根据节点输出决定下一步走向,形成闭环的错误修复回路(SQL 失败 → 重新生成,计划不合理 → 人工反馈 → 重新规划)
Q2:讲讲你全链路流式输出的技术实现,用到了哪些核心技术?
整套流式架构基于Spring WebFlux + Reactor + SSE实现,是典型的响应式编程工程落地,贯穿整个Graph工作流:- 底层载体:所有Graph节点的执行结果均以Flux流式数据流返回,替代传统阻塞式返回,支持逐Chunk输出;
- 中转推送:通过Sinks.Many背压感知管道订阅全链路流式输出,统一封装SSE协议推送前端,实现实时流式反馈;
- 内容差异化渲染:自定义TextType状态机,在流中嵌入内容类型标记,区分SQL代码、推理日志、Markdown报告、表格数据,前端针对性做高亮、折叠、展示处理;
- 资源安全管控:基于AtomicBoolean原子标记实现线程安全的流中止机制,前端断开连接时,自动释放Disposable资源、终止任务执行,彻底解决流式请求堆积、内存泄漏问题。
整套方案的亮点是全链路流式贯通,并非单一接口流式,而是每个AI执行节点都支持实时输出,用户体验和系统实时性远超普通单次返回的AI应用。
Q3:模型热切换、懒加载的核心实现思路是什么?解决了什么业务痛点?
传统AI项目模型Bean均为容器启动时初始化,模型配置修改、模型切换必须重启服务,迭代效率低、可用性差。我通过Spring高阶特性实现无重启热更新、按需懒加载:- Embedding模型动态代理:使用Spring AOP ProxyFactory + 动态TargetSource(非静态代理),容器中的代理Bean每次被调用,都会动态委托给注册器获取最新模型实例,实现无感热切换;
- ChatClient懒加载缓存:基于「模型名称+思考模式」组合键,通过ConcurrentHashMap实现懒加载,首次调用动态构建实例,后续复用;同时比对数据库配置updateTime,自动淘汰过期缓存,无需重启;
- 多模式统一封装:通过FactoryBean工厂模式,统一封装流式、阻塞式两种调用策略,业务层无需感知底层实现,按需切换。
核心价值:实现模型配置、模型版本、调用模式的线上无重启更新,大幅提升AI应用的迭代效率与生产环境可用性。
Q4:你的RAG混合检索架构和普通向量检索有什么区别?解决了什么独有场景问题?
通用单向量检索存在严重的语义稀释、枚举值漏召回、业务术语不匹配问题,完全无法适配运维资产场景,我针对性设计了分层混合检索架构:- 并行融合检索:采用模板方法模式,通过CompletableFuture实现向量检索、关键词检索并行执行,再通过RRF算法融合排序结果,兼顾语义匹配和精准词条匹配;
- 场景化知识分层:将运维知识库拆分为两类核心数据,差异化适配:一是实体枚举类(系统名、设备类型),调低相似度阈值、增大TopK,避免精准枚举被语义结果挤压;二是规则映射类(术语翻译、概念规范),精准匹配语义,优化查询规范性;
- 兜底增强检索:通过LLM提取用户问题中的实体关键词,联动数据库模糊匹配,解决向量检索无法精准匹配枚举值的短板;
- 多后端适配:基于条件注解实现Milvus、本地向量存储的动态切换,适配开发、生产不同环境。
最终实现用户自然语言,精准转化为CMDB运维标准查询口径,从根源提升SQL生成准确率。
Q5:多轮对话你是怎么设计的?为什么不直接用大模型原生上下文?
大模型原生上下文直接累加对话,会导致Token消耗激增、推理速度变慢、冗余信息干扰查询精度,我设计了轻量级低损耗会话管理方案:- 精简存储策略:不存储完整对话内容,仅持久化「用户问题+AI规划摘要」核心数据,极大降低存储与Token消耗;
- 滑动窗口机制:内存中仅保留最近N轮有效会话,自动淘汰过期上下文,避免对话无限累积;
- 冷启动恢复:客户端重新连接时,自动从数据库加载历史会话摘要,重建上下文状态,实现跨请求无缝续聊;
- 精准上下文注入:仅将有效会话信息注入各节点Prompt,辅助模型理解多轮语义关联,兼顾准确率与性能。
这套方案相比原生上下文,更低成本、更高性能、更适配企业级长期会话问答场景。
Q6:讲讲你项目中的容错、降级、修复机制,体现工程稳定性思维
我在项目中搭建了多层级容错自愈体系,彻底解决AI问答随机性高、易出错的问题:- 节点级重试:SQL生成失败、语义校验不通过时,自动触发节点重试,更新Prompt约束重新生成,最多支持3次迭代修复;
- 人工介入兜底:内置Human-in-the-Loop可中断节点,复杂场景自动暂停流程,等待用户审核规划方案,支持人工修正后恢复执行,避免错误扩散;
- 链路级降级:模型调用超时、网络异常时,自动降级为基础查询模式,保障核心查数功能可用;
- 上下文回滚:人工拒绝方案或重试失败时,通过上下文管理器回滚上一轮会话状态,避免脏数据影响后续问答。
整套容错机制让AI应用从“偶尔可用”变成“企业级稳定可用”,是核心工程亮点。
Q7:这个项目最大的技术难点是什么?你是怎么攻克的?
最大难点不是调用AI接口,而是如何通过Java工程手段,约束大模型概率性输出,适配严苛的企业运维场景。通用AI模型自由度过高,极易输出错误SQL、脱离业务口径、生成不符合CMDB资产规范的查询语句,随机性强、生产可用性极低,无法直接落地企业真实业务场景。
我的解决方案是「架构限流+代码约束+数据增强」三层工程闭环方案,完全靠后端架构手段压制模型不确定性:- 架构层拆解限流:通过Spring AI Graph把端到端黑盒流程拆分为单一职责原子节点,每一步只允许模型做一件事,从流程维度大幅降低出错概率;
- 代码&Prompt强约束:每个节点定义严格的结构化输出规范,通过Java实体类、转换器强制校验输出格式,非法结果直接拦截重跑,杜绝不规范输出;
- 业务数据前置增强:通过定制混合RAG、运维术语映射、实体精准匹配,给模型注入标准业务口径,让模型“懂运维、懂CMDB资产规则”,从输入源头减少语义偏差;
- 容错自愈兜底:搭配节点重试、SQL语义审计、上下文回滚机制,模型出错可自动修复,形成完整工程闭环。
简单来说,我没有依赖模型本身的能力提升,而是通过Java工程架构治理,把不可控的大模型能力,改造为企业可落地、可审计、可自愈的标准化业务能力,这也是AI应用工程落地的核心难点与核心价值。
Q8:你项目中用到了Spring AI Graph,讲讲DAG工作流的核心优势,和普通串行调用有什么区别?
传统AI应用基本都是串行线性调用,流程固化、无容错、无分支、不可控,一旦某一步出错直接整体失败,且无法针对性优化单节点能力。而我基于Spring AI Graph实现的DAG有向无环工作流,是企业级Agent落地的核心架构升级,核心优势有四点:- 职责原子化、可迭代优化:将复杂问答链路拆分为独立节点,意图识别、检索、规划、SQL生成、校验各司其职,可单独优化某一个节点逻辑、Prompt、策略,不影响整体流程,迭代性极强;
- 动态路由、支持分支与循环:通过Dispatcher分发器实现智能路由,支持条件分支、失败重试、循环修复,比如SQL校验失败自动回退重生成、规划不合理重新召回知识,串行流程完全无法实现这类自愈能力;
- 全链路可观测、可定位:每个节点独立执行、独立日志、独立状态,出错可以精准定位是召回问题、规划问题还是SQL生成问题,彻底解决大模型黑盒问题;
- 多模式适配、灵活性极高:基于同一套DAG图,通过路由配置切换纯SQL生成、完整分析、人工审核三种模式,适配不同业务场景,一套架构支撑多类问答需求。
总结:串行调用是“一次性黑盒调用”,而Graph DAG是可管控、可自愈、可迭代、可扩展的工程化AI工作流。
Q9:谈谈你对AI Agent工程化的理解,你的项目和普通简单调用LLM的AI demo有什么本质区别?
市面上绝大多数AIdemo只是简单封装LLM接口,单次调用、无约束、无容错、无业务适配,只能做演示无法落地生产。而我的CMDB-Agent是完整企业级AI工程化落地,核心区别体现在工程稳定性、业务适配性、可运维性三个维度:- 从“黑盒调用”升级为“可控工作流”:通过DAG原子化拆解+结构化输出约束,解决大模型概率不可控问题,让AI输出可标准化、可校验、可修复;
- 从“通用能力”升级为“场景定制能力”:针对运维资产场景定制混合RAG、术语翻译、实体补全、枚举精准匹配,解决通用模型不懂业务、答非所问的问题;
- 从“一次性调用”升级为“可运维可迭代系统”:具备模型热切换、懒加载缓存、全链路流式、多轮会话管理、故障重试降级、人工兜底回路等工程能力,支持线上持续迭代、稳定运行;
- 纯后端工程落地、贴合企业技术栈:全程基于Java/Spring生态改造,不依赖算法调优,通过架构设计、代码约束、工程优化实现AI落地,贴合后端开发的技术深耕方向,适配企业生产环境部署标准。
一句话总结:Demo解决“能不能跑”,我的项目解决能不能长期、稳定、准确、可运维地在企业生产环境落地。
Q10:项目还有哪些未深入、可优化的点?体现你的复盘思考能力
我目前核心深耕的是AI工程落地、Java架构改造、工作流约束、检索优化、流式交互,还有部分AI进阶能力尚未深入落地,也是后续优化方向,面试可坦诚说明、体现复盘思维:- MCP协议能力未深度落地:目前仅了解MCP工具调用规范,尚未实现Agent主动调用外部工具、主动拉取工单数据、主动校验资产状态的能力,后续可以基于MCP对接MOPS运维工单、监控接口,实现AI主动运维排查;
- 精细化文本切割与RAG优化未深挖:当前知识库以整体分类召回为主,未深入做细粒度切片、重叠度优化、召回权重精细化调优,后续可以通过自适应切片、语义分块进一步提升精准度;
- 人工反馈回路未完全闭环落地:目前仅实现人工暂停、审核、重规划的基础能力,尚未将人工反馈持续沉淀为知识库、自动优化Prompt与检索策略,缺少数据闭环迭代能力;
- 多级缓存体系待完善:可以新增热门问答、高频SQL缓存,减少重复模型调用与检索开销,进一步提升响应速度、降低推理成本。
整体来说,项目核心工程化能力已经落地成熟,进阶AI智能迭代能力是我后续重点深耕的方向。
Q11:如何评价问数效果?
- sql是否符合用户需求?
- sql执行成功率?
- 速度
Q12:问数 2.0 如何基于通用Agent开发平台的能力重构?
内网推出Agent开发平台,提供通用harness能力,通过配置Prompt,skills,tools,可以“低代码”、“统一”地实现Agent,(目前在做的事情,还未完整理解内网agent平台)Q13:问数 3.0 如何不用开源项目/通用平台/框架,手搓自研实现一套 Nl2SQL 系统?
预研脱离重型框架依赖的通用运维 Agent 方案,采用 Harness 会话管理 + Skill 工具化编排的设计思路,解耦业务能力与流程框架,支持快速扩展运维查询、工单处理等多类子 Agent 能力,形成差异化技术积累。(全手搓,还未开始)
⚡项目四:基于XXL-Job的高并发定时秒杀商城系统
原本设想用 ai coding 实现,来补充模拟电商的高并发场景的技术积累,后来评估暂时不做。
为什么自己实现秒杀系统?
日常工作以内部运维平台开发为主,业务偏向资产配置、自动化流程,较少接触高并发、流量削峰场景。因此自主搭建极简秒杀系统,一方面实践 Redis、消息队列、并发控制等技术;另一方面持续探索 AI Coding 工作流,借助大模型完成需求拆解、接口设计、单元测试、问题调试,沉淀一套标准化开发流程,同时学习优秀开源项目架构思想,补齐高并发系统工程落地经验。
🛢️ 项目五:InfluxDB数据库服务化能力建设
不是主要的工作项目,不引入简历。
我主导了InfluxDB数据库的服务化改造,在数据库管控平台(DataMars)中,建设InfluxDB的“数据库即服务”(DBaaS)能力,解决手动运维数据库效率低、易出错的问题。基于开源Influxdb Cluster,从0到1负责InfluxDB实例的备份恢复、在线变配、节点迁移/重搭等核心功能的调研、设计和开发。
具体工作与关键技术细节
- 核心场景实现:
备份恢复:调研并采用 influx_inspect export 逻辑备份与OSS对象存储结合方案,实现了数据库级和实例级的备份恢复,并集成到平台的工作流引擎中。
在线变配:通过修改Kubernetes StatefulSet配置,实现了CPU、内存的原地变配,以及通过PVC(持久化存储声明)扩容实现存储空间的在线扩展。
节点迁移与数据一致性保障:这是项目最大挑战。我设计并实现了 “热分片截断-冷副本恢复”双阶段迁移方案。在数据持续高速写入(2000 points/s)的场景下,通过先截断热分片引导新数据写入新节点,再从健康节点同步历史数据,有效解决了开源工具在迁移过程中可能丢失增量数据的问题,确保了数据一致性。 - 技术架构:功能深度集成到平台的apiserver、bakserver和agent组件中,通过K8S Operator模式对InfluxDB集群的生命周期进行管理。
- 核心场景实现:
市场竞品对比与成果衡量
- 对标产品:这类数据库服务化平台在业界类似云和恩墨zCloud、腾讯云DBbridge等数据库管理平台,核心是实现数据库资源的池化、按需供给和全生命周期智能化管理。
- 我的成果:事实上基本没有用户,随着发现开源数据库的功能不完善,开发也中断了。
在面试中介绍时,你可以遵循“背景-行动-结果-对比”的结构:
• 开头总结:用一句话点明项目核心价值。
• 具体阐述:重点突出你如何解决关键问题,特别是高可用、数据一致性、自动化闭环方面的设计。
• 量化成果:用数据(如效率提升百分比、可靠性指标)证明你的贡献。
• 展现视野:通过提及竞品,表明你不仅埋头编码,更抬头看路,了解行业最佳实践。
Java SE
为什么用BigDecimal不用double/double计算出现什么问题? (中)
double会出现精度丢失的问题,计算机无法精确地表示小数, 所以做浮点数计算时会出现精度丢失问题.
BigDecimal底层是用字符串存储数字, 运算也是用字符串做加减乘除计算的, 所以它能做到精确计算.一般牵扯到金钱等精确计算,都使用Decimal。== 和 equals() 的区别 (高)
== 对于基本类型和引用类型的作用效果是不同的,对于基本数据类型来说,== 比较的是值;对于引用数据类型来说,== 比较的是对象的内存地址
equals() 方法存在两种使用情况。类没有重写 equals() 方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Object类equals()方法。类重写了 equals() 方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
String 中的 equals 方法是被重写过的,因为 Object 的 equals 方法是比较的对象的内存地址,而 String 的 equals 方法比较的是对象的值。String的不可变性 (中)
String类中包含一个数组, 储存数组的每一个字符: private final byte[] value;
final数组, 地址不能改变, 导致长度不能改变
private, 数组中的内容不能改变
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 或 StringBuilder 每次都会对 StringBuffer 或 StringBuilder 对象本身进行操作,而不是生成新的对象并改变对象引用。String和StringBuffer和StringBuilder区别 (高)
String:字符串变量,private final修饰,不可变!
StringBuilder:字符串变量(线程不安全,可变) 没有使用final 和 private 关键字修饰
StringBuffer:字符串变量(线程安全,可变) 没有使用 final和 private 关键字修饰(StringBuilder是StringBuffer的简易版,更快!)
为什么StringBuffer是线程安全的?因为 StringBuffer 的很多修改字符串的方法都加了 synchronized 例如 public synchronized StringBuffer append(String str) {},同一时刻只能有一个线程进入这个 synchronized 方法,因此多个线程并发修改同一个 StringBuffer 时能够保证操作的互斥性和数据一致性。HashMap (高)
- HashMap和Hashtabe的区别
只关注一点就行了, HashMap线程不安全, Hashtabe线程安全
Hashtabe的线程安全是大量使用synchronized加锁, 性能差, 已经不会使用了
并发环境下建议使用ConcurrentHashMap, 它的性能更好. - HashMap底层数据结构
1.7 HashMap 是哈希表, 数组 + 链表;
1.8 HashMap 是数组 + (链表 或 红黑树) - HashMap什么时候进行扩容
HashMap 默认的初始化大小为16, 默认负载因子为0.75.当hashmap中的元素个数超过数组大小*负载因子(loadFactor) 时,就会进行数组扩容.之后每次扩充,容量变为原来的 2 倍 - HashMap为什么要使用红黑树, 为啥不用平衡二叉树
当某个位置, Hash冲突严重, 则链表的长度会很长. 那么查找的时候依次比较, 效率会很低 O(n).
将链表转为红黑树, 因为红黑树是排序树, 查找效率一般是O(logN), 使用红黑树就提高了查找效率.
平衡二叉树追求绝对平衡,每次插入新节点之后需要旋转的次数不能预知, 自平衡效率低
红黑树放弃了追求完全平衡,追求大致平衡,在与平衡二叉树的时间复杂度相差不大的情况下,自平衡的效率高
什么时候会树化?要满足两个条件:链表长度超过树化阈值8,数组长度大于等于64 - 为何树化阈值为8
红黑树是为了防止链表超长时性能下降,树化应当是偶然情况.长度超过8的链表出现几率非常小, 选择8就是为了让树化几率足够小
hash表的查找, 更新的时间复杂度是O(1). 而红黑树的查找, 更新的时间复杂度是O(log2n), TreeNode占用空间也比普通Node的大,所以如非必要,尽量还是使用链表 - 索引如何计算? hashCode有了, 为何还有hash()方法? 数组容量为何是2^n?
索引计算方式:对任何一个对象调用其hashCode()方法会获得其原始hash值.对原始hash值再调用HashMap的hash方法进行二次hash, 获取到二次hash值.二次hash值对数组容量进行取余操作获取到存放的数组下标.
为何需要二次hash?二次hash是为了让hash值分布更加均匀, 减少hash冲突, 从而使链表更短, 因此提升了查找效率.
数组容量为何是2^n?计算索引时,如果是2的n次方可以使用位与运算代替取模运算, 效率更高。数组容量为质数会使hash值分布均匀, 但是2^n计算索引的效率更高.
index = hash % capacity = hash & (capacity - 1) - HashMap的put()方法流程
- HashMap 是懒惰创建数组的,首次使用才创建数组
- 调用hashcode, 然后再调用hash(), 二次hash来计算索引(桶下标)
- 如果桶下标还没人占用,创建Node放入数据后返回
- 如果桶下标已经有人占用:1. 已经是TreeNode走红黑树的添加或更新逻辑;2. 是普通Node,走链表的添加或更新逻辑. 1.7是头插法, 1.8是尾插法;如果链表长度超过树化阈值,走树化逻辑(1.8才有树化)
- 返回前检查容量是否超过阈值, 一旦超过进行扩容;扩容时, 先将新的数据放进数组, 然后创建新的数组, 再将旧数组元素迁移到新数组
- HashMap和Hashtabe的区别
多线程操作HashMap会出现什么问题?
知道hashMap是线程不安全即可. 具体并发性会出现什么问题了解即可.
扩容死链 (1.7)
扩容的时候线程切换, 两个线程都要进行扩容.因为1.7是头插法, 进行数组扩容的时候, 需要链表迁移. 在并发环境下, 会出现循环链表, 造成扩容死链问题.
数据错乱 (1.7, 1.8)
??ConcurrentHashMap (高)
ConcurrentHashMap是HashMap的升级版,区别是线程安全。
Hashtable也是线程安全的,整个Hashtable对应一把锁,同一时刻,只能有一个线程操作它, 并发性低
1.7的ConcurrentHashMap使用分段锁, 也就是Segment+HashEntry数组+链表的结构,相当于将数组切分为多个 Segment. 每个Segment对应一把锁,如果多个线程访问不同的Segment, 则不会冲突.
1.8开始 ConcurrentHashMap将链表的每个头节点或者红黑树的根节点作为锁(锁桶),如果多个线程访问的头节点不同,则不会冲突. 也就是说, 只要没有hash冲突, 多个线程就可以同时访问ConcurrentHashMap.ConcurrentHashMap 是如何保证并发安全的?
JDK1.7中ConcurrentHashMap是通过ReentrantLock+CAS+分段思想来保证的并发安全的。ConcurrentHashMap的put方法会通过CAS的方式,把一个Segment对象存到Segment数组中一个Segment内部存在一个HashEntry数组,相当于分段的HashMap,Segment继承了ReentrantLock,每段put开始会加锁。
JDK1.8通过CAS+synchronized +锁桶的思想来保证并发安全的.它将每个桶(数组中的每个位置)作为独立的锁单位, 当操作不同的桶时,线程无需竞争同一把锁。锁的粒度更细了. 并发度更高。synchronized 锁头节点:插入或修改数据时,仅对当前桶的头节点加锁, 也就是只锁桶.JDK8 的 ConcurrentHashMap 和 JDK7 的 ConcurrentHashMap 有什么区别?
- JDK8中新增了红黑树
- JDK7中使用的是头插法,JDK8中使用的是尾插法
- JDK7中使用了分段锁,而JDK8中没有使用分段锁, 而是锁住链表或者红黑树的头结点.(锁的粒度更小) JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是数组的大小,并发度更大
- JDK7中使用了ReentrantLock,JDK8中没有使用ReentrantLock了,而使用了Synchronized
- JDK7中的扩容是每个Segment内部进行扩容,不会影响其他Segment,而JDK8中的扩容和HashMap的扩容类似,只不过支持了多线程扩容,并且保证了线程安全
面向对象的三大特征 (中)
- 封装
为了提高代码的安全性,隐藏对象的内部细节,封装将对象的内部状态(字段、属性)隐藏起来,并通过定义公共的方法(接口)来操作对象
外部代码只需要知道如何使⽤这些⽅法而无需了解内部实现 - 继承
允许一个类(子类)继承另⼀个类(父类)的属性和⽅法的机制
子类可以重用父类的代码,并且可以通过添加新的方法或修改(重写)已有的方法来扩展或改进功能
提高了代码的可重用性和可扩展性 - 多态
多态是指相同的操作或方法法可以在不同的对象上产生不同的行为,通过方法的重载和重写实现
多态允许以一致的方式式处理不同类型的对象,提高了代码的灵活性
子类其实是⼀种特殊的父类,因此Java允许把⼀个子类对象直接赋给⼀个父类引用变量,无须任何类型转换, 或者被称为向上转型,向上转型由系统自动完成。当把⼀个子类对象直接赋给父类引用变量时,例如 FatherObj o = new SonObj() , 这个 编译时类型是 FatherObj,而运行时类型是 SonObj,当运行时调其方法时,其方法行为实际是子类的行为, 也就是SonObj的行为.这就可能出现:相同类型的变量、调用同一个方法时出现不同的行为,这就是所谓的多态
- 封装
面向对象和面向过程的区别 (中)
面向过程: 直接将解决问题的步骤分析出来,然后用函数把步骤一步一步实现,然后再依次调用就可以了. 面向过程思想偏向于我们做一件事的流程,首先做什么,其次做什么,最后做什么。
面向对象: 将构成问题的事物,分解成若干个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在解决问题过程中的行为。 需要完成什么事情, 直接让某个对象来干即可.方法的重载和重写有什么区别 (中)
- 重载方法法的重载指的是在同⼀个类中,方法名相同但参数列表不同
- 重写是在子类中重新定义⽗类中已有的方法,方法名和参数列表必须相同
反射的基本思想 (中) ??
- 反射机制是在运行时,能够动态获取类的所有属性和方法;动态调用对象任意方法, 动态的创建对象
但是反射需要在运行时动态解析类、方法、字段的元数据信息, 性能较差 - 反射特性:
运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现 的接口、构造函数、方法和字段等。
动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过 Class类的newlnstance()方法或Constructor对象的 newlnstance()方法实现的。
动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过 Field类的get()和set()方法完成的。 - 你平时什么时候会用到反射 (中)
当要从配置文件中读配置创建类对象时需要使用反射.
配置文件配置某个类的全类名, 然后就需要读配置, 然后反射创建对象.
使用工厂模式时, 往往也需要根据全类名来获取对象, 也会使用反射创建对象.
当开发注解时, 往往需要反射来获取某个类/字段的注解
当使用spring, mybatis时, 这些框架底层会大量使用反射
Spring 的IoC机制:会通过反射实例化 Bean、注入依赖
Spring的AOP用到了动态代理, 也是大量使用反射
MyBatis 的 Mapper 接口动态代理:反射生成接口的代理对象,执行 SQL 映射方法
- 反射机制是在运行时,能够动态获取类的所有属性和方法;动态调用对象任意方法, 动态的创建对象
JUC
什么是线程不安全?并发场景下会引发哪三类核心问题?
线程不安全的本质是:多个线程并发访问同一份共享资源时,操作不满足原子性、可见性、有序性,最终导致数据结果不符合预期。
1、原子性:复合操作(如i++)中途会被打断,造成更新丢失
2、可见性:一个线程修改了变量,其他线程因CPU缓存看不到最新值
3、有序性:编译器/CPU重排指令,多线程场景下逻辑出错什么是死锁?产生死锁的四个必要条件是什么?常见的避免手段有哪些?
死锁是指两个或多个线程互相持有对方需要的锁,又都永久等待对方释放,程序卡住无法推进。
四个必要条件:互斥、请求并保持、不可剥夺、循环等待。
常见避免手段:统一加锁顺序(破坏循环等待)、使用超时锁(破坏请求并保持)、主动释放锁。Java的锁按核心思想分哪两大类?本质区别是什么?分别适用于什么场景?
- 悲观锁:默认并发冲突一定会发生,操作前必须先加锁,同一时间只能一个线程操作。适合写多、冲突频繁的场景。
- 乐观锁:默认并发冲突很少,全程不加锁,提交更新时校验数据有没有被修改。适合读多、冲突少的场景。
synchronized 有哪三种用法?分别锁什么对象?作用范围有什么区别?
本质都是锁对象(对象是共享资源的载体),区别只在于锁的对象不同,互斥范围不同。写法 锁的对象 作用范围 普通同步方法 当前实例 this同一个实例的所有同步方法互斥,不同实例互不影响 静态同步方法 类的 Class对象(全类唯一)所有静态同步方法互斥,和实例方法完全不冲突(是两个锁);适合用于操作静态对象 同步代码块 括号内指定的任意对象 只包裹代码块内逻辑,粒度最细,可多资源独立加锁 1
2
3synchronied void add() {
// 语法糖,本质上是 synchronied(this) {}
}1
2
3synchronied(lockA) {
// 同步代码块,粒度最细
}synchronized 的锁升级过程是怎样的?每一级分别有什么特点?
- 无锁:对象刚创建,对象头存哈希码、分代年龄等基础信息;锁只能升级、不能降级
- 偏向锁:单线程首次获取锁,对象头直接记录线程ID,下次同线程进入无需CAS,几乎零开销
- 轻量级锁:出现多线程竞争,偏向锁撤销,线程用CAS自旋抢对象头的锁标记,线程不阻塞,开销小
- 重量级锁:自旋次数超限或竞争激烈,升级为Monitor管理,线程挂起阻塞,交给操作系统调度,开销大
重量级锁 Monitor 的完整获取与释放流程是什么?它和 wait/notify 是什么关系?
Monitor内部有两套独立队列,分别对应抢锁和条件协作。- 正常抢锁流程(对应EntryList入口队列)
- 获取:线程先尝试占Monitor的
_owner标记,成功则执行;重入则计数器+1;失败则进入EntryList阻塞 - 释放:退出同步块时计数器-1,减到0则释放锁,自动唤醒EntryList队头线程;被唤醒线程重新参与竞争
- 获取:线程先尝试占Monitor的
- 条件协作流程(对应WaitSet等待集 + wait/notify)
wait():持有锁的线程主动释放锁,进入WaitSet休眠,等待业务条件满足notify():从WaitSet随机唤醒一个线程,它会先进入EntryList重新排队抢锁,不是直接执行- 两者是完全独立的机制:EntryList存等锁的线程,WaitSet存等条件的线程。
- 正常抢锁流程(对应EntryList入口队列)
synchronized 是公平锁吗?为什么?
不是公平锁。
偏向锁、轻量级锁阶段都是谁抢到是谁的;重量级锁释放时,新进来的线程可以直接插队抢锁,不用排队,不保证先来先到。ReentrantLock 是什么?底层是怎么实现的?
ReentrantLock是JUC包下的显式可重入锁,纯Java代码实现,底层基于AQS(抽象队列同步器),需要手动调用lock()加锁、unlock()释放锁,支持公平/非公平两种模式。更加灵活。AQS(抽象队列同步器)的核心原理是什么?
AQS是JUC几乎所有锁和同步工具的通用底层模板。
核心两大组件:- 一个
volatile修饰的state状态变量:标记锁的占用状态和重入次数 - 一个CLH双向等待队列:抢锁失败的线程封装成节点,排到队尾阻塞等待
上层锁只需要定义「抢锁、释放锁」的规则,排队、阻塞、唤醒等通用逻辑全部由AQS实现。
- 一个
ReentrantLock 和 synchronized 的核心区别是什么?
维度 synchronized ReentrantLock 实现层面 JVM内置关键字,C++底层实现 JDK纯Java代码实现,基于AQS 使用方式 自动加锁、自动释放 手动 lock()/unlock(),必须写在finally里公平性 仅非公平锁 支持公平/非公平锁切换 功能特性 仅基础互斥 支持超时抢锁、可中断抢锁、绑定多个Condition条件 性能 低竞争下更优 高竞争下性能更稳定可控 公平锁和非公平锁的区别是什么?分别有什么优缺点?
- 非公平锁(默认):线程进来先直接CAS抢锁,抢不到再排队。优点:吞吐量高;缺点:可能出现线程饥饿
- 公平锁:所有线程一律先排队,严格先来先到。优点:绝对公平;缺点:吞吐量稍低
volatile 关键字的两个核心作用是什么?
- 保证可见性:写操作立即强制刷回主内存,读操作直接从主内存读取,跳过CPU本地缓存
- 保证有序性:禁止编译器和CPU对指令进行重排序,保证执行顺序和代码书写顺序一致
为什么 volatile 能保证可见性,但不能保证原子性?举一个简单的例子说明。
volatile只保证单步读、写操作的可见性,但复合操作(如i++的读-改-写三步)中间会被其他线程打断,而原子性要求整个操作全程不可分割,volatile做不到。
例子:两个线程同时对volatile修饰的count各执行1000次count++,最终结果一定小于2000,因为三步操作中间会插入其他线程,造成更新丢失。CAS 是什么?它的执行逻辑是怎样的?有哪些优缺点?
- CAS全称Compare And Swap(比较并交换),是CPU硬件级原子指令,是乐观锁的核心实现。
执行逻辑:包含三个要素——内存地址V、预期旧值A、新值B。只有V的当前值等于A时,才将V更新为B并返回成功;否则返回失败。最关键的是:“比较”和“修改”是一个不可分割的原子操作。1
2
3
4
5
6if (count == 0) {
count = 1;
}
// 以上操作在有了 cas 后是一个原子操作
boolean success = atomicInteger.compareAndSet(0, 1);
// 只有一个线程可以拿到 success == true - 优点:全程不加锁,并发度高,开销小;
- 缺点:存在ABA问题、竞争激烈时自旋浪费CPU、只能保证单个变量的原子性。
- 那为什么 CAS 并发高了会有问题?失效了?
注意:CAS 不会“失效”。而是:竞争越激烈,CAS 失败的概率可能越高,于是重试次数增加。
对于 CAS,如果竞争的进程多了,最终只有一个成功,其他都失败,可能造成大量的失败重试,占用资源
对于锁,竞争失败后虽然排队,但:每个人基本只需要执行一次真正的修改。锁的优势是“冲突严重时可以让竞争者等待,而不是不停重试”。
- CAS全称Compare And Swap(比较并交换),是CPU硬件级原子指令,是乐观锁的核心实现。
CAS 已经保证了操作的原子性,为什么锁的状态变量还要搭配 volatile 使用?
- CAS保证「比较+交换」这个动作本身不可分割,管改的过程不被抢
- volatile保证所有线程读到的状态值是主存最新值,管看到的值不过期
两者解决完全不同的问题,缺一不可;缺了volatile,线程读到的是本地缓存的过期值,CAS比较的基准从一开始就错了,会直接导致锁失效。Java中所有用CAS的并发变量,本质都是「volatile + CAS」的组合。
Java 创建线程有哪几种常用方式?
- 继承
Thread类,重写run()方法(本质上就这一种) - 实现
Runnable接口,重写run()方法,传给Thread对象执行 - 实现
Callable接口,配合FutureTask,可获取任务返回值和异常
- 继承
线程的 start() 方法和 run() 方法有什么本质区别?
start():真正启动新线程,让线程进入就绪状态等待CPU调度,调度到后自动执行run()方法run():只是普通的方法调用,直接在当前线程执行方法体,不会开启新线程
线程的生命周期有哪几种状态?之间是怎么流转的?
- 新建(New):线程对象创建,还未调用start()
- 就绪(Runnable):调用start()后,等待CPU调度
- 阻塞(Blocked):等待锁时进入该状态
- 等待(Waiting):调用wait()、join()等方法,无限等待
- 超时等待(Timed Waiting):调用sleep()、超时wait等,有时长限制的等待
- 终止(Terminated):任务执行完成或异常退出
流转:新建→就绪→运行→(阻塞/等待/超时等待)→就绪→终止。
为什么要使用线程池?线程池的七大核心参数是什么?
- 使用线程池的好处:复用线程减少创建销毁开销、控制并发数避免资源耗尽、统一管理线程生命周期。
- 七大核心参数:核心线程数、最大线程数、空闲线程存活时间、时间单位、工作队列、线程工厂、拒绝策略。核心线程:默认长期存活,即使空闲也不会被回收,优先执行新任务
1
2
3
4
5
6
7ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
非核心线程:队列满了才会创建,空闲超过存活时间会被回收,用来应对突发流量高峰 - 线程池线程数如何设置?
CPU密集型: 任务需要大量计算, 很少阻塞, CPU一直处于忙碌状态. CPU核数 + 1
IO密集型: 任务需要频繁的IO操作(与磁盘, 网络交互), CPU经常等待IO完成. CPU核数 * 2
提交一个新任务到线程池时,具体的执行流程 / 线程池的工作流程 (高)
- 当我们提交任务,线程池会根据corePoolSize大小创建若干任务数量线程执行任务
- 当任务的数量超过corePoolSize数量,后续的任务将会进入阻塞队列阻塞排队
- 当阻塞队列也满了之后,那么将会继续创建(maximumPoolSizecorePoolSize)个数量的线程来 执行任务,如果任务处理完成,maximumPoolSize-corePoolSize额外创建的线程等待keepAliveTime之后被自动销毁
- 如果达到maximumPoolSize,阻塞队列还是满的状态,那么将根据不同的拒绝策略对应处理
线程池有哪四种默认拒绝策略?分别适用于什么场景?
- AbortPolicy(默认):直接抛出异常。适用于核心交易链路,失败明确可感知
- CallerRunsPolicy:提交任务的线程自己执行任务,起到反压降速作用。适用于后台批量任务,任务不能丢
- DiscardPolicy:静默丢弃新任务。适用于非核心埋点、日志,丢失不影响主流程
- DiscardOldestPolicy:丢弃队列里最老的任务,加入新任务。适用于状态更新类场景,旧任务可被新任务覆盖
常见的四种默认线程池是什么?分别有什么潜在问题?
- FixedThreadPool:固定线程数,无界队列,高并发下任务堆积可能引发OOM
- CachedThreadPool:缓存线程池,无界最大线程数,高并发下创建大量线程耗尽系统资源
- SingleThreadExecutor:单线程池,无界队列,同样存在任务堆积OOM风险
- ScheduledThreadPool:定时线程池,无界队列,任务堆积风险
生产环境推荐自定义线程池,不直接使用JDK默认实现。
JVM
内存结构
垃圾回收
类加载
MySql
数据库存储引擎有哪些 (高)
- innodb
事务, 外键, 行级锁
适合事务要求高, 数据完整性高的场景
InnoDB 支持数据库异常崩溃后的安全恢复, 依赖于redo log ,而MyISAM不支持
innodb支持MVCC, MyISAM不支持 - MyISAM
全表锁, 不支持事务, 不支持外键, 并发性低
适合对事务要求不高, 数据完整性要求不高, 并发性要求不高的场景 - memory
全表锁
数据存储在内存中, 默认使用hash索引, 检索速度非常高.
适合做缓存 (被redis代替)
- innodb
MySQL中的索引类型 (高)
- 逻辑维度
- 主键索引: 针对表主键的索引, 默认创建, 只能有一个
- 唯一索引: 避免一个表中的索引重复, 可以多个
- 常规索引: 快速定位数据, 可以多个
- 前缀索引: 在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度. 但是数值类型不 能指定长度
- 联合索引: 多个列组合的索引 (最左前缀匹配原则, 索引下推, 避免回表, select *)
- 全文索引: 查找文本中的关键词. 像es一样. 可以多个
- innodb中根据索引的物理存储形式, 又可以分为两种
- 聚集索引: 一般主键索引就是聚集索引, 且只有一个. 索引的叶子节点是id, id下挂了行数据.
- 二级索引: 索引的叶子节点是该列的值, 下面挂了id
如果走二级索引, 那么就先从二级索引中拿到id, 再根据id从聚集索引中查行数据. 这个过程叫回表, 一般要避免回表(不要使用select *).
- 逻辑维度
为什么InnoDB存储引擎选择使用B+树索引结构?
相对于二叉树,层级更少,搜索效率高;
B树无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低;
相对Hash索引,B+树支持范围匹配及排序操作;什么是覆盖索引/什么是回表 (高)
索引的使用原则/索引失效场景 (高)
explain执行计划 (高)
发现查询速度很慢,怎么解决 (高)
什么是数据库事务/事务四大特性 (高)
事务: 一系列sql语句, 要么全成功, 要么全失败.
原子性 (Atomicity): 事务是不可分割的最小单元, n个连续操作失败了一个, 前面的操作回滚 (要么都成功, 要么都失败);原子性通过undolog回滚来实现
一致性(Consistency): 执行事务前后,数据总量保持一致. 例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的;保证了其他三个特性, 一致性就自然实现
持久性 (Durability): 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的, 无法撤销;redolog来实现
隔离性 (Isolation): 多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离, 保证每个事务不受并发影响, 独立执行;mvcc+锁 配合undolog来实现隔离性产生的问题 (高)
- 脏读: 一个事务读取到另一个事务未提交的数据
1. 在事务A执行过程中,事务A对数据资源进行了修改,事务B读取了事务A修改后的数据。
2. 由于某些原因,事务A并没有完成提交,发生了RollBack操作,则事务B读取的数据就是脏数据。 - 不可重复读:
- 事务B读取了两次数据资源,在这两次读取的过程中事务A修改了数据,导致事务B在这两次读取出来的数据不一致。
- 这种在同一个事务中,前后两次读取的数据不一致的现象就是不可重复读(Nonrepeatable Read)。
- 幻读:
- 事务A按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了幻觉。(由于解决了不可重复读, 所以该事务读取不到别的事务已提交的数据)
- 幻读和不可重复读有些类似,但是幻读强调的是集合的增减,而不是单条数据的更新。(比如第一次读是有0条数据, 但是第二次读却有了1条数据).
不可重复读和幻读区别: 不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了
- 脏读: 一个事务读取到另一个事务未提交的数据
事务的隔离级别 (高)
- 为了解决以上的问题,主流的关系型数据库都会提供四种事务的隔离级别。事务隔离级别从低到高分别是:读未提交、读已提交、可重复读、串行化。
事务隔离级别等级越高,越能保证数据的一致性和完整性,但是执行效率也越低。
所以在设置数据库的事务隔离级别时需要做一下权衡,MySQL默认是可重复读的级别。 - 读未提交
读未提交(Read Uncommitted),是最低的隔离级别,所有的事务都可以看到其他未提交的事务的执行结果。不能解决脏读,可重复读,幻读,所以很少应用于实际项目。 - 读已提交
读已提交(Read Committed), 在该隔离级别下,一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。
可以防止脏读,但是不能解决可重复读和幻读的问题。 - 可重复读 (mysql默认隔离级别)
可重复读(Repeatable Read),MySQL默认的隔离级别。
可重复读是快照读, 在该隔离级别下,一个事务多次读同一个数据, 实际上读的是数据快照, 其他事务修改数据在当前事务是不可见的, 这样就可以保证在同一个事务内两次读到的数据是一样的。
可以防止脏读、不可重复读、第一类更新丢失、第二类更新丢失的问题,不过还是会出现幻读。 - 串行化
串行化(Serializable),这是最高的隔离级别。
它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行(会阻塞)。
在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常不会用这个隔离级别
- 为了解决以上的问题,主流的关系型数据库都会提供四种事务的隔离级别。事务隔离级别从低到高分别是:读未提交、读已提交、可重复读、串行化。
什么是 MVCC (中)
MVCC, 多版本并发控制
指维护一个数据的多个版本,使得读写操作没有冲突, 具体实现就是快照读, 快照读为MySQL实现MVCC提供了一个非阻塞读功能
MVCC的具体实现,还需要依赖于数据库记录中的隐式字段、undolog日志、readView。三大日志 (高)
- undo log(回滚日志):主要用于事务回滚和 MVCC, 实现了事务中的原子性
Undo Log(回滚日志)记录了事务操作前的数据状态,确保事务回滚时能恢复原始数据,并为并发事务提供数据的历史版本。
事务回滚(Rollback):当事务执行失败或显式调用 ROLLBACK 时,通过 Undo Log 将数据恢复到修改前的状态。
MVCC(多版本并发控制):提供数据的历史版本,使其他事务能读取到一致的快照(Read View),避免读写冲突。 - redo log(重做日志):主要用于掉电重启等故障恢复, 实现了事务中的持久性
redo log重做日志,记录的是事务提交时数据页的物理修改,是用来实现事务的持久性. 它让MySQL拥有了崩溃恢复能力. - binlog (归档日志/二进制日志):主要用于数据备份和主从复制;
binlog, 即二进制日志, 主要记录了对 MySQL 数据库执行了更改的所有操作(数据库执行的所有 DDL 和 DML 语句)
包括表结构变更(CREATE、ALTER、DROP TABLE…),表数据修改(INSERT、UPDATE、DELETE…),但不包括 SELECT、SHOW 这类不会对数据库造成更改的操作。
数据库的数据备份、主备、主从需要依靠binlog来同步数据,保证数据一致性。
- undo log(回滚日志):主要用于事务回滚和 MVCC, 实现了事务中的原子性
MQ
MQ有什么作用/为什么使用MQ (高)
- 消息队列的本质其实就是一个阻塞队列, 只是在阻塞队列的基础上增加了重试, 消息持久化等等功能.
- 优点就是: 异步 削峰 解耦
- 缺点就是:
- 系统的可用性降低了: 新引入了MQ, 那么如果MQ挂了, 和MQ相关的服务就崩溃了
- 系统复杂度提高了: 硬生生加个 MQ 进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况? 怎么保证消息传递的顺序性?问题一大堆
- 数据一致性问题: A 系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是 BCD 三个系统那里,BD两个系统写库成功了,结果 C 系统写库失败了,咋整?你这数就不一致 了
如何保证消息可靠性 (高)
- 消息丢失的几种情况
- 发送时丢失
生产者发送消息到交换机的过程中丢失了 (消息确认 confirm机制)
交换机分发消息到队列的过程中丢失了 (return机制) - MQ宕机
队列已经接收到了消息, 但未持久化, MQ宕机就会丢失消息(消息持久化以及MQ集群, 保证高可用) - 消费时丢失
消费者拿到消息后未消费消息就丢失了 (消费者手动ACK)
- 发送时丢失
- confirm机制, 发送者确认
消息成功投递到交换机,返回ack
消息未投递到交换机,返回nack
在生产者那里设置开启confirm模 式之后,你每次写的消息都会分 配一个唯一的 id,然后如果写入了 RabbitMQ 中,RabbitMQ 会给你回传一个ack消息,告诉你说这个消息 ok 了。如果 RabbitMQ 没 能处理这个消息,会回调你一个 nack接口,告诉你这个消息接收失败,你可以重试。 - return, 发送者回执
消息投递到交换机了,但是没有路由到队列。返回ACK,及路由失败原因。
消息成功从交换机路由到队列, 则不返回任何东西.
项目中配置ConfirmCallback和ReturnCallback. ConfirmCallback就是设置消息未成功投递到交换机, 要做什么, 比如记录日志之类的.ReturnCallback消息没有从交换机路由到队列时触发的回调, 可以记录一下日志. - 消息持久化: 开启 RabbitMQ 的持久化,就是消息写入之后会持久化到磁盘,哪怕是 RabbitMQ 自己挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢.
- 消费者ACK: 消费者确认机制,即:消费者处理消息后可以向MQ发送ack回执,MQ收到ack回执后才会删除该消息。
- 消息丢失的几种情况
死信队列? 如何导致死信 (中)
死信,顾名思义就是无法被消费的消息. 当一个队列中的消息满足下列情况之一时,可以成为死信(dead letter):
1、消息被拒绝消费
2、消息TTL到期,超时无人消费
3、要投递的队列消息堆积满了,最早的消息可能成为死信
如果该队列配置了dead-letter-exchange属性,指定了一个交换机,那么队列中的死信就会投递到这个交换机中, 而这个交换机称为死信交换机
死信队列是队列将死信(无法被消费的消息放到死信交换机中):死信队列可以像republish一样作为消息消费失败的兜底方案,在死信队列里面可以对我们的异常消息进行MySQL/Redis持久化,然后人工处理等操作;另一方面可以处理消息超时无人消费以及队列满了的问题消息的幂等性 (高)
- 网络不好的时候, A向队列发送一条消息, 消息发送成功, 队列将消息给B服务, 但是由于网络原因A服务一直未收到ack, 此时A服务重发了消息, 队列又将消息给B服务, 此时就出现了消息重复消费.
或者说MQ向B服务发送了消息, 凡是网络原因, B服务没有能向MQ发送ack, 所以将消息重复发送一遍, 产生了重复消费.
消息重复消费是无法避免的, 所以要做消息的幂等. 幂等性的处理方式如下: - 唯一约束
如果从MQ拿到数据是要存到数据库,那么可以根据数据创建唯一约束
同样的数据从MQ发送过来之后,当插入数据库的时候,会报违反唯一约束,不会插入成功的。
或者可以先查一次,是否在数据库中已经保存了,如果能查到,那就直接丢弃就好了(在高并发的情况下,有数据库写入
的瓶颈) - 消息唯一id
让生产者发送消息时,每条消息加一个全局的唯一id,然后消费时,将该id保存到redis里面
下次再消费时先去redis里面查一下有么有,没有再消费
- 网络不好的时候, A向队列发送一条消息, 消息发送成功, 队列将消息给B服务, 但是由于网络原因A服务一直未收到ack, 此时A服务重发了消息, 队列又将消息给B服务, 此时就出现了消息重复消费.
消息积压如何处理 (中)
- 当生产者发送消息的速度超过了消费者处理消息的速度, 或者如果消费者因为某些原因持续阻塞,就会导致队列中的消息堆积,直到队列存储消息达到上限。最早接收到的消息,可能就会成为死信,会被丢弃,这就是消息堆积问题
- 解决消息堆积
增加更多消费者,提高消费速度
提高单个消息者的处理能力, 在消费者内开启线程池加快消息处理速度
缺点: 消息太多就会开启太多新线程, cpu压力大 - 如果是bug导致几百万消息持续积压几小时。有如何处理呢?
- 先修复consumer消费者的bug,以确保其恢复消费速度,然后将现有consumer都停掉
- 新建一个topic, partition是原来的10倍,临时建立好原先10倍的queue数量
- 然后写一个临时的分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入临时建立好的10倍数量的queue
- 接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时queue的数据
- 这种做法相当于是临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
- 等快速消费完积压数据之后,得恢复原先部署的架构,重新用原先的consumer机器来消费消息
消息的顺序性 (中)
kafka??
Redis
^^
hr
口头
- 打招呼:您好,我是蔡枫,有 2 年 Java 后端 / Agent 开发经验,主要负责企业内部平台研发、线上稳定性治理及 ToB 交付,近期重点参与 Agent 应用工程化实践。看到贵司岗位 JD 与我的经历比较匹配,方便的话附上简历,期待有机会进一步沟通。
- 看机会的原因:因为当前运维平台工作偏向存量系统的需求迭代与运维支撑,我的个人兴趣不在这上面,并且长期下来感觉到个人成长触到了天花板。希望能在新环境中,参与到具备业务深度、能够产生技术沉淀的场景,或者 AI 应用工程化这类新兴技术落地的场景。
- 薪资:目前总包 18w,期望 20k
- base:倾向广深,北京、上海也可以考虑。目前还没有确定长期定居哪个城市,现阶段愿意来xx工作生活,还是会更看重岗位和发展机会。
谈薪
- 背景:当前现金总包 18W(12.1K×14 薪 + 月度餐补 400 + 年度旅游补贴 5000),员工宿舍属于公司后勤福利,不计入总包基数,只可口头顺带提,不能算进年收入。尽量让对方先开薪资;如果要你先说,直接输出总包目标,不说单月底薪。
- 目标:月薪 20K,高薪大厂 / 上海岗位 30k;
- 口径:“目前总包大概 18W。下一份我希望更多根据岗位职责、职级和市场情况评估,不太希望简单按当前薪资涨幅计算。普通 Java 后端岗位的话,我目前期望税前月薪 20K 左右,具体可以结合整体 package 沟通。”
- HR 问:“为什么涨这么多?” ➡️ “我目前所在行业的薪资基数和互联网/科技岗位有一定差异,所以历史薪资对下一份岗位的参考意义有限。我更希望结合实际工作经验和岗位要求来评估。”
- HR 压价:“20K 能不能再降?” ➡️ “如果岗位和发展方向比较匹配,我薪资上可以保持一定弹性,具体可以等面试评价和岗位级别确定以后再沟通。”
反问
- 业务
- AI Coding / AI 赋能
2026 Interview
start 9.21 lets goooo 🤑🤑
评测 (核心是前后选择作答的优先级统一!)
情绪稳定 > 理性/责任 > 乐观/自信 > 善意/社交 > 抗压/适应 >
保守/谦虚 > 创新 > 好胜/果断 > 目光成员/敏锐 > 有领导意愿易点云
- 一面 9.23
Java工程师P5(中台)面试问题 你的原始回答/表现 更好的回答思路 关键词 / 引导方向 1. 自我介绍 整体比较标准,介绍 MOPS、CMDB、Agent 三段经历 继续保持 2 分钟以内;重点突出“Java 后端 → 复杂系统 Owner → Agent 工程化”这条成长线 Java后端、MOPS、CMDB Owner、Agent 2.0、Harness 2. CMDB 是做什么的? 非自研的系统,我今年接手,有很多坑 先明确自己的角色:“我是系统 Owner/维护负责人,不是原架构设计者”;再讲自己真正深入的链路 Owner、业务接入、线上问题、数据链路、稳定性 3. CMDB 核心功能? “本质是数据读写系统”,讲了Mongo 读写架构、通用采集模型、es 全文检索、Kafka 订阅等 只讲自己负责/深入过的部分。不把话题带到了自己不够熟的模块 “这块我熟悉的是……;这个模块是存量能力,我主要负责维护/排障” 4. MongoDB 增量订阅怎么做? 讲了 Mongo Shake → MongoDB Oplog → Kafka → 订阅服务;但对 Mongo Shake 细节不深 直接划边界:“这是已有模块,我主要维护,不是我设计的;知道它基于 Oplog 做增量获取,底层实现没深入。” Mongo Shake、Oplog、Kafka、维护 vs 开发 5. 防火墙采集链路? 定时调度 → Kafka → 执行服务 → SSH → 文件读取 → 入库 这个可以保留,但不要把它说成自己的架构设计;补一句 “我主要是在这条链路上负责问题排查和稳定性处理” Scheduler、Kafka、SSH、采集、入库 6. 为什么发生 Full GC? 讲到了老年代打满、对象无法及时回收、表头重复反序列化、锁阻塞等 最稳的版本是:先说线上现象 → 止血措施 → 排查过程 → 定位到的主要诱因 → 后续优化方向;不要把分析中的优化说成已经落地 GC 监控、线程栈、Heap/JVM、重复反序列化、JVM 参数、重启 7. Full GC 有什么情况?怎么排查? 几种??有监控、登机看 JVM、GC 状态、线程栈等,但表达比较卡 固定成一套套路:监控确认 → GC/JVM 状态 → 线程栈 → 堆/对象分析 → 止血 → 验证 → 复盘 “先止血,再取证,再定位,再验证” 8. synchronized 原理 / 锁升级? 只能回答“保证并发写操作原子性”,明确表示不了解锁升级 至少准备到:互斥 + 可见性/有序性语义;Java 8 常见锁升级脉络。不确定版本细节就明确说“以 Java 8 HotSpot 经典实现来说” synchronized、对象监视器、Mark Word、锁升级 9. volatile 是什么? 能答可见性,但后面把“先读后改”和原子性混在一起 固定一句:volatile 保证可见性和一定的有序性,不保证复合操作原子性; count++是经典反例visibility、ordering、atomicity、count++ 10. Java 类加载机制? 说很久没接触,无法回答 不会时不要硬编:“这块我最近确实比较生疏,不想给您错误答案;我可以讲一下我工作中实际用到的 JVM 部分……” 然后转到熟悉方向 加载、验证、准备、解析、初始化;ClassLoader 11. 除了 synchronized,还有什么线程安全方案? 尝试往 volatile 方向回答,但比较模糊 建立分类:锁、CAS/Atomic、volatile、并发容器、无锁/消息串行化;再用实际项目举例 Lock、CAS、Atomic、ConcurrentHashMap、线程安全 12. 给我反馈? “把基础再学好点吧”
后续优化方向 补充内容 项目回答的固定结构 背景 → 问题 → 我的职责 → 方案 → 为什么这么做 → 结果 → 复盘。不要一上来罗列技术名词。 回答问题的合理结构 练习“先结论、再 2~3 个点、然后停” 不知道的问题怎么处理 承认边界 → 不瞎编 → 转自己熟悉的相关问题。 P0:Java 并发必须补 synchronized,volatile,CAS,Atomic,ReentrantLock,AQS,ThreadLocal,ConcurrentHashMap,ThreadPoolExecutor,CompletableFuture,JMM / happens-before P0:JVM 必须补 JVM ,对象创建,类加载,Young GC / Full GC,OOM,GC 排查,JVM 参数,Thread Dump / Heap Dump P1:MySQL 深度 B+Tree,联合索引,最左匹配,覆盖索引,回表,Explain,事务,MVCC,Redo / Undo,行锁 / 间隙锁 / Next-Key Lock P1:Kafka 简单八股 Topic / Partition,Producer,Consumer Group,Offset,Rebalance,ISR,ACK,消息丢失,重复消费,顺序性,幂等 P2:高并发 先不要包装成“高 QPS 实战”。按真实经验准备:多节点任务调度、线程池、长 IO、并发控制、Redis 锁、CAS、限流、幂等、超时、重试、MQ。 - 一面 9.23
科大讯飞
- 一面 9.24
中级软件开发工程师(AI应用)问题 原始回答/表现 更好的回答思路 关键词 / 引导方向 1. 自我介绍 Java 2 年、MOPS、CMDB、Agent 都讲到了,也主动强调 Agent 保持现在结构,但减少“工作内容罗列”,突出一条主线:Java 后端工程 → 自动化/稳定性 → Agent 工程化 Java、MOPS、CMDB、Agent 2. F5 自动化怎么做?难点是什么? 讲了工单/记录模型 → 生产配置、事前校验、事务控制、快照回滚 不只讲“做了什么”,补一句核心技术问题是什么:如何保证配置与平台状态一致、失败可恢复 状态控制、配置快照、回滚、幂等、一致性 3. 防火墙为什么要优化? 定时任务执行慢,新批次可能与旧批次重复执行,造成重复下发/配置漂移 先一句话给问题本质:“这是一个多节点定时调度 + 长 IO 导致的任务并发控制问题。” 再讲方案 多节点、任务重复、长 IO、并发控制 4. 为什么使用线程池? 原来串行慢,因此用专用线程池;核心 8、最大 20 先讲为什么能并行:策略之间没有顺序依赖;再讲线程池是为了控制并发,而不是单纯提速 IO 型任务、线程池、并发上限、下游承压 5. 为什么是 8 核心 / 20 最大? 结合设备压力设置,避免防火墙承受过多并发 如果没有严格压测数据,不要把数字讲成理论最优;说成业务约束下的经验参数更稳 下游承载、并发度、经验参数 6. 分布式锁为什么需要? 防止多个节点同时执行任务 继续补:锁解决的是调度层互斥;EXECUTING/CAS 解决任务状态正确性。 Redis Lock、DB CAS、EXECUTING 7. 锁为什么不能提前释放? 说任务执行时间要短于锁释放时间 更完整:锁 TTL 应覆盖任务执行时间,并结合超时/续期/异常场景保证不会出现两个执行者同时运行。 不熟具体实现时不要继续吹 Lock TTL、Timeout、并发一致性 8. Agent 1.0 怎么做? Workflow(具体提一下SpringAIAlibaba的Graph),拆成意图识别、增强检索、SQL 等节点,减少上下文幻觉 这个回答方向对,但可以强调:1.0 是“确定流程约束模型”,而不是模型自主决定全部步骤。 Workflow、Intent、Schema/Table Retrieval、NL2SQL 9. Agent 2.0 做了什么? Harness、ReAct、Skill、MCP、System Prompt(前提是有要求Agent全部迁移到AgentSpace上) 重点不是堆名词,而是讲为什么改:1.0 流程固定 → 2.0 希望动态决策 → Runtime 负责执行控制 Workflow → Agent Runtime、ReAct、Skill、MCP 10. 为什么拆多个 Skill? 澄清问题、SQL 生成等能力拆开 用“单一职责 + 能力边界”解释:让 Agent 可以按任务选择能力,也便于独立治理和复用。 Skill Boundary、Tool、复用、职责单一 11. MCP 在项目里做什么? 将物理机、网络等资产查询接口封装成 MCP 给模型调用 进一步明确:MCP 是能力暴露/调用接口层,不等于模型本身;真正执行仍由后端能力完成。 MCP、Tool、API、结构化调用 12. ReAct 是不是你自己实现的? 说明直接复用内部平台能力,通过 System Prompt 引导 这是正确的边界控制。以后可以直接说:“底层 Runtime/ReAct 不是我们从零开发,主要做业务能力封装和场景适配。” 平台复用、业务适配、边界 13. Agent 效果怎么评估? 讲了表检索、SQL 生成、结果相关性,用数十个真实问题测试(应该是两方面:sql生成是否符合用户问题,sql执行是否成功) 可以形成固定评估框架:数据集 → 意图正确率 → Schema/表召回 → SQL 正确率 → 最终答案正确率 (需要研究下) Evaluation Set、Recall、SQL Accuracy、End-to-End 14. CMDB 有什么技术亮点? 强调系统复杂、自己接手、稳定性维护,但没有提炼出明显技术亮点 这里其实暴露了面试官反馈的问题。不要把“系统复杂”当亮点,应主动聚焦 Full GC 故障闭环 + 复杂系统排障能力 JVM、GC、链路排障、线上稳定性 15. CMDB 体量?美的 IT 体系? 讲了美的是制造业规模最大的,整体数字化程度高,AI基建好。。 这里可能要介绍 MOPS/CMDB 是如何承接美的“庞大”的IT运维需求的。。 提炼系统的特点 16. Full GC 怎么排查? 先看监控/JStack/JMap,再止血,最后定位重复反序列化 形成固定叙事:现象 → 影响 → 快速止血 → 现场取证 → 根因 → 验证。比单纯讲工具更有技术含量 GC、Thread Dump、Heap Dump、根因、复盘 17. Eureka 怎么做服务发现? 能讲服务注册、网关根据请求找到服务 基础回答够,但不要主动往负载均衡深挖;如果被问,再讲 Eureka Registry、心跳、实例上下线 Eureka、Registry、Heartbeat 18. 多实例怎么负载均衡? 只知道轮询、权重、健康检查,对底层不深 不确定时直接划边界:“业务侧我主要使用这一层能力,具体负载均衡实现我没有深入研究。” 不瞎编、知识边界 19. Docker/K8s 做过什么? 讲了 MOPS 私有化交付、容器打包、解压、重启 不要说成 K8s 专家;定位为具备基础部署和排障能力 Docker、K8s、CI/CD、ToB 20. 反问:我在面试中核心应该表达出什么?! 技术亮点现场表达能力!!(具体的技术深度没有体现,排障经验没有提醒明晰的思路,为什么大家都会做,你做的是不是更好呢) 以后准备固定答案:防火墙并发控制、CMDB Full GC 排障、Agent 1→2 架构演进,每个都能在 30 秒内讲出“问题→方案→结果” 技术亮点、问题本质、方案、结果
后续强化方向 需要补 / 打磨的内容 优先级 / 目标 1. 技术亮点表达 每个项目提炼 1 个核心技术问题 + 1 套方案 + 1 个结果,不要只讲业务流程 P0:以后被问“技术难点是什么”不能再现场找 2. 防火墙项目 Redis Lock、DB CAS、EXECUTING、线程池、拒绝策略、Timeout、Retry、为什么能并行、为什么这样设置线程数 P0:这是你目前最成熟的后端项目 3. CMDB 故障排查 Full GC 排查流程、Thread Dump、Heap Dump、对象分配、老年代、OOM、止血与根因的区别(锁粒度) P0 4. JVM JVM 内存区域、对象创建、类加载、GC、Young GC、Full GC、OOM、JVM 参数、Thread Dump / Heap Dump P0 5. Java 并发 synchronized、volatile、CAS、Atomic、ReentrantLock、AQS、ConcurrentHashMap、JMM、happens-before P0;昨天/今天都验证了短板 6. ThreadPoolExecutor 核心参数、任务提交流程、队列、拒绝策略、CPU/IO 密集型区别 P0;实际项目直接相关 13. Agent Evaluation 建立真实问题集、意图/召回/SQL/最终答案分层评估 P1;以后项目表达很有用 16. 表达训练 先结论 → 2~3 个点 → 停;不要边想边讲、不要一次说完所有细节 P0 19. AI Coding Claude Code、Cursor、内部 Workspace 怎么实际使用,能举真实例子 P2 GPT 评价 你的“工程经验量”已经到了可以去面 2 年 Java 的程度,但你的“把工程经验抽象成技术原理并表达出来的能力”还没有跟上。所以,简历能过,面试容易崩。 P0 - 一面 9.24
百度·智能云
- 一面 9.28
- MOPS的关键词都是运维相关的?先讲身份:我是 Java 后端,业务场景是运维。再讲平台价值:Midea是制造业体量最大,传统手工运维风险大,不可追溯,不可回滚。平台把人工操作抽象成自动化、流程化、可追踪、可回滚的系统能力。
- 自动化功能怎么设计?通过“工单+记录”的模型来轮转,工单记录一次操作的流程跟踪和状态记录,记录是对实际配置的一个快照,协同保证自动化流程的可控,可追踪,可回滚
- 防火墙批量下发中Redis分布式锁+CAS是做什么的?CAS保证策略有中间态,做策略级的防重复下发,redis锁保证同时一个任务在跑,是调度层互斥,两个是分级优化
- 防火策略下发失败怎么处理?几个维度,策略本身3次重试,线程池超时兜底执行,分布式锁释放时间要大于线程池结束时间
- 线程池的参数怎么配的?8+100+20,和业务沟通(压测/经验)确定基础并发在8,避免造成防火墙设备负载过高;队列本质是任务积压缓冲,20 是并发上限
- (todo 线程池策略)超时策略为什么是callerrunpolicy?这里概念混了!!注意区别三个东西➡️ ThreadPoolExecutor keepAliveTime(空闲线程多久没任务后退出),Future.get(timeout)(调用方最多等多久,超时不等于任务停止),CallerRunsPolicy(线程池满了以后谁来执行被拒绝的新任务)我想表达的线程池兜底是:对于一条策略,我需要它在一个线程里完整执行完成,不管成功失败,自身可以完成闭环,而不是卡在一个中间态;
- Redis锁怎么做的?锁 key + 唯一 value/token + TTL;释放时校验 token,防止误删其他节点的锁。实际项目可以说:使用 Redisson 封装实现,没有自己从零实现
分布式锁先查后加怎么做?“先查再加”两步操作本身有竞态;简单加锁使用 SET NX EX/PX 一条原子命令;复杂的查+改可以用 Lua 脚本一次执行。 - NBU异步是怎么做的?前台流程与长耗时自动化任务解耦,避免工单请求被 SSH/安装过程阻塞;执行完成后异步回写状态。 如果追问,再讲线程池,不要强调 new Thread
- 如何理解进程和线程?我理解一个应用就是进程,如后端服务多节点是多个进程;每个进程里可以开启线程做并发操作;ps 要补充两者分别是资源分配/调度的最小单位
- 线程不安全怎么理解?核心是:多个线程并发访问共享可变状态,并且操作不是线程安全的,可能出现竞态条件。
- threadlocal?不了解。至少记一句:给每个线程保存独立变量副本,避免多个线程共享同一个变量;线程池场景注意 remove() 防止数据串线程/长期持有。
- 锁有什么实现,更加轻量化的?锁实现和并发控制思想要分开。Java 锁:synchronized、ReentrantLock;乐观/悲观锁是策略。乐观锁常见实现:CAS、version。不能简单说“乐观锁就是更轻量的锁”
- mybatis用过吗,#和$有什么区别?标准答案:#{} → PreparedStatement 参数绑定;${} → 字符串直接替换,可导致 SQL 注入,也可能改变 SQL 结构
- fullgc排查思路?监控,定位影响面,jstat快速判断,jstack/jmap保存现场,扩容重启快速止血,分层定位优化闭环(主要原因是表头对象复用,额外优化是锁粒度调整)
- jvm垃圾回收器?G1?至少建立基本地图:Serial、Parallel、CMS(历史)、G1;重点理解 G1:Region、分区管理、兼顾吞吐和停顿目标
- Kafka自平衡?CMDB采集代理服务fllgc挂了➡️ 消费者挂掉 → 从 Kafka 状态中去掉 → Kafka 自平衡,但不知道作用;重点是 Consumer Group Rebalance:消费者加入/退出/故障后,重新把 Partition 分配给存活消费者,让消费任务重新均衡
- 手撕:股票买入买出,贪心算法,int[] prices = new int[]{1, 3, 5, 2, 7};
- 反问:我作为面试者的核心表达有什么优化的地方?反馈:讲的慢,导致问的少,基础题答不上,还要补
- 业务:CRM / 企业报表平台,也有ai coding提效
- GPT:很明显的进步,“项目我能讲了,但基础知识还没跟上。”之前做的“项目 → 技术树 → 引导问题”这个方向是有效的,现在重要的是让每个项目节点下面都长出 2~3 层 Java 原理。防火墙项目技术树:Lock → CAS → ThreadPool → Timeout → Retry → 幂等 → 下游承载;F5 自动化技术树:工单状态 → 配置快照 → 一致性 → 回滚 → 幂等,Full GC 技术树: GC 现象 → jstat → Thread Dump → Heap Dump → 对象分配 → 根因 → 止血
- 一面 9.28
高思
- 一面 9.29
面试问题 你的原始回答/表现 更好的回答思路 关键词 / 引导方向 1. 团队开发流程中怎么使用 AI Coding? 新项目先准备知识库、规范文档、Agent.md;开发时规范+代码一起作为上下文 方向对,但可以进一步抽象为:AI Coding 不只是代码生成,而是让 AI 理解项目上下文、规范和约束,从而进入完整研发流程 Context、Agent.md、Coding规范、代码生成 2. AI Code Review 怎么做? AI 提效后代码量大幅增加,人工 review 看不过来;可以把历史经验沉淀成 Skill 供 Agent Review;开发 Agent 和 Review Agent 分离 思路很好。再补一个核心:Review Agent 应该基于独立规则/历史经验/质量标准进行“第二视角检查”,避免生成与评价职责耦合 Review Skill、独立 Agent、规则、历史经验、质量门禁 3. 除了需求、开发、Review,AI 还能进入哪些研发环节? 想到监控、AIOps;CMDB-Agent 可以作为子 Agent 提供资产查询 回答偏少。可以建立完整研发生命周期:需求分析 → 设计 → 开发 → Review → 测试 → CI/CD → 发布 → 监控 → 故障排查 → 复盘/知识沉淀 SDLC、测试、CI/CD、发布、监控、故障、复盘 4. 有没有不是老板要求,而是你自己主动推进的事情? CMDB-Agent 2.0 是自己主动争取的;借迁移通用平台的机会推动重构 这是本场比较好的回答。 可以进一步强调:你发现 1.0 Workflow 的局限 → 结合 Agent 趋势 → 主动争取机会 → 拆 Skill / MCP → 用查 IP 作为最小场景迭代 主动性、问题发现、技术趋势、MVP、渐进式重构 5. 除了 Agent,还有没有自己争取的事情? 想不到,后来提 CMDB 是前任离职后“半主动”接手,但也算是积累了架构梳理/稳定性治理的经验 原回答只能算勉强。以后可以把防火墙并发优化、F5 自动化也整理成“发现现有流程问题→主动提出方案→推动落地”,不一定非要是老板完全没要求;还有CMDB中把答疑经验、故障排查思路沉淀为 Skill 并共享!! 主动发现问题、方案推动、优化 6. AI 模糊了开发/运维等能力边界,你怎么看? 核心能力还是要做深,例如后端把分布式、一致性做好;通用能力可以沉淀成 Skill,补充其他领域能力 这回答其实不错。可以更凝练成:AI 让能力边界变宽,但人的核心竞争力应该从“亲自做所有事情”转向“掌握核心专业能力 + 调度/复用通用能力” 核心能力、能力边界、Skill、复用、专业深度 7. 工作之外最近在学什么? 音乐,这是我受用一生的能力;不是单纯学乐器,而是理解优秀音乐中的表达、情感、哲思,并训练共情和输出能力;为此会找不同风格的优秀专辑,在合适的环境,以学习的态度,列表顺序播放收听 很有个人特色,但略偏抽象。 如果再答一次,可以保留音乐,但最好落到具体行为和收获 音乐、长期能力、审美、共情、表达 8. 怎么判断自己做的事情有价值? 对个人:提升核心能力;对团队:理解老板需求,把个人经验沉淀成 Skill,形成团队资产 核心方向不错,但“理解老板需求”太窄。可以升级成三个层次:个人能力提升 + 团队效率提升 + 业务/用户价值 个人、团队、业务、效率、可复用资产 9. 你未来怎么看个人发展? 认为需要把核心后端能力深挖,才能更好地让AI做执行,而不是做什么“全栈开发”;对于一些其他能力,如基础的运维知识,交给 Skill 补 这是你回答比较成熟的一题,可以继续强化成:深专业 + AI 放大专业能力,而不是 AI 替代专业能力 10. 这个岗位到底在做什么? 岗位本质:DevOps + AI 提效;更准确地说:研发效能工程 / AI for Engineering。核心考察的是“你有没有能力站在研发流程上思考:AI 能在哪里改变研发效率,以及你自己有没有主动发现、推动这些变化。” 面试官:团队自身也在探索效能提升怎么做,因此需要考察候选人的思考。
- 一面 9.29
长鑫存储
- 一面 9.30
pass
- 一面 9.30
字节跳动
- 一面 9.30
后端研发工程师-电商营销 https://job.toutiao.com/s/B0lwM_8PFaA面试问题 你的原始回答/表现 更好的回答思路 关键词 / 引导方向 1. 有 Java 开发经验 面试官马上强调团队只用 Go 不要展开争论语言;直接回应:“我主要是 Java 后端,不过后端工程能力和项目经验是可迁移的;Go 我目前没有生产经验。” Java→通用后端、Go 是短板 2. AI Coding 具体做了什么? 实际仅用于编码,但扩展到了 PRD→开发→测试整个链路,随后被追问 PRD 生成细节 严格区分“实际做过”和“设想中的完整流程”。先说真实做过的是编码阶段,再说“从流程上我认为还可以向前延伸到需求/设计”。 实际实践、设想、不要扩大职责 3. AI 生成 PRD 怎么做? 开发前:AI 结合文档库+存量业务代码进行事实澄清,迭代中:项目文档+代码+测试一起维护,开发时:AI 生成 PRD 更稳:“这部分我没有实际落地,我实际使用主要在 Coding 阶段;PRD 自动生成是我对完整 AI Coding 流程的设想。” 事实边界、Context、文档维护 4. 如何保证文档实时性? 说手工维护、Git 一起提交 回到真实情况即可:“目前主要靠人工维护,确实存在同步成本;如果系统化,可以把代码变更、接口/测试变更作为文档更新触发点。” 不要装成已有方案 文档漂移、Source of Truth、自动同步 5. Redis 崩了怎么办? 当前任务直接失败,没有特别设计降级 承认现实方案,再补设计思路:Redis 是调度互斥层,Redis 挂了可以让本轮调度失败;业务正确性仍由 DB 状态约束兜底。若要求可用性,可设计 DB/单节点调度/租约等降级,但要考虑重复执行风险。 正确性 vs 可用性、降级 6. CMDB-Agent 1.0 Workflow 怎么拆? 回答得还可以:意图识别、检索、SQL 等节点 保持,重点说明为什么拆:控制上下文、缩小模型任务边界、降低幻觉 Workflow、Intent、Schema/Table Retrieval、NL2SQL 7. Agent 效果怎么评估? 100 个真实问题做调优;看 SQL 与用户问题相关性、SQL 执行正确性 继续细化成:问题集 → 召回/意图 → SQL 正确性 → 最终结果正确性。你当时回答已经开始往这个方向走 Evaluation Set、SQL Accuracy、Result Correctness 8. 做自然语言问数的价值是什么?为什么不做表单? 自然语言更灵活,多表/联表不容易做;普通用户不理解页面操作;也可作为子 Agent 提供能力 这个问题其实回答方向对。最好总结成:“不是简单替代表单,而是降低查询门槛 + 提供复杂组合查询能力 + 为上层 Agent 提供可调用的查询能力。” 易用性、复杂查询、能力复用、子 Agent 9. SQL 查询数据从哪里来? MongoDB 中 100+ 表,选取几十张同步到 MariaDB,生成 SQL 直接查 这块可以讲,但要强调:这是问数 Agent 为了 NL2SQL 而使用的关系型查询数据源。 Mongo→MariaDB、Schema、NL2SQL 10. Mongo → MariaDB 如何保证一致性? 说 CMDB 有数据订阅,秒级增量同步;没有做对账 可以承认边界:“主要依赖增量订阅,目前没有建立完整的双向对账/全量校验机制。” 再补设计思路:全量校验+增量订阅+异常补偿 CDC、增量同步、全量校验、补偿 11. 为什么重构 Agent 2.0? 主要回答集团要求迁移到统一 Agent 平台,并利用平台能力做更灵活的配置 这是本场较弱的一题。 应从“迁移要求”提升到技术原因:1.0 Workflow 固定、能力复用差、扩展成本高(以及问题整个调试一轮耗时很长);2.0 利用统一 Harness/Skill/MCP 做动态能力编排(做Skills/MCP是就对能力边界、最小接口粒度做了管控) Workflow局限、能力抽象、复用、Runtime 13. ThreadPool 在电商高流量下拒绝策略怎么选? 没有形成完整回答 先判断任务类型,再看是否允许降级:CPU/IO、是否允许丢任务、是否要求反压、是否同步调用;不能脱离业务直接说某个策略最好 CallerRuns、Abort、Discard、反压 15. synchronized 怎么实现? 没答出来 补:Monitor/对象监视器、对象头/Mark Word;不同 JDK 实现细节有变化。 synchronized、Monitor、Mark Word 16. MySQL 可重复读怎么实现? 不会 重点补:MVCC + Read View + Undo Log;当前读和快照读区别。 RR、MVCC、Read View、Undo 17. MySQL 主从架构? 不会 至少知道:Primary/Replica、复制日志、异步/半同步、读写分离、延迟与一致性权衡。 Replication、读写分离、延迟 18. Redis 如何支撑超大电商流量? 不会 先从架构角度答:缓存热点、分片集群、主从/高可用、热点 Key、限流、降级;再回 DB 压力 Cluster、Sharding、Cache、Hot Key 19. 手撕:环路加油站(lc 134) 没解出来 需要补“经典贪心”的解题流程,不只是背答案 Gas Station、贪心、前缀亏损 20. 整体面试节奏 面试官频繁打断、跳题,导致一些回答被带到陌生区域 学会5~15 秒先给结论,被打断也能快速收口;对于设想问题主动标注“实际没有落地” 结论先行、事实/设想边界
- 一面 9.30
融通供应链
顺丰同城
领星
安克创新
PDD