Skip to content

字节国际电商一面 2025/4/17

首先感觉大概率是KPI面,面试官没有开摄像头,声音也比较沙哑,问到的项目是QQ机器人和我的世界的插件,论坛项目和NTQQ插件没有问到。

但是还是很感谢给了面试机会,是我人生中的第一个技术面,之前面的华资企业的面试是纯聊天,是做广东的人事和社保方面的业务。

面试的手撕题是一个字符串中的最长不重复子串,我一开始很紧张,没想到用滑动窗口解决,面试官还是很耐心地在等我,然后卡壳的时候给我思路,然后终于搞定了。其实过不过就无所谓,当积累经验!

问的问题比较基础,Redis的数据类型、Java三大基本特性、Java的hashmap、数据库的二级索引等

我本次不会的问题:

数据库

问到的一个不会的是,ABC三个独立的索引,查询A=1,B=1,C=1,那么结果会走几次索引呢?

我当时没想明白,说的是三次,但是实际上应该是一次。因为先查A=1。走A的索引,就已经拿到了所有的数据了,直接在结果集里面继续筛选B和C的值就好啦。

项目

问到了QQ机器人点餐服务,感觉应该扩展开来讲。我对这个的准备不足。

问到了在并发点餐的量很大的情况下,如何保证订单不丢失之类的?

还有假如用户点餐了,本应收到回馈消息,但是可能机器人没发出来,而用户又去点餐了,造成了重复点餐操作,这个场景怎么办?

我回答是redis互斥锁,他说变成串行化了,效率不够我回答Redis乐观锁,他说会不会冲突很严重,怎么办呢?

我还回答说对数据库的操作转移到Redis中,用LUA脚本实现,加快速度。但是面试官似乎并不是很满意。

我又回答说要用消息队列,面试官问我那这个消息队列里面应该有哪些元素?我回答说订单ID、下单者之类的。但是面试官说这样是不是这个消息队列没发挥作用?

合理的解决方案

把订单进行分区分片处理。我一开始想着,这个分片应该在哪里进行?比如500个余量,我分片到Redis中,然后让服务线程自己去抢吗?但是如果多线程在并发抢夺的时候,又有了新增库存的需求怎么办,是不是会变得复杂?

朋友提示我这个分片机制应该直接交给一个中间服务去解决,比如完全可以创建一个订单分派服务,批量处理订单需求。订单服务接收增加库存和下单操作,有申请订单需求进来的时候,就根据当前的剩余容量判断是否能分发。也就是把订单的生成和下单分开,不是一个线程申请下单,就马上给它生成订单,而是批量化处理。

由后台主动分配给处理订单的服务,订单的服务是不能主动从我这里拿的。只有我能分配,你不能主动拿,消息队列解决的就是这个问题。

现在可以这样实现:用户下单,不要每次下单一次就处理一次,后台压力大,应该先把订单需求放到Redis里,然后后台应该安排一个定时任务,定时从Redis中获取订单需求,拿到需求之后,向订单派发服务申请订单号。如果成功了,那么就说明下单成功了。否则就可能是剩余容量不足了。

后台从Redis获取到任务时,可以发送到消息队列里面,防止服务宕机了消息丢失。

还可以往防抖和节流上想,如果只是固定时间间隔处理,会不会太死板了,可以用防抖函数,每次请求打进来,就判断距离上次处理订单的时间,假如不足一秒,那这个订单就放入到Redis里面缓存。如果超过了,那么就从Redis里面拉取订单请求出来,发给下游服务。又或者说,设置一个订单容量上限,假设缓存到Redis中后,检测到Redis里面缓存的订单数据已经超过十万条了,那么就直接拉出来做处理。

节流的思想就是不要每个请求都直接申请订单,在上面的分片思想已经体现了。订单是定时批量处理而不是说直接每个请求都去申请订单。

消息队列怎么在这里用?

面试官实际上在考察我对消息队列的解耦场景和业务设计粒度的理解是否到位!

关键问题:消息队列的核心职责被忽略了 你提到的 订单ID、下单者 属于业务基础信息,但这些数据只能描述“谁下单”,而消息队列在防超卖场景中真正要解决的是 库存操作的可靠性与时序性。面试官暗示的潜台词是: “你的消息队列设计没有体现库存扣减的关键逻辑,队列和业务目标脱节了。”

正确设计:消息队列中必须包含的4类元素

  1. 库存操作核心数据 菜品ID + 购买数量:明确要对哪个资源扣减多少库存

操作类型(扣减/释放):例如 action: deduct(预扣库存)或 action: release(超时未支付释放库存)

操作阶段标识:例如 stage: pre_lock(预扣阶段)和 stage: final_commit(最终提交)

消息队列在防超卖中的具体作用?

  1. 流量削峰 设计要点:将瞬间的抢购请求堆积到队列中,按 数据库处理能力 匀速消费。

    消息字段关联:通过 timestamp 实现请求时序控制,按批次处理。

  2. 异步解耦 设计要点:扣库存(关键操作)和创建订单(非关键操作)分离,避免长事务锁表。

消息字段关联:action 字段决定走库存服务还是订单服务。

  1. 最终一致性 设计要点:通过消息重试+死信队列保证库存操作最终成功。

消息字段关联:retry_count 和 fallback_topic 实现容错。

deepseek查询的结果

第一层,数据库原子性操作 用数据库的行级锁或CAS保证最终的一致性。

优点是绝对可靠,在数据库层面防止超卖,缺点是高并发下压力大,瓶颈明显

第二层,Redis预扣库存。 核心逻辑是把库存预热到Redis,先扣缓存,再异步落库。用LUA脚本保证操作的原子性。

异步落库:用消息队列通知数据库扣除缓存。

优点:Redis的QPS很高。 问题:

  • 缓存和数据库不一致;如果异步消息丢失,要引入定时对账补偿。

第三层:库存分片。

比如我们有500个storage。那么其实我们可以分成50片!每片的容量是50。 可以让用户请求随机分片扣减,分散压力。 将并发竞争从单点变成多点,提升吞吐量。

如何回答面试官?

  1. 前端限流,用令牌桶算法过滤超量的请求,避免系统的过载;
  2. Redis预扣减,通过LUA脚本原子扣减分片库存,支持高并发。
  3. 异步落库,用消息队列保证最终一致性,同时定时对账修复差异。
  4. 数据库兜底,最终扣减时用WHERE条件防止超卖。 此外,针对热点商品,可以通过 库存分片 + 本地缓存 进一步分散压力,并在库存耗尽时快速熔断。

面试官追问方向预判

Redis和数据库数据不一致怎么办?

答:异步消息投递时用 唯一ID防重,对账系统定期校验修复,业务上允许少量短暂不一致(如显示卖完但短暂可下单)。

消息队列挂了如何处理?

答:生产端本地存储失败消息,异步重试;消费端幂等处理。

恶意用户反复刷接口如何防范?

答:用户维度限流(如每秒1次)、验证码、设备指纹封禁。

Java八股文

观察到面试官说到了Java的四大基本特性,我当时有点疑问,不应该是三大特性吗?就是封装、继承、多态,感觉像是面试官问错了。问AI说第四个可能是抽象。

面试官问我HashMap的底层原理中,都知道链表长度大于8的时候会转成红黑树。那么什么时候红黑树又变回链表呢?查阅资料得知,当红黑树节点数<=6时,则退化成链表。

应该是因为避免在临界区频繁出入导致的链表和红黑树之间的频繁转化。

总结

个人介绍有点简短,面试官也提到了,下次应该多一点个人介绍。

这个面试官似乎不关注我的开源经历,问的都是比较基本的问题。有可能是KPI面,但是再次感谢他让我有面试机会,积累经验。

还有我才知道原来打电话来约面试的和最终给你面试的可以不是同一个人哦!

继续努力!

wow!