友邦咨询(成都)- Java开发 二面 面经

1. 详细介绍一下你最有成就感的项目,技术架构是怎样的?

我参与开发过一个在线教育平台的后端系统。这个项目采用微服务架构,主要技术栈包括:

技术架构:

  • 后端框架:Spring Boot + Spring Cloud
  • 服务注册与发现:Nacos
  • 网关:Spring Cloud Gateway
  • 负载均衡:Ribbon
  • 熔断降级:Sentinel
  • 远程调用:OpenFeign
  • 数据库:MySQL 8.0(主从复制)+ Redis
  • 消息队列:RabbitMQ
  • 对象存储:阿里云OSS
  • 搜索引擎:Elasticsearch

我主要负责课程管理模块和订单支付模块。课程管理涉及课程的增删改查、分类管理、课程搜索等功能。订单支付模块对接了支付宝和微信支付,需要处理订单创建、支付回调、订单状态更新等流程。

最有挑战的是处理支付回调的幂等性问题,因为第三方支付平台可能会重复发送回调通知。我使用Redis的分布式锁和订单状态机来保证幂等性,确保订单不会被重复处理。

这个项目让我对微服务架构有了深入理解,也积累了处理分布式场景下常见问题的经验。

2. HashMap的底层实现原理,JDK 1.7和1.8有什么区别?

HashMap底层是数组+链表+红黑树的结构。

基本原理:

  • 通过key的hashCode计算hash值,再通过hash值确定在数组中的位置
  • 如果该位置没有元素,直接放入
  • 如果有元素(hash冲突),采用链表或红黑树存储
  • 当链表长度超过8且数组长度大于64时,链表转为红黑树
  • 当红黑树节点少于6时,退化为链表

JDK 1.7和1.8的主要区别:

数据结构:1.7是数组+链表,1.8引入了红黑树,优化了hash冲突严重时的查询性能,从O(n)提升到O(log n)。

插入方式:1.7使用头插法,多线程环境下扩容可能形成环形链表导致死循环;1.8使用尾插法,避免了这个问题。

扩容时机:1.7是先扩容再插入,1.8是先插入再扩容。

hash算法:1.8的hash算法更简单,只做了一次异或运算,性能更好。

扩容优化:1.7需要重新计算所有元素的位置;1.8通过高位运算,元素要么在原位置,要么在原位置+旧容量的位置,不需要重新计算hash。

3. ConcurrentHashMap的实现原理,1.7和1.8有什么区别?

ConcurrentHashMap是线程安全的HashMap,性能比Hashtable好很多。

JDK 1.7实现:

  • 采用分段锁(Segment)机制,每个Segment继承自ReentrantLock
  • 默认16个Segment,理论上支持16个线程并发写
  • 每个Segment内部是一个类似HashMap的结构
  • 读操作不加锁(使用volatile保证可见性),写操作只锁当前Segment
  • size()方法需要遍历所有Segment,先尝试不加锁统计,如果发现有修改则加锁重新统计

JDK 1.8实现:

  • 取消了Segment分段锁,采用CAS + synchronized实现更细粒度的锁
  • 锁的粒度是数组的每个位置(Node),并发度更高
  • 数据结构改为数组+链表+红黑树,与HashMap 1.8一致
  • 使用CAS操作进行无锁的插入和更新
  • 只有在hash冲突时才使用synchronized锁住链表或红黑树的头节点
  • size()方法使用baseCount + counterCells数组来统计,性能更好

1.8的实现更加高效,锁的粒度更细,并发性能更好,代码也更简洁。

4. 说说MySQL的索引优化策略和最佳实践

索引优化策略:

选择合适的字段建索引:

  • where、order by、group by、join on的字段
  • 区分度高的字段(重复值少)
  • 字段长度小的优先

联合索引设计:

  • 遵循最左前缀原则
  • 区分度高的字段放在前面
  • 范围查询的字段放在最后

避免索引失效:

  • 不在索引列上使用函数或表达式
  • 避免隐式类型转换
  • like查询不以%开头
  • 避免使用!=、<>、not in
  • 注意or条件,确保所有字段都有索引

覆盖索引:查询的字段都在索引中,避免回表,性能最优。

索引下推:MySQL 5.6引入,在索引遍历过程中就过滤掉不符合条件的记录,减少回表次数。

前缀索引:对于长字符串字段,可以只索引前几个字符,节省空间。

索引维护:

  • 定期分析表(ANALYZE TABLE)更新索引统计信息
  • 删除冗余和重复的索引
  • 监控慢查询日志,针对性优化

最佳实践:

  • 单表索引数量不要太多(一般不超过5个),影响写入性能
  • 小表不需要建索引,全表扫描更快
  • 对于频繁更新的字段,谨慎建索引
  • 使用EXPLAIN分析执行计划,确保索引生效

5. 介绍一下Spring Bean的生命周期

Spring Bean的生命周期主要包括以下阶段:

实例化(Instantiation):

  • Spring容器通过反射调用Bean的构造方法创建对象

属性赋值(Populate):

  • 通过依赖注入,为Bean的属性赋值
  • 包括@Autowired、@Value等注解的处理

初始化前(BeanPostProcessor.postProcessBeforeInitialization):

  • 执行BeanPostProcessor的前置处理方法
  • 比如@PostConstruct注解的方法就在这个阶段执行

初始化(Initialization):

  • 如果Bean实现了InitializingBean接口,调用afterPropertiesSet方法
  • 如果配置了init-method,调用指定的初始化方法

初始化后(BeanPostProcessor.postProcessAfterInitialization):

  • 执行BeanPostProcessor的后置处理方法
  • AOP代理就是在这个阶段创建的

使用(In Use):

  • Bean已经准备就绪,可以被应用程序使用

销毁前(@PreDestroy):

  • 容器关闭前,执行@PreDestroy注解的方法

销毁(Destruction):

  • 如果Bean实现了DisposableBean接口,调用destroy方法
  • 如果配置了destroy-method,调用指定的销毁方法

关键扩展点:

  • BeanFactoryPostProcessor:在Bean实例化之前修改Bean定义
  • BeanPostProcessor:在Bean初始化前后进行增强
  • Aware接口:让Bean感知到Spring容器(如BeanNameAware、ApplicationContextAware)

了解Bean的生命周期有助于在合适的时机进行扩展和定制。

6. 分布式锁有哪些实现方式?各有什么优缺点?

常见的分布式锁实现方式:

基于Redis实现:

方案一:SETNX + EXPIRE

  • 使用SETNX设置key,成功则获得锁
  • 设置过期时间防止死锁
  • 问题:SETNX和EXPIRE不是原子操作,可能导致死锁

方案二:SET key value NX EX seconds

  • Redis 2.6.12后支持,原子操作
  • value设置为唯一标识(如UUID),释放锁时校验,防止误删
  • 使用Lua脚本保证释放锁的原子性
  • 问题:主从复制异步,主节点宕机可能导致锁丢失

方案三:Redisson

  • 封装了分布式锁的实现,支持可重入锁
  • 使用watchdog机制自动续期,防止业务执行时间过长导致锁过期
  • 支持RedLock算法,解决主从切换问题
  • 推荐使用

基于Zookeeper实现:

  • 利用临时顺序节点实现
  • 客户端创建临时顺序节点,序号最小的获得锁
  • 其他客户端监听前一个节点,前一个节点删除时获得锁
  • 优点:可靠性高,不会因为网络问题导致锁丢失;支持阻塞等待
  • 缺点:性能不如Redis;依赖Zookeeper集群

基于数据库实现:

  • 方案一:使用唯一索引,插入成功则获得锁
  • 方案二:使用for update行锁
  • 优点:实现简单,不需要额外组件
  • 缺点:性能差,不适合高并发场景;可能有死锁风险

选择建议:

  • 性能要求高:Redis + Redisson
  • 可靠性要求高:Zookeeper
  • 简单场景:数据库实现

7. 如何保证消息队列的消息不丢失?

消息丢失可能发生在三个阶段,需要分别处理:

生产者端丢失:

问题:消息发送到MQ前,网络故障或MQ宕机导致消息丢失。

解决方案:

  • 开启生产者确认机制(Publisher Confirm)
  • RabbitMQ:使用confirm模式或事务模式
  • R

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

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

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

全部评论

相关推荐

本人双九(一般的985)现在🐻厂实习字节hr两次电联同一个职位的实习,问我考不考虑,第二次说的很恳切🐻厂给的测开,字节给的后端本人还没确定,秋招走大厂还是走央国企,目前暂时偏向稳定,更倾向于在江浙一带工作犹豫的点:1.目前组内氛围好,mt也好,组长也好2.如果再面试,我需要重跑我的项目,再准备+八股+手撕,会很累3.如果准备不充分,怕脏面评过来人告诉你&nbsp;字节hr&nbsp;就是这德行面试前对每个候选人特别舔&nbsp;面完找他问进度就查无此人了哈哈哈哈哈,这几天在xhs上略有了解海捞吧hhh前期很热情,突然很冷淡😁双九这么不自信的吗?我这个实习的准备时间短,感觉在接其他面试被拷打了,项目和技术栈不熟,就没什么自信稳定也挺好呀在面字节前面几家其它的后端面试会好一些哈哈哈哈我同门面字节,一面二面就没有低于1h的,哈人🐻厂留用率?哥们儿你不知道字节是一线大厂里面发面最多的吗,发面试又不是oc,hr再恳切有作用吗现在知道了哈哈哈为啥要犹豫,字节不还是正常面试吗,又不是已经拿到oc了不想脏面评,不过现在也无所谓了,也不准备去大厂现在这么卷,0实习能进的,都是有真本事的测开转后端也不错吧,不过HR虽然舔,面试要求不会放松的🐻厂留用率?听说转正会比较容易?不是特别了解还以为是到hr面了让你去呢,就约个面试能有多诚恳那就赶紧准备啊,看看别人面经,刷八股啥的暂时没有特别想换的准备,大概率all&nbsp;in央国企了字节hr都很热情吧,最近27届暑期开了,也收到几个电话&nbsp;但是考虑不想脏面评就不面了已经略有了解了,感觉很海捞建议不要脏面评,年后投随便约面666又遇到兄弟了hr是说约年后试一下咯暂时不考虑了hhh双***历都到顶了不是很厉害的9,曾经被嘲过要不等年后吧,不差这一次吧??字节那不是投了就能面吗?一定要面这个岗?打电话也是准备说年后了,年前肯定不行哈哈哈我投的少,我也不知道是不是投了就能面主要怕我面得不好,唉双9干什么测开哈哈哈哈哈,准备实习的时间很短很仓促,收到的后端面试不多,想着先有个实习就走了测开熊是什么厂百度大胆去面吧&nbsp;我就是不自信加上没后端实习&nbsp;秋招大厂只投了测开&nbsp;现在有点后悔,不自信加上项目不熟,我感觉会被拷打所以害怕没事&nbsp;不会损失啥&nbsp;等你真的拿了测开就会像我一样从激动欣喜到焦虑前景还有一个就是怕字节脏面评,所以如果接面试肯定也会努力准备一下的如果秋招不满意的话,还能搏一搏春招吧,我身边就有在春招找到不错的岗位的还有就是,emmmm,周围也有面字节的,普遍表示有难度,我觉得我菜
点赞 评论 收藏
分享
02-28 13:25
已编辑
门头沟学院 Java
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
2
3
分享

创作者周榜

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