Java实习模拟面试|上海禾赛科技后端实习一面面经:高并发数据去重、事务与MQ一致性、反射争议与缓存选型深度解析
关键词:禾赛科技后端实习|Java高并发|消息可靠性|事务传播行为|Redis vs 本地缓存|反射破坏封装?|线程池调优|CSDN面经
在准备2025届暑期实习的过程中,我针对上海禾赛科技(Hesai Tech)的Java后端岗位进行了一场高度仿真的模拟一面。作为一家专注于激光雷达与智能感知系统的硬科技公司,其后端系统对数据可靠性、高并发处理、跨服务通信一致性有极高要求。
本文以真实对话形式还原整场60分钟面试,采用“面试官提问 + 候选人口头回答 + 连环追问”结构,覆盖项目深挖、并发基础、Spring事务、缓存选型、反射哲学、MQ可靠性设计六大核心模块,助你精准把握硬科技公司对实习生的技术期待!
1. 拷打上一份实习:如何保证数据可靠、去重、高并发?
面试官提问:你在上一段实习中提到处理大量传感器上报数据,怎么保证数据不丢、不重、高并发写入?
我:
我们面对的是每秒数万条设备上报的原始点云元数据,核心挑战是幂等写入 + 高吞吐 + 低延迟。我们的方案分三层:
(1)去重机制
- 每条数据携带全局唯一
trace_id(由设备生成,符合UUIDv7规范)。 - 写入前先查Redis布隆过滤器(Bloom Filter):
- 若不存在 → 放行;
- 若存在 → 再查 MySQL 唯一索引(
trace_id)做二次校验,避免布隆误判。
- 布隆过滤器定期滚动(如每小时重建),防止内存无限增长。
(2)高并发写入
- 使用ShardingSphere 分库分表,按
device_id % 16分片,分散写压力。 - 批量插入:Kafka消费者攒批(1000条/批 or 100ms超时),用
INSERT ... ON DUPLICATE KEY UPDATE实现幂等。
(3)可靠性保障
- Kafka 生产者开启
acks=all+ 幂等(enable.idempotence=true); - 消费者手动提交 offset,确保“处理成功才提交”。
追问:如果 Redis 宕机,布隆过滤器失效,会不会导致大量重复请求打到 DB?
我:
会!所以我们做了降级策略:
- 当 Redis 不可用时,自动跳过布隆过滤,直接走 MySQL 唯一索引;
- 同时触发告警,运维介入;
- 虽然 QPS 会短暂下降(因 DB 唯一索引冲突回滚),但数据正确性不受影响。
2. List 和 Set 的区别?
面试官提问:说说
List和Set的核心区别?
我:
| 特性 | List | Set |
|---|---|---|
| 有序性 | 有序(按插入顺序) | 无序(除 LinkedHashSet) |
| 重复元素 | 允许 | 不允许 |
| 典型实现 | ArrayList, LinkedList | HashSet, TreeSet, LinkedHashSet |
| 底层结构 | ArrayList: 动态数组;LinkedList: 双向链表 | HashSet: HashMap(key-only);TreeSet: 红黑树 |
使用场景:
List:需要保留插入顺序、允许重复(如日志记录、任务队列);Set:去重、快速查找(如用户ID集合、权限校验)。
3. 如何实现线程?
面试官提问:Java 中创建线程有哪几种方式?
我:
主要有三种:
- 继承
Thread类(不推荐,Java单继承限制) - 实现
Runnable接口(常用,解耦任务与线程) - 实现
Callable<V>+FutureTask(支持返回值和异常)
现代最佳实践:永远不要手动 new Thread(),而是使用线程池(如
Executors或ThreadPoolExecutor),避免资源耗尽。
4. 线程池核心参数 & 如何设置?
面试官提问:线程池有哪些核心参数?怎么合理设置?
我:ThreadPoolExecutor七大参数:
newThreadPoolExecutor(intcorePoolSize,// 核心线程数(常驻)intmaximumPoolSize,// 最大线程数longkeepAliveTime,// 空闲线程超时时间TimeUnitunit,BlockingQueue<Runnable>workQueue,// 任务队列ThreadFactorythreadFactory,RejectedExecutionHandlerhandler// 拒绝策略);如何设置?看任务类型!
- CPU密集型(如计算):
core = CPU核数 + 1 - IO密集型(如DB/网络):
core = 2 * CPU核数(经验值)
队列选择:
- 高吞吐:
LinkedBlockingQueue(无界,慎用!可能OOM)- 控制资源:
ArrayBlockingQueue(有界,配合拒绝策略)
拒绝策略:生产环境建议自定义,记录日志 + 告警,而非直接抛异常。
5–6. Spring 事务方式 & 传播行为
面试官提问:Spring 有哪两种事务管理方式?事务传播行为有哪些?
我:
两种方式:
- 编程式事务:
TransactionTemplate手动控制(灵活但冗余) - 声明式事务:
@Transactional注解(主流,AOP实现)
七种传播行为(重点记前3个):
| 行为 | 说明 |
|---|---|
REQUIRED(默认) | 有事务则加入,无则新建 |
REQUIRES_NEW | 挂起当前事务,新建独立事务 |
SUPPORTS | 有事务则用,无则非事务执行 |
NOT_SUPPORTED | 挂起事务,非事务执行 |
MANDATORY | 必须在事务中,否则抛异常 |
NEVER | 不能在事务中,否则抛异常 |
NESTED | 嵌套事务(Savepoint回滚) |
典型场景:
- 日志记录用
REQUIRES_NEW,避免主业务回滚导致日志丢失;- 批量处理用
NESTED,单条失败不影响整体。
7. Redis 与本地缓存的优点对比?
面试官提问:Redis 和 Caffeine/Guava 这类本地缓存各有什么优劣?
我:
| 维度 | Redis(分布式缓存) | 本地缓存(Caffeine) |
|---|---|---|
| 访问速度 | ~1ms(网络开销) | ~100ns(内存直取) |
| 一致性 | 强(可集群同步) | 弱(各节点独立) |
| 容量 | GB~TB级 | 受限于单机内存 |
| 高可用 | 主从+哨兵/Cluster | 无(进程挂即丢) |
| 适用场景 | 跨服务共享数据(如用户会话) | 高频读、低变更数据(如配置、字典) |
最佳实践:多级缓存!
先查本地缓存 → 未命中查 Redis → 再查 DB,并异步回填两级缓存。
8. Spring 预先加载的注解和顺序?
面试官提问:Spring Bean 初始化时,哪些注解控制加载顺序?
我:
关键注解及其执行顺序:
- 构造函数注入
@Autowired/@Resource字段/方法注入@PostConstruct(初始化方法)InitializingBean.afterPropertiesSet()init-method(XML或@Bean指定)
注意:
@PostConstruct是 JSR-250 标准,比 Spring 特有接口更通用。
9–10. 如何访问私有方法?反射破坏封装了吗?
面试官提问:怎么调用一个类的私有方法?这是否破坏了面向对象的封装性?
我:
访问私有方法:
Methodmethod=clazz.getDeclaredMethod("privateMethod");method.setAccessible(true);// 绕过访问检查method.invoke(obj);是否破坏封装?
我的观点:工具无罪,滥用有害。
- 合理场景:单元测试(Mock私有逻辑)、框架开发(如Spring AOP代理);
- 风险:破坏类的不变性(invariants),导致难以维护;
- 设计原则:优先通过公有接口暴露能力,若必须用反射,应加安全校验(如模块权限控制)。
JDK9+ 模块系统(JPMS)已限制跨模块反射,进一步强化封装。
11. Java 常见的锁实现?
面试官提问:Java 中有哪些常见的锁?
我:
| 锁类型 | 实现 | 特点 |
|---|---|---|
| synchronized | JVM内置(Monitor) | 自动加解锁,不可中断 |
| ReentrantLock | AQS(AbstractQueuedSynchronizer) | 可中断、超时、公平锁 |
| ReadWriteLock | ReentrantReadWriteLock | 读写分离,提升并发 |
| StampedLock | 乐观读(tryOptimisticRead) | 性能更高,但复杂 |
| 分布式锁 | Redis(SETNX)、ZooKeeper | 跨JVM协调 |
选型建议:
- 简单同步 →
synchronized(JVM优化后性能接近ReentrantLock);- 需要超时/公平 →
ReentrantLock;- 读多写少 →
ReadWriteLock。
12. 并发多线程需要注意的问题?
面试官提问:写多线程程序要注意哪些坑?
我:
五大核心问题:
- 可见性:用
volatile或锁保证主存同步; - 原子性:复合操作(如 i++)需加锁或用
AtomicInteger; - 有序性:指令重排序可能导致逻辑错误(
volatile/锁禁止重排); - 死锁:避免嵌套锁、按固定顺序加锁;
- 线程安全类误用:如
SimpleDateFormat非线程安全,应改用DateTimeFormatter。
调试工具:
jstack查死锁,Arthastrace 方法耗时。
13. MQ 消息发生在事务前还是事务后合适?
面试官提问:在数据库事务中,MQ 消息应该在 commit 前发还是后发?
我:
绝对不能在 commit 前发!否则可能:
- 事务回滚 → 消息已发 →下游收到无效消息(数据不一致)
正确做法(最终一致性):
- 事务内:写 DB + 写消息表(与业务表同库同事务)
- 事务外:异步扫描消息表,发送 MQ,成功后删除记录
进阶方案:
- 本地消息表(如上)
- RocketMQ 事务消息(半消息 + 回查)
- Seata TCC(Try-Confirm-Cancel)
14. 场景题:如何可靠推送消息给外部公司服务?
面试官提问:你们要通过 MQ 推送数据给合作公司的 HTTP 服务,如何保证不丢失、不重复、顺序正确?
我:
这是一个典型的跨系统最终一致性问题,我的方案如下:
(1)消息生产端(我们)
- 使用持久化队列(如 RocketMQ/Kafka + 副本)
- 开启生产者幂等 + ACK=all
- 消息体包含:
msg_id(全局唯一)、biz_seq(业务序号)、payload
(2)消息消费端(我们)→ 外部HTTP
- 可靠投递:
- 消费后先存 DB(状态=“待发送”)
- 调用外部 HTTP 接口(带 retry + 超时)
- 成功 → 更新状态=“已发送”;失败 → 加入重试队列(指数退避)
- 幂等接收(要求对方支持):
- 请求头带
X-Request-ID: msg_id - 对方基于
msg_id去重
- 请求头带
(3)兜底机制
- 对账系统:每天比对我们 DB 与对方回执日志,修复差异;
- 人工干预入口:支持重推单条消息。
关键原则:我们只保证“尽力送达”,最终一致性靠双方协同。
总结:禾赛科技一面考察重点
| 模块 | 考察意图 | 复习建议 |
|---|---|---|
| 项目深挖 | 工程落地能力、故障意识 | 准备“问题-方案-结果”案例 |
| 并发基础 | 线程、锁、线程池 | 动手写 Demo 验证 |
| Spring 事务 | 分布式系统一致性基石 | 理解传播行为 + 事务边界 |
| 缓存选型 | 性能 vs 一致性权衡 | 对比 Redis/Caffeine/Ehcache |
| MQ 可靠性 | 跨系统协作核心 | 掌握本地消息表、事务消息 |
硬科技公司特点:不追求炫技,但极度重视系统稳定性、数据准确性、可维护性。回答时多提“监控”、“降级”、“对账”、“幂等”等关键词!
觉得这篇面经有帮助?欢迎点赞 + 收藏 + 关注!