一份2018年的笔试卷,放在今天还能读出什么价值?我做运维开发这些年,面试过不少人,也帮着出过题,回头再看网易有道这套卷子,反而比当年刚拿到手时体会更深。它考的不是某个冷门命令的背诵,而是把“网络、系统、编程、数据库、分布式”这些运维开发日常工作的底子,用一套卷子串了起来。哪怕你是刚入行,或者正打算往这个方向转,这套题里暴露出的能力模型,就是一份很实在的岗位说明书。
1. 整体设计与考察意图拆解
1.1 试卷结构背后的岗位画像
网易有道是做在线教育产品和工具的,旗下有词典、云笔记、在线课程这类业务。这类产品的特点是:用户量大、请求并发高、数据敏感性强(用户笔记、学习记录都不能丢),而且迭代速度快,经常要搞活动、上新功能。这就决定了他们需要运维开发工程师能搞定几类事情:保证线上服务稳定,快速响应促销活动带来的流量高峰,把日常运维工作自动化,以及快速定位和解决线上故障。
整套试卷拆开看,大致能分成四个能力板块:
| 能力维度 | 考察内容 | 对应实际场景 |
|---|---|---|
| 网络基础 | TCP/IP协议、HTTP状态码、三次握手 | 排查接口超时、分析调用链、配置负载均衡 |
| 系统与Linux | 进程管理、内存分配、文件系统、Shell | 日常服务器维护、写自动化脚本、性能排查 |
| 编程能力 | Python为主、算法与数据结构、编程题 | 开发运维平台、写监控脚本、数据处理 |
| 分布式与数据库 | 缓存、消息队列、MySQL主从、索引 | 设计高可用架构、优化接口性能、保证数据一致性 |
这个结构本身就是个信号:运维开发要的不是只会敲命令的“机房管理员”,而是懂开发的运维,也就是SRE思路在国内落地的一个变体。你既要有深度(比如TCP的细节),又要有广度(从网络到数据库都得能聊),还要有动手能力(编程题直接看你写代码的水平)。
1.2 为什么运维开发岗要考算法和编程
很多人不理解,运维为什么还要刷算法题?我当年也觉得这有点为难人。直到后来自己踩了坑才明白:写监控脚本、开发部署平台、处理日志数据,这些工作表面上是“运维”,但本质都是“开发”。你写的代码质量直接决定了平台的稳定性和效率。
举个例子,你写一个日志分析脚本,如果只考虑“能跑”,那用两层for循环暴力处理就行。但生产环境的日志一天几个GB,暴力解法跑一次要半小时,等你分析完,故障早就过去了。这时候你就得考虑用哈希表去重、用滑动窗口做统计,甚至用多进程去并行处理——这不就是算法题里的东西吗?
再比如开发一个自动化部署平台,你要管理几千台服务器的发布状态,需要设计合理的数据结构来表达依赖关系、处理并发冲突,这不就是数据结构与算法的实际应用吗?所以说,笔试考算法,筛选的不是“会背题的人”,而是“遇到性能问题能自然想到用数据结构和算法解决的人”。
注意:这里说的算法不是ACM那种竞赛难度,而是基础的数据结构(数组、链表、哈希表、树、图)和基础算法(排序、查找、动态规划、贪心)。关键是理解它们的适用场景,而不是只会默写代码。
2. 核心知识点解析与实操要点
2.1 网络基础:不只是背协议,还要会抓包验证
网络这块是运维开发的立身之本。线上出了问题,90%的时候都要先从网络入手排查。TCP/IP协议族不是背几个定义就完事,而是要能把“连接建立、数据传输、连接断开”的过程和代码、抓包结果对应起来。
以TCP三次握手为例,这是面试题里的钉子户,但很多人只背了SYN、SYN+ACK、ACK这三步。实际工作中,你需要理解这些细节:
第一次握手:客户端发送SYN包,自己不消费,等着对端回应,这时候客户端进入SYN_SENT状态,如果长时间没收到对端的SYN+ACK,就会触发超时重传。这个超时时间不是随便定的,Linux默认是1秒开始,然后用指数退避的方式重试,重试次数由
tcp_syn_retries参数控制,默认是6次。第二次握手:服务器收到SYN后,把连接放入半连接队列(syn queue),回复SYN+ACK,此时服务器进入SYN_RECV状态。如果服务端处理不过来,半连接队列被占满,就可能出现SYN Flood攻击,表现为客户端明明没做什么,但服务器连接资源被耗尽。
第三次握手:客户端收到SYN+ACK后,回复ACK,此时连接进入ESTABLISHED状态,同时内核会把连接从半连接队列移到全连接队列(accept queue)。注意,客户端在发送ACK时,其实就可以携带数据了——这叫TCP Fast Open的简化版,知道这个细节,面试时能加不少分。
这些参数在排查高并发连接问题时非常关键。之前我遇到过一个问题:线上服务连接数上不去,客户端老报“Connection reset by peer”。后来用ss -s一看,全连接队列溢出次数一直在涨,说明accept队列满了,而应用层读取连接的速度跟不上。调整了应用线程数和系统参数net.core.somaxconn,才彻底解决。
建议你平时用tcpdump和Wireshark实际抓一次握手和挥手过程。命令很简单:
tcpdump -i any host 192.168.1.100 and port 8080 -w handshake.pcap把抓包文件导入Wireshark,跟着TCP Stream看,三次握手的每个状态转换都在眼前,比死记硬背强十倍。
2.2 HTTP状态码:线上排查的第一道关卡
HTTP状态码是运维开发看家本领,但很多人在笔试时只能写出200、404、500这几个常见的。作为运维开发,你要能根据状态码快速判断问题方向:
- 200:正常返回。但如果接口业务上失败了,也可能返回200,这说明需要看业务码,不能只看HTTP码。
- 301/302:重定向。排查接口被劫持、域名配置错误、CDN回源异常时,经常会看到异常的重定向。
- 403:服务器拒绝了请求。通常是权限问题,但也要排查是否有WAF拦截,IP是否被封禁。
- 404:URL不存在。排查路由配置、反向代理规则,看是不是Nginx的location写错了。
- 499:这个最特殊——Nginx定义的状态码,表示客户端在服务器还没返回时提前关闭了连接。线上出现大量499,通常意味着后端响应太慢,客户端等不及了。
- 500:服务器内部错误。最常见的原因就是代码抛了未捕获的异常。
- 502:网关收到无效响应,通常是后端服务挂了或者超时。
- 503:服务不可用,可能是服务在重启、负载过高或正在维护。
- 504:网关超时。后端处理请求太久,Nginx等不及了,这时候要看是慢查询还是代码死循环。
我自己的习惯是,线上问题一出现,先看状态码分布,再结合错误日志和访问日志打点,基本能定位到一半以上的故障。这也是笔试中设计此类考题的初衷——考察你排查问题的第一反应是否专业。
2.3 Linux与系统:从命令行到内核参数
Linux命令是基本功,但笔试试卷不会直接问你ls、cd这些,而是会考察组合运用和排查思路。比如:
如何查看当前系统的平均负载?
uptime或者cat /proc/loadavg。但要理解,负载高不代表CPU忙,可能是磁盘I/O在等待,也可能是大量不可中断的进程。如何找到CPU占用最高的进程?
top进去按P键排序;也可以一条命令搞定:ps -eo pid,ppid,cmd,%cpu --sort=-%cpu | head。如何查看端口监听情况?
netstat -tlnp或者更现代化的ss -tlnp。注意ss在处理大量连接时性能远好于netstat。如何分析磁盘I/O瓶颈?
iostat -x 1关注%util、await这些指标,再配合pidstat -d定位具体是哪个进程在疯狂写盘。如何查看内存使用详情?
free -h只是第一眼,要深入用cat /proc/meminfo看细节。特别要注意Cached和Buffers,在Linux中文件缓存会被计入“used”,但实际上内存紧张时是可以回收的,所以别一看到used高就慌了。如何快速定位日志文件里某个关键字出现的次数?
grep -c "ERROR" app.log。如何找出日志中访问量最大的前10个IP?
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10。注意awk取字段、sort排序、uniq去重然后统计,这几个命令在Shell中配合使用频率极高。
这些都是高频命令,但很多人在笔试时会卡壳,因为平时只用了图形界面或别人写好的脚本。这里有个实操心得:每接触一个新环境,先用系统自带的man命令把常用命令的参数过一遍,然后自己动手写小脚本去处理真实的日志文件,练多了自然就熟练了。
从系统原理层面,还需要理解进程、线程、内存分配的基本概念。比如Linux下进程和线程的关系(线程是轻量级进程,用ps -efL可以看到线程)、fork和exec的区别、虚拟内存和物理内存的映射关系。这些概念虽然抽象,但排查内存泄漏、CPU飙高时必须用到。举个例子,你用top看到一个Java进程内存占用飙到80%,并不能直接下结论说“内存泄漏了”——可能是JVM堆在扩容,也可能是元空间在涨,要结合JVM的监控数据去看。
2.4 编程能力:Python是运维开发的第一语言
网易的运维开发笔试一定会考Python(或者其他主流语言,但我强烈建议投运维开发岗一定把Python练熟)。Python在运维领域的生态太强了:自动化部署用Fabric、Ansible;监控采集用psutil、pySNMP;Web开发做运维平台用Flask、Django;数据处理用pandas;写爬虫用requests、Scrapy。基本上你能想到的运维场景,Python都有对应的库。
笔试编程题的重点不在于用多么花哨的语法,而在于:
- 代码的健壮性:是否能处理边界情况(空输入、None值、最大最小值)。
- 代码的复杂度:是否用了合适的数据结构和算法来降低时间/空间复杂度。
- 代码的规范性:变量命名是否清晰,函数拆分是否合理,是否写了必要的注释。
- 代码的可维护性:是否把逻辑抽象成了可复用的函数,而不是一大坨脚本。
举个经典的编程题例子:给定一个字符串,找出其中不含有重复字符的最长子串的长度。不要小看这种题,在制作日志分析工具时经常能用类似思路去处理数据切片。如果用暴力解法,两个for循环穷举所有子串,时间复杂度是O(n^2)甚至O(n^3);而用滑动窗口,时间复杂度直接降到O(n)。这就是笔试要考察的“代码能力”。
我见过很多同学在笔试时,代码半小时写不出来,但用中文写了一大堆注释,或者在代码里写“思路:应该用动态规划”。这些都是大忌。笔试卷子看的是能跑的代码,不是思路草稿。
2.5 数据库与分布式:大厂业务的必考项
MySQL、Redis、消息队列,这三样几乎是互联网公司后端业务绕不开的组件。有道这种体量的产品,日请求量动辄千万级,必然需要缓存来扛读流量,需要消息队列来做异步削峰,需要MySQL主从来保证读写能力扩展。
数据库这块的高频考点:
MySQL索引的数据结构:为什么用B+树而不用B树或哈希索引?因为B+树的非叶子节点不存数据,一个节点能存更多索引项,树更矮,磁盘I/O次数更少;且叶子节点有链表指针,范围查询高效。这个区别一定要能讲清楚。
聚簇索引和非聚簇索引的区别:聚簇索引(InnoDB主键索引)的叶子节点直接存整行数据,非聚簇索引(二级索引)的叶子节点存主键值,所以通过二级索引查找数据时要“回表”。
覆盖索引:如果查询的字段都在索引里,就不需要回表,这叫覆盖索引,性能很高。线上优化慢查询时,这是一个非常常用的手段。
事务隔离级别:读未提交、读已提交、可重复读、串行化。InnoDB默认是可重复读,但MySQL的binlog在默认情况下(statement格式)要求使用可重复读,否则会导致主从数据不一致。
MVCC多版本并发控制:通过隐藏的两个列(创建版本号、删除版本号)来实现快照读,读不加锁,写不阻塞读。
Redis这块,要理解为什么Redis快(纯内存、单线程避免锁竞争、I/O多路复用),以及常见的数据结构(String、Hash、List、Set、Sorted Set)各自适用于什么场景。比如用Sorted Set做排行榜,用List做消息队列的简单实现,用Hash存储对象属性。
消息队列考得相对轻一些,但你要理解为什么要引入消息队列:削峰填谷、异步解耦、数据分发。以及消息队列带来的新问题——消息丢失、消息重复消费、顺序消费,这些后续都要在设计方案时考虑进来。
3. 实操过程与核心环节实现
3.1 用一段Python连接真实笔试场景
笔试的编程题通常会给你一个相对完整的小需求,而不是纯算法题。比如“写一个脚本,统计一个日志文件中各IP的访问次数,并按降序输出”。这种题在运维开发工作中很常见——分析访问日志、统计接口调用量、识别异常流量。
下面我给出一个比标准答案更完整、也更贴近生产环境的实现,并解释每一步的考量:
#!/usr/bin/env python3 """统计日志中IP访问次数,按降序输出 用法: python3 count_ip.py access.log """ import sys import re from collections import Counter def parse_ips_from_log(file_path: str) -> list: """使用正则表达式提取IP v4 地址""" ip_pattern = re.compile(r"\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b") ips = [] try: with open(file_path, "r", encoding="utf-8", errors="ignore") as f: for line in f: match = ip_pattern.search(line) if match: ips.append(match.group()) except FileNotFoundError: print(f"错误:文件 {file_path} 不存在", file=sys.stderr) sys.exit(1) return ips def main(): if len(sys.argv) != 2: print("用法: python3 count_ip.py access.log", file=sys.stderr) sys.exit(1) logs = sys.argv[1] ips = parse_ips_from_log(logs) # Counter 本质上是一个带计数的字典,底层实现和哈希表一致 ip_counter = Counter(ips) # 访问量最多,降序输出 for ip, count in ip_counter.most_common(): print(f"{ip}\t{count}") if __name__ == "__main__": main()这个实现里包含了几个容易被忽略的细节:
encoding="utf-8", errors="ignore":日志文件经常有编码问题,加这个参数能避免脚本被某个乱码行干挂。- 用正则的
(?:...)非捕获分组,避免不必要的内存开销,虽然这里日志量不大,但养成好习惯。 - 用
sys.argv[1]而不是input(),方便管道操作(比如把文件路径从别的地方传进来)。 - 用
collections.Counter而不是自己写字典计数,代码更简洁,性能也更好。
如果你在笔试中能把这一类代码写得健壮、规范、考虑边界情况,得分一定会高于仅仅输出“正确答案”的行数。
3.2 从笔试题到线上故障定位:一个完整案例
笔试题目不会直接告诉你怎么排查故障,但你平时一定要多做案例演练。我拿一个典型的线上场景来说——用户反馈App经常转圈,请求超时,而查看监控发现数据库的CPU使用率已经快100%了。
排查过程如下:
- 先看慢查询日志:
slow_query_log是不是开了?如果开了,找到执行时间超过1秒的SQL。 - 对着慢日志里的SQL,执行
EXPLAIN SELECT ...\G查看执行计划,看走没走索引、扫描了多少行。 - 发现某个SQL的WHERE条件字段没有索引,导致全表扫描。加上索引后,执行时间从3秒降到几十毫秒。
- 但光加索引还不够。再看业务代码,这个SQL在一个循环里被调用了N次,等于每次请求对数据库造成N次查询。在代码里引入缓存,把热点数据缓存到Redis,请求路径从“App→API→MySQL”变成了“App→API→Redis→MySQL”,数据库压力直接降下来。
这个过程涉及的就是笔试里考的网络、数据库、缓存和代码能力。你平时自己练习时,完全可以模拟这个路径:用一个测试数据库,造一些数据量,然后练习加索引、用EXPLAIN优化SQL。纸上谈兵没有用,动手跑一遍才有感觉。
3.3 Shell脚本:自动化运维的日常
除了Python,Shell脚本是运维开发吃饭的家伙。笔试偶尔会考Shell的基础语法,比如变量赋值、条件判断、循环、函数、awk和sed的用法。
一个常见的自动备份脚本可以这样写:
#!/bin/bash # 功能:备份指定目录,保留最近7天备份 BACKUP_SRC="/data/app" BACKUP_DST="/backup/app" DATE=$(date +%Y%m%d_%H%M%S) KEEP_DAYS=7 mkdir -p "$BACKUP_DST" # tar 压缩备份 tar -czf "${BACKUP_DST}/app_${DATE}.tar.gz" -C "$(dirname "$BACKUP_SRC")" "$(basename "$BACKUP_SRC")" # 删除7天前的备份文件 find "$BACKUP_DST" -name "app_*.tar.gz" -mtime +"$KEEP_DAYS" -exec rm -f {} \; echo "备份完成: ${BACKUP_DST}/app_${DATE}.tar.gz"这个脚本里隐藏了几个经验点:
mkdir -p:保证目标目录存在,避免脚本第一次执行时报错。tar -C先切换目录:为了避免备份包内出现绝对路径的问题。find + -mtime +7 -exec rm -f:清理过期备份,防止磁盘被占满。如果不清理,备份脚本迟早会把磁盘打爆,这种事故我见过不止一次。- 变量名全部大写:Shell社区约定俗成的习惯,防止和系统环境变量混淆。
- 用
date +%Y%m%d_%H%M%S生成时间戳,避免文件名冲突。
如果你笔试时能写出这样的脚本,并提到“备份前应该检查磁盘空间”,那就已经超出普通应试者一大截了,因为这说明你有真实的运维经验。
3.4 常见算法题的思路与实现
笔试中的算法题,说难不算太难,但很考验基本功。以字符串处理为例,我把常见的几种问法和对应的思路整理一下:
问题一:反转字符串
def reverse_string(s: str) -> str: return s[::-1]这题就是送分题,但要注意Python中字符串是不可变对象,所以不能原地反转。切片[::-1]是最Pythonic的写法,时间复杂度O(n),空间复杂度O(n)。
问题二:判断回文串
def is_palindrome(s: str) -> bool: s = ''.join(ch.lower() for ch in s if ch.isalnum()) left, right = 0, len(s) - 1 while left < right: if s[left] != s[right]: return False left += 1 right -= 1 return True用双指针从两端向中间走,时间复杂度O(n),空间复杂度O(1)(算法部分,不含预处理的新字符串)。这道题考的是双指针技巧,比直接用s == s[::-1]更有区分度。
问题三:找出字符串中第一个不重复的字符
def first_unique_char(s: str) -> int: from collections import Counter counter = Counter(s) for i, ch in enumerate(s): if counter[ch] == 1: return i return -1这题考的是哈希表。遍历一次字符串,统计每个字符出现次数;再遍历一次,找到第一个计数为1的字符。时间复杂度O(n),空间复杂度O(k),k为字符集大小。
这些题看起来简单,但笔试考的是你能不能在规定时间内写出无bug的代码,以及能不能考虑边界情况(空字符串、全重复字符等)。平时可以多在LeetCode上刷一些简单和中等难度的题,保持手感。
4. 常见问题与排查技巧实录
4.1 运维开发笔试中容易暴露的三大软肋
我批过不少笔试卷,也看别人批过不少,总结下来,候选人最容易在下面几个地方丢分。
第一是基础知识不牢靠,但强行往“高深”方向装。比如问TCP三次握手,他能背出SYN、SYN-ACK、ACK,但追问“什么时候会出现SYN重传”“客户端在什么情况下不等待ACK就发送数据”就卡壳了。这种半桶水的状态在笔试和面试里最容易被识破。
第二是动手能力跟不上。有些同学概念背得滚瓜烂熟,但一旦让他手写一个Shell脚本或者Python函数,就漏洞百出:变量名拼写错误、忘记处理文件不存在的情况、没有考虑特殊字符转义。笔试不是填空题,最终看的就是你写出来的代码能不能跑、能不能用。
第三是排查问题的思路不清晰。运维开发日常就是和故障打交道,笔试如果能出一两道“给出一个线上故障,请描述你的排查步骤”的题,其实是最能拉开差距的。很多人写排查步骤就是“查看日志”“重启服务”这几个字,完全看不出分析过程。正确的写法应该是:先收集监控数据(CPU/内存/磁盘/网络/日志)→ 初步定位可能方向 → 用命令逐个验证 → 定位根因 → 制定解决和预防方案。逻辑缜密的人,光写排查步骤就能看出经验。
4.2 经典易错真题复盘
再来复盘几个我印象深刻的易错知识点。
第一个:TCP四次挥手为什么是四次,而不是三次?
因为TCP连接是全双工的,一方(比如客户端)发送FIN表示“我这边不再发送数据了”,但还能继续接收对方的数据;对方收到FIN后回复ACK,但可能还有数据要发,所以不能同时回FIN。等对方数据发完了,再发FIN表示“我也不发了”。所以正常情况下是四次:FIN、ACK、FIN、ACK。但如果双方同时都想关闭连接,或者数据都已经发完,某些场景下可以优化成三次。笔试时把这个过程讲清楚,就能拿到分。
第二个:MySQL索引为什么“最左前缀原则”?
联合索引(a, b, c)会被拆分成三个索引:(a)、(a, b)、(a, b, c)。查询如果只用b和c,是走不了这个联合索引的。原因在于B+树的排序规则:先按a排序,a相同再按b排序,b相同再按c排序。如果你跳过a直接按b查,整个索引顺序对你没有帮助。这个原理理解了,线上建索引时就不会盲目乱建。
第三个:Linux下如何查找占用磁盘空间最大的文件?
du -ah /data | sort -rh | head -20或者更精准地锁定大文件:
find /data -type f -size +500M -exec ls -lh {} \;这题考的是组合命令的能力,以及知道du(统计目录大小)、sort -h(按人类可读数字排序)、head这几个命令的配合。
4.3 运维开发日常排查工具清单
除了笔试,我更想分享的是平时工作中真正会用到的排查工具清单。有了这些,你面对问题的底气会完全不同。
| 工具 | 用途 | 示例命令 |
|---|---|---|
| top/htop | 系统负载、CPU、内存实时监控 | top -Hp <pid> |
| vmstat | 虚拟内存、进程、CPU、I/O统计 | vmstat 1 5 |
| iostat | 磁盘I/O状态 | iostat -x 1 |
| netstat/ss | 网络连接、端口监听 | ss -tlnp |
| tcpdump | 抓包分析网络数据包 | tcpdump -i eth0 port 80 |
| strace | 跟踪进程的系统调用 | strace -p <pid> |
| lsof | 查看进程打开的文件 | lsof -i :8080 |
| dmesg | 内核日志,排查硬件/死锁问题 | dmesg -T | tail -50 |
| journalctl | 查看systemd日志 | journalctl -u nginx -f |
我曾用strace定位过一个诡异问题:某服务刚启动就挂掉,看/var/log/messages没有任何异常,用strace -f -o /tmp/trace.log ./start.sh跑一遍,发现进程在访问一个不存在的系统调用参数时直接退出。这种问题,日志没有记录,光靠看代码可能要看半天,但strace一下就能锁定行号,效率极高。
4.4 备考顺序与时间分配建议
如果你目标是像网易有道这样的互联网公司运维开发岗,我建议备考按这个顺序来:
- 先打基础(30%时间):计算机网络(TCP/IP、HTTP)、Linux系统基础、数据库基础。这是笔试的底盘,底盘不稳,上面全白费。
- 再强化编程(30%时间):Python语法、常用数据结构与算法、LeetCode简单和中等题。每天保持2-3道题的刷题量,重点是理解思路而不是背答案。
- 然后刷场景题(25%时间):日志分析、监控告警、自动化部署、故障排查。这些题目没有标准答案,但非常考验经验。多看看技术博客、开源项目的运维文档,有条件的自己搭一套小的环境来实践。
- 最后查漏补缺(15%时间):把不懂的知识点列出来,逐个攻破。可以重点看看自己容易出错的地方,比如Shell中单引号双引号的区别(单引号不展开变量,双引号会展开),
awk中$1和$NF的含义,这类细节很容易在笔试翻车。
我个人觉得,真正决定你能不能拿到这个offer的,不是某个冷门知识点的记忆,而是你有没有形成一套“从问题到方案”的思维习惯。笔试卷子只是检验这套思维习惯的窗口。
最后再分享一个小技巧:做完笔试题之后,不管结果如何,都把自己的答案抄录下来(或者至少记住题目结构),等出来之后再认真复盘一遍。运维开发这个岗位,真正拉开差距的是在工作两三年后的故障处理能力和系统设计能力,而不是笔试那一次的高分。趁早把手底下的功夫练扎实了,比什么都重要。