项目学习

项目学习笔记

1. AI医疗服务平台

1.0 整体

说说RAG、MCP的技术流程和技术交互栈分别是怎样的?

  • RAG:解决 大模型知识更新与外部知识接入问题
  • MCP:解决 大模型如何标准化调用工具/数据源的问题

RAG技术流程

  1. 数据准备:将知识库文档进行解析、文本切分。
  2. 向量化:通过 Embedding 模型将文本转为向量。
  3. 向量存储:存入向量数据库(如 Milvus)。
  4. 检索阶段:用户问题向量化后进行相似度搜索。
  5. 上下文构建:取 TopK 文档作为上下文。
  6. 生成阶段:将问题 + 检索内容一起发送给大模型生成答案。

MCP技术流程

  1. LLM生成 Tool Call请求
  2. MCP Server解析请求
  3. 调用对应工具(数据库/API/函数)
  4. 返回结果给LLM
  5. LLM整合结果生成最终回答

MCP和Fuction call的区别

对比 MCP Function Calling
本质 工具调用协议 模型能力
作用范围 跨系统 单模型
标准 统一协议 各家不同
资源来源 远程MCP Server 本地代码
扩展性 极高 有限

Function Calling

是大模型生成函数调用意图,由应用程序执行函数。

MCP

是一个标准化协议,让大模型可以通过统一接口调用外部工具和资源。

什么是向量化?

向量化是指将文本、图片等数据通过 Embedding模型 转换成高维向量表示。

这样语义相近的文本在向量空间中的距离会更近,从而可以通过向量相似度计算实现语义检索。

Embedding模型

  • text-embedding-3-large (1024维)

为什么向量维度越高,语义表达能力越强?

  1. 信息容量:维度越多,可编码的信息越多
  2. 维度越多 → 能表达的语义特征越多。
  3. 特征空间:维度越高,语义区分能力越强

做向量化匹配时的主要标准是什么?

主要是 向量相似度计算

  • 余弦相似度(Cosine Similarity)(最常用)
  • 欧氏距离
  • 内积(Inner Product)

向量越接近,相似度越高。

MCP协议的本质构成是什么?

MCP本质是一个 标准化工具调用协议,主要包含:

  1. Tool Schema
    描述工具的功能和参数

  2. Tool Call

LLM发起工具调用请求

  1. Tool Response

返回调用结果

本质是 LLM + Tool 的标准接口协议

尝试过在本地部署AI服务吗?

尝试过,比如:

  • 使用 Ollama
  • 部署开源模型(如 Llama / Qwen)

通过 HTTP API 调用模型服务。

一般会结合:

  • GPU
  • Docker
  • 向量数据库

搭建本地 AI 服务。

部署本地大模型服务时需要注意哪些问题?

  1. 硬件资源:GPU显存/CPU/内存
  2. 模型大小:7B/13B/70B
  3. 推理性能:QPS/延迟

简单谈谈Transfomer和注意力机制?

注意力机制是一种让模型在处理序列数据时关注更重要信息的机制

Transformer 是一种 基于注意力机制的深度学习模型架构

完全用注意力机制替代 RNN 和 CNN 来处理序列数据

1
2
3
4
5
6
7
8
9
输入文本

Embedding

Encoder (多层)

Decoder (多层)

输出文本

该医疗系统能承受的QPS是多少,如何测试?

可以通过 压测工具测试:JMeter

  1. 构造并发请求
  2. 模拟真实用户访问
  3. 监控系统指标

指标包括:

  • QPS
  • RT(响应时间)
  • CPU

系统QPS取决于接口复杂度和硬件资源(CPU/GPU/内存)

我主要压测了药品搜索的接口,应为是用到了二级缓存,所以大概能承受上万的QPS。
其次还压测了基于ES的分页查询的接口,能承受1000的QPS

我们一般通过压测工具进行测试,例如JMeter。压测流程通常是:

  1. 编写接口压测脚本
  2. 设置并发线程数和持续时间
  3. 逐步提高并发量进行压测
  4. 观察QPS、响应时间、错误率以及CPU和内存指标
  5. 找到系统稳定QPS和极限QPS,并分析系统瓶颈。

你认为当前这个医疗平台存在哪些优化点、做得不好的地方?

1️⃣ 性能与并发优化

潜在问题:

  • AI 模型调用是瓶颈,高并发下容易出现响应延迟甚至服务拒绝(你提到过 1000 用户同时问诊)。
  • ChatMemory、MongoDB 写入压力大,如果没有做好分库分表或缓存优化,可能导致系统阻塞。
  • 秒杀/高并发场景下,如果 AI 触发下单直接调用业务链路,可能绕过原有的并发保护。

优化点:

  • 请求削峰:使用消息队列、异步处理、令牌桶等策略,避免瞬时流量打满 AI 服务。
  • 模型负载均衡:多实例部署 AI 服务,并结合 GPU 资源调度。
  • 缓存热点数据:RAG 的 embedding、FAQ、药品说明等可以放在 Redis 或 Caffeine,减少重复计算。
  • 分布式数据库优化:MongoDB 可以考虑分片,MySQL 可使用读写分离和索引优化。

2️⃣ 数据一致性与可靠性

潜在问题:

  • MySQL 与 Elasticsearch 的同步是最终一致,存在短时间数据延迟,可能影响搜索结果。
  • 高并发更新可能导致 Elasticsearch 写入乱序或数据丢失。
  • 缓存雪崩、穿透、击穿风险,如果没有二级缓存或预加载策略。

优化点:

  • 保证顺序性:按主键分区,binlog 顺序消费,单分区单线程写入 ES。
  • 错误重试和幂等:同步 ES 写入操作增加幂等逻辑。
  • 缓存策略:热点药品、用户信息使用 Caffeine + Redis 二级缓存,结合 TTL + 预加载。
  • 降级兜底:缓存失效或 DB 宕机时返回默认值或历史数据。

什么是大模型幻觉问题,你的项目中是如何尝试解决的?

好的,我们来认真梳理这个问题。你的问题涉及 大模型的“幻觉”问题(hallucination),以及在你的医疗问诊项目中具体的解决方案。

1️⃣ 什么是大模型幻觉(Hallucination)?

  • 定义:大语言模型在生成回答时,可能给出 与事实不符、没有依据、甚至完全虚构的内容

  • 特点

    1. 内容看起来合理、流畅、符合语法,但实际上是错误的。
    2. 在医疗或金融等领域风险更高,可能导致误导用户。
    3. 幻觉常发生在模型缺乏明确事实支撑或训练数据中信息不足时。

举例(概念性,不是实际患者数据)

用户问:“某药物的标准剂量是多少?”
模型回答:“推荐剂量是 1000mg,每天三次。”
—— 但实际药典显示标准剂量是 500mg。这个就是幻觉。

2️⃣ 为什么会发生?

  • 模型 基于概率生成,并不真正“理解”事实。
  • 模型的训练数据可能 不完整、过时或含噪声
  • 问题涉及 冷门或专业领域信息,模型容易自由发挥。
  • Prompt 或上下文提供的信息不足,模型需要“填空”,容易出错。

3️⃣ 项目中解决幻觉问题的方法

在你的医疗问诊平台中,你已经做了一些技术尝试来 减少幻觉的发生

(1)RAG(Retrieval-Augmented Generation)检索增强

  • 思路:模型回答之前先 从可信知识库(药典、指南、问诊 FAQ)检索相关文档

  • 效果:模型生成的回答可以引用具体文档,减少凭空生成内容。

  • 实现细节

    • 将药品说明、医疗指南向量化(embedding)并存入向量数据库。
    • 用户问诊时先召回最相关文档,然后将文档内容和问题一起作为 prompt 输入模型。
    • 这样模型生成回答就有事实支撑。

(2)多路检索 + reranker

  • 思路:不同检索器(关键词、embedding)召回候选文档,再用 cross-encoder 模型排序,选最可信的文档提供给大模型。
  • 效果:进一步保证输入信息的质量,减少模型使用无关信息“编故事”。

(3)上下文与提示优化(Prompt Engineering)

  • 明确告诉模型:

    “请基于提供的医疗文档回答,不允许凭空生成剂量或疗程信息。”

  • 效果:减少自由发挥的可能性,但不能完全消除幻觉。

(4)二次校验 / 规则过滤

  • 对敏感信息(药品剂量、检查标准)增加规则:

    • 回答中关键数据必须 在知识库中找到匹配值
    • 如果模型回答未匹配到事实,返回提示“请参考官方药典”。
  • 效果:保证不会直接输出错误医疗建议。

(5)用户可视化引用

  • 对模型回答中引用的知识文档进行 高亮或标注来源
  • 用户可以直接点击查看原始文档,增加透明度和可信度。

4️⃣ 整体总结

  • 大模型幻觉是生成式 AI 的本质问题,尤其在专业领域高风险。

  • 解决策略

    1. RAG 检索增强 → 提供事实依据
    2. 多路检索 + reranker → 提高文档质量
    3. Prompt 指令约束 → 限制自由发挥
    4. 二次校验 / 规则过滤 → 数据安全兜底
    5. 引用透明化 → 用户可验证信息

该医疗平台在医疗问诊场景下对用户的核心价值是什么?

1️⃣ 便捷的医疗咨询

  • 价值:用户可以随时在线获取问诊服务,无需排队、预约医院。

  • 体现方式

    • 高并发 AI 问诊支持多用户同时咨询。
    • 智能问答可以快速理解用户症状,提供初步分析。
    • 对于轻微病症或常见问题,用户无需等待医生即可得到参考建议。

用户收益:节省时间、提升体验,尤其是在夜间或偏远地区。

2️⃣ 准确可靠的医疗信息

  • 价值:减少错误或不准确信息的风险。

  • 体现方式

    • 使用 RAG + 知识库检索,确保 AI 回答基于权威文档(药典、指南、FAQ)。
    • 高亮引用源,用户可以直接验证信息来源。
    • 规则校验和数据过滤保障关键医疗数据(如剂量、疗程)可靠。

用户收益:获取可信、可验证的医疗信息,降低因误诊或错误信息产生的风险。

CoT/ReAct了解过吗?

一、CoT(思维链)
核心定义:2022 年谷歌提出的基础推理范式,引导大模型把复杂问题拆解为线性、连续的中间推理步骤,先输出 “思考过程” 再给答案,替代 “问题→答案” 的直接输出。
工作流程
输入复杂问题(数学、逻辑、推理题)
模型按 “第一步→第二步→…→最后一步” 输出推理链
汇总步骤,给出最终答案

二、ReAct(Reason + Act)
核心定义:融合推理(Reason)与行动(Act)的高级范式,让模型形成Thought → Action → Observation → Thought闭环,可主动调用搜索、API、计算器等外部工具。
工作流程
Thought:分析问题、规划下一步(如 “我需要查南京今天天气”)
Action:执行工具调用(如search(“南京天气 2026-03-21”))
Observation:接收工具返回结果(如 “晴,12–22℃”)
循环直至得出最终答案

1.1 大模型对话 / Prompt / 上下文记忆

如何对大模型的回答和行为进行约束?

可以通过:输入/输出/模型本身/RAG/Fuction call

  1. Prompt约束:System Prompt(系统提示词)

定义模型角色,例如:

1
你是一名专业医生助手,只能提供医学参考建议。
  1. 输出格式约束

通过 Prompt 强制结构化输出:例如 JSON 输出。

1
2
3
4
5
请严格按照以下格式输出:
{
"summary": "",
"keywords": []
}
  1. 内容过滤

对模型输出进行安全检测。

  1. 模型层约束:指令微调
1
2
3
用户问题

理想回答

模型会学习:

  • 如何回答问题
  • 什么内容应该拒绝
  1. 系统层约束
    在 模型外部增加控制逻辑
  2. RAG知识范围限制: 只能基于知识库回答
  3. 如果模型可以调用工具(Tool/Fuction call),需要限制权限。

使用提示词(Prompt)时需要注意哪些要点?

主要有几点:

  1. 角色设定

通过 角色设定可以显著提升回答质量。

1
你是一名医学专家,请解释糖尿病的主要症状。
  1. 任务描述

Prompt 必须 明确告诉模型要做什么任务

1
2
3
介绍一下机器学习

请用200字介绍机器学习的基本概念,并列举三个典型应用场景。
  1. 提供足够的上下文

大模型本身 没有长期记忆,如果上下文不足容易产生 幻觉

1
2
3
解释这个算法

请解释 Transformer 模型中的 Self-Attention 机制,并说明它与传统 RNN 的区别。
  1. 明确输出格式

约束返回结构。

1
2
3
4
5
6
请按照以下 JSON 格式回答:
{
"title": "",
"summary": "",
"keywords": []
}
1
2
3
4
5
6
7
System Prompt

用户问题

RAG检索内容

输出格式约束
1
2
3
4
5
6
7
8
9
10
11
12
13
你是一名AI助手,请根据以下知识回答问题。

知识库:
{context}

问题:
{question}

请使用以下JSON格式回答:
{
"answer": "",
"confidence": ""
}

若大模型给出错误医疗建议,如何从系统层面发现/拦截?

一、输入层拦截(问题识别)

首先识别用户问题是否属于 医疗高风险问题

二、RAG知识约束(防止胡编)

医疗场景必须限制模型 只能基于权威知识回答

三、输出层安全检测(关键)

在 LLM 输出之后,需要进行 自动安全检测
如果回答不合规,就终止回答。


如何解决大模型对话的上下文超限问题?

常见方法:

  1. 对话摘要(Conversation Summary)

当对话变长时,可以 对历史对话进行总结压缩

  1. 滑动窗口:只保留最近N轮对话

例如最近10轮。

  1. 向量记忆

将历史对话 向量化存储,只在需要时检索。
只加载相关记忆

1
2
3
4
5
6
7
8
9
历史对话

Embedding

向量数据库

语义检索

加入上下文

为什么使用Mongodb存储大模型对话上下文,而不是Mysql

大模型对话系统(ChatGPT 类似应用) 中,很多系统会选择用 MongoDB 存储对话上下文,而不是 MySQL。核心原因主要是:数据结构、写入模式、扩展能力、查询方式更适合 MongoDB。我们从工程角度详细分析。


一、对话上下文的数据结构特点

大模型对话通常是这种结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"conversationId": "123",
"messages": [
{
"role": "user",
"content": "我最近咳嗽怎么办?",
"time": "2026-03-16 10:00"
},
{
"role": "assistant",
"content": "建议先判断是否有发烧...",
"time": "2026-03-16 10:00"
},
{
"role": "user",
"content": "没有发烧",
"time": "2026-03-16 10:01"
}
]
}

特点:

  • 嵌套结构
  • 数组结构
  • 字段不固定

这种结构在 MongoDB 中非常自然,因为 MongoDB 本身就是 Document(JSON)数据库


二、MongoDB vs MySQL 结构对比

1 MongoDB存储方式

1
2
3
4
5
6
7
{
"_id": "conversationId",
"messages": [
{ "role": "user", "content": "..."},
{ "role": "assistant", "content": "..."}
]
}

优点:

  • JSON天然支持
  • 一条记录就是一个会话
  • 读取非常快

2 MySQL存储方式

通常需要拆表:

会话表

1
2
3
4
5
conversation
-------------
id
user_id
create_time

消息表

1
2
3
4
5
6
7
message
-------------
id
conversation_id
role
content
time

查询最近对话:

1
2
3
4
5
SELECT * 
FROM message
WHERE conversation_id = ?
ORDER BY time DESC
LIMIT 10

缺点:

  • 表结构复杂
  • 需要 join 或排序
  • SQL操作较重

三、写入模式差异(非常关键)

大模型对话有一个特点:

写多读少

每一句对话都会写入:

1
2
user message
assistant message

一轮对话:

1
2次写操作

MongoDB:

1
2
3
4
db.chat.update(
{conversationId:"123"},
{$push:{messages:newMessage}}
)

只需要追加数组

而 MySQL:

1
INSERT INTO message (...)

虽然也可以,但:

  • 高并发写入下 MongoDB 更轻量
  • JSON append 更方便

四、读取方式差异

大模型推理时需要:

读取最近N条对话

例如:

1
最近10message

MongoDB:

1
db.chat.find({_id:"123"})

直接返回:

1
整个会话

或者:

1
messages[-10:]

一次查询完成。


MySQL:

1
2
3
4
5
SELECT * 
FROM message
WHERE conversation_id = ?
ORDER BY time DESC
LIMIT 10

需要:

  • 排序
  • limit

性能相对差一点。


五、扩展性(分布式能力)

聊天数据有一个特点:

1
数据量非常大

例如:

1
2
100万用户
每人1000条消息

就是:

1
10亿条记录

MongoDB:

天然支持:

  • Sharding 分片
  • 水平扩展

可以:

1
conversationId hash分片

MySQL:

虽然也可以:

1
分库分表

但实现复杂度更高。


六、数据结构灵活性

对话数据可能不断扩展字段:

例如:

1
2
3
4
5
token_count
model_name
embedding
temperature
tool_call

MongoDB:

不需要修改表结构:

1
2
3
4
5
{
"role": "assistant",
"content": "...",
"tokens": 300
}

MySQL:

需要:

1
ALTER TABLE

扩展性较差。

使用 MongoDB 存储大模型对话上下文的原因:

原因 说明
JSON结构天然匹配 对话数据是嵌套JSON
写入效率高 append数组
读取简单 一次查询整个会话
扩展性强 支持分片
结构灵活 不需要改表

所以:

MongoDB 更适合存储聊天记录这种半结构化、写多读少、结构灵活的数据。

MongoDB和MySQL的核心区别是什么?

主要区别:

数据模型

MySQL:

  • 关系型数据库
  • 表结构固定

MongoDB:

  • 文档数据库
  • JSON/BSON存储
  • Schema灵活

查询方式

MySQL:

  • SQL

MongoDB:

  • JSON查询

使用场景

MongoDB更适合:

  • 日志
  • 对话记录
  • 非结构化数据

1.2 RAG

RAG检索增强的好处是什么?

RAG可以:

  1. 解决大模型知识过期问题
  2. 引入私有知识库
  3. 减少幻觉

没有RAG直接调用大模型会存在什么问题?

主要问题:

  1. 知识过期
  2. 无法访问私有数据
  3. 幻觉问题严重

**RAG(Retrieval-Augmented Generation,检索增强生成)**的核心思想是:在大模型生成回答之前,先从外部知识库检索相关信息,再结合这些信息进行回答。它的主要好处可以简要概括为以下几点:

1️⃣ 减少大模型“幻觉”
大模型直接生成答案时可能会编造内容,而 RAG 通过先检索真实知识(如文档、数据库等),再生成回答,可以显著降低错误信息。

2️⃣ 支持私有知识接入
可以将企业内部文档、数据库、知识库等接入检索系统,使模型能够回答训练数据中没有的领域问题

3️⃣ 知识更新成本低
不需要重新训练模型,只需更新知识库或向量数据库(如 MilvusElasticsearch)即可让模型获得最新知识。

4️⃣ 提高回答准确性和可控性
模型回答会基于检索到的上下文内容,更容易做到有依据、可溯源

5️⃣ 降低模型训练成本
无需频繁微调大模型,只通过外部检索即可扩展知识能力,节省算力和时间。


除了RAG,还有哪些方式能为大模型提供领域上下文?

常见方法:

  1. Prompt Engineering
  2. Fine-tuning(微调)
  3. 知识图谱
  4. Tool调用

1️⃣ Prompt Engineering(提示词工程)
通过在提示词中加入领域背景、规则或示例(Few-shot),让模型在生成时参考这些上下文。

2️⃣ Fine-tuning(模型微调)
使用领域数据对大模型进行微调,使模型内部参数学习到专业知识。

3️⃣ System Prompt / 角色设定
在系统提示词中固定模型的身份和知识范围,例如设定为“医疗专家助手”。

5️⃣ 工具调用(Tool / Function Calling)
通过调用外部系统(数据库、搜索引擎、API)实时获取信息,再由模型整合生成答案。

6️⃣ 长期记忆(Memory)机制
将用户历史对话或长期知识存储到数据库(如 MongoDB),在后续对话中作为上下文使用。


微调有哪些技术手段?

  1. 全参数微调(Full Fine-tuning)
    对模型的全部参数进行训练更新,效果最好,但计算成本高、显存需求大。
  2. 部分参数微调
    只更新模型的一小部分参数,其余参数保持冻结。
    适合在单机或少量GPU环境下训练

可以从训练阶段的角度来这样划分

1️⃣ 继续预训练(Continued Pre-training / Domain Pre-training)

在模型预训练阶段之后继续使用领域数据训练,但仍然保持原来的训练目标(如语言建模)。
作用是让模型学习领域知识分布

例如:

  • 用大量医疗文献、法律文本、金融数据继续训练模型
  • 让模型更理解专业术语和语境

特点:

  • 数据量通常很大
  • 不改变模型任务,只增强领域知识

2️⃣ 指令微调(Instruction Tuning / SFT)

使用指令-回答数据对模型进行监督微调,使模型学会按照指令回答问题

例如:

1
2
指令:解释什么是高血压
回答:高血压是指……

特点:

  • 数据量相对较小
  • 目标是让模型更会对话、更会执行任务

1.3 多模态 / 文件上传

图片上传时若出现图片过大、超时问题,该如何解决?

1
2
3
4
5
6
7
8
9
前端压缩

分片上传

直传对象存储(OSS)

返回URL

业务服务器存储URL

可以从 客户端、上传方式、服务端配置 三个方面解决:

  1. 客户端压缩图片
    在上传前对图片进行压缩或降低分辨率,例如通过 Canvas 或压缩库减少图片体积,降低上传时间。

  2. 分片上传 / 断点续传
    对大图片采用 分片上传,将文件拆分成多个 chunk 上传,服务端再合并,可以避免单次上传过大导致的超时问题。

  3. 直传 OSS
    采用 客户端直传 OSS(预签名 URL / STS),避免图片先经过业务服务器,减少服务器带宽压力,提高上传速度。

  4. 调整服务端限制
    Spring Boot / Nginx 中适当增加 max-file-sizemax-request-size 或请求超时时间。


设计C端多张大图同时上传的方案?

  1. 客户端图片压缩

在上传前对图片进行压缩或降低分辨率,例如通过 Canvas 或图片压缩库将图片质量压缩到 70%~80%,减少上传体积,提高上传速度。

  1. 并发上传控制

多张图片不要一次性全部并发上传,而是采用 并发队列控制

  • 将图片放入上传队列
  • 控制最大并发数(如 3~5 个)

示例流程:

1
2
3
4
5
6
7
图片列表

上传队列

并发控制(最大3个)

依次上传

优点:

  • 避免网络拥塞
  • 防止浏览器或服务器压力过大
  1. 分片上传(大图)

如果单张图片较大(如 >5MB),采用 分片上传

流程:

1
2
3
4
5
6
7
图片

切分为多个 chunk

并行上传 chunk

服务端合并

优点:

  • 避免单次上传超时
  • 支持断点续传
  1. 直传对象存储(OSS)

推荐采用 客户端直传 OSS

1
2
3
4
5
6
7
8
9
客户端

获取上传凭证(STS / 预签名URL

直接上传 OSS

返回图片URL

业务服务器只保存URL

优点:

  • 减少业务服务器带宽压力
  • 上传速度更快
  • 支持 CDN 加速
  1. 上传进度与用户体验

前端需要展示:

  • 单图上传进度
  • 总体上传进度
  • 失败重试提示

提升用户体验。


1.4 Nacos / 微服务

Nacos作为注册中心的作用是什么?

主要作用:

  1. 服务注册
  2. 服务发现
  3. 配置中心

Nacos 作为注册中心的主要作用是 实现微服务的服务注册与服务发现

具体作用:

  1. 服务注册
    微服务启动时会将自己的 IP、端口、服务名等信息注册到 Nacos

  2. 服务发现
    其他服务调用时可以 从 Nacos 获取目标服务实例列表,而不需要写死地址。

  3. 负载均衡支持
    客户端(如 Spring Cloud LoadBalancer、OpenFeign)从 Nacos 获取多个实例后,可以进行 负载均衡调用

  4. 健康检查
    Nacos 会通过 心跳机制检测服务实例是否存活,自动剔除异常实例。


服务调用时是否需要实时从Nacos拉取服务信息?

不需要。

一般会 缓存服务实例列表

客户端通过 本地缓存 + 心跳机制更新。

服务启动时会 从 Nacos 拉取服务实例列表并缓存到本地,之后服务调用一般是 从本地缓存中获取实例信息并进行负载均衡,而不是每次都实时访问 Nacos。

当服务实例发生变化(如实例上下线)时,Nacos 会通过 推送机制或客户端定时拉取 更新本地缓存。

总结:
服务调用通常使用 本地缓存的服务列表,只有在实例变化时才会与 Nacos 同步更新,而不是每次调用都实时拉取。


注册中心存在什么坏处?

主要问题:

  1. 系统复杂度增加

  2. 网络依赖增强

  3. 注册中心可能成为单点故障

  4. 引入系统复杂度
    需要额外部署和维护注册中心(如 Nacos、Eureka),增加系统架构复杂度。

  5. 可能成为单点故障
    如果注册中心集群不可用,新的服务实例可能无法注册,服务治理能力下降。

  6. 一致性与延迟问题
    服务实例上下线后,客户端缓存更新可能存在 短暂延迟,导致调用到失效实例。

  7. 运维成本增加
    需要维护注册中心集群、监控其健康状态和数据同步。


注册中心挂了服务会不可用吗?

不一定。

如果 注册中心挂了

  1. 已有服务通常仍然可以调用
    因为服务消费者会 缓存本地的服务实例列表,调用时使用本地缓存进行负载均衡。

  2. 新的服务实例无法注册
    新启动的服务无法注册到注册中心,其他服务也无法发现它。

  3. 服务实例变化无法同步
    如果有服务下线或扩容,客户端缓存无法及时更新,可能会调用到失效实例。

总结:
注册中心挂掉后,已有服务短时间内仍然可以正常调用,但服务治理能力会下降,新服务注册和实例更新会受到影响。


注册中心的注册表该如何设计?

注册中心的 注册表(Service Registry) 主要用于存储所有服务实例信息,一般采用 服务维度 → 实例维度 的结构设计。

  1. 核心数据结构

通常设计为 Map + 实例列表

1
ServiceName  →  List<ServiceInstance>

示例:

1
2
3
4
5
6
7
UserService
├── 192.168.1.10:8080
├── 192.168.1.11:8080

OrderService
├── 192.168.1.20:8080
├── 192.168.1.21:8080
  1. 实例信息设计

每个 ServiceInstance 一般包含:

  • serviceName:服务名
  • ip:服务IP
  • port:端口
  • instanceId:实例唯一ID
  • metadata:元数据(版本、环境等)
  • status:健康状态
  • lastHeartbeatTime:最后心跳时间

示例:

1
2
3
4
5
6
7
8
9
10
{
"serviceName": "user-service",
"ip": "192.168.1.10",
"port": 8080,
"instanceId": "user-service-1",
"status": "UP",
"metadata": {
"version": "v1"
}
}

能否用Caffeine做本地注册表?

可以,但不推荐。

因为:

  • 本地缓存无法保证多节点一致性
  • 更新不及时

Caffeine作为本地注册表的好处坏处?

优点:

  • 访问速度快
  • 减少网络调用

缺点:

  • 数据一致性问题
  • 更新延迟

注册中心和本地注册表之间的更新机制?

注册中心和本地注册表之间通常通过 订阅 + 推送 / 定时拉取 的方式进行更新。

基本机制:

  1. 服务启动时拉取
    服务启动后从注册中心(如 Nacos)拉取服务实例列表,并缓存到本地注册表。

  2. 订阅服务变更
    客户端会向注册中心 订阅服务实例变化事件

  3. 变更推送更新
    当有服务实例 上线、下线或健康状态变化时,注册中心会 推送变更通知给客户端。

  4. 更新本地注册表
    客户端收到通知后 更新本地缓存的服务实例列表

  5. 兜底定时同步
    同时客户端还会 定时拉取注册表作为兜底机制,防止推送丢失。


1.5 OpenFeign / Sentinel


1.6 Elasticsearch

项目中如何构建ES搜索服务?

可以按 “数据同步 → 索引设计 → 查询实现 → 功能增强” 的流程回答:

1. 构建索引结构
根据药品信息设计 Elasticsearch 索引,例如字段包括:

  • drugName(药品名称,text 类型,用于全文检索)
  • spec(规格)
  • manufacturer(生产厂家)
  • description(药品说明)
  • createTime(时间字段)

对需要搜索的字段使用 分词器(如 IK 分词器)

2. 数据同步到 ES
将数据库中的药品数据同步到 ES:

  • 项目启动时进行 全量同步
  • 新增或修改药品信息时,通过 异步消息或业务逻辑同步更新 ES 索引

3. 构建搜索接口
在后端实现搜索服务,例如:

  • 使用 multi_matchmatch 实现 关键词全文检索
  • 使用 fuzzywildcard 实现 模糊匹配

4. 支持高亮与分页

  • 高亮:使用 ES highlight 功能对命中的关键词进行高亮显示
  • 分页:通过 from + size 实现分页查询

5. 返回搜索结果
将 ES 返回的结果解析为 DTO,返回给前端展示。

在项目中我主要做了三步:

  1. 设计 ES 索引结构,对药品名称和说明等字段使用 IK 分词实现全文检索
  2. 将 MySQL 中的药品数据同步到 ES,启动时全量同步,新增或修改时增量更新
  3. 实现搜索接口,使用 match/multi_match 做关键词检索,并结合 highlight 实现关键词高亮、from+size 实现分页查询

这样就实现了药品信息的 全文检索、模糊匹配、高亮展示和分页查询


深度分页如何优化?

在 Elasticsearch 中,深度分页(如 from + size 很大)会导致 性能下降和内存占用增加,因为 ES 需要先排序并丢弃大量数据。常见优化方式有:

  1. 使用 search_after(推荐)

基于上一页最后一条数据的 排序值继续查询,而不是使用 from

特点:

  • 不需要跳过大量数据
  • 适合 无限滚动或翻页场景
    只适合顺序翻页,不支持随机跳页,但可以避免深度分页性能问题。
  1. 限制最大分页深度

通过 index.max_result_window 限制最大分页,例如默认 10000 条,防止用户请求过深分页。


1.7 Canal

MySQL中binlog的作用是什么?

MySQL 中 **binlog(二进制日志)**主要用于记录数据库的 数据变更操作(如 INSERTUPDATEDELETE)。

主要作用:

  1. 主从复制
    从库通过读取主库的 binlog,同步数据,实现 数据复制

  2. 数据恢复
    结合全量备份,可以通过 重放 binlog 实现数据恢复(Point In Time Recovery)。

  3. 数据同步 / 数据订阅
    一些系统(如 Canal、Debezium)可以解析 binlog,实现 数据同步到 ES、MQ 等系统


binlog模式

MySQL 的 binlog 有三种模式

  1. STATEMENT(语句模式)
    记录执行的 SQL 语句,例如 UPDATE user SET age=20 WHERE id=1

    • 优点:日志体积小
    • 缺点:某些函数或非确定性语句可能导致 主从数据不一致
  2. ROW(行模式)
    记录 每一行数据的变化(修改前和修改后)。

    • 优点:数据一致性好
    • 缺点:日志体积较大
  3. MIXED(混合模式)
    MySQL 会 自动选择使用 STATEMENT 或 ROW。

    • 一般情况使用 STATEMENT
    • 不安全语句使用 ROW

面试简答版:
MySQL 的 binlog 有 STATEMENT、ROW、MIXED 三种模式,分别记录 SQL 语句、行数据变化、或两者混合。生产环境通常推荐使用 ROW 模式以保证主从数据一致性。


1.8 二级缓存

Redis更新后多节点Caffeine数据不一致如何解决?

如果 Redis 更新后,多节点的 Caffeine 本地缓存出现数据不一致,常见解决方案有:

  1. 使用消息通知(推荐)

当 Redis 数据更新时,通过 MQ 或 Redis Pub/Sub 发送缓存失效通知,各节点收到消息后 删除或更新本地 Caffeine 缓存

流程:

1
2
3
4
5
6
7
8
9
数据更新

更新 Redis

发布缓存失效消息

各服务节点接收消息

删除本地 Caffeine 缓存
  1. 设置本地缓存过期时间

给 Caffeine 设置 TTL(过期时间),即使未及时同步,也会在一定时间后自动失效,作为兜底方案。

  1. 定时刷新缓存

通过 定时任务定期刷新本地缓存,保证最终一致性。

面试简答版:

通常通过 发布订阅或消息队列实现缓存失效通知:当 Redis 更新时发送消息,各服务节点收到通知后删除本地 Caffeine 缓存,同时配合 TTL 过期机制作为兜底,保证缓存最终一致。


2 智能高并发秒杀系统

2.0 整体

高并发秒杀系统的整体设计流程是什么?

1️⃣ 活动准备阶段
在秒杀开始前,将秒杀商品和库存信息初始化到 MySQL,并提前预热到 Redis 中,减少活动开始后的数据库压力。

2️⃣ 活动开启
系统开启秒杀活动,用户开始发送秒杀请求。

3️⃣ 用户抢购请求处理
用户请求到达后,系统在 Redis 中进行库存扣减和资格校验,通过原子操作快速判断是否还有库存。

4️⃣ 库存抢购阶段
多个用户并发抢夺库存,当 Redis 中的库存被扣减到 0 时,说明商品已经售罄。

5️⃣ 最终计算阶段
系统统计成功抢购的用户请求,并生成对应的订单数据。

6️⃣ 异步落库阶段
通过 消息队列(MQ) 将成功订单异步写入 MySQL,完成订单持久化,同时更新相关状态。

7️⃣ 流程结束
库存售罄后秒杀结束,系统返回用户秒杀结果。


2.1 高并发秒杀策略

展开讲秒杀系统中多种高并发策略的实现、区别、优缺点,以及不同场景的选型依据?

1. synchronized 同步锁

实现方式:
在应用层对秒杀逻辑加 synchronized 锁,同一时间只允许一个线程对同一个商品执行库存扣减和订单创建。

优点:

  • 实现简单
  • 能保证线程安全,强一致性
  • 逻辑清晰

缺点:

  • 只能在 单机环境生效
  • 锁竞争严重,并发能力差
  • 不适合高并发场景

适用场景:

  • 低并发秒杀
  • 单体应用或功能演示场景
2. 数据库乐观锁

实现方式:
在数据库表中增加 version 字段或通过 update stock where stock > 0 的方式进行 CAS 更新库存,更新成功才算抢购成功。

优点:

  • 依赖数据库保证一致性
  • 强一致性
  • 不需要应用层加锁
  • 实现相对简单

缺点:

  • 高并发时 大量请求会更新失败
  • 数据库压力较大
  • 吞吐量有限

适用场景:

  • 中等并发场景
  • 对数据一致性要求较高

3. 线程池 + Redis原子计数 + MQ异步落库

实现方式:

  1. 将库存预热到 Redis
  2. 用户请求进入 线程池 进行并发控制
  3. 使用 Redis原子计数(如 INCR ) 增加库存
  4. 抢购成功后将订单消息发送到 MQ
  5. 消费者 异步创建订单并落库 MySQL

优点:

  • Redis保证 高并发库存扣减性能
  • MQ实现 削峰填谷
  • 大幅降低数据库压力
  • 支撑高并发秒杀

缺点:

  • 系统复杂度较高
  • 需要处理 消息重复、幂等、缓存一致性问题

适用场景:

  • 高并发秒杀系统
  • 电商大促等高流量场景

仅判断库存大于零能否解决CAS的ABA问题?

不能解决。

原因:
CAS 的 ABA 问题指的是:一个值从 A → B → A 发生变化,但 CAS 只比较当前值是否等于 A,无法感知中间是否被修改过。

如果只判断 库存是否大于 0,只能保证库存不会变成负数,但无法判断库存是否在中间被其他线程修改过,因此 仍然可能存在 ABA 问题

常见解决方案:

  • 使用 版本号(version)或时间戳
  • 通过 AtomicStampedReference 等带版本的 CAS 机制

不用原子类仅用Integer能否解决ABA问题?

不能解决。

  1. ABA问题本质
  • ABA 问题发生在 CAS(Compare-And-Swap)操作中:某个变量值从 A → B → A,CAS 比较时只看当前值是否等于 A,无法感知中间是否被其他线程修改过。
  • 如果你仅用 Integer(普通对象)进行比较或判断,比如 if (stock > 0),这只是一个 值检查,而不是原子操作。
  1. Integer 的局限性
  • 普通 Integer 操作不是原子性的,多线程同时读写会出现 竞态条件
  • 即使用 Integer 对象包装,也无法记录“中间是否被修改过”的版本信息。
  1. 正确做法
  • 解决 ABA 问题需要 带版本号的 CAS原子类,例如:

    • AtomicInteger + 版本号/时间戳
    • AtomicStampedReference
  • 仅靠普通 Integer 判断库存大于零 无法感知中间变化,ABA 问题依然存在。


分布式场景下如何解决ABA问题?

在 MySQL 分布式环境下,单机的乐观锁(基于 versionstock > 0 的 CAS 更新)本身 无法保证全局一致性,也容易出现 ABA 问题,因为多个节点可能同时读到同一个库存值或版本号,然后都尝试更新,导致冲突无法感知。解决方法通常有以下几种:

1️⃣ 全局版本号或时间戳

做法:

  • 在库存表增加 全局唯一版本号(version 或 timestamp)字段
  • 更新时使用:
1
2
3
UPDATE stock_table
SET stock = stock - 1, version = version + 1
WHERE product_id = ? AND version = ?
  • 每次更新前读取最新版本号,确保并发节点看到的版本号不同
  • 若版本号不匹配,则更新失败,需要重试

优点:

  • 保留乐观锁思路
  • 保证节点之间不会因为 ABA 问题重复扣减

缺点:

  • 高并发下可能导致大量更新失败重试
  • 数据库压力增加

2️⃣ 分布式锁 + 乐观锁

做法:

  • 使用 Redis / ZooKeeper / etcd 等分布式锁,对库存修改操作加锁
  • 在锁保护下执行 乐观锁 CAS 更新

优点:

  • 分布式环境下可以保证全局唯一性
  • ABA 问题消失

缺点:

  • 分布式锁管理复杂,需要考虑锁超时、节点宕机、锁续期等

用线程池削峰填谷的好处、坏处分别是什么?

线程池削峰填谷 是秒杀系统中常见的手段,主要是对瞬时高并发请求进行平滑处理。可以这样简要说明:

✅ 好处
  1. 削峰平滑请求
  • 将瞬时高并发的请求排队到线程池中,避免直接冲击后端资源(数据库、缓存等)。
  1. 控制并发数量
  • 通过线程池大小限制同时处理的请求数,避免系统过载。
  1. 提高系统稳定性
  • 避免高并发导致应用线程过多、CPU/内存消耗过大或崩溃。
  1. 资源复用
  • 线程池复用线程,减少线程频繁创建和销毁的开销,提高性能。
❌ 坏处 / 局限性
  1. 不能保证全局顺序
  • 多节点分布式场景下,每个节点独立线程池,无法统一全局顺序。
  1. 不能解决库存一致性问题
  • 仅控制并发数量,库存仍需要 Redis 原子操作或数据库乐观锁保障。
  1. 线程池满时请求被拒绝或阻塞
  • 如果瞬时请求过大,线程池队列满,会出现请求丢失或等待超时,需要额外限流策略。
  1. 复杂度增加
  • 需要合理设置线程池大小、队列长度、拒绝策略,否则可能成为系统瓶颈。

线程池队列满了会发生什么?

线程池会触发 拒绝策略

常见策略:

  1. AbortPolicy(默认)

    • 直接抛异常
  2. CallerRunsPolicy

    • 由调用线程执行
  3. DiscardPolicy

    • 丢弃任务
  4. DiscardOldestPolicy

    • 丢弃最旧任务

服务器扩容后,单机线程池在分布式场景下会导致什么问题?

问题是:

线程池隔离

每个实例的线程池是独立的。

可能导致:

  1. 流量不均衡
  2. 某些实例线程池满
  3. 某些实例空闲

在分布式场景下,如果服务器扩容了新节点,但每台服务器仍使用单机线程池,可能会导致以下问题:

  1. 请求分布不均:单机线程池只能控制本机并发,无法感知集群整体负载,可能导致部分节点过载,而其他节点空闲。
  2. 削峰失效:线程池的“削峰填谷”效果仅在单机生效,整个集群仍可能出现瞬时请求高峰,无法全局平滑流量。
  3. 资源利用不均:新节点可能闲置,而老节点线程池满载,整体资源利用率低。
  4. 延迟不稳定:部分节点排队等待线程执行,可能出现响应延迟差异,影响用户体验。

核心问题是:单机线程池只能做本地并发控制,无法协调分布式系统的整体负载。


单机场景下对比三种秒杀方案优劣

方案 优点 缺点
synchronized 实现简单 并发能力差
数据库乐观锁 数据一致性好 数据库压力大
Redis+MQ 并发能力强 系统复杂

秒杀场景中选择MongoDB而非MySQL的原因?

主要原因:

  1. 写入性能高

MongoDB适合高并发写入。

  1. 文档结构灵活

订单结构变化时无需修改表结构。

  1. 水平扩展能力强

支持分片。


MongoDB和MySQL核心区别

MySQL:

  • 关系型数据库
  • 强事务
  • 表结构固定

MongoDB:

  • 文档数据库
  • JSON结构
  • 灵活 schema

MongoDB的集群部署机制有哪些?

主要有三种:

  1. Replica Set(副本集)

主从复制。

  1. Sharding(分片集群)

水平扩展。

  1. Standalone

单节点。


MongoDB数据分片策略有哪些?

MongoDB主要有三种:

  1. Range Sharding

范围分片。

  1. Hash Sharding

哈希分片。

  1. Zone Sharding

区域分片。

MongoDB 的分片(Sharding)策略主要用于水平扩展,将数据分布到多个分片(Shard)上,提高读写吞吐量和存储能力。MongoDB 提供了几种主要的 分片键策略,决定了数据如何分布:

  1. 基于哈希的分片(Hashed Sharding)
  • 原理:对分片键的值进行哈希计算,然后根据哈希值把数据均匀分布到各个分片。

  • 优点

    • 自动均衡数据,避免热点分片。
    • 写入均匀,不容易集中到某个节点。
  • 缺点

    • 范围查询效率低,因为连续的值被打散。
    • 不适合需要大量范围扫描的场景。
  • 适用场景:高并发写入、均匀分布的键值。

  1. 基于范围的分片(Range Sharding)
  • 原理:根据分片键的范围把数据划分到不同分片,例如用户ID 0–9999 在分片A,10000–19999 在分片B。

  • 优点

    • 范围查询高效,连续数据落在同一个分片。
    • 易于进行有序扫描。
  • 缺点

    • 写入可能集中在部分分片(热点问题),导致负载不均衡。
  • 适用场景:范围查询频繁、读操作多的业务场景。


如何保证MQ中的消息不丢失?

在消息队列(MQ)中保证消息不丢失,可以从 生产端、消息中间件和消费端 三个环节控制:

  1. 生产端

    • 开启 消息持久化(Persistent),确保消息写入磁盘。
    • 使用 同步发送(Sync Send)而非异步发送,保证发送成功确认。
  2. 消息中间件

    • 持久化存储:将消息写入磁盘或可靠存储。
    • 主从复制/集群模式:当节点宕机时,从节点可以接管。
    • 幂等机制:保证重复消息不会影响业务。
  3. 消费端

    • 手动确认(ACK):消费者处理完消息后再确认,避免处理失败导致消息丢失。
    • 重试机制:消费失败时可重试或入死信队列(DLQ)。

核心原则:消息在任意环节失败时,都能依赖持久化、确认和重试机制确保消息最终不丢失。


2.2 责任链模式

基于责任链模式做的预处理器机制有什么好处?

  1. 职责单一、解耦清晰

    • 每个预处理器只负责自己的一类初始化任务(如数据库、缓存、状态机),彼此独立。
    • 新增或修改某一环节时,不会影响其他环节,便于维护。
  2. 灵活可扩展

    • 可以动态调整责任链的顺序或增加新的预处理器,例如增加日志、限流、验证等步骤。
    • 无需修改核心流程逻辑,只需新增节点即可。
  3. 统一处理流程

    • 责任链模式统一管理各个初始化步骤,保证秒杀前所有必要资源都被正确准备,减少遗漏。
  4. 增强容错与可控性

    • 可以在某个节点失败时中断责任链,或做重试、补偿处理,提高系统稳定性。
  5. 代码复用性高

    • 各个预处理器可以在不同业务场景复用,例如秒杀、团购、预约活动都可共享同一套链式初始化逻辑。

为什么选择责任链而非状态机模式?

原因:

责任链适合 流程型处理

  1. 责任链模式的用途
  • 关注点:顺序执行、模块化处理、解耦初始化逻辑。

  • 适用场景:秒杀前的 预处理,比如:

    • 数据库初始化
    • 缓存加载
    • 状态机状态设置
  • 特点:每个节点只做自己的事情,按顺序执行,链条可以动态扩展或插入节点。

  • 核心价值:保证初始化步骤按正确顺序依次完成,且每个步骤独立、可复用、易维护。

  1. 状态机模式的用途
  • 关注点:状态管理 + 事件驱动,处理流程的合法性。

  • 适用场景:秒杀活动的 运行时流程,比如:

    • 秒杀开始、抢购中、秒杀结束
    • 用户下单、支付、发货等状态流转
  • 特点:用状态和事件驱动流程转换,保证流程合法性和可追踪。

  • 核心价值:控制整个秒杀活动的状态变化,防止非法操作或状态异常。

  1. 预处理逻辑本质是“顺序任务执行”,而不是“状态流转”

    • 数据库、缓存、状态机初始化只要按顺序执行即可,不需要复杂的状态和事件控制。
  2. 责任链更灵活

    • 可以动态增加、删除或重排初始化步骤,状态机实现起来复杂且不必要。
  3. 关注点分离

    • 责任链关注初始化任务
    • 状态机关注运行时流程和状态合法性
    • 两者结合使用,既保证初始化正确,又保证活动执行安全。

还了解哪些设计模式?

好的,我给你把你列的这几个设计模式做一个 简明扼要的总结,包括核心概念和典型应用场景(适合秒杀/分布式系统理解):

1. 策略模式(Strategy)
  • 核心思想:定义一系列算法(策略),将每个算法封装起来,使它们可以互相替换。
  • 特点:算法独立、可动态切换、符合开闭原则。
  • 应用:秒杀库存扣减策略(Redis Lua 脚本、乐观锁、CAS),根据不同活动动态选择策略。
2. 责任链模式(Chain of Responsibility)
  • 核心思想:将请求沿链传递,每个处理节点决定处理或交给下一个节点。
  • 特点:解耦发送者与接收者,灵活可扩展、顺序执行。
  • 应用:秒杀预处理器(数据库初始化 → 缓存加载 → 状态机设置),请求验证、订单处理链条。
3. 状态机模式(State)
  • 核心思想:允许对象在内部状态改变时改变行为,把状态封装成独立对象。
  • 特点:状态清晰、流程可控、避免大量 if/else。
  • 应用:秒杀活动状态管理(未开始、进行中、已结束)、订单状态流转(待支付 → 已支付 → 已完成)。
4. 单例模式(Singleton)
  • 核心思想:确保类只有一个实例,并提供全局访问点。
  • 特点:全局唯一、节约资源、控制访问。
  • 应用:全局配置管理、Redis 客户端、MQ 连接池、日志管理。
5. 代理模式(Proxy)
  • 核心思想:为对象提供代理,控制访问或增强功能,而不修改原对象。
  • 特点:可做权限控制、懒加载、接口增强。
  • 应用:接口限流、权限校验、RPC 远程调用代理。
6. 模板方法模式(Template Method)
  • 核心思想:定义算法骨架,将具体步骤延迟到子类实现。
  • 特点:复用算法框架、子类定制实现细节。
  • 应用:秒杀流程框架(统一执行流程:校验库存 → 扣减库存 → 下单 → 通知),不同活动逻辑由子类实现。

总结表格

模式 核心 特点 秒杀/分布式应用场景
策略模式 封装可替换算法 动态切换算法 库存扣减策略
责任链模式 顺序处理请求 解耦、可扩展 预处理器初始化
状态机模式 根据状态改变行为 流程可控 活动/订单状态管理
单例模式 全局唯一实例 节约资源、全局访问 配置、连接池、日志
代理模式 代理控制访问 权限/增强/懒加载 接口限流、RPC代理
模板方法模式 算法骨架 + 子类实现 复用框架 秒杀流程统一框架

实现两个经典的线程安全的单例模式?

饿汉式单例

1
2
3
4
5
6
7
8
9
public class MySingleton {
private static MySingleton instance = new MySingleton(); // 类加载时创建

private MySingleton(){}

public static MySingleton getInstance() {
return instance;
}
}

懒汉式单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MySingleton {
private static MySingleton instance = null; // 懒加载

private MySingleton(){}

public static MySingleton getInstance() {
if(instance == null){
synchronized(MySingleton.class){
if(instance == null){
instance = new MySingleton();
}
}
}
return instance;
}
}

2.3 状态机

为什么选择Spring StateMachine?

原因:

  1. Spring生态集成好

  2. 支持状态与事件驱动

  3. 支持持久化

  4. 状态流转逻辑清晰

  5. 与 Spring 框架无缝集成

  • 依赖注入:可以直接注入 Bean 到状态机的动作(Action)中,方便访问服务、DAO、缓存等。
  • 配置统一:可使用 @Configuration + Java DSL 或 XML 配置状态机,和 Spring 其它组件风格一致。
  • 事务支持:Action 执行可以参与 Spring 事务管理,保证状态变更与业务操作一致。
  1. 支持持久化
    基于 Redis 持久化
    将状态机上下文序列化存入 Redis

目标:状态机的当前状态、上下文变量等信息可以存储到外部存储中(数据库、Redis等)。

作用:系统宕机或重启后,可以恢复状态机到上次的状态,避免流程丢失或错误。

秒杀系统中状态机的状态有哪些?

常见状态:

1
2
3
4
活动开始创建
活动进行中
活动结算中
活动结束

依靠什么事件流转?

事件驱动:

例如:

1
2
3
开始活动
计算
结束

事件触发后状态发生转换。


为什么采用状态机模式?

传统写法:

1
2
if(status == 1)
else if(status == 2)

问题:

  • 状态逻辑混乱
  • 可维护性差

状态机好处:

  1. 状态清晰
  2. 状态转换明确
  3. 扩展方便

采用 状态机模式(State Pattern) 的核心原因是 将复杂流程和状态管理明确化、可控化,并解耦业务逻辑,尤其适合秒杀、订单或活动管理这类有明确状态和状态流转的业务场景。具体原因如下:

1️⃣ 明确状态和事件

  • 秒杀系统或订单系统通常有多种状态:

    • 秒杀活动:未开始 → 进行中 → 已结束
    • 订单:待支付 → 已支付 → 已完成/已取消
  • 状态机模式把 状态和事件分离出来,每个状态只关心自己允许的事件和转换规则,避免用大量 if/else 判断。

2️⃣ 状态和行为绑定

  • 每个状态可以绑定进入/离开动作(Action),事件触发动作(Action),保证业务逻辑和状态切换一致:

    • 例如:活动进入“进行中”状态 → 初始化库存、通知前端
    • 用户下单 → 库存扣减、生成订单

3️⃣ 避免状态错误

  • 状态机模式强制定义状态转换图(State + Event):

    • 防止非法操作或非法状态切换
    • 易于在高并发下保证流程正确性

5️⃣ 可维护性和扩展性高

  • 新增状态或事件只需修改状态机配置或新增状态类
  • 不需要修改核心业务逻辑,符合 开闭原则







1. AI医疗服务平台

1.0 整体

说说RAG、MCP的技术流程和技术交互栈分别是怎样的?

什么是向量化?

做向量化匹配时的主要标准是什么?

MCP协议的本质构成是什么?

尝试过在本地部署AI服务吗?

部署本地大模型服务时需要注意哪些问题?

该医疗系统能承受的QPS是多少,如何测试?

你认为当前这个医疗平台存在哪些优化点、做得不好的地方?

什么是大模型幻觉问题,你的项目中是如何尝试解决的?

该医疗平台在医疗问诊场景下对用户的核心价值是什么?

1.1 大模型对话/Prompt工程/对话上下文记忆/MongoDB

如何对大模型的回答和行为进行约束?

使用提示词(Prompt)时需要注意哪些要点?

若大模型给出错误医疗建议,如何从系统层面发现/拦截?

如何解决大模型对话的上下文超限(无法回话)问题?

MongoDB和MySQL的核心区别是什么?

自定义Memory组件的作用是什么,为何选用MongoDB存储对话上下文而非MySQL?

医疗问诊场景下的提示词工程体系是如何构建的?

除了角色定义、结构化输出约束,提示词工程还有哪些优化方法?

1.2 RAG

RAG检索增强的好处是什么?没有RAG直接调用大模型会存在什么问题?

除了RAG,还有哪些方式能为大模型提供领域上下文?

微调有哪些技术手段?

详细介绍AI医疗微服务平台的核心功能,重点讲RAG的落地实现?

RAG知识库的数据源来自哪里?

各类医疗文档(PDF/Word/TXT)如何转化为RAG的知识库,核心步骤是什么?

1.3 多模态/OSS/文件上传

图片上传时若出现图片过大、超时问题,该如何解决?

设计C端多张大图同时上传的方案,该如何设计?

项目中图像识别功能的识别对象、核心目的是什么?

1.4 Nacos/微服务/网关/Sa-token

Nacos作为注册中心的作用是什么?为什么需要注册中心?

服务调用时是否需要实时从Nacos拉取服务信息?有什么机制减少对Nacos的请求?

注册中心存在什么坏处?如果注册中心挂了,所有服务会不可用吗?

注册中心的注册表该如何设计?用什么存储类型/数据结构实现?

Redis挂了该如何处理?能否用Caffeine做本地注册表?

Caffeine作为本地注册表的好处、坏处分别是什么?

注册中心和本地注册表之间的更新机制是什么?

1.5 OpenFeign/Sentinel

1.6 ES

项目中是如何构建一个ES搜索服务的?

数据量特别大时,分页查询该如何优化(深度分页问题)?

1.7 Canal

MySQL中binlog的作用是什么?有几种模式,分别是什么?

1.8 二级缓存

二级缓存(Caffeine+Redis)中,如何解决Redis更新后多节点Caffeine数据不一致的问题?

2. 智能高并发秒杀系统

2.0 整体

高并发秒杀系统的整体设计流程是什么?

2.1 高并发秒杀策略

展开讲秒杀系统中多种高并发策略的实现、区别、优缺点,以及不同场景的选型依据;

仅判断库存大于零能否解决CAS的ABA问题?不用原子类仅用Integer能否解决ABA问题?分布式场景下如何解决ABA问题?

用线程池削峰填谷的好处、坏处分别是什么?线程池队列满了会发生什么?

服务器扩容后,单机线程池在分布式场景下会导致什么问题?

单机场景下对比synchronized锁、数据库乐观锁、Redis+MQ异步落库三种秒杀方案的优劣;

秒杀场景中选择MongoDB而非MySQL的原因?二者核心区别是什么?

MongoDB的集群部署机制有哪些?数据分片的策略有哪些,该策略的专业名称是什么?

如何保证MQ中的消息不丢失?

2.2 责任链模式

基于责任链模式做的预处理器机制有什么好处?没有该模式会有什么问题?

除了责任链模式,还有哪些更好的设计模式解决if-else嵌套问题?为什么选择责任链而非状态机模式?

除了责任链、状态机模式,还了解哪些设计模式?

2.3 状态机

选择Spring StateMachine做状态机管理的原因?调研过其他状态机框架吗?该框架的优势是什么?

秒杀系统中状态机的具体状态有哪些,依靠什么事件流转?

为什么采用状态机模式处理秒杀业务,与传统写法相比的好处是什么?

2.4 AI对话

3. 八股

3.1 JAVA基础

讲一下Java对象的生命周期?

用过HashMap吗?讲一下HashMap中哈希的原理?

equals和==的区别是什么?

哈希冲突的解决办法有哪些?HashMap是如何解决哈希冲突的?

HashMap的初始容量、负载因子分别是什么?为什么初始容量是2的幂次方?为什么负载因子是0.75?

HashMap是线程安全的吗?

Hashtable相较于ConcurrentHashMap解决线程安全问题有什么不好的地方?

3.2 JUC

线程池的参数有哪些,你都是怎么设置的?

讲一下你对synchronized的了解?

你理解的线程安全是什么?线程安全的三要素是什么?

synchronized保证了线程安全的哪些特性?它是怎么保证可见性的?

synchronized可以修饰什么东西?修饰静态方法时锁的是什么?修饰非静态方法时锁的是什么?

synchronized有哪些特点(可重入、公平性等)?

synchronized的锁膨胀优化过程是什么?

什么是CAS操作?

synchronized锁和Redis分布式锁的区别及各自原理?

使用线程池的注意事项有哪些,核心参数该如何设置?

Java为何会出现并发问题,需要用锁来处理?

若在Controller中定义int字段,每次请求对其+1会出现什么问题?

3.3 JVM

介绍一下JVM内存结构?

谈一下你对垃圾回收的理解?

垃圾的定义有两种方法,除了可达性分析还有一种是什么?分别详细讲解这两个方法。

垃圾回收算法的种类(新生代、老年代相关GC)有哪些,分别讲讲?

详细说说标记清除、标记整理、复制这三种垃圾回收算法?

Minor GC、Full GC一般分别用哪种垃圾回收算法,结合存活对象/垃圾对象的数量分析?

对于Java的内存优化有什么理解和想法?有没有用过内存友好的数据结构?如何避免内存泄漏的经验?

Java对象头的作用是什么,与锁有什么关联?

JVM中工作内存和主内存的区别是什么?

哪些对象会进入JVM的老年代?

若新生代内存不足,会如何处理?

介绍Java的类加载机制,核心步骤有哪些?

讲解Java的双亲委派机制,包含哪些类加载器、核心原理及设计好处?

3.4 JAVA框架

介绍一下IOC和AOP?

3.5 数据库

MySQL的索引有哪些类型,分别有什么特点?

聚簇索引和非聚簇索引的区别是什么?

B+树作为索引结构的核心特点是什么,与哈希表、B树的区别?

3.6 Redis

介绍一下redis持久化的机制?

讲解Redis跳表的底层数据结构,以及Redis为何选用跳表而非B+树、红黑树等数据结构?

简述红黑树的核心规则和特性?

3.7 微服务

4. 算法

  1. 反转链表
  2. 二叉树的层序遍历
  3. 实现线程安全的单例模式
  4. 将一个从小到大的数组,和一个从大到小的数组,合并成一个从小到大的数组

5. 面经

5.1 快手JAVA一面 KPI面(30分钟/3.9)

  1. 问个人情况?
  2. 问论文,介绍一下论文?
  3. 讲一下你的项目流程?
  4. 线程池的参数有哪些,你都是怎么设置的?
  5. 写一个动态规划算法,一般要注意什么?
  6. 介绍一下IOC和AOP?
  7. 介绍一下redis持久化的机制?
  8. 介绍一下你的项目中是如何构建一个ES搜索服务的?
  9. 介绍一下JVM内存结构?

算法:反转链表

5.2 B站客户端一面 (30分钟/3.11)

(一)背景与岗位匹配类

  1. 你做过客户端开发的工作吗?
  2. 你是只用过Java,Kotlin这门语言你听过吗?

(二)Java基础 - JVM与垃圾回收类

  1. 讲一下Java对象的生命周期?
  2. 谈一下你对垃圾回收的理解?
  3. 垃圾的定义有两种方法,除了可达性分析还有一种是什么?分别详细讲解这两个方法。
  4. 垃圾回收算法的种类(新生代、老年代相关GC)有哪些,分别讲讲?
  5. 详细说说标记清除、标记整理、复制这三种垃圾回收算法?
  6. Minor GC、Full GC一般分别用哪种垃圾回收算法,结合存活对象/垃圾对象的数量分析?
  7. 对于Java的内存优化有什么理解和想法?有没有用过内存友好的数据结构?如何避免内存泄漏的经验?

(三)Java基础 - 集合与哈希类

  1. 用过HashMap吗?讲一下HashMap中哈希的原理?
  2. equals和==的区别是什么?
  3. 哈希冲突的解决办法有哪些?HashMap是如何解决哈希冲突的?
  4. HashMap的初始容量、负载因子分别是什么?为什么初始容量是2的幂次方?为什么负载因子是0.75?
  5. HashMap是线程安全的吗?
  6. Hashtable相较于ConcurrentHashMap解决线程安全问题有什么不好的地方?

(四)Java基础 - 并发编程类

  1. 讲一下你对synchronized的了解?
  2. 你理解的线程安全是什么?线程安全的三要素是什么?
  3. synchronized保证了线程安全的哪些特性?它是怎么保证可见性的?
  4. synchronized可以修饰什么东西?修饰静态方法时锁的是什么?修饰非静态方法时锁的是什么?
  5. synchronized有哪些特点(可重入、公平性等)?
  6. synchronized的锁膨胀优化过程是什么?
  7. 什么是CAS操作?

(五)AI应用与认知类

  1. 你用过OpenAI、ChatGPT等AI工具吗?你是怎么看这些AI工具的?
  2. 你具体是怎么用AI工具做项目、写代码的?AI生成的代码占比大概有多少?
  3. 你觉得使用AI工具存在哪些风险和不顺手的地方?

5.3 美团JAVA一面(45分钟/3.12)

(一)高并发秒杀系统项目深挖

  1. 展开讲秒杀系统中多种高并发策略的实现、区别、优缺点,以及不同场景的选型依据;
  2. 仅判断库存大于零能否解决CAS的ABA问题?不用原子类仅用Integer能否解决ABA问题?分布式场景下如何解决ABA问题?
  3. 用线程池削峰填谷的好处、坏处分别是什么?线程池队列满了会发生什么?
  4. 服务器扩容后,单机线程池在分布式场景下会导致什么问题?
  5. 单机场景下对比synchronized锁、数据库乐观锁、Redis+MQ异步落库三种秒杀方案的优劣;
  6. 秒杀场景中选择MongoDB而非MySQL的原因?二者核心区别是什么?
  7. MongoDB的集群部署机制有哪些?数据分片的策略有哪些,该策略的专业名称是什么?

(二)设计模式相关

  1. 基于责任链模式做的预处理器机制有什么好处?没有该模式会有什么问题?
  2. 除了责任链模式,还有哪些更好的设计模式解决if-else嵌套问题?为什么选择责任链而非状态机模式?
  3. 除了责任链、状态机模式,还了解哪些设计模式?

(三)Spring相关

  1. 选择Spring StateMachine做状态机管理的原因?调研过其他状态机框架吗?该框架的优势是什么?

(四)AI医疗微服务平台项目深挖

  1. RAG检索增强的好处是什么?没有RAG直接调用大模型会存在什么问题?
  2. 除了RAG,还有哪些方式能为大模型提供领域上下文?
  3. 微调有哪些技术手段?
  4. Nacos作为注册中心的作用是什么?为什么需要注册中心?
  5. 服务调用时是否需要实时从Nacos拉取服务信息?有什么机制减少对Nacos的请求?
  6. 注册中心存在什么坏处?如果注册中心挂了,所有服务会不可用吗?
  7. 注册中心的注册表该如何设计?用什么存储类型/数据结构实现?
  8. Redis挂了该如何处理?能否用Caffeine做本地注册表?
  9. Caffeine作为本地注册表的好处、坏处分别是什么?
  10. 注册中心和本地注册表之间的更新机制是什么?

(五)缓存相关

  1. 二级缓存(Caffeine+Redis)中,如何解决Redis更新后多节点Caffeine数据不一致的问题?

(六)算法编程题

  1. 实现二叉树的层序遍历,分析算法时间复杂度;
  2. 手写线程安全的单例模式(后续因不会换题)。

5.4 B站客户端二面(20分钟/3.12)

(一)岗位匹配与客户端基础类

  1. 你有过端上应用级开发/DEMO编写的经验吗?
  2. 对端上的标准技术栈有了解吗?
  3. 了解端上的消息通信方案是怎样的吗?
  4. 安卓中比较常用的本地数据存储方式有哪些?
  5. 了解端上的一些UI组件吗?
  6. 对标准的视频播放协议流程有了解吗?

(二)AI核心技术类

  1. 说说RAG、MCP的技术流程和技术交互栈分别是怎样的?
  2. 什么是向量化?
  3. 做向量化匹配时的主要标准是什么?
  4. MCP协议的本质构成是什么?
  5. 尝试过在本地部署AI服务吗?
  6. 部署本地大模型服务时需要注意哪些问题?

(三)服务端高并发与缓存类

高并发业务场景下,设计服务端缓存架构需要注意哪些东西?

(四)方案设计类

如果要做本地数据库的升级方案,过程中需要注意哪些问题?

(五)项目与技术分享类

有没有做过有趣/有技术深度的东西,可以做基础分享?

5.5 高德JAVA一面(55分钟/3.13)

(一)AI医疗微服务平台项目深挖

  1. 如何对大模型的回答和行为进行约束?
  2. 使用提示词(Prompt)时需要注意哪些要点?
  3. 若大模型给出错误医疗建议,如何从系统层面发现/拦截?
  4. 如何解决大模型对话的上下文超限(无法回话)问题?
  5. 图片上传时若出现图片过大、超时问题,该如何解决?
  6. 设计C端多张大图同时上传的方案,该如何设计?
  7. 数据量特别大时,分页查询该如何优化(深度分页问题)?
  8. 该医疗系统能承受的QPS是多少,如何测试?

(二)数据库核心知识

  1. MySQL中binlog的作用是什么?有几种模式,分别是什么?
  2. MongoDB和MySQL的核心区别是什么?
  3. MySQL的索引有哪些类型,分别有什么特点?
  4. 聚簇索引和非聚簇索引的区别是什么?
  5. B+树作为索引结构的核心特点是什么,与哈希表、B树的区别?

(三)并发编程与锁

  1. synchronized锁和Redis分布式锁的区别及各自原理?
  2. 使用线程池的注意事项有哪些,核心参数该如何设置?
  3. Java为何会出现并发问题,需要用锁来处理?
  4. JVM中工作内存和主内存的区别是什么?
  5. 若在Controller中定义int字段,每次请求对其+1会出现什么问题?
  6. Java对象头的作用是什么,与锁有什么关联?

(四)MQ核心知识

如何保证MQ中的消息不丢失?

(五)高并发秒杀系统项目深挖

  1. 高并发秒杀系统的整体设计流程是什么?
  2. 秒杀系统中状态机的具体状态有哪些,依靠什么事件流转?
  3. 为什么采用状态机模式处理秒杀业务,与传统写法相比的好处是什么?

(六)JVM核心知识

  1. 哪些对象会进入JVM的老年代?
  2. 若新生代内存不足,会如何处理?

(七)算法编程题

一个从小到大、一个从大到小的两个数组,合并成一个整体从小到大的新数组。

5.6 美团大模型应用开发一面(一个小时/3.20)

(一)Java后端核心基础

  1. 讲解Redis跳表的底层数据结构,以及Redis为何选用跳表而非B+树、红黑树等数据结构?
  2. 简述红黑树的核心规则和特性?
  3. 介绍Java的类加载机制,核心步骤有哪些?
  4. 讲解Java的双亲委派机制,包含哪些类加载器、核心原理及设计好处?

(二)AI医疗微服务平台项目深挖

  1. 详细介绍AI医疗微服务平台的核心功能,重点讲RAG的落地实现?
  2. 项目中图像识别功能的识别对象、核心目的是什么?
  3. 自定义Memory组件的作用是什么,为何选用MongoDB存储对话上下文而非MySQL?
  4. 医疗问诊场景下的提示词工程体系是如何构建的?
  5. 除了角色定义、结构化输出约束,提示词工程还有哪些优化方法?
  6. RAG知识库的数据源来自哪里?
  7. 各类医疗文档(PDF/Word/TXT)如何转化为RAG的知识库,核心步骤是什么?
  8. 你认为当前这个医疗平台存在哪些优化点、做得不好的地方?
  9. 什么是大模型幻觉问题,你的项目中是如何尝试解决的?
  10. 该医疗平台在医疗问诊场景下对用户的核心价值是什么?

(三)高并发秒杀系统项目

秒杀系统是否和AI做了结合,具体是如何结合的?

(四)AI工具使用与认知

  1. 了解RAG、Agent吗?是否实际做过Agent相关开发?
  2. 日常使用AI编程工具(如Coding)的频率和方式是什么?
  3. AI生成代码的质量如何,使用过程中遇到了哪些问题,如何解决?

(五)算法编程题

  1. 根据指定字符串构建多叉树;
  2. 手写算法题:给定整数数组,找出最大子数组和;
  3. 要求阐述算法思路并完成代码实现。