首页 > 观妙科技 - Java开发 二面 面经
头像
小工妹
发布于 今天 10:39 上海
+ 关注

观妙科技 - Java开发 二面 面经

1. 上次一面聊了你的项目,能再深入说说项目中遇到的最大挑战吗?

最大的挑战是处理秒杀场景的高并发问题。

当时模拟了一个促销活动,瞬间有几千个请求抢购商品。最初的实现是直接查数据库库存,然后扣减,结果出现了超卖问题,而且数据库压力特别大,响应很慢。

我分析后发现主要有三个问题:数据库成为瓶颈,库存扣减不是原子操作,没有做流量控制。

解决方案是多层优化。首先把库存预热到Redis,用Lua脚本保证扣减的原子性。Lua脚本先判断库存是否充足,充足才扣减,整个过程是原子的。然后在网关层用Sentinel做限流,超过阈值直接拒绝。订单创建改成异步的,扣完库存就返回,通过RabbitMQ慢慢处理订单。

还做了一些细节优化,比如前端按钮防重复点击,Nginx做负载均衡,数据库读写分离。通过JMeter压测,优化后QPS从几百提升到三千多,没有出现超卖。

这个过程让我理解了高并发场景下要从多个层面优化,单点优化效果有限。也学会了用工具定位瓶颈,针对性解决问题。

2. 说说ConcurrentHashMap的实现原理,和HashMap有什么区别?

ConcurrentHashMap是线程安全的HashMap,但实现机制不同。

JDK 1.7用分段锁,把数据分成多个Segment,每个Segment是一个小的HashMap,继承自ReentrantLock。读操作不加锁,写操作只锁当前Segment,理论上支持16个线程并发写。

JDK 1.8取消了Segment,改用CAS加synchronized实现更细粒度的锁。数据结构和HashMap一样,是数组加链表加红黑树。锁的粒度是数组的每个位置,只有hash冲突时才用synchronized锁住链表或红黑树的头节点。使用CAS进行无锁的插入和更新,性能更好。

和HashMap的区别:

线程安全性,HashMap不是线程安全的,ConcurrentHashMap是。如果多线程用HashMap会出现数据丢失、死循环等问题。

null值,HashMap允许一个null key和多个null value,ConcurrentHashMap都不允许,会抛异常。

性能,单线程HashMap更快,多线程ConcurrentHashMap更快。

迭代器,HashMap的迭代器是fail-fast的,ConcurrentHashMap是弱一致性的,不会抛ConcurrentModificationException。

实际使用中,单线程用HashMap,多线程用ConcurrentHashMap。不要用Hashtable,性能差而且过时了。

3. JVM的垃圾回收机制了解吗?有哪些垃圾回收器?

JVM垃圾回收主要针对堆内存。

判断对象是否可回收用可达性分析算法,从GC Roots开始遍历,能到达的对象是存活的,不能到达的是垃圾。GC Roots包括虚拟机栈中的引用、静态变量、常量、本地方法栈的引用等。

垃圾回收算法有几种:

标记-清除,标记存活对象,清除未标记的,简单但会产生内存碎片。

标记-复制,把内存分两块,每次只用一块,垃圾回收时把存活对象复制到另一块,然后清空这一块。不会有碎片但浪费空间,适合新生代。

标记-整理,标记存活对象,然后移动到一端,清理边界外的内存。不会有碎片也不浪费空间,但移动对象有开销,适合老年代。

常见的垃圾回收器:

Serial,单线程,适合客户端应用。Parallel,多线程,吞吐量优先,适合后台计算。CMS,并发标记清除,停顿时间短,但会产生碎片。G1,分区回收,可预测停顿时间,JDK 9后的默认回收器,适合大内存应用。ZGC和Shenandoah,低延迟回收器,停顿时间在10ms以内,适合对延迟敏感的应用。

实际使用中,根据应用特点选择回收器。吞吐量优先用Parallel,延迟敏感用G1或ZGC。通过JVM参数调整堆大小、新生代比例等,使用工具分析GC日志优化性能。

4. 分布式事务有哪些解决方案?你项目中用过吗?

分布式事务是跨多个数据库或服务的事务,保证一致性比较复杂。

常见解决方案:

2PC两阶段提交,协调者先询问所有参与者能否提交,都同意再发提交命令。强一致性但性能差,有阻塞和单点问题,实际很少用。

TCC,业务层面的两阶段提交。Try预留资源,Confirm确认提交,Cancel回滚。需要业务代码实现三个接口,开发成本高但性能好,适合对一致性要求高的场景。

本地消息表,在本地数据库记录消息,事务提交后发送MQ,消费者处理后更新状态。定时任务扫描未发送的消息重试。实现简单,保证最终一致性。

消息事务,RocketMQ支持事务消息。发送半消息,执行本地事务,根据结果提交或回滚消息。消费者处理消息,失败则重试。

Saga模式,把长事务拆分为多个本地短事务,每个事务有对应的补偿操作。某步失败就执行之前所有步骤的补偿。适合长流程业务。

我项目中用的是消息队列保证最终一致性。比如下单后扣库存、扣积分、发通知,这些操作通过Rabb

全部评论

(1) 回帖
加载中...
话题 回帖

近期热帖

热门推荐