春招那阵子我投了一圈游戏公司,游卡网络(就是做《三国杀》的那家杭州公司)的运维开发校招岗,简历过了之后收到一封笔试邀请,牛客网线上答题,限时90分钟,双机位摄像头全程监控。那段时间我刷了挺多运维开发的笔试经验帖,但真正坐到电脑前拿到这套卷子的时候,还是踩了几个坑。最近整理面试笔记,把这场笔试从头到尾复盘了一遍,发现它其实是非常典型的一份游戏公司“运维开发”岗位试卷:Linux基础、网络、数据库、Shell/Python脚本能力、容器和发布场景全都有涉及,而且和普通后端开发的笔试题风格差别很大。这篇复盘我尽量把题目还原清楚,连同每道题背后的考察意图和正确解法一起列出来,给后面准备运维开发方向校招的同学做个参考。
1. 从岗位JD反推笔试题型:游卡这场笔试的考察逻辑
1.1 游卡运维开发到底是干什么的
游卡网络这个名字,不混游戏圈的人可能不太熟,但一提《三国杀》基本都知道。这家公司以卡牌桌游起家,后来扩展到PC端、移动端和页游,多个游戏项目并行运营。游戏业务的运维和普通互联网业务运维有个明显区别:区服多、版本迭代快、在线人数波动大,而且“开服”“合服”“跨服活动”这类操作特别频繁。
游卡招聘页面上“运维开发”岗位的描述,核心职责大概是这几块:
- 负责游戏服务器的部署、变更、容量规划和日常巡检;
- 建设监控告警、日志采集、故障定位等自动化运维平台;
- 开发持续集成/持续部署相关的工具链,支撑研发快速迭代;
- 参与运维规范制定和稳定性保障。
所以这个岗位本质上不是纯运维,而是“运维+开发”的复合型岗位,既要懂服务器和网络,又要能写代码去解决重复性的人工操作。这一点直接决定了笔试题的考察方向——光是背命令不行,光是刷算法题也不够,两者得结合起来。
1.2 笔试平台和考试流程的基本盘
我记得是2024年3月中旬收到的邮件,通知我3天后参加统一笔试。线上笔试用的牛客网,登录之后有防作弊页面,要求电脑摄像头打开,同时手机扫一个二维码放在斜后方作为第二视角。整个考试时间90分钟,系统到时自动交卷,中途不能切出页面。这里要提醒一句:考试环境一定要提前找个安静的房间,我当时调试摄像头就折腾了好几分钟,直接压缩了答题时间。
从题量上看,整张卷子分了四个部分:
| 题型 | 题量 | 分值占比 | 我的实际用时 |
|---|---|---|---|
| 单选题 | 20题 | 约30% | 25分钟 |
| 多选题 | 10题 | 约20% | 15分钟 |
| 编程题 | 2题 | 约30% | 30分钟 |
| 简答/场景设计题 | 2题 | 约20% | 20分钟 |
这个比例对运维开发岗来说非常典型:选择题基础覆盖面广,编程题考察代码落地能力,场景题考察真遇到故障时能不能拿出解决方案。
1.3 从考点分布看公司要什么样的人
我当时做完第一反应是“怎么这么多命令题”,后来复盘才发现每道题背后都有指向性。整理下来,考点大概可以分成这样几类:
| 考察模块 | 具体知识点 | 对应岗位能力 |
|---|---|---|
| Linux基础 | 文件查找、权限、进程管理、定时任务 | 日常巡检和问题排查 |
| 网络基础 | TCP三次握手、HTTP状态码、DNS解析 | 网络故障定位 |
| 数据库 | SQL查询、索引、慢查询优化 | 游戏数据查询与报表 |
| 脚本编程 | Shell文本处理、Python小工具 | 自动化运维工具开发 |
| 容器与发布 | Docker镜像、灰度发布、回滚策略 | 版本发布与稳定性保障 |
| 算法思维 | 限流、并发控制、TopN统计 | 平台开发中的基础能力 |
可以明显感觉到,这套卷子不是隨随便便从算法题库里抽的,而是围绕“游戏服务器运维日常工作”来设计的。选择题里大量出现“某命令在什么场景下使用”,编程题里直接让你处理游戏日志,场景题问的是“某区服出现大面积掉线怎么排查”。所以准备这类笔试,光刷LeetCode不够,还得把Linux命令和运维场景摸熟。
2. 真题逐题复盘:选择题、编程题、场景题分别怎么答
2.1 单选题:看似送分,实际全是细节
选择题一共20道,覆盖范围很广。我印象比较深的有下面几道,大意还原一下:
第一道,考的是Linux文件查找命令。题目问:需要在 /data/log 目录下递归查找最近7天内被修改过、且以 .log 结尾的文件,下面哪条命令可以实现?选项里有find /data/log -mtime -7 -name "*.log"、find /data/log -mtime +7 -name "*.log"、find /data/log -atime -7 -name "*.log"、find /data/log -ctime -7 -name "*.log"。
这道题有两个坑:一是-mtime和-atime、-ctime的区别。-mtime是文件内容被修改的时间,-atime是访问时间,-ctime是文件元数据变化的时间。日常排查通常关心“内容什么时间改的”,所以选-mtime。二是-7和+7的区别:-7表示最近7天以内,+7表示7天以前。所以正确答案是find /data/log -mtime -7 -name "*.log"。
第二道考网络。题目大概意思是:客户端和服务器建立TCP连接时,客户端收到服务端返回的SYN-ACK但没有再次发出ACK,最可能的原因是什么?选项有:服务端全连接队列满、客户端防火墙丢弃了确认包、网络延迟过高、服务器进程崩溃。
这道题考察对TCP三次握手和内核连接队列的理解。三次握手过程中,服务端收到SYN后会进入 SYN-ACK 状态,如果服务端的全连接队列(accept队列)已满,内核可能丢弃SYN或SYN-ACK;但题目说的是“客户端收到了SYN-ACK却没发出ACK”,那问题更可能在客户端侧。客户端没发出ACK,要么是防火墙把ACK包丢了,要么是客户端进程状态异常。结合游戏场景,如果玩家大量涌入,服务端 accept 队列满了,会出现连接建立缓慢的情况;但如果“收到SYN-ACK不发ACK”,常见诱因是客户端本地出方向丢包或者安全软件拦截。这道题正确选项是“客户端防火墙丢弃了确认包”。
第三道是数据库索引相关的。题目问:一张游戏玩家充值表,查询语句为SELECT uid, SUM(amount) FROM recharge WHERE game_id = 3 AND pay_time BETWEEN '2024-03-01' AND '2024-03-31' GROUP BY uid ORDER BY SUM(amount) DESC;,最适合建立什么索引?选项包括(pay_time)、(game_id, pay_time)、(uid, amount)、(game_id, uid)。
这个考点是联合索引的最左前缀原则。WHERE 里先用game_id等值过滤,再用pay_time范围过滤,所以联合索引应该把等值查询字段放前面,范围字段放后面,也就是(game_id, pay_time)。如果只建(pay_time)索引,game_id的过滤还得回表;如果建(uid, amount)索引,查询用不上。选(game_id, pay_time)。
选择题里面还有不少考命令细节的,比如awk默认分隔符是什么(空格)、grep -E和egrep的关系、crontab表达式*/5 * * * *的含义(每5分钟执行一次)等等。这类题没有技巧,纯粹靠平时积累,特别是awk、sed、sort、uniq这些高频命令,一定要做到看到语法就能反应出执行结果。
2.2 多选题:少选漏选都失分,规则比内容更重要
多选题10道,难度明显上来了。我记得有一个题是:关于Docker镜像分层的描述,正确的有哪些?选项里有“镜像是只读的”“容器层保存运行时变更”“不同镜像可以共享相同的基础层”“每次构建都会在基础层上新增一层”。
这个题的考点是Docker的联合文件系统(UnionFS)和镜像分层机制。Docker镜像由多个只读层组成,容器在镜像之上加一个可写层,所有对容器的修改都发生在可写层;不同镜像如果基于同一个基础镜像,可以共享下面的只读层,所以占用空间更小;每次 Dockerfile 指令执行都会生成一个新的层。四个选项描述全对,这就是全选。说实话这种题如果对Docker原理只有“会用docker run”的程度,很容易漏选“共享基础层”这个点。
还有一道是关于HTTP状态码的。题目问:以下哪些状态码表示客户端错误?选项有 301、400、401、502。这个比较容易,400和401是客户端错误,301是重定向,502是网关错误。多选容易错的地方在于,很多人看到401会犹豫是不是“未认证”算服务端问题,其实401、403都是客户端类错误,不属于服务端。
对我个人来说,多选策略是“拿不准的不要多选”。因为很多平台多选题的计分规则是:全部选对得满分,选对但不全得一半分,选错一个得零分。所以拿不准的选项宁可不选,保住保底分。我当时有一道关于Kubernetes Pod重启策略的题,选项里有一个“DaemonSet管理的Pod不能设置restartPolicy: Always”,我犹豫了一下没选,后来查资料确认这个选项是错的,算是躲过一劫。
2.3 编程题:不是算法题,是“用代码解决运维问题”
编程题两道,都不算传统意义上的算法题,更像是实际工作场景中的小工具开发。这一点和纯后端开发的笔试题有本质区别。
第一道题大概是:写一个Python函数,实现一个简单的限流装饰器,要求每秒最多允许调用N次,超过限制的调用直接拒绝并抛出异常或返回False。
第二道题是:给定一个 nginx 或游戏服务器的访问日志文件路径,写Shell脚本统计访问次数最多的前10个IP,并输出IP和次数,按次数降序排列。
看到这两道题我就明白了,游卡想招的人不是“懂运维概念”的人,而是“能自己写工具解决运维问题”的人。限流是服务端开发里非常常见的需求,尤其是游戏登录、活动领奖、API调用这些场景,防止刷接口和雪崩;日志统计则是运维日常分析最基础的操作,Top IP、Top URL、接口错误率全都要靠这种统计能力。
这也就解释了为什么笔试时间90分钟,但编程题只有两题。因为题目本身不难,难的是在有限时间内写得对、写得规范。下面我会专门写一节,把这两道题的完整解答思路展开讲。
2.4 简答/场景题:不要求唯一答案,但一定要有排查链路
简答题两道,都是开放式的。
第一道是:跑着的游戏服务出现大量玩家掉线,客服反馈“某大区玩家集中掉线”,你怎么排查?请写出完整的排查思路。
第二道是:项目组要发一个新版本,这个版本涉及玩家背包系统的数据库表结构变更,你怎么设计发布和回滚方案?
这种题没有标准答案,但评分点其实非常明确:有没有排查顺序、有没有具体命令、有没有考虑到回滚。我当时的回答思路是分层的:先确认影响面(是单个服务器还是整个区),再看网络链路(负载均衡节点、带宽、防火墙),然后看业务进程(CPU、内存、句柄数、日志),最后看数据库(连接数、慢查询、锁)。每一层都给出对应的排查命令,比如top、ss -lntp、dmesg、tail -f app.log。虽然不一定全对,但至少让面试官看到你有“链路意识”。
第二道涉及数据库表结构变更的发布,我回答的是“灰度+备份+脚本化”三件套:先把表结构变更写成可重复执行的SQL脚本,执行前备份全表;然后选一个低峰期的小区做灰度发布,观察日志和监控指标;确认没问题后逐批扩大到其他区;最后如果出问题,利用备份做回滚,回滚脚本也提前准备好。这里有个游戏行业的特殊性——玩家数据不能丢,所以任何涉及数据库变更的操作,回滚方案必须在发布前就验证过,而不是出事了才想。
3. 最值得细说的三道题:答案背后的原理拆解
3.1 令牌桶限流:为什么选它,而不是计数器或漏桶
限流那题我用的Python装饰器实现,可以先看代码:
import time import threading class TokenBucket: def __init__(self, rate, capacity): self.rate = rate # 每秒补充令牌数 self.capacity = capacity # 桶容量 self.tokens = capacity # 初始装满 self.last_refill = time.monotonic() self.lock = threading.Lock() def acquire(self): with self.lock: now = time.monotonic() elapsed = now - self.last_refill self.tokens = min(self.capacity, self.tokens + elapsed * self.rate) self.last_refill = now if self.tokens >= 1: self.tokens -= 1 return True return False def rate_limit(rate, capacity=None): if capacity is None: capacity = rate bucket = TokenBucket(rate, capacity) def decorator(func): def wrapper(*args, **kwargs): if not bucket.acquire(): raise RuntimeError("rate limit exceeded") return func(*args, **kwargs) return wrapper return decorator # 使用示例:每秒最多调用5次 @rate_limit(rate=5, capacity=5) def handle_login(uid): print(f"handle login: {uid}")为什么选令牌桶而不是计数器或者漏桶?这里有个很重要的区别:
- 计数器限流最简单,每秒钟重置一次计数。但它的缺点是有“临界突变”问题:比如每秒限制100次,有人在前一秒最后100ms打满100次,后一秒前100ms又打满100次,实际200ms内就打了200次,服务端还是会被冲击。
- 漏桶算法是匀速出水,不管进来多猛,处理速度恒定。适合保护下游,但缺点是突发流量全部被削平,不够灵活。
- 令牌桶算法允许一定程度的突发:桶里攒了多少令牌,就能一次性消耗多少,相当于给业务留了“余量”。
游戏场景下单用户登录接口是典型的高并发突发场景:平时请求量不高,但活动开抢的那一瞬间流量会猛增。如果限流器不允许突发,正常玩家的请求也会被误伤;允许一定突发,就能接住前几秒的峰值流量。所以令牌桶最适合这类场景。
代码里我用了time.monotonic()而不是time.time(),这也是一个容易被问到的细节。time.time()返回的是墙上时钟,如果系统时间被NTP校准或者被手动修改,可能出现时间倒退,导致限流计算错误;time.monotonic()不受系统时间调整的影响,只计算单调递增的时间差,适合做这种“时长差”的计算。
另外有个边界条件容易被忽略:多线程环境下,令牌桶的计数操作要加锁。我用了threading.Lock保证acquire方法的原子性。如果漏掉锁,并发调用时可能出现两个线程同时通过限流,导致限流失效。笔试阅卷时,这个锁就是区分“能跑”和“写得专业”的关键细节。
3.2 日志统计Top IP:Shell一行流和性能思考
第二道编程题是统计日志Top IP,我当时写的是:
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10如果日志格式的第一列是IP,这段命令可以一次完成统计。整个管道拆开来看:
awk '{print $1}':取出日志每行的第一个字段,也就是客户端IP;sort:把相同的IP排在一起。注意先要排序,uniq只能统计相邻的重复行;uniq -c:统计每行重复出现次数;sort -rn:按统计次数逆向排序,-n是按数字而不是按字典序,-r是降序;head -10:取前10条。
这道题真正的坑有两个。第一个是有些人会忘记先sort直接uniq -c,结果发现统计出来的全是1,因为uniq只合并相邻行。第二个是sort -rn里必须加-n,如果不加,假设有两条记录分别被访问了9次和100次,字典序排列下100会排在9前面,最终Top10结果就错了。
如果日志文件特别大,比如几十GB,一行管道命令也能跑,但sort是内存+外存混合排序,速度可能比较慢。更高效的做法是用awk先做分组计数,再排序输出。比如:
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head -10awk里用数组做关联计数,只遍历一遍文件,避免了大文件全局排序带来的额外IO开销。当然如果文件大到连awk数组都放不下,那就得考虑用更重的方案,比如mapreduce或者把日志按IP哈希分片。笔试题一般不会考到这么大,但能写出这个优化点会加分。
3.3 数据库表结构变更的发布方案:游戏行业的“数据心头肉”
这道简答题我答得比较有条理,核心思路是:任何涉及线上数据变更的操作,都必须围绕“可回滚、可灰度、可监控”来设计。
具体来说,我的方案分成四步。第一步是“备份”:正式操作前在变更涉及的数据库实例上做一次逻辑备份或物理备份,具体用mysqldump还是其他备份工具看数据量。同时要把变更SQL脚本本身也纳入版本管理,保证任何一次操作都能追溯。
第二步是“预处理”:如果表很大,直接ALTER TABLE会锁表,导致游戏卡顿甚至停服。常见做法是用pt-online-schema-change这类工具做在线DDL,或者手动在备库先执行变更,再通过主从切换把流量切过去。
第三步是“灰度”:先找一个测试区或者人少的旧区执行变更,观察监控指标和日志,确认没异常后再逐步推进到其他区。游戏公司通常有几十上百个区,不能一口气全量变更。
第四步是“回滚预案”:如果变更后出现了数据异常,需要能快速还原到变更前的状态。这时候最理想的情况是变更前已经保留了旧表,变更失败时直接改表名切换回来。但要注意,回滚操作本身也可能引入新的问题,所以回滚脚本要提前测一遍,不能发布时才发现回滚脚本是坏的。
这个方案里最容易被应届生忽略的是“灰度”和“回滚”之间的配合。很多人答发布方案只会说“备份+执行+重启”,完全没提灰度观察,更没提回滚。但面试官和阅卷人最想看到的恰恰就是这两点——因为它们意味着你经历过线上事故,脑子里有一根“随时准备出事”的弦。
4. 这些笔试中的“隐藏坑”是应届生最容易丢分的地方
4.1 在线笔试环境本身就是一个坑
牛客的在线IDE其实挺好用,但和本地开发环境还是有区别。第一是没有自动补全,Python和Shell都不会提示函数签名,所以平时写代码不能太依赖IDE,尤其是os、sys、re、collections这些标准库的常用函数得记牢。第二是Shell脚本题没有真实的日志文件可以验证,只能靠“默写”命令,这就要求平时真的在终端里敲过,而不是只看过命令的说明。第三是有摄像头监控,不能像在家做题那样随意查资料,所以基础知识必须当场反应得出来。
我建议准备阶段就刻意练习“裸写”能力:不用IDE补全,直接在一个纯文本编辑器里写Python和Shell,然后放到终端跑一遍验证结果。这样做几次之后,笔试时的手感和用了IDE是一样的。
4.2 命令细节失分:不是不会,是记岔了
我复盘错题的时候发现,选择题里丢分最多的往往是那些“看着眼熟但记不精确”的命令参数。比如find的-mtime、-newer、-exec和-ok的区别;tar的-z、-J、-v组合;grep的-E、-w、-c选项;awk的-F指定分隔符等等。
这里有个笨办法我觉得很有效:把笔试高频命令整理成一张速查表,每个命令只记2到3个最常见的用法,然后每天在终端里跑一遍。比如find就记查文件、按时间过滤、配合-exec批量操作;sed就记替换和按行删除;awk就记打印列、条件过滤和分组统计。不需要把每个命令的所有参数都背下来,但高频组合一定要形成肌肉记忆。
还有一个容易忽略的点:Shell脚本题的格式问题。有些线上OJ对Shell脚本的检查方式是“输出与预期一致”,如果你的命令输出多了个空格或者空行,可能被判错。写完命令后,可以在心里模拟一遍每一步产生的输出格式,确保没有多余的空白。
4.3 多选漏选规则:别因小失大
前面提到多选计分规则,这里再强调一遍。游戏公司校招笔试的多选题往往不确定选错是否倒扣分,但大多数是“选错零分、少选按比例得分”。我当时定的策略是:5个选项里只要有一个选项“不确认”,就只选最确定的两个,放弃那些模糊项。事实证明这个策略帮我保住了不少分,因为有些多选题本身就有4个正确选项,结果我只选了2个,但至少没归零。
当然,这个策略也要分情况。如果题目是那种一眼就知道所有选项都对的,比如“关于TCP三次握手的正确描述”,那就全选,不用犹豫。关键是识别“模糊判断题”——你对某个选项的知识点只记得“好像对”但说不出原理,这种就果断放弃。
4.4 编程题的时间分配:先保一题AC,再冲刺第二题
我写限流装饰器那题花了差不多20分钟,占了编程题的大部分时间。之所以敢这么花,是因为我把时间管理的优先级定得很清楚:编程题性价比最高,先做完;选择题因为分值分散,后面用剩余时间快速推进。
具体到编程题本身,我的建议是:先保证第一题完全正确,再去看第二题。两道题如果都半吊子,不如一道题完整AC加一道题写出核心逻辑拿部分分。笔试阅卷通常按测试用例给分,部分用例过了也有分,所以哪怕时间不够,也要把主流程写出来,别留在脑子里。
我当时的策略是:看到限流题先写核心类TokenBucket,保证单测场景能过;日志题用一行管道命令直接过关。写完再回头补注释和边界条件。这个节奏让我在90分钟内把所有题都覆盖到了,没有出现交白卷的部分。
5. 笔试之后的复盘:从“会做题”到“面试能讲”
5.1 我给自己做了一次“错题归因”
笔试结束当天晚上,我没有直接躺平,而是趁热打铁把整张卷子回忆了一遍,按照“完全不会”“概念模糊”“粗心失误”“时间不够没做”分了四类。结果发现,粗心失误是最多的——不是知识盲区,而是考试紧张+读题不仔细导致看错了条件。
其中最典型的例子是那题find -mtime +7和-mtime -7的选择,我第一遍读题觉得是“找7天前的文件”,差点选+7,后来反复读题干才发现写的是“最近7天内”。这种丢分非常可惜,因为它不是“不会”而是“没看清”。复盘的意义就是把这些非知识性失分单独拎出来,下次考试时强制自己在选项前先复述一遍题目的核心要求,再下笔。
5.2 笔试题是会“长进”面试题里的
游卡笔试通过之后,紧接着就是面试。面试官盯着简历问项目经验之外,还会顺着笔试题往下问。我最深刻的一个感受是:笔试不是考完就完了,你写在卷子上的每一个答案,都可能成为面试官追问的起点。
比如面试官问我:“你写的限流装饰器,有没有考虑过分布式环境下多个实例怎么办?”这个问题其实就是从笔试那道编程题拓展出来的。单机令牌桶用threading.Lock没问题,但部署多个游戏服、多个API节点之后,每个节点的限流状态是独立的,玩家的请求被负载均衡分发到不同节点,总流量可能变成N倍。要解决分布式限流,要么用Redis的INCR+ 过期时间做滑动窗口,要么用Lua脚本实现令牌桶的原子操作。虽然面试时我答得不算完美,但至少笔试里“我写过限流器”这个事实给了面试官一个引子,也给了我一个展示思考深度的机会。
所以说,准备笔试不能只看“能不能做出来”,还要想“面试官可能会从这道题延伸出什么”。每做完一道有代表性的题,就额外想一想“分布式版本怎么做”“压力再大一倍怎么办”“数据不一致怎么处理”,这种延展性思考才是从笔试到面试无缝切换的关键。
5.3 给下一届考生的运维开发笔试备战清单
如果让我把经验浓缩成几条可执行的动作,大概是这样的:
第一,Linux命令每天练半小时。不用背大全,重点覆盖文件查找、文本处理、进程管理、网络连接、权限、定时任务这六类。每个命令至少要有一次“自己动手排查真实文件”的经验,而不是只在教程里看输出样例。
第二,Shell和Python二选一练熟。运维开发笔试一定会有脚本题。Shell适合快速处理文本和管道任务,Python适合写带逻辑的小工具和算法题。建议Shell做到能写出Top日志统计、批量备份、进程检查这类脚本;Python做到能实现限流、并发任务分发、日志解析这类小模块。
第三,网络和数据库原理要吃透。TCP三次握手、四次挥手、TCP粘包、HTTP状态码、DNS解析流程,这些是选择题高发区。数据库则重点掌握索引原理、联合索引最左前缀、慢查询分析、GROUP BY和ORDER BY的执行逻辑。
第四,场景题要形成自己的“排查SOP”。面试官不看你能背多少命令,而是看你能不能形成一套稳定的排查思路。我总结的通用链路是:确认影响范围 → 检查网络链路 → 检查系统层资源 → 检查业务进程 → 检查日志 → 检查数据库 → 检查依赖服务。遇到任何故障,按照这个顺序走,不会乱。
第五,笔试环境要提前适应。至少完整模拟一次牛客网在线笔试流程:限时、无IDE补全、相机开着、只能用手机当草稿。这些客观限制不提前适应,考试时会额外消耗很多精力。
复盘完这场笔试,我最大的感受其实是:运维开发这个岗位的笔试,筛选的就是那些“既蹲得了机房,也写得了代码”的人。命令背得熟,说明你肯在日常运维里下功夫;代码写得规范,说明你有工程化思维;场景题答得有层次,说明你脑子里装过真实故障。这三样东西,没有哪一样是可以靠考前突击三天就速成的,都得在日常学习里一点点积累。希望这份复盘能帮后面准备运维开发校招的同学少走一些弯路。