高标后端二面:全程追问实习项目的视频拉流链路、协程与 channel
- 轮次
- 二面
- 时间
- 2026-10
- 来源
- 牛客网
《面试题目》
- 自我介绍。
- 介绍一下这个项目?软件架构是什么样的?系统架构是什么样的?
- 你说的视频拉流用的是什么协议?是在 web 端还是 Go 后端拉流的?怎么路由到某架无人机的视频流?有没有遇到拉流卡顿的情况,怎么排查的?
- 你说的目标识别图片是存在哪里的?怎么显示在前端的?
- 你说的 DDS 是什么,是个消息队列吗?你说的这个后台是部署在云上还是本地?
- 你说的这个电台是什么?你们这个有通入互联网的环节吗?
- 你说这个软件是部署在终端上的,那它是怎么接入局域网的?
- 你在项目中遇到最难的技术难点是什么?具体是什么原因?你觉得有什么优化策略能减少这些 bug?
- 什么时候转的 Go?Go 和 Java 哪个更熟悉?这两者开发你觉得有什么区别?
- 你刚谈到协程,那协程和线程有什么区别?
- 你刚谈到 channel,那你们用全局变量吗?哪些应该用 channel、哪些应该用全局变量?
- 你平时有用 AI Agent 辅助开发吗?你怎么评价 AI 辅助编码,以及你觉得人应该更多充当怎样的角色?
《参考解析》
被层层追问时怎么答:先给一句话架构,再按层展开,最后落一个数字
这场面试的节奏很典型:你提到什么,他就顺着什么往下挖一层;你答完这一层,他再挖下一层。视频拉流那串问题就是这么来的——先问协议,再问在哪一端拉流,再问怎么定位到具体一架无人机,最后问卡顿怎么排查,四问其实是一条链。撑住这种链式追问的办法不是提前背答案,而是脑子里有一张分层图:端(无人机/终端)→ 链路(协议与网络)→ 服务(信令、转发、存储)→ 前端(播放与展示),每层都能说出「谁在做什么、数据长什么样、出了问题看哪里」。
具体到视频这条线,答题模板是:一句话定位(「这是一个多路无人机实时视频的调度与播放系统」)→ 分层(设备注册与信令、流媒体转发、前端播放)→ 我负责的那层做了什么 → 一个具体细节加一个数字(首帧时间、并发路数、卡顿率)。协议本身要能说清取舍:WebRTC 基于 UDP、延迟可到几百毫秒、适合实时操控,但要处理丢包与抖动;RTMP 基于 TCP、延迟一到三秒、推流生态成熟;HLS 切片延迟更大但穿透性好、适合分发;RTSP 是 IPC 和无人机图传的常用信令,浏览器不能直接播,必须由服务端转成 WebRTC 或 HLS。路由到某架无人机的答案在信令层:设备上线时把 deviceId 和流地址注册到信令服务,前端按 deviceId 请求播放,服务端查映射下发房间号或播放地址,转发服务据此把这一路流拉起来。
卡顿排查要答成方法论而不是「重启试试」:先分层定位,采集端看分辨率码率与丢帧,网络侧看丢包率、抖动、RTT 与带宽(ping、mtr、抓包看 RTP 序列号与时间戳),服务端看转码 CPU、GOP 长度、关键帧间隔与线程池排队,播放端看解码耗时与 jitter buffer 配置。手段上可以拉长 GOP、动态降码率、开 FEC 与 NACK 重传、必要时切 TCP 传输或换协议。原帖作者面完才想起漏答一个协议名,这并不致命——面试官更在意你能不能把「怎么定位」讲成路径。
DDS 不是消息队列:这类题考的是你有没有把名词套进熟悉的概念
DDS(Data Distribution Service)是 OMG 的以数据为中心的发布订阅中间件标准,用在无人机、工业控制、机器人这类实时分布式系统里(ROS 2 的底层通信就是它)。它和 Kafka、RabbitMQ 那种消息队列表面相似(都是发布订阅、都有 topic),实质差别很大:DDS 是去中心化的点对点发现,没有 broker,不负责长期存储,走 UDP 多播,追求微秒到毫秒级时延;QoS 是它的核心能力,可以按 topic 约定可靠性(best effort / reliable)、时限、历史深度、分区、存活期。消息队列则是 broker 中心、持久化、重放、削峰,追求吞吐与解耦。
所以「DDS 是不是消息队列」的正确答法是先给边界:「它有发布订阅的形态,但没有 broker 也不做持久化,是为实时数据流设计的中间件」,再说为什么这个场景选它(机载设备之间要低时延、断网要能本地自治、不能依赖一个中心 broker)。顺着这个思路,「后台部署在云上还是本地」「终端怎么接入局域网」也答得顺:机载与地面站是本地组网,云端只做汇聚与展示,两者之间要靠电台或专网桥接,跨互联网时延与带宽都会成为瓶颈。
协程与线程:说清调度层级,再给 Go 的实现细节
线程是操作系统调度的单位,有独立的内核栈和用户栈(MB 级),切换要陷入内核,抢占由调度器决定;协程是用户态的轻量执行体,栈初始只有 KB 级并可动态增长,切换不经过内核,成本低一个量级,代价是调度要靠运行时(协作式让出,Go 1.14 之后加了基于信号的异步抢占)。所以「一万个协程」很轻,「一万个线程」基本不可能。
Go 的模型是 GMP:G 是协程,M 是内核线程,P 是调度上下文(本地队列、缓存),P 的数量默认等于 CPU 核数;M 绑定 P 执行 G,本地队列空了会从其他 P 偷一半(work stealing);网络 IO 由 netpoll 负责,就绪的 G 被放回队列;一个 M 阻塞在系统调用上时会把 P 交出去(handoff),让别的 M 接着跑。能讲到这一层,面试官基本不会再追。
代价也要说:协程里 panic 不 recover 会带崩整个进程;CPU 密集的协程不主动让出会饿死同 P 上的其他协程;没有退出路径的协程会泄漏(等一个永远不关的 channel、select 里没有 ctx.Done());循环变量捕获是老坑,1.22 之后语义改了但仍要小心。这些坑比「协程轻量」这句定义更能证明你在生产环境写过。
channel 还是全局变量:判据是「传消息」还是「共享状态」
Go 的谚语是「不要通过共享内存来通信,而要通过通信来共享内存」,但它不是教条。判据可以落到三个问题上:这份数据的所有权是否在多方之间转移?是否有时序要求(必须按顺序处理、必须有背压)?是否需要超时与取消?三个都是「是」,就用 channel;如果只是多个读方随机访问同一份状态(配置、计数器、连接注册表、缓存),用全局变量配 sync.RWMutex 或 atomic.Value 更直接。
反面例子最能说明问题:把一个大 map 的所有读写都塞进一个 channel 由一个 goroutine 串行处理,本质是自己实现了一把更慢的锁,还会成为吞吐瓶颈。正面的用法是任务分发(worker pool)、退出信号与超时(context.Done()、select 带 time.After)、限流令牌、一对一的流水线传递。工程上的原则是「用 context 管生命周期,把可变全局状态压到最少」——全局状态一多,测试难写、并发行为难推理,出问题只能靠猜。
AI 辅助编码:讲用法,也讲人的位置
这题不要答成表态。「有用,提效明显」或者「不可靠,不敢用」都得不了分。有效的答法是三段:怎么用(生成样板代码与 CRUD、补单测、读陌生仓库、解释报错、review 时找边界条件与并发问题)、一个具体例子(比如让它把一段拉流地址解析的逻辑补上异常分支与测试,省了多少时间)、人的角色(定义问题与验收标准、判断方案取舍、为正确性与线上后果负责)。再补一句它的典型翻车场景——编造不存在的 API、在并发与资源释放上想当然、给出「看起来对」的边界处理——所以关键路径必须自己读一遍、跑一遍。这套答法既证明你在用,也证明你知道边界在哪。
最后一句可以留着:这场面试几乎没有八股,全靠九个月实习里攒下的细节。能把系统的分层、协议取舍、自己负责的边界讲清楚,比多背十个名词更管用。