最近帮学员复盘面试时发现一个共性问题:很多人明明做过智能 SAAS、客服系统这类项目,却在面试时讲不清**「索引怎么优化」「多租户怎么落地」「OOM 怎么排查」这些核心问题** —— 要么只说 “我用了 Redis”“加了索引”,要么东拉西扯没重点,错失 offer 太可惜。
今天分享的这份面经,来自学员真实面试复盘,涵盖数据库优化、缓存设计、多租户、故障排查、微服务 5大核心模块,14 个高频问题全附「项目场景 + 落地答案」,看完直接套用,帮你把项目经验转化为面试加分项!
1.优化数据库交互 怎么优化的
学员回答:我说的是批处理 批量提交什么的 我觉得比较牵强
复盘建议:可以往数据库优化方面说 比如索引优化
- 场景 1:用户筛选 前端大类VUE分类的可用题目 刷题时最高频的查询 优化前:只建了subject_id单字段索引,查询时仍需回表过滤难度和启用状态,全表扫描比例高,耗时 1.2s; 优化后:建联合索引,且把查询常用的 题目、选项、答案 字段加入索引(覆盖索引),查询时无需回表,直接从索引取数据,耗时降到 0.1s,效率提升 10 倍
- 场景 2:用户查自己的 错题记录 优化前:无专用索引,查用户错题时需遍历全表,耗时 800ms; 优化后:建唯一联合索引(user_id, subject_id),查询直接命中索引,耗时降到 100ms,同时避免重复错题记录 唯一索引保证
2.接口平均响应时间提升时间 怎么提升的
学员回答:我说的是少量接口高频题目 加redis缓存
这个首先确立背景,缓存的优化其实和数据库优化也是相关联的,可以参考我这个背景:优化智能学习SAAS系统的两个核心高频接口——科目下题目列表查询(用户刷题时按科目、难度筛选题目)和个人错题查询,首先是数据库优化和1问一样就按照上面的说,然后是缓存优化:
- 缓存高频静态数据:比如 Java、Python 等热门科目的题目数据(这些科目占总查询量的 80%),用科目 ID + 难度作为 key,缓存题目列表,设置 30 分钟过期时间;同时用布隆过滤器过滤不存在的题目 ID,避免缓存穿透(比如用户传错题目 ID,直接在缓存层拦截,不请求数据库);
- 缓存用户个性化高频数据:用户的错题集用 Redis Hash 结构存储,key 是用户 ID + 错题集标识,field 是题目 ID,value 是错题解析、订正状态,这样用户查错题时不用查数据库,直接从 Hash 里取,耗时从 200ms 降到 20ms 内,这个场景项目里虽然没有错题集但是也可以说,或者把错题集换成题目id也可以
- 防缓存雪崩:给不同科目的缓存设置随机过期时间(比如 25-35 分钟),避免同一时间大量缓存失效,导致数据库突然承压,这一步让 80% 的查询直接走缓存,数据库 QPS 降低了 40%,接口基础耗时从 100ms 进一步降到 50ms 左右 然后还可以提到并发优化比如个人错题查询接口,原本要先查用户的错题 ID 列表,再循环查每个错题的题目信息、解析,相当于 1 次接口请求要发 5-10 次数据库查询(或缓存查询),串行执行耗时久。我用 CompletableFuture 做了并发处理:先查错题 ID 列表,然后用批量查询 + 并发获取的方式,同时请求多个错题的信息,把串行的 500ms(5 次 100ms)变成并行的 120ms(单次 100ms + 并发开销 20ms)。 然后再进阶点可以把下面的内容说一下,这个就看你的理解程度了,对题目列表查询接口的题目选项、解析等非核心数据,用异步线程异步加载,先返回核心的题目列表,非核心数据加载完再通过长连接推给前端,接口首屏响应时间从 150ms 降到 80ms,用户感知更流畅。
3.多租户隔离怎么实现的
学员回答:我说用了数据库加tenantid加权限控制
首先多租户隔离概念是多企业 / 机构共用系统,但数据完全独立、资源不冲突,同时还要适配部分企业私有化部署的需求,所以数据库+tenantId有点单薄,可以从覆盖数据、权限、资源的全链路设计出发,tenantId 是核心,但要说明 如何用技术确保 tenantId 不遗漏,避免手动加条件的低级问题
-
数据隔离是基础,可以采用共享数据库 + 单表隔离的方案,首先在所有业务表(如用户表、题目表、套卷记录表、考试记录表)中加入tenant_id字段,作为租户唯一标识,为了避免开发时忘记加 tenantId 条件导致数据泄露,我们用MyBatis 插件做自动注入:在 SQL 执行前,插件会从当前请求上下文比如通过ThreadLocal获取租户 ID,自动在where条件后追加and tenant_id = #{tenantId},比如用户查询课程时,SQL 会自动变成select * from course where ... and tenant_id = 123,无需手动写,或者你给面试官说的手动在SQL加;
-
2.tenantId 与权限的联动,之前提到数据、权限、资源的全链路设计出发,那还要有权限控制限制,所以可以结合网关+业务:
- 网关层拦截:用Gateway网关 统一拦截所有请求,从请求头(如X-Tenant-Id)或 Token 中解析租户 ID,先校验当前请求的租户是否存在、是否已开通服务,不合法直接拦截,避免无效请求进入业务层;
- 业务层数据权限:结合 Spring Security,在用户角色中加入租户标识—— 比如租户管理员的角色会关联tenant_id,用户登录后,除了校验角色权限(如是否能删除科目),还会校验操作的数据是否属于当前用户的租户,比如 A 租户的管理员无法删除 B 租户的课程;
-
除了数据,SAAS 系统的配置、存储等资源也需隔离,结合简历中的 Nacos、Minio,让方案更完整比如我们还考虑了非数据类资源的隔离,避免租户间资源冲突:
- 配置隔离:用 Nacos 的命名空间隔离各租户的配置 —— 比如 A 租户的数据库连接等配置,放在 A 的命名空间下,B 租户无法读取,同时支持私有化部署租户的本地配置覆盖;
- 存储隔离:用 Minio 存储租户的文件(如课程资料、附件)时,为每个租户创建独立的存储桶(Bucket),桶名包含 tenantId(如bucket-tenant-123),文件上传时自动路由到对应桶,确保租户文件不混放;
- 服务资源隔离:通过 Sentinel 为不同租户设置接口调用阈值(如 A 租户每秒最多 100 次考试提交请求),避免单个租户高并发占用所有资源,影响其他租户使用。
4.智能客服这个项目是干嘛用的?实际上业务上面在干嘛?
本质是为企业内部客服团队打造的智能协作工具,核心是解决传统客服响应慢、知识不统一、跨系统查数据麻烦的痛点,把企业所有内部知识(手册、产品文档、商品数据等)转换成向量存在 PGvector 数据库里,用户提问后,AI 会先检索最匹配的知识片段,再结合大模型生成准确回答。
5.智能学习SAAS的这个项目是干嘛的?
这个智能学习 SAAS 系统,本质是为企业、培训机构等 B 端客户打造的内部测试和培训工具—— 核心是解决传统企业培训 线下成本高、学习数据难追踪、内容定制化弱、考核与学习脱节的痛点,通过线上化、智能化的功能,让企业能快速搭建自己的内部培训体系,同时支持多企业共用系统(多租户)和部分企业私有化,满足不同规模客户的需求
6.这两个的用户量是多少?
- 智能学习 SAAS 系统: 这个项目是按服务中小规模企业 / 培训机构的场景设计的,用户规模是:覆盖 10-20 家企业租户(比如 50 人以下的小公司、100 人左右的培训机构),每家租户的内部用户(员工 / 学员)在 50-200 人不等,总模拟用户量大概 1000-4000 人。
- RAG 智能客服系统: 这个项目是中小型企业\公司内部客服团队的场景,用户规模分两类:
- 前端用户(咨询用户):模拟企业的终端客户,日均咨询量设计为 500-1000 条(比如电商、教育类企业的客服咨询量);
- 后端用户(客服人员):模拟 10-20 人的客服团队,每人日均处理 30-50 条咨询。
7.内存溢出一般有哪些方式,还有解决方案?你遇到过哪种怎么解决的?
内存溢出的方式都在下面,训练营的JVM知识库里也有,同时还有调优方案:
- 堆内存溢出(java.lang.OutOfMemoryError: Java heap space)
-
原因:堆内存(对象存储区域)不足,通常是创建了大量对象且未被 GC 回收(如内存泄漏),或堆内存设置过小。
-
典型场景:批量处理大量数据(如一次性加载 10 万道题目到内存)、集合中缓存大量对象却未清理(如错题本缓存未设置过期)。
-
通用方案:
- 临时解决:调大堆内存参数(-Xms初始堆、-Xmx最大堆,如-Xms2g -Xmx4g);
- 根本解决:用jmap -dump:format=b,file=heap.hprof 导出堆快照,用 MAT 工具分析 哪些对象占内存大、是否有内存泄漏(如集合未清空、长生命周期对象引用短生命周期对象),针对性优化(如改用分页加载、给缓存加过期清理)。
- 方法区 / 元空间溢出(java.lang.OutOfMemoryError: Metaspace)
-
原因:元空间(存储类信息、方法信息、常量池等)不足,通常是频繁动态生成类(如反射、CGLIB 动态代理),或元空间参数设置过小。
-
典型场景:大量使用 Spring AOP(动态生成代理类)、频繁用反射生成类(如智能组卷时动态生成考题规则类)。
-
通用方案:
- 调大元空间参数(-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m);
- 减少动态类生成(如复用代理类、避免频繁创建新的类加载器)。
- 栈溢出(java.lang.StackOverflowError)
-
原因:线程栈(存储方法调用栈帧)深度超过限制,通常是递归调用过深或方法调用链过长。
-
典型场景:递归计算(如计算复杂的题目相似度递归逻辑)、多层嵌套调用(如权限校验嵌套 10 层以上)。
-
通用方案:
- 调大线程栈大小(-Xss1m,默认通常 512k);
- 重构代码,把递归改成循环(如用迭代替代递归计算)、减少方法嵌套层数。
- 直接内存溢出(java.lang.OutOfMemoryError: Direct buffer memory)
-
原因:直接内存(堆外内存,NIO 常用)不足,通常是频繁创建 DirectByteBuffer 且未释放,或直接内存限制过小。
-
典型场景:用 NIO 处理大文件(如上传 100MB 的课程视频)、缓存大量直接内存缓冲区却未回收。
-
通用方案:
- 调大直接内存限制(-XX:MaxDirectMemorySize=1g);
- 确保 DirectByteBuffer 及时释放(调用cleaner().clean()),避免长时间持有引用。 可以在智能学习 SAAS 系统中说有遇到过,场景是多租户高峰刷题,本地缓存堆积导致堆溢出: 我在智能学习 SAAS 系统的刷题模块,遇到过堆内存溢出的问题,和高频题目本地缓存设计不当有关,解决过程也用到了 JVM 调优工具,当时刷题模块有个设计,为了减少 Redis 的网络开销,我们在应用本地加了一层高频题目缓存把近 1 小时内被查询超过 50 次的题目(比如 Java、Python 的热门题,可以举例其他场景,比如企业内部新需要学习的政策或规章)缓存到本地 HashMap 中,想着本地取比远程调 Redis 快。但上线后,每逢周一 / 周五(企业租户集中组织员工刷题的高峰时段,模拟 2000 + 用户同时在线),系统就会报Java heap space错误,甚至部分实例宕机。 我当时负责排查这个问题,分了三步(按照训练营JVM优化案例里的思路即可):
- 先看 JVM 运行状态:用jstat -gc 1000监控 GC—— 发现老年代内存使用率从平时的 40% 飙升到 98%,Full GC 每秒触发 1 次,但每次回收后内存只降 2%-3%,说明有大量对象活着但没用,无法被回收;
- 导出堆快照分析:用jmap -dump:format=b,file=heap_dump.hprof 导出出问题实例的堆快照,用 MAT 工具打开后,发现HashMap占用了 60% 的堆内存 —— 里面存了近 1 万条题目详情对象(单条包含题干、选项、解析),且这些对象的引用都被一个静态的LocalCache工具类持有;
- 定位根因:查代码发现,这个本地 HashMap 没有设置最大容量和过期清理机制—— 热门题目被不断加入缓存,但只有新增没有删除,高峰时段 1 小时内就堆积了 1 万条,而实例堆内存只有 2G,很快就被占满,导致溢出。 然后我先通过紧急修复:临时调大堆内存(-Xmx从2G调到4G),同时在 LocalCache 中加手动清理逻辑—— 每天凌晨 3 点(非高峰时段)调用HashMap.clear(),先缓解溢出问题,后面再进行代码层面的优化 代码层面的优化(核心):
-
替换缓存实现:把无界 HashMap改成Caffeine缓存(Spring 推荐的本地缓存,支持自动过期和最大容量),配置最大缓存 1 万条题目10 分钟过期,超过容量时按LRU 策略(最近最少使用)淘汰旧题目 —— 这样本地缓存始终控制在 20MB 以内(1 万条 ×2KB),不会堆积;
-
缓存分层优化:本地缓存只存超高频题目(近 10 分钟查询超 50 次),普通高频题目还是走 Redis,同时用布隆过滤器过滤不存在的题目 ID,避免无效缓存请求;
-
监控兜底:在 Prometheus 中加本地缓存占用内存的监控指标,超过阈值(如 30MB)就告警,避免再次出现内存溢出。 8.权限控制怎么做? 其实这个和3问是一样的跨租户权限隔离,只不过会多一点就是问你后台管理系统的权限控制,这个换一种说法也可以是租户内角色权限控制,但是也一样用 RBAC 模型和spring security:
-
采用 RBAC(基于角色的访问控制)— 用户→角色→权限的三层映射:
- 权限:比如 课程查询、课程删除、考试创建等(对应后端接口的@PreAuthorize注解);
- 角色:预定义租户内角色,如TENANT_ADMIN(企业管理员)、EMPLOYEE(普通员工)、TEACHER(讲师),每个角色绑定不同权限(比如TENANT_ADMIN有 课程删除 权限,EMPLOYEE没有);
- 用户:企业管理员在 用户管理 页面给员工分配角色(如给张三分配EMPLOYEE角色);
-
技术实现:
- 用 Spring Security 的@PreAuthorize注解控制接口权限,比如课程删除接口加@PreAuthorize("hasPermission('', 'course:delete')"),表示只有拥有course:delete 权限的用户才能调用;
- 自定义PermissionEvaluator,在判断权限时,先从数据库加载 当前用户的角色→权限映射,再校验是否包含目标权限;
-
项目场景:企业管理员进入 课程管理 页面,能看到删除按钮(因为有course:delete权限);普通员工进入同一页面,删除按钮隐藏(前端根据后端返回的权限列表控制),且即使伪造删除请求,后端也会被@PreAuthorize拦截,这个就和平常的后台管理系统一样
8.权限控制怎么做?
其实这个和3问是一样的跨租户权限隔离,只不过会多一点就是问你后台管理系统的权限控制,这个换一种说法也可以是租户内角色权限控制,但是也一样用 RBAC 模型和spring security:
-
采用 RBAC(基于角色的访问控制)— 用户→角色→权限的三层映射:
- 权限:比如 课程查询、课程删除、考试创建等(对应后端接口的@PreAuthorize注解);
- 角色:预定义租户内角色,如TENANT_ADMIN(企业管理员)、EMPLOYEE(普通员工)、TEACHER(讲师),每个角色绑定不同权限(比如TENANT_ADMIN有 课程删除 权限,EMPLOYEE没有);
- 用户:企业管理员在 用户管理 页面给员工分配角色(如给张三分配EMPLOYEE角色);
-
技术实现:
- 用 Spring Security 的@PreAuthorize注解控制接口权限,比如课程删除接口加@PreAuthorize("hasPermission('', 'course:delete')"),表示只有拥有course:delete 权限的用户才能调用;
- 自定义PermissionEvaluator,在判断权限时,先从数据库加载 当前用户的角色→权限映射,再校验是否包含目标权限;
-
项目场景:企业管理员进入 课程管理 页面,能看到删除按钮(因为有course:delete权限);普通员工进入同一页面,删除按钮隐藏(前端根据后端返回的权限列表控制),且即使伪造删除请求,后端也会被@PreAuthorize拦截,这个就和平常的后台管理系统一样
9.智能客服redis有没有用到
在 RAG 智能客服系统中,Redis 主要用来解决会话上下文延续和高频咨询快速响应这两个个核心问题 —— 因为客服场景对响应速度和会话连贯性要求很高,Redis 的高性能、支持过期策略的特性刚好匹配,也能减轻数据库和 RAG 检索的压力。
- 场景 1:用户会话上下文缓存 —— 解决多轮对话语境断裂 用户和智能客服多轮对话时(比如 先问会员退费规则,然后再问我的退费到账了吗),若每次对话都重新查询历史记录,会频繁调用数据库 / 对话日志表,导致响应变慢;且用户中途断开(比如刷新页面),再咨询时 AI 会忘记之前的对话,体验差。 Redis 具体用法:
- 用Hash 数据结构缓存用户会话上下文,键设计为 customer:session:{userId}:{sessionId}(userId 是用户唯一标识,sessionId 是单次会话 ID,避免多设备登录冲突);
- Hash 的字段包括:last_query(用户上一轮问题)、ai_last_reply(AI 上一轮回复)、context_keywords(对话中的关键信息,如 会员账号、退费订单号)、update_time(最后更新时间);
- 过期策略:设置 1 小时过期(EXPIRE key 3600)—— 因为客服对话单次持续时间通常不超过 1 小时,过期后自动清理,避免内存堆积;若用户 1 小时内继续对话,用EXPIRE刷新过期时间。 优化效果: 多轮对话时,AI 无需每次查数据库,直接从 Redis 读取上下文,响应时间从原来的 500ms(查库 + RAG 检索)缩短到 100ms 内;用户中途断开再连接,会话能无缝延续,用户满意度提升 20%。
- 场景 2:高频 FAQ 缓存 —— 减轻 RAG 检索压力,提升基础咨询响应速度 客服系统中 70% 的咨询是 重复基础问题(比如 怎么修改手机号、工单提交后多久处理),若每次都走 RAG 检索知识库(PGvector)→生成回答 的流程,会浪费计算资源,且响应速度慢(RAG 检索需 1-2 秒)。 Redis 具体用法:
- 用String 数据结构缓存高频 FAQ(常见问题解答),键设计为 faq:keyword:{questionKeyword}(比如 faq:keyword: 修改手机号faq:keyword: 工单处理时间),值是标准化的 AI 回答(提前通过 RAG 生成并人工审核,确保准确性);
- 缓存策略:设置 24 小时过期(EXPIRE key 86400),每天凌晨 3 点(非高峰)用定时任务更新热门 FAQ 的缓存(对比数据库中 FAQ 的更新时间,有变更则重新缓存);
- 兜底机制:若 Redis 中没有缓存(比如新添加的 FAQ),则走 RAG 检索流程,同时把结果写入 Redis,下次同类问题直接命中缓存。 优化效果: 70% 的基础咨询直接命中 Redis 缓存,RAG 检索的调用量降低 60%,PGvector 向量数据库的 QPS 从 200 降到 80,系统整体响应速度提升 70%,且基础问题回答准确率保持 100%。
10.客服系统,那个异常处理就是怎么去避免异常抛给用户
在智能客服系统中,异常处理主要是三层思路:分层拦截 + 友好提示 + 降级兜底,客服系统的异常主要分三类,每种都要避免直接抛给用户:
- 业务异常:用户操作不符合规则(如 查询的工单 ID 不存在未登录却发起咨询);
- 系统异常:后端服务出问题(如数据库连接超时、Redis 宕机、AI 模型调用失败);
- 第三方异常:依赖的外部服务故障(如调用用户信息接口超时、短信验证码服务不可用)。 用户在界面上的操作(如输入工单 ID、发送咨询),先通过前端 JS 校验,避免明显错误请求到后端,后端全局拦截:用统一异常处理器 捕获所有异常(这个其实和SaaS系统中通过网关进行全局异常处理是一样),用 Spring 的@ControllerAdvice实现全局拦截,确保所有未处理的异常都被捕获 关键服务可以降级:核心功能出问题时,用 兜底方案 保证基本可用 针对客服系统的核心依赖(如 RAG 检索、AI 模型、工单系统),做降级处理,避免单点故障导致整体不可用:
- 例 1:AI 模型调用失败 正常流程是 用户提问→RAG 检索→AI 生成回答,若 AI 模型超时 / 报错,降级为 直接返回 RAG 检索到的知识库片段,提示用户 根据知识库信息,您的问题答案是:xxx(若有疑问可补充说明);
- 例 2:Redis 缓存宕机 若缓存会话上下文的 Redis 挂了,降级为 每次对话只保留当前轮次信息(不依赖历史上下文),提示用户 当前会话可能需要您重复说明之前的问题,感谢理解;
11.比如说现在这个应用它内存是只要居高不下,就是刚才OOM了这种的话,去定位它问题出在哪里
在应用内存居高不下甚至 OOM 的情况下,定位问题的核心逻辑是:先通过监控工具观察内存变化规律→再抓取关键数据(堆快照、GC 日志)→最后用分析工具定位异常对象 / 代码,其实就和JVM调优一样
- 确认 内存区域:是堆内存还是非堆内存(元空间 / 直接内存)异常?
-
用jstat -gc 1000(每隔 1 秒输出一次 GC 统计),重点看:
- 堆内存:Eden 区(E)、Survivor 区(S0/S1)、老年代(O)的使用率是否持续上涨(比如老年代从 40% 涨到 95% 且不下降);
- 非堆内存:元空间(M)是否持续增长(比如从 200M 涨到 1G,可能是类加载过多);
-
例:在刷题系统中,若O(老年代)使用率持续 90%+,且FGC(Full GC)频繁(每秒 1 次)但回收后内存只降 5%,说明堆内存有对象无法回收(大概率是内存泄漏)。
- 确认 异常时机:是启动后就异常,还是高并发时才出现?
- 若启动后内存就飙升,可能是 初始化时加载了大量数据(如一次性加载所有题库到内存);
- 若只有高并发时(如 1000 + 用户同时刷题)才出现,可能是 并发场景下对象创建过多且未释放(如每个用户请求都创建新的缓存对象,未设置过期)。 然后通过堆快照、GC 日志、线程栈去确定问题所在:
- 导出堆快照(分析 哪些对象在占内存)
- 用jmap -dump:format=b,file=heap_dump.hprof (注意:OOM 时可通过-XX:+HeapDumpOnOutOfMemoryError自动导出);
- 例:在刷题系统中,导出的堆快照显示HashMap占用 60% 堆内存,里面存了 1 万条对象(题目详情),这就是重点怀疑对象。
- 抓取 GC 日志(分析 GC 是否正常回收)
- 启动时添加参数-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log,记录 GC 时间、回收内存大小、耗时;
- 分析重点:若Full GC耗时超过 1 秒,且回收的内存(PSOldGen的used下降值)远小于总容量,说明 大量对象进入老年代且无法回收(内存泄漏特征)。
- 查看线程栈(排除 线程阻塞导致对象无法回收)
- 用jstack > thread_dump.txt,检查是否有大量线程阻塞(如synchronized锁竞争、数据库连接池耗尽);
- 例:若线程栈中大量Thread.State: BLOCKED的线程都在等待 缓存更新锁,会导致对象长时间被引用,无法被 GC 回收,间接引发内存居高不下。 拿到堆快照和日志后,用专业工具分析:
- 用 MAT(Memory Analyzer Tool)分析堆快照:找 大对象 和 内存泄漏点
- 第一步:看 Dominator Tree(支配树),按 Retained Heap(对象及其引用对象占用的总内存)排序,找到占用内存最高的对象类型(如HashMap、ArrayList);
- 第二步:查看该对象的 引用链(Path to GC Roots),看是谁在持有这些对象的引用(如静态变量、长生命周期的缓存工具类);
- 例:在刷题系统的堆快照中,发现Question对象被LocalCache(静态工具类)的HashMap引用,且HashMap没有清理机制,导致题目对象持续堆积,这就是内存泄漏的根因。
- 用 VisualVM 分析 GC 日志:判断 GC 参数是否合理
- 导入 GC 日志,观察 老年代增长趋势:若每小时增长 100M 且不下降,说明有内存泄漏;若增长后能稳定在某个值(如 80%),可能是 堆内存设置过小;
- 例:若老年代容量 2G,高并发时 1 小时就占满,且 Full GC 后仍有 1.8G 无法回收,说明是内存泄漏;若 Full GC 后能降到 50%,则可能是-Xmx设置太小(需调大)。
- 结合业务代码:验证 异常对象的创建逻辑
-
根据 MAT 找到的 大对象类型 + 引用链,去代码中找对应的创建和引用逻辑:
- 若对象被静态集合(static Map)持有,且没有清理逻辑→内存泄漏(如刷题系统的本地缓存未设置过期);
- 若对象是 一次性查询的大量数据(如一次查 10 万道题)→大对象未分页,导致堆内存瞬间占满;
- 若对象是 频繁创建的临时对象→未复用对象,导致年轻代 GC 压力大,频繁晋升到老年代。
12.远程调用用的什么框架 调用失败怎么处理 服务降级怎么做的
远程调用框架:用 OpenFeign+Nacos 实现声明式调用,每个服务在 Nacos 注册中心注册。调用方通过 OpenFeign 定义接口,指定目标服务名和接口路径,例如刷题服务调用课程服务的查询课程权限接口,针对 网络波动、服务临时不可用 等调用失败场景,我们通过 超时控制 + 有限重试 + 熔断保护 三层机制处理,避免失败扩散,在 OpenFeign 中配置超时时间,防止因被调用方响应慢导致调用方线程堆积,开启重试机制,用 Spring Retry 配合 OpenFeign,当被调用方频繁失败(如课程服务宕机),通过 Sentinel 触发熔断,停止调用并快速失败,保护调用方资源
13.学习系统,seata分布式事务哪里用到的
在智能学习 SAAS 系统中,Seata 分布式事务主要用于跨微服务的 数据一致性操作 场景—— 当一个业务流程需要同时修改多个服务的数据库比如 课程购买企业批量开通权限,必须保证 要么全成功,要么全回滚,避免出现 部分成功导致的数据混乱,比如付费课程购买流程(用户服务 + 订单服务 + 课程服务)业务背景:系统中有付费课程(如培训机构的高级编程实战课),用户购买时需完成三个步骤:
- 订单服务创建 待支付订单(状态为 未支付);
- 支付成功后,订单服务更新订单状态为 已支付;
- 课程服务为用户开通该课程的学习权限(写入用户 - 课程关联表)。 为什么需要分布式事务: 若没有事务保证,可能出现 订单已支付但课程权限未开通用户付费后学不了,或权限开通但订单支付失败。尤其是 SAAS 系统涉及多租户付费,数据一致性直接影响到企业用户的体验。 Seata 实现方式:
- 采用 Seata 的AT 模式(基于 undo_log 表的自动补偿,适合关系型数据库),三个服务的数据库均需创建 Seata 的 undo_log 表(用于回滚日志记录);
- 在订单服务的 支付完成 接口上添加@GlobalTransactional注解,标记为全局事务入口 在学习系统中,Seata 主要保障 跨服务的写操作原子性,尤其是涉及 付费、权限、名额 等核心数据的场景,通过全局事务确保 要么全成功,要么全回滚,最终支撑了系统在多租户、高并发下的数据一致性,这也是 SAAS 系统商业化的基础
14.多线程查数据 线程数怎么定下来的 线程池是共享的还是隔离的 用户所有请求发过来怎么做的
线程数不是固定值,而是根据任务是 CPU 密集型还是 IO 密集型,这个项目是IO 密集型任务(系统中最常见,如数据库查询、Redis 访问、远程调用)计算公式:线程数 = CPU核心数 × 2 + 1,用户请求的处理流程:路由 + 分级 + 监控 全链路管控,当用户请求(如查题、提交答案、看统计报表)进入系统后,按 优先级分级→线程池分配→执行 + 监控 的流程处理,确保核心请求优先响应,用户请求先经过网关,根据 URL 路径标记优先级比如
- 查题、考试(这里是项目路径)标记为 核心请求;
- 日志等标记为 非核心请求;
- 网关将优先级信息通过请求头传递给后端服务。 由服务层代码分配到核心线程池执行多线程任务,非核心请求分配到通用线程池,若线程池满,则进入队列等待(非核心任务允许延迟),执行时通过 AOP 记录每个线程池的活跃线程数、队列积压数、拒绝次数,实时推送到 Grafana 监控面板
坚定不移,听话照做,按部就班,早日上岸!
加我微信,免费领面经,升职加薪:wangzhongyang1993,备注:面经。