25年11月快手 Java开发 二面
#JAVA##JAVA面经##JAVA内推#
1. 分段锁设计,扩容时如何避免并发冲突?
思路
核心是“分段加锁+扩容时单段独立处理”,结合实际项目中应对并发的经验,用通俗的语言说明分段锁逻辑和扩容时的并发控制,不涉及代码,突出实操性。
回答示例
面试官您好,我们项目中用分段锁解决高并发下的扩容冲突问题,核心思路很简单,就是把整个存储容器分成多个独立的段,每个段单独加一把锁,互不影响。扩容的时候,我们不会对整个容器全局扩容,而是只针对单个段进行操作。
具体来说,当某个段的数据量达到扩容阈值时,我们只对这个段加锁,然后进行数据迁移、容量翻倍的操作,其他段正常处理读写请求,完全不受影响。而且扩容时会做双重校验,防止多个线程同时对同一个段进行扩容,迁移数据的时候全程加锁,避免数据丢失或重复,最后瞬间切换数据引用,完成扩容,整个过程不会出现并发冲突,也不会影响整体服务性能。
2. 秒杀库存扣减:Redis+Lua脚本实现原子性,解释脚本逻辑
思路
先说明秒杀库存扣减的核心痛点,再结合实际项目中Redis+Lua的应用场景,通俗解释原子性优势和脚本核心逻辑,不写脚本代码,突出落地性。
回答示例
面试官您好,秒杀场景最核心的问题就是并发超卖,比如多个用户同时抢购最后一件商品,很容易出现多个人都扣减成功的情况,本质就是“查库存+扣库存”这两个操作不是原子的,中间可能被其他请求打断。
我们项目中解决这个问题,就是用Redis配合Lua脚本来实现的,核心原因就是Lua脚本在Redis中是原子执行的,一旦开始执行,就会阻塞其他所有命令,直到脚本执行完成,从根本上避免了并发打断的问题。
脚本的逻辑很简单,没有复杂语法,就是先获取当前的库存数量,判断库存是否充足,如果库存为空或者不够扣减,就直接返回失败,不让用户抢购;如果库存充足,就执行库存扣减操作,最后返回成功,前端再根据返回结果提示用户是否抢购成功。实际落地时,我们还会配合Redis的AOF和RDB持久化,防止Redis宕机导致库存丢失,确保数据可靠,而且脚本很轻量,执行速度快,能支撑高并发的秒杀场景。
3. 手写LRU缓存(O(1)时间复杂度),用LinkedHashMap实现
思路
结合实际开发中使用LinkedHashMap实现LRU缓存的经验,通俗说明核心逻辑、O(1)时间复杂度的原因,不写代码,突出实用性和易懂性。
回答示例
面试官您好,LRU缓存就是“最近最少使用”的缓存策略,核心要求就是查询和存入操作的时间复杂度都是O(1),我们项目中没有手动去实现哈希表和双向链表,而是直接复用了Java自带的LinkedHashMap,开发效率高,也能满足需求。
LinkedHashMap底层其实就是“哈希表+双向链表”的组合,这也是它能实现O(1)时间复杂度的关键。哈希表的作用是快速定位数据,不管是查询还是存入,都能通过key的哈希值直接找到对应位置,时间复杂度是O(1);双向链表的作用是维护数据的访问顺序,最近使用的数据会被移到链表尾部,最久未使用的在链表头部,当缓存容量满了,就直接移除头部的数据,这个移动和移除操作也不用遍历,直接操作链表首尾,也是O(1)。
我们实际使用时,只需要重写LinkedHashMap的一个方法,用来判断缓存是否达到容量上限,达到上限就自动移除最久未使用的元素,不用额外写复杂逻辑,简单又实用,完全能满足项目中对LRU缓存的需求。
4. 分布式事务TCC模式,如何处理空回滚问题?(补偿事务设计)
思路
先解释TCC三个阶段和空回滚的实际场景,再结合项目中真实的解决方案,说明如何通过事务状态记录和补偿幂等性处理空回滚,不涉及代码,突出落地经验。
回答示例
面试官您好,首先我先简单说下TCC模式,它主要分三个阶段:Try阶段是预留资源,Confirm阶段是确认提交、真正执行业务,Cancel阶段是补偿回滚、释放预留的资源。空回滚就是指,Cancel阶段被触发了,但对应的Try阶段其实没执行成功,甚至根本没执行,这时候执行Cancel,就会出现空操作,甚至可能导致数据异常。
我们项目中处理空回滚,主要用了两个方案,结合起来双重保障,落地性很强。第一个是核心方案,就是建一个事务状态表,每个分布式事务都有一个唯一的事务ID,在Try阶段执行前,先往表里插一条记录,状态设为“正在执行Try”;如果Try执行成功,就把状态改成“Try成功”;如果Try失败,比如库存不足、接口调用失败,就改成“Try失败”。当Cancel阶段触发时,先查这个状态表,如果状态不是“Try成功”,就直接返回成功,不用执行回滚操作,避免空回滚。
第二个是兜底方案,保证Cancel方法的幂等性。就算出现了空回滚,也不会影响数据,比如Cancel阶段是释放预留的库存,我们会先判断这个事务ID对应的库存是否被预留过,存在就释放,不存在就直接返回成功,避免重复回滚导致库存多增等异常。比如下单扣库存的场景,Try阶段失败了,状态改成“Try失败”,后续Cancel触发时,查状态表就不会执行回滚,既避免了空回滚,也保证了数据安全。
5. Nacos自定义负载均衡策略:基于权重的轮询算法
思路
结合项目中Nacos的实际使用经验,说明自定义权重轮询策略的原因、核心逻辑和配置方式,不写代码,突出实操性和业务适配性。
回答示例
面试官您好,Nacos本身自带负载均衡功能,但默认是简单轮询,也就是每个服务节点被选中的概率是一样的。但我们项目中,服务节点的性能有差异,比如有的节点配置高、性能好,有的节点配置一般,简单轮询会导致性能差的节点被压垮,所以我们自定义了基于权重的轮询策略。
核心逻辑很简单,就是根据服务节点的性能,在Nacos控制台给每个节点配置不同的权重,性能好的节点权重设高一点,比如5,性能一般的设1,性能差的设0.5,自定义策略会读取每个节点的权重,然后按权重分配请求,权重越高的节点,被选中的概率越大,这样就能让性能好的节点承担更多流量,避免性能差的节点过载,提升整体服务的稳定性和响应速度。
实现的时候,我们是通过实现Nacos兼容的负载均衡接口,复用了Nacos自带的权重轮询工具类,不用自己写复杂的权重计算逻辑,开发起来很简单,配置好权重后,策略会自动生效,后续如果节点性能有变化,直接在Nacos控制台修改权重就行,不用改代码、不用重启服务,很方便。
6. 高并发ID生成器:改进雪花算法(增加数据中心ID,每秒10万+)
思路
结合高并发场景下ID生成的实际需求,说明原生雪花算法的不足、改进思路,以及如何实现高并发、唯一ID生成,不写代码,突出实用性和性能保障。
回答示例
面试官您好,我们项目是高并发场景,对ID生成的要求是唯一、高效,每秒能生成10万+ID,原生雪花算法虽然能生成唯一ID,但它没有数据中心ID,在多数据中心部署的时候,比如不同地域的服务节点,很容易出现ID重复的问题,所以我们对雪花算法做了改进。
改进的核心就是增加了数据中心ID字段,同时调整了各字段的位数,保证整体是64位ID。具体来说,64位ID里,1位是符号位,固定为0,保证ID是正数;31位是毫秒级时间戳,能使用约69年,足够我们项目使用;5位是数据中心ID,支持32个数据中心,满足多地域部署需求;5位是机器ID,每个数据中心支持32台
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专栏在精不在多,内容分为八股文、大厂真实面经,面试通过后将offer和面试题私发给我,可退还专栏的收益部分费用。欢迎大家共建专栏