
上周HR通知我快手社招技术面全部通过时我其实最想复盘的是第三轮。1面和2面各有侧重但基本都在预料内——手写算法、基础八股、常规项目介绍。只有3面整场给我的感觉是完全不同维度的面试官几乎没有问我任何标准答案类的问题所有提问都朝着你有没有真正做过、想没想过为什么这个方向去。整个过程55分钟考了系统设计、项目深挖、现场编码和线上故障排查几乎没有一分钟是浪费的。标题里写的社招快手技术3面面经今天就把它完整还原出来包括每一道题、我的答法、我卡壳的地方以及事后复盘才想明白的东西。先说一下整体流程和时间线方便大家对照。我是通过内推投递的简历目标岗位是后端研发Java方向总包级别大概在P6到P7之间工作年限5年。流程是简历筛选大概3天→ 技术1面45分钟算法→ 技术2面60分钟算法→ 技术3面55分钟→ HR面30分钟整个周期从一面到HR面通知大约两周半。很多同学以为3面就是随便聊聊或者加一道难题这是个误区。根据我的经验以及和身边同事交流的结论快手社招的3面通常由更高层级的工程师或技术负责人来面它的核心考察点有三个系统设计能力和架构视野、项目深挖的真实度、排查问题和应急处理的思维链。这三点恰恰是背八股背不出来的。1. 快手社招3面到底在面什么先把这轮面试的定位搞清楚我为什么强调要理解3面的定位因为如果你把它理解成更高难度的算法轮很可能会在准备方向上犯大错。我见过不止一个候选人花大量时间刷Hard题结果3面遇到一个开放式的系统场景题就直接懵了。3面不是考你知识的宽度而是考你思考的深度。1.1 社招面试的整体节奏与内推细节简历投出去之后内推的速度明显比海投快这个大家应该都知道。我这次从简历通过筛选到约面中间隔了大概两天左右。约面通常是通过邮件或者HR微信建议你把时间协调好尤其社招是工作日面试注意提前请假。面试形式是视频面全程用飞书会议。每轮的节奏大概是这样的轮次时长主要内容面试官角色1面约45分钟基础八股 一道算法题团队内资深工程师2面约60分钟项目深挖 一道偏复杂的算法题团队TL或技术专家3面约55分钟系统设计 项目追问 场景排查部门负责人或更高层级专家HR面约30分钟薪资期望、离职原因、职业规划HR这个表格是我根据自身经验和身边朋友的情况整理的快手不同部门可能略有不一但大方向基本如此。1面更看重基础牢不牢2面更看重项目实操3面更看重架构思维和综合判断。1.2 3面和其他轮次的核心差异我说个非常直观的感受。1面和2面的面试官我回答完问题之后能明显感觉到他们是按照一个checklist在打分知识点答到了就过算法题跑通了就过。但3面面试官完全不一样他的提问方式是递进式的。举个例子同样问Redis缓存和数据库的一致性问题1面可能问你怎么保证一致性你回答先更新数据库再删缓存就差不多了。但3面的问法是你上线之后怎么验证这个策略是有效的如果删缓存失败了怎么办如果删完缓存发现数据库也回滚了怎么办你怎么在代码层面设计这个兜底逻辑这种追问方式本质是在模拟真实的工程决策过程。面试官不关心你背了多少方案他关心的是把你放到一个真实的分布式系统问题面前你能不能基于约束条件做出合理取舍。这种能力只能从真刀真枪的项目实践中来。所以我强烈建议准备3面的同学不要只刷题而是要把自己做过的最复杂的项目重新梳理一遍包括技术选型的理由、踩过的坑、当时的替代方案以及如果再给你一次机会你会怎么改。这部分内容在后面第2章我会详细拆解。2. 项目深挖订单超时自动关闭被连环追问的完整过程3面一开始面试官并没有直接让我做系统设计题而是先让我用三分钟介绍一下自己做过的最有代表性的项目。我当时选了简历里写的订单超时未支付自动关闭系统因为这个项目涉及的技术点比较多有延迟队列、状态机、定时任务比较适合展开讲。这里我强烈建议大家自我介绍时提到的项目一定要选自己最熟悉、最经得起追问的那一个而不是选看起来最厉害的。我见过有人在自我介绍里说我负责过秒杀系统结果面试官追问秒杀的核心瓶颈、库存扣减的具体方案、压测数据他支支吾吾。宁可项目平凡但讲得透彻也不要项目高大上但一问三不知。我当时大概这样介绍项目背景下单后15分钟未支付自动关闭订单并回滚库存日均订单量约120万高峰期QPS约3000。早期用的是定时任务每分钟扫描一次订单表但随着单量增长扫描效率太低后来改成了基于Redis延迟队列的触发式方案。这个介绍不算出彩但信息密度比较高面试官基本能快速了解项目的核心痛点。然后他就开始逐层追问了让我把当时从方案选型到落地观测的真实过程完整还原出来。2.1 第一轮追问为什么不用MQ延迟消息面试官的的第一个问题是为什么你们选择了Redis延迟队列而不是RabbitMQ的延迟消息或者RocketMQ的定时消息这个问题其实是典型的选型考察。我当时的回答是基于三个维度做对比团队技术栈、公司基础设施、数据量级。团队技术栈公司内部消息中间件以Kafka为主但Kafka原生不支持精确的延迟消息如果想要延迟15分钟触发只能用消息里带时间戳、消费端判断时间的方案这会消费很多无效消息。公司基础设施RabbitMQ虽然支持延迟消息通过死信队列或者插件实现但我们部门没有现成的RabbitMQ集群引入一个新中间件要过运维审批、要申请机器资源成本很高。数据量级我们订单量日均120万15分钟内的待支付订单其实只有几万到十几万这个量级Redis的ZSET完全可以承载不需要重型中间件。然后我补充了一句技术选型不是选最先进的而是选在当前团队和基础设施约束下成本最低、最容易维护的。这一点面试官明显比较认可。2.2 第二轮追问延迟队列的实现细节与容灾接下来面试官开始抠细节你们的延迟队列具体怎么实现的Redis宕机了怎么办消息丢失了怎么办这个问题再往深处挖就到了考验真实落地经验的时候了。我大概说了我们的方案用Redis的ZSET实现延迟队列score存触发时间戳member存订单号。订单创建时把订单号写入ZSET同时启动一个定时任务每秒扫描一次ZSET取出score小于当前时间戳的前N个元素丢给线程池执行关闭逻辑。执行成功之后从ZSET删除执行失败的放入重试队列。关于容灾我承认这是我们第一版方案的薄弱环节。因为Redis是单机部署虽然有持久化但确实存在宕机风险。后面我们做了一版优化把ZSET的key按订单ID的哈希值分片到多个Redis实例降低单点风险同时增加了MySQL兜底任务每天凌晨跑一次全量扫描把超过15分钟仍未关闭的订单捞出来补偿处理。这个兜底补偿的设计其实很关键它保证了即使延迟队列全部丢失最终也能通过定时任务收敛只是时效性差一些。面试官接着追问兜底任务跟延迟队列同时跑会不会出现同一个订单被两边同时处理的情况这就转到了一个经典的幂等性问题。我当时的回答是关闭订单操作本身需要做状态校验只有状态为待支付的订单才能被关闭这个状态流转用数据库行锁或者版本号保证所以即使两个渠道同时触发也只有一个能成功另一个在状态校验这层就会被挡掉。2.3 第三轮追问这个方案有什么改进空间到这里面试官问了一个让我有点意外的问题如果让你重新设计你觉得现在的方案最大问题是什么这个问题看似开放实际上还是在考察你的反思能力。我当时犹豫了一下说了一个自己一直没改的痛点虽然有了MySQL兜底但延迟队列里的订单如果一直没触发Redis又没宕机那这些订单只能等第二天凌晨的兜底任务处理时效性较差。比如一个订单在15:00下单Redis里那条记录因为某种原因丢了那这个订单要等到第二天凌晨才会被关闭。改进方向是可以引入双写的机制即订单创建时既写Redis延迟队列也往数据库的待处理表中插入一条记录由后台任务扫描数据库表作为主链路Redis延迟队列作为加速触发的辅助链路。这样即使Redis出问题数据库扫描也能在分钟级内完成关闭操作。关于这个回答我自己觉得属于及格但不够亮眼。面试官后来给了我一个提示说其实可以用订单表的创建时间索引做增量扫描而不是全表扫描这样扫描成本会低很多。这个思路我当时确实没想到后来复盘的时候觉得很受启发——很多优化其实不是方案本身有多复杂而是你有没有往那个方向去想。最后补一句关于项目深挖的通用建议面试官追问项目时最好的状态是你能把当时做决策的上下文完整还原出来——当时有什么约束、你考虑过哪些方案、为什么选了这个、后来发现了什么问题、如果再给你一次机会你会怎么改。这五段式回答基本能覆盖90%的项目深挖问题。3. 系统设计题设计一个短视频热榜TOP100项目深挖大概花了二十多分钟然后面试官说我们换一个开放性的题目。假设快手要做一个全站短视频热榜实时更新Top100你怎么设计这道题我印象深刻因为它的开放性非常强其实没有标准答案面试官一直在引导我补全需求。我总结一下完整的设计过程也复盘一下哪些地方我答得好、哪些地方有明显不足。3.1 需求澄清面试官在等你说这句话我先说我观察到的一个普遍现象很多候选人在做系统设计题时一上来就直接画架构图这里加个Redis那里加个Kafka完全不先确认需求。这其实是大忌。面试官出这种开放题第一层考察的就是你面对模糊需求时能不能把边界理清楚。我当时先问了几个问题榜单是实时的还是准实时的热的定义是什么用哪些指标计算热度数据量级大概是多大榜单多久更新一次面试官逐步给出了约束条件每分钟更新一次榜单即可不需要秒级实时热度综合播放量、点赞量、评论量、分享量四个指标按一定的加权公式计算全站视频总量过亿每天新增约千万级视频需要支持按小时榜、日榜查看系统需要能容忍轻微的不一致不需要强一致。这几个约束很关键直接决定了架构的选型方向。注意很多候选人不问清楚就动手设计最后做出来的方案和需求完全对不上。比如如果要求秒级实时那处理方式就完全不同如果要求强一致那缓存和异步计算的方案就可能不合适。3.2 整体架构从事件上报到榜单服务根据上面的需求约束我给出的架构是这样分层的第一层是数据接入层。App端的所有用户行为播放、点赞、评论、分享都通过埋点上报经过一个统一的接入网关写入Kafka。这里需要注意上报是有延迟和丢失可能的但因为榜单本身允许轻微不一致所以不需要对上报链路做过多的可靠性保障。第二层是实时计算层。用Flink消费Kafka里的行为事件流按视频ID维度做窗口聚合每个窗口比如1分钟计算一次该视频在这个窗口内的播放量、点赞量等指标增量然后写入Kafka的下游topic。第三层是热度计算与存储层。榜单服务消费聚合后的指标数据把增量累加到Redis里维护的每个视频当前热度值。这里用到Redis的ZSET数据结构key可以按榜单维度区分小时榜、日榜member是视频IDscore是热度值。每次更新就执行ZINCRBY。第四层是读服务层。用户请求榜单时直接查Redis ZSET的ZREVRANGE取前100个然后回源查视频的基本信息封面、标题、作者等组装之后返回给客户端。考虑到线上读多写少可以在服务端做一层本地缓存把榜单列表缓存20-30秒。3.3 热度公式与时间衰减的处理面试官这时候追问了第一个深入的问题热度怎么算是不是直接累加播放量权重就行我当时回答不能只看总量必须考虑时间衰减否则老视频会一直霸榜新视频永远上不来。我给出的公式大致是热度 (播放量 × 0.4 点赞量 × 0.3 评论量 × 0.2 分享量 × 0.1) / 热度衰减系数衰减系数的设计可以按时间段设置半衰期比如12小时内不衰减12小时到24小时每小时衰减5%超过24小时衰减加快。更简单的做法是热度计算时引入一个时间因子比如 score baseScore × 2^(-age/halfLife)其中halfLife取6小时或12小时age是视频发布时间距现在的小时数。面试官进一步问那用Redis ZINCRBY累加的方式怎么实现时间衰减衰减不是要改历史数据吗这确实是个好问题。如果用ZINCRBY把每个视频的热度总值累起来时间衰减确实不好做因为你不可能每隔一小时把所有视频的score都重新算一遍。我们探讨下来的方案是不维护全量的实时绝对值而是维护最近窗口内的增量比如按分钟粒度记录每个视频最近60分钟内每个1分钟窗口的新增事件数定时把过期窗口的数据从热度中扣除。技术上可以每个视频存一个环形数组或者用Flink做带状态的滚动窗口计算输出每个视频在当前时间窗内的活跃度。不过面试官也说了如果只是做榜单而不需要精确的热度绝对值其实可以用一个更取巧的方案Flink每5分钟输出一次当前活跃视频Top200榜单服务只处理这Top200的加权合并。这样需要维护状态的key数量就大大减少了。这个思路我在设计时确实没想到属于面试官给的免费教学。3.4 热点问题与降级策略面试官最后一个系统设计问题是如果某个视频突然爆了比如某个明星发了一条视频瞬间播放量百万级你的系统会怎样这问的是热点打散和系统保护。我当时给的方案是在Redis和数据库层面做热点防护。Redis ZSET天然处理了热点视频的score增长没问题但可能出问题的是下游的回源查询视频信息这一步。如果榜单里Top1是一个爆款视频所有用户请求都查同一条视频信息直接打到数据库就完了。我的方案是视频基本信息在Redis做缓存缓存过期时间加随机抖动避免集体失效同时如果是明星视频这种已知热点提前做预热。另外限流层面可以给读接口配置按用户粒度的限流策略异常流量直接拒绝。这个回答算是中规中矩。面试官还追问了一个点如果Flink计算任务重启状态丢了榜单会不会出现明显跳变我们讨论的方案是为Flink开启Checkpoint并配置从最近的Checkpoint恢复同时榜单服务对数据做一层渐进式更新不要每次收到增量就直接替换当前榜单而是做一个平滑过渡比如新旧榜单按比例加权合并。这样即使中间有一段数据缺失榜单也是缓慢变化而不是突然跳变。做系统设计题到现在我最大的体会是面试官其实不怕你答得不够完美怕的是你没有一个清晰的框架。我从需求澄清、分层架构、热度计算、热点防护、降级兜底这个顺序来讲虽然每一步都有可以优化的地方但整体思路是完整的面试官能从中看到你的架构思维。4. 现场编码两道算法题的实际表现与复盘系统设计聊完面试官说再做两道题吧。说实话3面一般不会在算法上卡人但也不会完全不考。我那两道题难度都不算高第一道是经典的最长无重复字符的子串第二道是判断链表是否有环并找出入环节点。让我详说一下过程和里面的细节。4.1 第一题最长无重复字符的子串题目很直白给定一个字符串找出其中不含有重复字符的最长子串的长度。这题我刷过很多次最优解是滑动窗口用哈希集合维护窗口内的字符。我当时没用IDE写完整代码面试官让在共享文档里写核心逻辑。我大概花了3分钟写完public int lengthOfLongestSubstring(String s) { if (s null || s.length() 0) { return 0; } SetCharacter set new HashSet(); int left 0; int maxLen 0; for (int right 0; right s.length(); right) { char c s.charAt(right); while (set.contains(c)) { set.remove(s.charAt(left)); left; } set.add(c); maxLen Math.max(maxLen, right - left 1); } return maxLen; }面试官没有让我跑测试而是问了两点一是时间复杂度和空间复杂度二是如果字符串里包含的不是ASCII字符而是Unicode字符这个解法是否还成立。时间复杂度O(n)空间复杂度O(字符集大小)这个没问题。关于Unicode字符只要Java的char能表示的范围BMP内就没问题但如果是超出BMP的字符比如某些emoji需要用codePoint来处理否则charAt会把它拆成两个char。这个细节我答出来了面试官比较满意。4.2 第二题环形链表找入环节点第二题是经典的快慢指针解法也就是Floyd判圈算法。第一步用快慢指针找到相遇点第二步从链表头部和相遇点同时以相同速度前进再次相遇的位置就是入环节点。这个解法我相信大多数人都知道但面试官问了一个很多人没想过的问题为什么这样能找到入环节点你能推导一下吗我当时现场推导了一遍设链表头到入环节点的距离为a入环节点到相遇点的距离为b沿环方向环的长度为c。快指针走的总距离是慢指针的两倍快指针走了a b kc慢指针走了a b其中k是快指针在环内多绕的圈数。所以 a b kc 2(a b)化简得到 kc a b所以 a kc - b。这说明了从头部走到入环节点的距离a等于从相遇点继续走k*c - b的距离也就是在环内走k圈回到相遇点再退b步恰好走到入环节点。所以当头指针和相遇点的指针都走a步时它们会在入环节点相遇。面试官听完点头然后问我如果链表可能有环也可能没环边界情况怎么处理这个简单快指针为空或者快指针的next为空就说明无环直接返回null。两道算法题整体花的时间大概20分钟。我的感受是3面的算法题重点是解题思路和数学推导而不是跑不跑得过所有用例。面试官更想确认你有没有真正理解这个算法而不是背了模板。所以我建议准备3面的同学对经典算法不仅要会写还要能推导时间和空间复杂度能解释为什么。5. 场景排查题线上接口RT飙升你的排查链路是什么两道算法题之后面试官问了一个非常实际的问题假设你们有一个核心接口平时RT是50ms左右突然某天线上告警显示RT涨到了500ms你作为负责人第一步做什么这个问题简直是社招必考题但很多人答得没有章法。我当时的回答是按确认范围 → 定位瓶颈 → 止损 → 根因 → 复盘这个顺序来的面试官后来反馈说这个框架是清晰的。5.1 确认范围先别急着查代码我的第一步一定是先确认影响范围是个别用户受影响还是所有用户是某一个接口受影响还是多个接口是所有机房都异常还是单机房这个信息是通过监控大盘快速确认的它决定了后边的排查方向。我之所以强调这一步是因为见过太多次工程师一上来就在代码里翻翻半天发现是某个Redis节点网络抖动。先看范围大概率能把排查范围缩小到代码问题、中间件问题、基础设施问题这三者之一。5.2 分层排查应用层、中间件层、基础设施层确认范围之后我会按层次逐层排查第一层看应用自身的指标。比如GC、线程池、CPU、内存。GC频繁可能导致STW拉高RT线程池队列堆满说明有慢请求积压。这些通过监控平台基本都是实时可查的。第二层看依赖的中间件。接口依赖了MySQL、Redis、MQ等任何一个组件都要看它们的延迟和错误率指标。有一个实用小技巧在应用的链路追踪系统里看这个接口的调用链中哪一段耗时最大。比如看SkyWalking或Jaeger的trace如果发现Redis耗时占了400ms那问题大概率在Redis侧进一步查是慢查询、大Key、还是Redis网络延迟如果MySQL耗时占400ms那就去看慢SQL、锁等待、连接池等。第三层看基础设施。如果代码没问题、中间件也正常就需要看宿主机负载、网络丢包率、跨机房专线延迟等。面试官对这套分层思路是认可的他补问了一个细节如果查下来发现是数据库连接池被打满了你会怎么处理我当时答先临时扩容连接池比如把最大连接数从50调到200快速缓解问题同时查是什么原因导致连接不够用——是不是慢SQL积压、连接池泄漏、还是突发流量翻倍。这属于先止损再查根因的思路。面试官追问连接池泄漏你们是怎么排查的我说可以用连接池监控看活跃连接数和空闲连接数配合线程dump分析连接获取后没有归还的调用栈路径或者用中间件预留的探测机制定期获取连接池的健康状态。5.3 一个我漏掉的点变更排查其实这个问题我整体答得还好但有一个点没有主动说出来变更排查。面试官后来提示说遇到RT飙升第一时间应该想到看看最近有没有发布、有没有配置变更。很多时候上线引入的Bug引起的性能劣化比突发的流量异常更常见。我当时确实愣了一下。工作中我当然知道上线后要看监控但在面试的高压场景下我漏掉了先看变更这个最高优先级动作。后来复盘时我把这个教训记住了线上故障排查先把最近变更放在排查链路的靠前位置。这个点听起来简单但如果不经过一次真切的教训确实很难条件反射地想到。所以这里也提醒各位准备面试的同学遇到场景排查题回答的时候尽量带上先看变更、先看监控大盘、先确认影响范围再深入代码和中间件。这个顺序体现了你的实战纪律性。6. 3面中那些让我