招银网络科技 java后端开发 二面 面经

1. 深入讲讲你项目中遇到的最复杂的技术问题,如何解决的?

参考答案:

我在做电商系统时,遇到过一个分布式事务的问题。场景是用户下单时,需要同时操作订单服务(创建订单)、库存服务(扣减库存)、积分服务(扣减积分)三个服务。如果其中一个服务失败,其他服务需要回滚,保证数据一致性。

最初我使用了两阶段提交(2PC)方案,但发现性能很差,而且存在单点故障问题。协调者挂掉后,所有参与者会一直阻塞等待。后来改用了Seata的AT模式,它是一种改进的2PC,通过拦截SQL自动生成回滚日志,性能和可用性都有提升。

但在高并发场景下,Seata的全局锁机制导致性能瓶颈。最终我采用了基于消息队列的最终一致性方案。下单时先创建订单,发送消息到MQ,库存服务和积分服务监听消息异步处理。如果处理失败,通过重试机制保证最终成功。对于无法重试的情况,记录到失败表,人工介入处理。

这个方案牺牲了强一致性,但换来了高性能和高可用。通过幂等性设计(使用订单号作为唯一标识)避免重复消费。通过消息持久化和确认机制保证消息不丢失。通过补偿机制处理异常情况。最终系统的TPS从200提升到2000,满足了业务需求。

这个问题让我深刻理解了分布式系统中CAP理论的权衡,以及不同一致性方案的适用场景。

2. 如果让你设计一个秒杀系统,你会如何设计?

答案:

秒杀系统的核心挑战是高并发和超卖问题。我会从以下几个方面设计:

前端优化方面,使用CDN加速静态资源,减少服务器压力。秒杀按钮置灰,只在活动开始前几秒才可点击,避免提前请求。使用验证码或滑块验证,防止机器刷单。前端限流,同一用户短时间内只能提交一次请求。

后端架构方面,使用Nginx做负载均衡和限流,超过阈值的请求直接返回"系统繁忙"。将秒杀服务独立部署,与正常业务隔离,避免相互影响。使用Redis缓存商品信息和库存,减少数据库压力。

库存扣减方面,将库存预加载到Redis中,使用Redis的原子操作(DECR)扣减库存。扣减成功后,异步创建订单并扣减数据库库存。使用Redis的Lua脚本保证库存检查和扣减的原子性,避免超卖。

订单处理方面,秒杀成功后先生成预订单,放入消息队列异步处理。订单服务从队列中消费消息,创建正式订单。如果创建失败,释放库存并通知用户。设置订单超时未支付自动取消,释放库存给其他用户。

防刷措施包括:同一用户同一商品只能秒杀一次,使用Redis记录。IP限流,同一IP短时间内请求次数限制。使用令牌桶算法限流,平滑处理突发流量。接入风控系统,识别异常行为。

监控和降级方面,实时监控系统QPS、响应时间、错误率等指标。设置熔断机制,服务异常时快速失败。准备降级方案,比如返回静态页面、关闭非核心功能等。

3. 讲讲MySQL的事务隔离级别,以及如何解决幻读问题?

答案:

MySQL的事务隔离级别从低到高分为四种:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)、串行化(Serializable)。

读未提交允许读取未提交的数据,会出现脏读问题。读已提交只能读取已提交的数据,解决了脏读,但会出现不可重复读问题(同一事务中多次读取同一数据,结果不一致)。可重复读保证同一事务中多次读取结果一致,解决了不可重复读,但会出现幻读问题(同一事务中多次查询,结果集不一致)。串行化强制事务串行执行,解决了所有问题,但性能最差。

MySQL的InnoDB引擎默认使用可重复读隔离级别。它通过MVCC(多版本并发控制)机制解决了大部分幻读问题。MVCC的原理是每行数据都有多个版本,每个版本有一个事务ID。读取数据时,根据事务的快照读取对应版本的数据,不会被其他事务的修改影响。

但MVCC只能解决快照读的幻读问题,对于当前读(SELECT ... FOR UPDATE、UPDATE、DELETE等)仍然会出现幻读。InnoDB通过Next-Key Lock(记录锁+间隙锁)解决当前读的幻读问题。间隙锁锁定记录之间的间隙,防止其他事务在间隙中插入数据。

在实际应用中,大多数场景使用默认的可重复读就够了。如果对一致性要求极高,可以使用串行化或者在SQL中加锁(FOR UPDATE)。如果对性能要求高,可以降低到读已提交,但要注意不可重复读问题。

4. Redis的持久化机制有哪些?各有什么优缺点?

答案:

Redis提供了RDB和AOF两种持久化机制,以及混合持久化方式。

RDB(Redis Database)是快照持久化,将某个时间点的所有数据保存到磁盘。触发方式有手动触发(SAVE、BGSAVE命令)和自动触发(配置save规则)。BGSAVE会fork一个子进程进行持久化,不阻塞主进程。RDB的优点是文件紧凑,恢复速度快,适合备份和灾难恢复。缺点是可能丢失最后一次快照之后的数据,fork子进程时如果数据量大会有短暂阻塞。

AOF(Append Only File)是追加日志持久化,记录每个写操作命令。有三种同步策略:always(每个命令都同步,最安全但性能差)、everysec(每秒同步一次,平衡性能和安全)、no(由操作系统决定,性能最好但可能丢失较多数据)。AOF的优点是数据更完整,最多丢失1秒数据。缺点是文件体积大,恢复速度慢。Redis会定期重写AOF文件,删除冗余命令,压缩文件大小。

混合持久化(Redis 4.0+)结合了RDB和AOF的优点。AOF重写时,将重写时刻之前的数据以RDB格式写入AOF文件,之后的增量数据以AOF格式追加。这样既保证了数据完整性,又提高了恢复速度。

在实际应用中,如果对数据安全性要求高,使用AOF的everysec策略。如果对性能要求高,可以只使用RDB或者关闭持久化。如果两者都要兼顾,使用混合持久化。还要注意定期备份RDB文件到其他机器,防止机器故障导致数据丢失。

5. 讲讲JVM的内存模型,以及如何排查内存泄漏问题?

答案:

JVM内存模型分为线程共享区和线程私有区。线程共享区包括堆(Heap)和方法区(Method Area)。堆是最大的内存区域,存储对象实例和数组,是垃圾回收的主要区域。方法区存储类信息、常量、静态变量、即时编译器编译后的代码等。线程私有区包括程序计数器、虚拟机栈、本地方法栈。虚拟机栈存储局部变量、操作数栈、方法出口等信息,每个方法对应一个栈帧。

内存泄漏是指对象不再使用但无法被垃圾回收,导致内存占用持续增长,最终导致OOM。常见原因包括:静态集合类持有对象引用,对象无法释放。监听器和回调没有注销,持有外部类引用。ThreadLocal使用后没有remove,线程池中的线程会一直持有。数据库连接、IO流等资源使用后没有关闭。

排查内存泄漏的方法是:首先通过监控工具(如Prometheus+Grafana)观察内存使用趋势,确认是否存在内存泄漏。使用jstat命令查看堆内存使用情况和GC情况,如果Full GC频繁但内存回收不下来,可能存在内存泄漏。使用jmap命令dump堆内存快照,在不同时间点dump多次,对比分析哪些对象在增长。使用MAT(Memory Analyzer Too

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

Java面试圣经 文章被收录于专栏

Java面试圣经,带你练透java圣经

全部评论
好详细的面经 码住
点赞 回复 分享
发布于 今天 15:05 四川

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务