没有自我介绍环节,全程项目拷打
拼团项目
1. 介绍一下拼团项目
邻家鲜团是一个以社区拼团为基础的自营型电商平台。具体来说:
平台聚焦于线下社区,以生鲜和日用品这些高频、刚需品切入。在社区关系链的基础上,我们引入了社交拼团功能。用户可以作为团长发起拼单,邀请好友参与,也可以作为团员加入其他人的拼团。通过拼团购买,用户可以获得更优惠的价格。
这不仅仅通过价格激励提供了商品的销量和转换率,更重要的是,它利用熟人之间的信任关系,实现了低成本的社交裂变与用户增长。将一次性的交易行为转变为了基于社交互动的持续性消费。
2. 为什么要用拼团,和普通购买有什么区别
见1
3. 比如购买手机,很难拼团成功,你是怎么考虑的
见1
4. 拼团怎么邀请好友
- 生成链接:当用户发起拼团(开团)成功后,前端会根据返回的 teamId(队伍ID)和 activityId(活动ID)生成一个唯一的分享链接。
- 分享渠道:用户通过微信、朋友圈等社交渠道将链接发送给好友。
- 后端支持:后端提供了用于查询当前拼团的进度的接口,实时返回当前拼团的已拼人数和剩余名额。
5. 好友打开拼团链接能看到什么
好友点击链接后,前端会调用后端的查询接口,展示以下信息:
- 商品信息:商品图片、拼团优惠价、原价。
- 拼团状态:
- 进度条:当前人数/目标人数
- 倒计时:显示拼团的剩余时间
- 操作按钮:
- 如果未满员且未过期:显示“立即加入拼团”。
- 如果已满员:显示“该团已满,我来开个团”。
6. 假如好友点拼团链接,但是只是浏览没组队,会发生什么
- 无状态浏览:在后端层面,单纯的浏览请求是无状态的查询,不会对数据库中的订单表产生任何写操作,也不会占用库存。
- 但是为了后续分析转化率,目前项目采用redis的hyperLogLog来记录每一个队伍有多少人浏览,以及每个活动有多少人浏览,这些数据可以用作分析活动的转换率和团长的传播能力。
7. 讲一下项目中用到的Redis原子预占与数据库乐观锁的多级防超卖机制
首先,在缓存层面,我们利用 Redis 的原子自增命令实现了一个“全局叫号机”。每个尝试参与拼团的用户都会先去取一个号。由于 Redis 单线程执行命令的特性,我们不需要加锁就能保证每个用户拿到的号码是唯一且递增的。
其次,为了处理拿到号但后续业务失败的情况,我们引入了错误恢复量机制。每当有请求在后续流程失败,我们不是去回滚叫号机,而是将错误恢复量加 1。
判断逻辑是:只有当用户号码 <= 拼团目标 + 错误恢复量 时,才允许进入数据库层。这相当于动态扩大了准入窗口,补上了之前失败留下的空缺。
此外,当高并发流量瞬间涌入时,Redis 的叫号机可能会瞬间冲得很高。此时,虽然多余的请求被拦截了,但叫号机的值已经远超目标值。我们会选择通过CAS回滚叫号机的号码为target,这样剩余的组队人数就是错误恢复量,来避免有无法利用的空位。
最后,在数据库层面,对于通过了 Redis 校验的请求,我们利用 MySQL 的乐观锁(UPDATE … WHERE lock_count < target)进行最终的库存扣减。数据库的行锁机制会强制串行化并发的更新操作,确保绝对不会发生超卖。
这种Redis 挡住绝大部分流量 + 数据库兜底最终一致性的多级防护架构,既保证了高性能,又确保了数据安全。
8. 假如多个用户同时点击拼团按钮会发生什么
- 并发请求:假设目标是 5 人团,当前有 3 人,瞬间来了 10 个请求。
- Redis 层:10 个请求同时执行 INCR,Redis 会依次返回 4, 5, 6, … 13。
- 拿到 4, 5 的请求:通过校验,进入下一步。
- 拿到 6-13 的请求:occupy > target 判断为 true,直接返回失败(提示“手慢了”),不会访问数据库,并且将叫号重置为5。
- DB 层:拿到 4, 5 的两个请求并发执行 UPDATE SQL。数据库会串行化这两个更新操作,确保 lock_count 正确增加。
9. 数据库兜底怎么做的
- 如果 Redis 放行了多余请求(例如 Redis 数据丢失),数据库更新条件
lock_count < target_count会导致更新失败,从而拦截超卖。 - 唯一索引约束:
在group_buy_order_list表中,order_id是唯一索引,且biz_id(由 activityId_userId_count 组成)也是用于幂等控制的。如果同一个用户重复提交,数据库会报 DuplicateKeyException,程序捕获后抛出异常。
10. 数据库的行锁怎么用的
当执行 UPDATE group_buy_order ... WHERE team_id = ? 时,MySQL 的 InnoDB 引擎会自动给 team_id 对应的这行记录加上 排他锁(X锁)。
11. 数据库给行上独占锁后可以读吗
可以读,但取决于怎么读:
- 快照读(Snapshot Read):普通的
SELECT * FROM ...可以读。InnoDB 利用 MVCC(多版本并发控制)机制,会读取该行记录在加锁之前的历史版本(快照),不会被阻塞。 - 当前读(Current Read):
SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE不可以读(会被阻塞),必须等待锁释放。
12. 有考虑过Bean的启动顺序吗?假如业务的Bean先起来,但是Redis连接Bean没起来怎么办
- 可以使用
@DependsOn注解明确业务Bean依赖于redis连接的Bean - 实际上由于业务Bean显式持有了
RedisTemplate有显示的依赖关系,所以Spring会自动处理依赖关系。