武汉亚信科技后端面经:Spring Boot 启动、并发工具与中间件选型
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
开场与意向确认
- 你是大三吗?实习周期大概能安排多久?
- 学校是否允许全职实习?能否解决异地租房问题?
- 你未来的技术方向偏向哪一块?
Java 后端基础
- 请描述一下 Spring Boot 的启动过程。
- Spring Boot 的启动入口函数是什么?
- 你了解 SingleFlight 框架吗?它和分布式锁有什么区别?
- CAS 乐观锁的原理是什么?存在哪些潜在问题?
- 在项目中,你更偏好使用哪种锁?为什么?
- 如果部署多实例,SingleFlight 框架会有什么问题?如何解决?
- 你写过哪些脚本?有没有遇到过 MySQL 索引失效的情况?哪些场景会导致索引失效?
- 为什么单表数据量过大会导致索引失效?
- 如果接口响应变慢,你会如何排查?
- 主线程如何判断子线程已经执行完毕?有哪些实现方式?
- 除了
CompletableFuture,还有其他方式吗?比如join函数你用过吗?
消息队列与中间件
- 你用过哪些消息队列?
- 请对比一下 RabbitMQ 和 Kafka 的区别及使用场景。
- Redis 支持哪些数据结构?底层有什么优化机制?
语言对比与项目经验
- 你同时掌握 Java 和 Python,它们之间有什么区别?
- 用 Java 对接 AI 大模型,相比传统 CRUD 项目有什么挑战?
- 你简历上写了 Spring AI,这个项目是你独立完成的吗?
- 你有服务器部署经验吗?具体怎么操作?
- 你用过 Docker 容器化部署吗?怎么用?
- 你了解 JVM 调优吗?具体有哪些参数或手段?
业务介绍与反问环节
- 你了解我们公司吗?有没有了解过我们的业务?
- 你入职后希望接触什么方向的业务?
- 你接触过基于 AI 的提效工具(如 Cursor)吗?
- 这次面试的结果什么时候能出来?后续还有哪些流程?
《参考解析》
Spring Boot 启动过程:入口是 main 方法里的 SpringApplication.run。流程大致是:创建 SpringApplication(推断应用类型、加载 ApplicationContextInitializer 与 ApplicationListener)→ 准备环境(读取配置文件、命令行参数、环境变量,发布 EnvironmentPrepared)→ 创建并准备 ApplicationContext → 刷新容器(refresh:解析配置类、BeanFactoryPostProcessor、注册 BeanPostProcessor、实例化单例、启动内嵌 Web 容器)→ 发布 ApplicationStarted/Ready 事件 → 执行 ApplicationRunner/CommandLineRunner。自动配置的关键是 @SpringBootApplication 里的 @EnableAutoConfiguration,它通过 AutoConfigurationImportSelector 读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(Spring Boot 3)并按 @Conditional 条件过滤。
SingleFlight 与分布式锁:分布式锁解决「同一时刻只允许一个执行者」,强调互斥;SingleFlight 解决「同一个 key 的并发请求只执行一次,其余复用结果」,强调去重与共享。两者都涉及「谁执行」,但 SingleFlight 的等待者不是被阻塞在临界区外,而是等待同一个结果;锁的持有者执行完就释放,SingleFlight 的结果可能还要缓存一段时间。多实例部署时,单机版 SingleFlight 只能合并本机请求,跨实例仍会重复调用;要么把选举放到 Redis(SET NX PX + 结果 key + 订阅/轮询),要么接受重复并在下游做幂等。
CAS 的问题:ABA、长时间自旋、只能保护单个变量。ABA 用版本号或 AtomicStampedReference;自旋用限制次数、退避、或直接升级为锁;多变量用锁或不可变对象整体替换。选锁的原则是「临界区长短、竞争强度、是否跨进程」:进程内短临界区用 synchronized/ReentrantLock,跨进程用 Redis/ZooKeeper 分布式锁,读多写少且能容忍短暂不一致时用乐观锁。
索引失效与单表数据量:索引失效的语法原因见上一类问题(函数、隐式转换、前导通配、最左前缀、OR 等)。「单表数据量大导致索引失效」这个说法不准确——索引不会因为数据多而失效,真正发生的是:B+ 树层数增加导致磁盘 I/O 变多、buffer pool 命中率下降、优化器估算后认为全表扫描更便宜、以及维护索引的成本上升。表现上像「索引不灵了」,本质是数据量与访问模式变了。应对是归档冷数据、分区、覆盖索引、读写分离,最后才是分库分表。
慢接口排查:先看监控定位是哪个接口、哪个时间段、是否伴随流量突增或发布;再看链路追踪(SkyWalking/OpenTelemetry)找耗时最长的 span;然后分四层查:入口(连接数、线程池是否打满、GC 停顿)、应用(锁竞争、慢 SQL、序列化、外部调用)、数据库(慢查询日志、执行计划、锁等待)、下游(超时、重试风暴)。用 jstack 看线程栈、jmap/jstat 看堆与 GC;不要一上来就调参数。
主线程等待子线程:Thread.join()、Future.get()、CountDownLatch.await()、CyclicBarrier、CompletableFuture.allOf(...).join()、ExecutorService.invokeAll()。区别:join 只能等线程结束、拿不到返回值;Future.get 能拿结果但阻塞;CountDownLatch 适合「等 N 个任务完成」但不重复使用;CompletableFuture 能组合、异常处理和超时更灵活。生产上还要设置超时,避免无限等待。
RabbitMQ 与 Kafka:RabbitMQ 是传统消息代理,强调可靠投递、灵活路由(Exchange/Queue/Binding)、低延迟,适合业务解耦、任务分发、RPC 式调用;Kafka 是分布式日志,强调高吞吐、顺序写、分区并行、可回放,适合日志采集、流处理、事件溯源。选型看吞吐与消费模型:需要复杂路由和逐条确认选 RabbitMQ,需要海量吞吐和回溯选 Kafka。两者都要考虑重复消费(幂等)、顺序(分区键)、积压与死信。
Redis 数据结构与底层:String、List、Hash、Set、ZSet、Bitmap、HyperLogLog、GEO、Stream。底层是「对象 + 编码」:String 用 SDS(预分配、二进制安全),小字符串用 embstr;List 早期用 ziplist/linkedlist,现在用 quicklist(链表套压缩列表);Hash 小数据用 listpack/ziplist,大了转 hashtable;Set 用 intset 或 hashtable;ZSet 用 listpack 或 skiplist + dict;还有渐进式 rehash、过期删除(惰性 + 定期)、内存淘汰策略。回答时挑两三个讲透,比报菜名好。
Java 与 Python:Java 静态类型、编译成字节码跑 JVM、强类型、生态偏企业级、并发模型基于线程与锁;Python 动态类型、解释执行、开发效率高、生态偏数据与 AI、GIL 限制 CPU 并行。选型看团队与场景:高并发长生命周期的服务偏 Java,数据处理、模型实验、脚本偏 Python。
Java 对接大模型的挑战:流式输出(SSE/WebSocket)与背压、超时与重试、token 计费与配额、上下文管理与截断、结构化输出的校验、模型不稳定的降级、以及多模型渠道的抽象。传统 CRUD 是「请求-响应确定且快」,大模型是「长耗时、非确定、按量计费」,线程池、连接池、事务边界都要重新设计,不能让模型调用占满业务线程。
部署与 Docker:部署讲清「构建产物 → 镜像 → 推送仓库 → 目标机拉取 → 启动 → 健康检查 → 回滚」。Docker 的常用命令是 build、run、ps、logs、exec、compose up -d;镜像用多阶段构建减小体积,配置用环境变量注入,数据卷持久化,健康检查配合重启策略。
JVM 调优:先测量再调。常用参数:-Xms/-Xmx(堆初始/最大,建议相等避免动态扩缩)、-Xmn 或 -XX:NewRatio(新生代)、-XX:MetaspaceSize、-XX:+UseG1GC、-XX:MaxGCPauseMillis、-XX:ParallelGCThreads、-Xss(栈大小)、-XX:+HeapDumpOnOutOfMemoryError。工具用 jps、jstat -gc、jmap、jstack、jcmd、VisualVM/Arthas。调优目标是满足停顿与吞吐指标,不是把参数调「大」。
公司与反问:亚信科技主要做电信运营商支撑系统(BSS/OSS)、数字化运营与云网服务,客户以运营商和大型企业为主。反问可以问团队的技术栈、新人培养、实习转正机制、以及当前业务面临的挑战;结果时间按 HR 的说法回答,不要替公司承诺。