腾讯后端面试复盘:TCP、Redis与算法题全解析

腾讯后端面试复盘:TCP、Redis与算法题全解析 腾讯面经有点难度~ 我自己复盘了下发现这些题值得好好说前阵子面了腾讯岗位是后端开发。说实话去之前我对大厂面试的难度是有心理准备的但真正走完流程之后才发现网上那些“腾讯面试有点难度”的说法真不是夸张。它难不是难在题目偏门而是难在每一个问题都会往下深挖直到你承认自己不会为止。我这次一共经历了三轮技术面加一轮HR面中间被问到的问题覆盖了计算机网络、操作系统、数据结构和算法、数据库、Redis、消息队列、场景设计基本上你能想到的后端核心知识都被过了一遍。这篇文章我把整个过程中印象最深的题目、我当时是怎么答的、以及事后复盘觉得应该怎么答更稳妥全部整理出来了。内容会按照面试流程来组织把每一轮的高频考点和答题思路拆开讲。不管你是准备面腾讯还是其他大厂这篇文章都值得看完尤其是我在最后整理的那些复盘建议都是踩过坑之后才总结出来的。1. 内容整体设计与思路拆解1.1 腾讯面试的整体风格和考察逻辑腾讯的技术面一般讲究“由浅入深”和“连环追问”。什么意思就是面试官会先抛出一个比较基础的问题比如“TCP三次握手是什么”等你答完之后再顺着你的回答继续追问“为什么不是两次”“SYN洪泛攻击怎么防御”“TIME_WAIT状态有什么用”一层一层往下直到你答不上来为止。这种面试方式的目的不是把你问倒而是想通过追问判断你知识掌握的边界到底在哪里是背了八股文还是真的理解原理。从我这次的经验来看腾讯面试官普遍比较看重候选人以下几个能力一是扎实的计算机基础。网络、操作系统、数据结构这些属于必考项而且基本都会往深处问。二是真实的项目经验。面试官会花很长时间扣你简历上写的项目细节包括项目背景、你负责的模块、遇到的技术难点、最终怎么解决的如果你项目中写的技术点自己说不清楚这轮基本就危险了。三是解决问题的思路。遇到一个场景设计题面试官不会要求你说出标准答案而是想看到你如何拆解问题、如何权衡取舍、如何一步步推导出合理方案。1.2 我这次面试的整体流程回顾整个流程安排得很紧凑。投递简历之后大概一周收到笔试邀请笔试是常规的算法题加选择题难度中等偏上算法题有两道一道是动态规划一道是链表相关。笔试通过后进入面试环节第一轮技术面主要考察基础也就是网络、操作系统、数据库这些第二轮技术面开始结合项目和场景设计题第三轮技术面偏向于综合能力考察会问一些发散性的系统设计问题最后一轮是HR面聊职业规划、团队协作、离职原因这些方面。这里先给准备面试的朋友提个醒腾讯的面试节奏比较快两轮面试之间间隔不会太久有的甚至当天面完一面第二天就约二面。所以不要等一面前再开始准备最好提前两到三周进入状态把高频考点过一遍把项目里涉及的技术点吃透再每天刷一两道算法题保持手感。2. 核心细节解析与实操要点2.1 计算机网络是重灾区TCP和HTTP必须吃透我一面上来就被问了TCP连接的建立和释放过程。这题看似基础但面试官在基础上加了几个非常细的问题。比如他把三次握手的问题变成“如果客户端发送的SYN包丢了会发生什么”我当时愣了一下因为常规准备的时候只背了三次握手的过程很少去想过包丢失的场景。后来我复盘这道题其实是想考察你对TCP状态机和超时重传机制的理解。SYN包丢失后客户端不会一直等而是会启动一个定时器在超时后重新发送SYN包重传的次数由/proc/sys/net/ipv4/tcp_syn_retries参数控制默认是6次每次超时时间会翻倍总耗时大约120秒左右。如果6次都失败了客户端才会放弃连接并返回错误。除了TCPHTTP相关的问题也问了不少。面试官让我说出HTTP常见状态码的含义我回答了200、301、302、401、403、404、500、502、503这些但他接着追问“301和302有什么区别”“什么时候用301什么时候用302”。这题本质上是考察你对重定向语义的理解。301是永久重定向浏览器会缓存这个跳转结果302是临时重定向每次都需要重新请求原地址。如果是网站改版、域名更换这种场景用301比较合适如果只是临时跳转比如未登录用户跳转到登录页用302更合适。HTTP这块还延伸到了HTTPS。面试官问HTTPS的握手过程为什么是四步和TCP的三次握手有什么关系。我当时把握手流程差不多讲出来了客户端发送ClientHello服务端返回ServerHello和证书客户端验证证书后生成预主密钥并用服务端公钥加密发送双方根据预主密钥生成会话密钥之后开始对称加密通信。但他在我答完之后追问了一句“证书验证的具体过程是什么”这题我回答得不是很好只说了会用CA的公钥验证签名但没有提到证书链的逐级验证和证书吊销的检查。这块建议准备面试的朋友专门补一下。高频考点速查表知识点常见问法深挖方向TCP三次握手为什么需要三次两次行不行、SYN洪泛、半连接队列TCP四次挥手为什么需要四次TIME_WAIT的意义、2MSL时长原因滑动窗口怎么实现流量控制窗口大小变化、零窗口探测HTTP缓存强缓存和协商缓存区别Cache-Control与Expires优先级HTTPS握手握手过程具体步骤证书验证、密钥交换算法、TLS版本差异2.2 操作系统题专挑细节死锁和进程调度最常考操作系统这块我被问到的是死锁产生的条件以及如何处理死锁。这个算是经典题了四个条件分别是互斥、持有并等待、不可剥夺、循环等待我背得很熟。但面试官随后问了一个比较刁的问题“如果两个线程都在等待对方释放锁怎么在不重启进程的情况下解决这个问题”这个问题的背景其实是实际生产环境里经常遇到的死锁场景不能简单说重启了事。我当时思考了一下给出了一个分析思路先通过jstack拿到线程dump找到线程的持有锁和等待锁的信息确认死锁的线程是哪些然后根据业务逻辑判断哪个线程可以先释放锁。如果代码支持中断响应可以通过Thread.interrupt()来中断其中一个线程触发它在lockInterruptibly()处抛出InterruptedException进而释放已持有的锁打破循环等待。如果线程不支持中断那就只能通过kill进程来恢复了所以更重要的其实是事后从代码层面避免死锁比如用tryLock加超时、按固定顺序加锁等方式。进程调度也被问了一轮。面试官问的是“Linux默认的调度算法是什么”我回答CFS完全公平调度器然后他追问“它怎么做到公平”。这个问题我答得还行把CFS的核心思想讲清楚了CFS不再使用时间片的概念而是为每个进程维护一个虚拟运行时间vruntime调度器每次都选择vruntime最小的进程来运行。实际运行时间短的进程vruntime增长得快所以优先级低的进程也能获得CPU时间不会出现饥饿现象。另外优先级高低会通过权重影响vruntime的增长速度优先级高的进程vruntime增长得慢自然获得更多CPU时间。面试官还问了一个关于进程和线程区别的问题这个不算难但我建议不要只回答“进程是资源分配的基本单位线程是CPU调度的基本单位”就完了。更好的做法是补充说明进程和线程在地址空间、上下文切换开销、通信方式、崩溃影响这几个维度的差异再把协程作为延伸话题提一下能展示你的知识面更广。2.3 手撕算法前先确认清楚输入输出边界算法题是腾讯面试必不可少的环节但不同面试官的考察方式不太一样。我这次遇到的情况是面试官直接开了一个在线IDE让我实现一个带过期时间的LRU缓存要求在get和put操作中做到平均时间复杂度O(1)。这道题其实相当于把LRU和定时过期两个功能叠加在一起。我当时先跟面试官确认了几个边界条件过期时间单位是什么、到达过期时间后是在访问时惰性删除还是主动删除、容量满了之后先淘汰过期项还是先淘汰最久未使用项。面试官说只需要做惰性删除也就是访问时检查是否过期过期了就当作不存在。核心思路就是哈希表加双向链表。哈希表负责O(1)找到节点双向链表维护访问顺序。每次get的时候先从哈希表拿到节点检查有没有过期没过期就把节点移动到链表头部put的时候如果key已存在更新值和过期时间并移动到头部如果key不存在先判断容量是否已满满了就从链表尾部淘汰一个节点再从哈希表删除对应key然后插入新节点到头部。class LRUCache { class Node { int key, value; long expireAt; Node prev, next; Node(int key, int value, long expireAt) { this.key key; this.value value; this.expireAt expireAt; } } private MapInteger, Node map; private int capacity; private Node head, tail; public LRUCache(int capacity) { this.capacity capacity; map new HashMap(); head new Node(0, 0, Long.MAX_VALUE); tail new Node(0, 0, Long.MAX_VALUE); head.next tail; tail.prev head; } public int get(int key) { Node node map.get(key); if (node null) return -1; if (isExpired(node)) { removeNode(node); map.remove(key); return -1; } moveToHead(node); return node.value; } public void put(int key, int value, long expireAt) { Node node map.get(key); if (node ! null) { node.value value; node.expireAt expireAt; moveToHead(node); return; } Node newNode new Node(key, value, expireAt); map.put(key, newNode); addToHead(newNode); if (map.size() capacity) { Node tailNode tail.prev; removeNode(tailNode); map.remove(tailNode.key); } } }写完代码之后面试官又问了一个问题“如果过期时间不是固定的而是每个key都不一样这个设计还成立吗”我说成立因为过期时间存在每个Node节点里没有依赖全局的统一过期机制所以每个key独立的过期时间完全没问题。他又问“如果需要在过期时触发回调通知业务方怎么设计”这个我答得比较笼统说可以用一个后台任务扫描过期节点。其实更好的方案是使用时间轮或者延迟队列来处理这块建议提前准备一下。2.4 项目深挖环节准备好这几个方向的追问项目是腾讯面试的重头戏而且面试官问得很细。我简历上写了一个高并发的消息推送系统项目于是二面面试官花了大概二十分钟来问这个项目的细节。他先问了一个引导性的问题“你这个推送系统的架构是怎么设计的”我把整体架构讲了一遍包括客户端接入层、消息路由层、存储层、推送通道层这些模块。然后他紧接着问“你说用了Redis做消息去重那Redis的value存的是什么为什么不用数据库的唯一索引去重”这里要提醒一下你在项目中写到的每一个技术点面试官都会往深处问。我当时写的是用Redis的SETNX命令做去重key是消息IDvalue是当前时间戳同时设置过期时间。面试官随即追问“SETNX的key什么时候删除如果Redis里积压了大量无效的key怎么办”这个问题其实是在考察你有没有考虑到Redis的内存消耗问题。我当时回答的是设置合理的TTL比如一分钟超过这个时间的消息ID就不会再被重复推送。他追问“为什么是一分钟不是三十秒也不是五分钟”。这就有点考察业务理解了我解释因为消息推送存在网络延迟重复消息可能会在几十秒后才到达所以TTL需要覆盖这个最大延迟窗口设成1分钟是在内存占用和去重效果之间的一个折中。这轮面试下来我最大的体会就是项目细节一定要自己提前往深处想。比如你用了消息队列就要想清楚为什么要用消息队列、不用行不行、消息丢失怎么办、重复消费怎么办、顺序怎么保证。这些问题都是大厂面试官问项目时非常喜欢连环追问的方向。3. 实操过程与核心环节实现3.1 从投递简历到笔试我是怎么准备的在投递简历之前我先花了一个周末把简历梳理了一遍重点整理了两个内容一是项目的整体框架和自己在其中的具体工作二是每个项目里用到的核心技术栈比如框架的版本、中间件的使用方式这些都做到了心里有数。因为面试官很可能会直接问“你用的Redis是哪个版本”“这个版本有什么特性”如果你答不上来印象分会打不少折扣。简历投出去之后我在等待笔试通知的间隙开始按模块刷题和复习。算法方面我每天在LeetCode上做两到三题重点练了链表、二叉树、动态规划和滑动窗口这四类因为大厂笔试和面试手撕的题目大多集中在这几个类别。计算机网络和操作系统方面我把面试高频考点列了一个提纲按照提纲逐个过确保每个知识点都能用三到五分钟把原理讲清楚。3.2 笔试环节的题目复盘笔试总共90分钟题型包括选择题、填空题和两道编程题。选择题考察的范围很广包括数据库索引、Redis数据结构、Java并发、Linux命令这些难度属于正常水平知识点覆盖到了基本能答对。编程题第一道是“给定一个数组找出所有满足和为target的三元组要求不重复”。这题就是经典的三数之和用排序加双指针可以解决。我当时用了Java实现先把数组排序然后用一个for循环固定第一个数再用双指针从两端向中间逼近遇到重复的跳过。时间复杂度O(n^2)空间复杂度O(log n)到O(n)取决于排序算法实现。第二道题是“实现一个支持并发读写的线程安全计数器要求读操作不加锁”。这题说白了是用原子变量实现CAS操作。Java里AtomicLong就能实现这种效果利用CAS循环不断尝试更新值。如果要求更精确的计数可以结合LongAdder来降低CAS竞争如果你的面试环境允许你讨论不同方案可以把几种思路都说出来。3.3 一面到三面的完整问答复盘一面主要围绕基础。面试官先问了一遍我简历上的项目背景然后开始考基础分别是TCP建立连接、HTTP状态码、数据库索引、Redis数据结构、算法题。整个一面持续了大约50分钟因为前面基础问答已经占了不少时间手撕算法只留了20分钟。这里有个小建议面试回答问题的时候尽量分点作答不要一下子把结论扔出来。比如面试官问“MySQL的索引底层结构是什么”不要只回答“B树”而是先说结论再补充“为什么选B树而不是B树也不是红黑树”分几个点把原理讲清楚。这样面试官会觉得你不仅知道结论还理解背后的权衡。二面就是项目深挖加场景设计。项目问了大概三十分钟场景设计题问了“假如有一个用户量千万级的消息推送系统让你设计它的架构你会怎么做”。这类场景设计题没有标准答案核心在于展现你的拆解能力。我当时的分析框架是先从功能出发把推送流程拆成消息产生、消息存储、消息路由、消息推送四个环节再针对每个环节分析可能出现的瓶颈比如高并发写入、消息堆积、推送通道的流量控制最后针对瓶颈给出对应方案比如引入消息队列削峰、用Redis缓存用户和设备的关系、通过异步批量推送降低对推送通道的压力。三面是综合面面试官比较关注技术视野和解决问题的方法论。他问了我一个问题“遇到线上服务CPU使用率突然飙升到100%你会怎么排查”。这题我回答得比较完整也推荐大家准备一下因为这是生产环境非常高频的问题。我的排查思路是先用top命令查看是哪个进程占用CPU高再用top -Hp pid查看进程内哪个线程占用高接着用printf %x\n 线程ID把线程ID转成十六进制然后用jstack pid | grep 线程ID拿到线程栈定位到具体代码行最后结合代码逻辑分析是死循环、频繁GC、还是锁竞争导致的CPU飙升。3.4 面试中的表达技巧别急着说答案先拆问题很多人在面试时会犯一个错误问题刚听完就急着回答结果答偏了或者漏掉了重要条件。我在这次面试中吃了一个小亏之后之后的几轮就调整策略了听完问题先花几秒钟想一下问题的边界和意图再开始回答。比如面试官问“如果让你设计一个短链接系统你会考虑哪些问题”不要一上来就说“用哈希生成短码”。你先确认几个关键点系统面向的用户量多大、生成的短链有效期多长、需不需要统计点击数据、需不需要自定义短链。这些问题会影响你的设计选型比如短码长度怎么确定、用什么存储、需不需要布隆过滤器挡一波无效请求。面试官看到你能主动确认需求边界其实是很加分的。还有一个表达细节回答技术问题的时候尽量把“为什么”也带上。比如你说项目里用了Redis做缓存就补一句“因为用户维度的热点数据读多写少用Redis能扛住高并发读”你说数据库表加了索引就补一句“因为查询场景集中在某个字段而且这块数据量已经上百万了”。这样做能让面试官感觉你每个技术选择都是经过思考的而不是跟着别人照搬。4. 常见问题与排查技巧实录4.1 算法题写了Bug如何补救我在一面的时候LRU那题第一次提交其实有个小问题节点的过期时间判断放在get里没问题但在put时如果更新了一个已过期但还没被删除的key我应该先删掉旧节点再插入新节点还是直接覆盖原节点当时我直接覆盖了但覆盖之后旧节点的过期时间失效了这不算Bug只是逻辑不严谨。面试官没有直接说我错了而是问我“如果这个key已经过期了覆盖之后它还能被访问到吗”。我马上意识到问题改成先判断过期再覆盖。这里想提醒大家面试时写代码如果发现自己有地方不对不要慌更不要嘴硬。直接跟面试官说“这里我考虑不周我调整一下”然后把代码改对反而会给面试官留下“这个候选人知道自己在写什么”的好印象。没有人能一次性写出完美无缺的代码面试官主要看你的思路和纠错能力。4.2 项目里的技术点被挖到不会怎么应对二面的时候面试官问了我一个项目里没写但相关的问题“你们推送系统有没有遇到过消息乱序的问题怎么解决的”我坦白说没有遇到过因为我们的消息是单分区消费的同一用户的消息会发送到同一个分区所以天然有序。这个回答算是取巧了但也算合理。我想说的是遇到不会的问题不要不懂装懂。比较好的策略是先跟面试官说“这块我们在实际项目中没怎么遇到”然后基于你的理解说“如果遇到的话我可能会从这几个方向去排查”。这样即使你不确定也展示了你的思辨能力。4.3 高频问题排查速查表根据我这次面试的体验把腾讯面试里最容易遇到的高频问题整理成了一个表格方便大家对照准备。考察方向高频问题推荐回答思路计算机网络TCP和UDP的区别从连接性、可靠性、速度、使用场景几个维度展开计算机网络网页输入URL后发生了什么DNS解析、TCP连接、HTTP请求、服务端处理、渲染操作系统进程间通信方式有哪些管道、消息队列、共享内存、信号量、Socket说明优缺点操作系统线程池的参数怎么设计核心线程数、最大线程数、队列、拒绝策略结合业务场景讲数据库索引失效的场景最左前缀失效、隐式类型转换、like前缀模糊等数据库事务隔离级别读未提交、读已提交、可重复读、串行化结合MySQL默认级别讲Redis缓存穿透、击穿、雪崩怎么解决布隆过滤器、互斥锁、缓存预热、过期时间加随机值消息队列消息丢失怎么处理生产端ack、broker持久化、消费端手动提交、重试机制场景设计短链接系统短码生成、存储选型、跳转方式、过期策略、统计功能场景设计秒杀系统限流、削峰、防超卖、缓存设计、接口幂等4.4 面试官追问时怎样保持思路不慌连环追问是腾讯面试最大的难点。一面的时候面试官问我HTTP的Keep-Alive和TCP的KeepAlive有什么区别我一开始以为他在说同一个东西答得有点偏。后来他提醒了一下我才反应过来HTTP Keep-Alive是应用层的连接复用机制TCP KeepAlive是传输层的保活探测机制两者虽然名字相近但作用完全不同。这次经历让我总结了两个应对连环追问的技巧。第一回答每个问题的时候结尾主动做个总结把当前结论和上下文的关联说清楚这样即使被追问也能保证主线不丢。第二如果发现面试官的追问方向和你预想的不一样不要慌了阵脚先反问一句“您是指XX这个方向吗”确认理解一致再继续回答这样可以避免答非所问。5. 总结与复盘建议如果只看面经不看复盘那面试准备就少了一大半的意义。我每次面试完都会做一次详细的复盘哪怕只面了一轮也会趁记忆新鲜的时候把题目和回答记录下来然后逐题分析哪里答得好、哪里答得不好、正确的答案应该是什么。这次腾讯面试结束后我花了一个晚上把四个轮次的面试题全部整理了一遍然后按照知识点分类标出哪些是高频考点、哪些是容易卡住的地方。以我个人的经验来看腾讯面试最核心的备考方向有三个第一是计算机基础尤其是TCP、操作系统、数据库索引和Redis这几个方向几乎必考第二是算法重点练手撕链表题、动态规划题、设计题做题的时候要主动跟面试官讨论边界条件和复杂度第三是项目把你的项目里每一个技术点都往深处拆一遍想想面试官会对什么感兴趣、会追问什么。最后再分享一个心得体会不要等面试前一周才开始准备。大厂面试的知识点非常密集一周时间只够刷一遍高频题根本没时间做深入思考。最好提前一个月就开始分模块复习每天花两到三个小时前面两周过知识点和刷题后面两周做模拟面试可以找朋友帮忙模拟也可以自己对着录音练关键是训练在有限时间内把思路讲清楚的能力。面试本来就是一场高强度输出准备越充分发挥越稳定。希望这份面经能帮到你。