途虎养车-Java开发-二面 面经

1、介绍一下你最近做的一个有挑战性的项目

我最近负责的是一个汽车配件供应链系统的重构项目,这个系统日均订单量在30万左右,面临的主要挑战有三个方面。

  1. 性能问题,原系统在高峰期响应时间经常超过3秒,用户体验很差。我通过引入Redis多级缓存、优化数据库索引、改造慢查询,将P99响应时间降到了200ms以内。具体做法是对热点商品数据做了本地缓存+Redis缓存的二级缓存,对库存查询做了批量查询优化,还把一些复杂的关联查询改成了分步查询在应用层组装。
  2. 数据一致性问题,涉及到订单、库存、物流多个系统的数据同步。我引入了RocketMQ的事务消息机制,保证了分布式事务的最终一致性。同时设计了补偿机制和对账任务,每天凌晨会跑对账脚本检查数据是否一致。
  3. 系统稳定性问题,原来是单体应用,一个模块出问题整个系统都挂。我主导了微服务拆分,按业务域拆成了商品、订单、库存、支付等服务,引入了Sentinel做限流熔断,配置了服务降级策略。上线后系统可用性从95%提升到了99.5%以上。

这个项目让我对高并发系统设计和分布式架构有了更深的理解,也锻炼了我解决复杂问题的能力。

2、JVM内存模型和垃圾回收机制详细说一下

JVM内存模型分为线程共享区和线程私有区。

  1. 线程共享区包括堆和方法区。堆是最大的内存区域,存储对象实例,分为新生代和老年代,新生代又分为Eden区和两个Survivor区,比例是8:1:1。方法区存储类信息、常量、静态变量,JDK8之后改成了元空间使用本地内存。
  2. 线程私有区包括程序计数器、虚拟机栈、本地方法栈。程序计数器记录当前线程执行的字节码行号,虚拟机栈存储局部变量表、操作数栈、方法出口等,每个方法对应一个栈帧。

垃圾回收机制方面:

  1. 判断对象是否存活用的是可达性分析算法,从GC Roots开始遍历,能到达的对象是存活的,不能到达的就是垃圾。GC Roots包括虚拟机栈中的引用、方法区的静态变量、常量引用等。
  2. 回收算法有标记-清除、标记-复制、标记-整理三种。新生代用复制算法,因为对象存活率低,复制成本小。老年代用标记-清除或标记-整理,因为对象存活率高,复制成本大。
  3. 垃圾收集器我们用的是G1,它把堆分成多个Region,可以预测停顿时间,适合大堆内存。G1的回收过程包括新生代回收、并发标记、混合回收、Full GC。我们配置了-XX:MaxGCPauseMillis=200控制停顿时间,-XX:G1HeapRegionSize=16m设置Region大小。
  4. 实际调优经验是,先用jstat监控GC情况,如果Full GC频繁就要分析原因。可能是堆内存不够需要调大-Xmx,可能是内存泄漏需要用jmap dump堆然后用MAT分析,也可能是大对象直接进老年代需要调整-XX:PretenureSizeThreshold。

3、Spring Boot的自动装配原理是什么

Spring Boot自动装配的核心是@EnableAutoConfiguration注解,它的实现原理是:

  1. 启动类上的@SpringBootApplication包含了@EnableAutoConfiguration注解,这个注解通过@Import导入了AutoConfigurationImportSelector类
  2. AutoConfigurationImportSelector会读取所有jar包下的META-INF/spring.factories文件,获取EnableAutoConfiguration对应的配置类列表
  3. 这些配置类通过@Conditional系列注解来判断是否生效,比如@ConditionalOnClass判断类路径下是否存在某个类,@ConditionalOnMissingBean判断容器中是否缺少某个Bean
  4. 如果条件满足,配置类就会生效,通过@Bean注解向容器中注册组件。比如RedisAutoConfiguration会判断是否有Redis相关的类,如果有就自动配置RedisTemplate
  5. 用户可以通过application.yml配置属性来定制自动配置的行为,这些属性通过@ConfigurationProperties注解绑定到配置类上
  6. 如果用户自己定义了某个Bean,自动配置就会失效,因为有@ConditionalOnMissingBean的判断

这种设计的好处是约定大于配置,开发者只需要引入starter依赖就能自动配置好组件,大大简化了开发工作。我在项目中也自定义过starter,把公司的通用组件封装成starter供其他团队使用。

4、分布式锁的实现方案有哪些,各有什么优缺点

分布式锁主要有三种实现方案:

  1. 基于Redis实现,最常用的方式。可以用SETNX+EXPIRE命令,但要注意原子性问题,建议用SET key value NX EX seconds一条命令完成。释放锁时要判断是不是自己持有的锁,用Lua脚本保证原子性。还要考虑锁续期问题,可以用Redisson框架,它实现了看门狗机制自动续期。优点是性能高、实现简单,缺点是Redis主从切换时可能丢失锁,可以用RedLock算法向多个Redis实例申请锁来解决。
  2. 基于Zookeeper实现,利用临时顺序节点。每个客户端创建一个临时顺序节点,序号最小的获得锁,其他节点监听前一个节点。释放锁时删除节点,后续节点收到通知尝试获取锁。优点是可靠性高,节点删除会自动释放锁,缺点是性能不如Redis,需要依赖Zookeeper集群。
  3. 基于数据库实现,创建一张锁表,通过唯一索引或for update来实现。优点是不需要额外组件,缺点是性能差,数据库压力大,不适合高并发场景。

我在项目中用的是Redisson实现的分布式锁,它封装了很多细节,比如锁续期、可重入、公平锁等特性都支持。在处理订单库存扣减时,用分布式锁保证了并发安全,同时配置了合理的超时时间避免死锁。

5、如何设计一个高可用的系统架构

设计高可用系统需要从多个层面考虑:

  1. 服务层面,首先是无状态设计,应用服务器不保存状态,session存Redis,这样可以随意扩缩容。然后是服务冗余,每个服务至少部署3个实例,挂掉一个不影响整体。还要做好服务隔离,核心服务和非核心服务分开部署,避免相互影响。
  2. 数据层面,数据库做主从复制,主库挂了从库可以顶上。Redis做哨兵或集群模式,保证高可用。重要数据要做异地多活,不能只依赖一个机房。还要定期备份数据,制定灾难恢复预案。
  3. 限流降级,用Sentinel做接口限流,防止流量突增压垮系统。配置降级策略,非核心功能可以降级,比如推荐服务挂了就返回默认推荐。熔断机制也很重要,

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

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

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

全部评论

相关推荐

03-13 00:04
已编辑
吉林大学 Java
约面的挺突然。。狠下心接了1.自我介绍2.讲讲JAVA的反射3.可以继续讲讲AOP,动态代理[ 因为讲反射不小心吟唱到了例如AOP的动态代理,但是这块记忆的非常不熟,结果磕磕绊绊 ]4.项目我看你写了AOP和注解,具体怎么实现滑动窗口限流的[ 梦到什么说什么,吟唱八股发散千万不要散到自己不熟悉的区域 ]5.也讲讲为什么另一个项目选择令牌桶,具体流程6. OK,讲讲 Redis 的数据类型?还有吗?就了解这五种嘛[ 把5个的基础类型从应用对比到历届底层全都吟唱了一遍。一句还有吗直接没力气了,简历就写了理解5种,别的我是真一点没看TT ]7.讲讲Redission分布式锁实现8.这个指数退避怎么实现的9.在这里有考虑去保障幂等性嘛10.这里为什么使用指数退避呢? 什么时候用均匀重传[已经晕过去了说不了解,刚说了后就意识到,估计应该说指数退避能缓解压力防止下游服务器雪崩之类的]11.ok,那讲讲JMM12.讲讲RocketMQ如何保证的不丢消息13.讲讲RocketMQ延迟消息原理14.讲讲项目Redis实现会话记忆这一块15.如果ai调用function calling出现幻觉,有考虑怎么解决吗?[ 不了解,面试官说什么接口幂等化,高危操作人工防护,没在听,感觉人已经飞升了TT ]16.mcp了解嘛?和function calling有什么区别[ 依旧不了解,只能说了个前者规范架构抽象解耦,后者耦合高只能算个工具调用]17.AI生成代码的代码质量怎么保障,那平时如何review的呢18.算法。lc215  数组中最大第k个元素19.打算考研还是本科就业20.反问1️⃣有哪里不足,有哪些需要提高的部分。[主要说知识广度不够,多刷算法,让我别太紧张]2️⃣部门业务会做什么人生第二次面试。感觉大厂面试官的气场压力很大应该凉了不过这次面试非常锻炼心态,多面试,多面试。
冰炸橙汁_不做oj版:redis除了五种基本数据类型,其他的几种还是要掌握一下的,挺常用
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

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