==========================================
真实面试环境中,被问到的相关问题,感兴趣的可以看下
==========================================
1. 这个项目是你独立完成的吗?团队中你的职责是什么?
是的,这个项目是我独立完成的,从需求分析、系统设计到项目部署都我做的。重点工作包括:
- 使用 C++ 编写判题核心模块,实现对用户代码的编译、运行,判题功能
- 搭建了一个简单的 Web 界面,支持题目提交、实时查看运行的结果
- 也使用 MySQL 管理题目与提交记录,用makefile脚本完成自动化部署
2. 这个项目的主要目标是什么?为什么要做这个项目?
- 主要目标是搭建一个高性能、稳定、安全的在线评测系统,用于自动评判编程题的提交结果,支持多人并发提交
- 做这个项目的初衷,一方面是出于我对算法和系统编程的兴趣,另一方面我也想通过实践锻炼自己在 Linux 系统编程和 C++ 开发方面的能力,所以我就做了一个基于Linux的高性能在线OJ平台
3. 项目用了哪些技术?为什么选这些技术?
C++ STL 标准库,
Boost 标准库,对文件路径,文件名字符串进行分割,和根据需要拼接字符串
- ctemplate第三方开源前端网页渲染库,为了将我们的题目信息显示在网页上
jsoncpp 第三方开源序列化、反序列化库,因为对用户提交的请求进行解析时,数据格式应该是json的,如果我手动拼接字符串不仅繁琐、易错,还不利于维护。而
jsoncpp
提供了清晰的 API,可以快速构建、解析 JSON 数据结构,提升开发效率,代码也更稳定易读jsoncpp
是跨平台的,开源、轻量,能很好地与 C++ 项目集成,也避免了自己重复造轮子。- Ace前端在线编辑器,是为了提供一个高亮、自动缩进、语法提示友好的在线代码编辑环境,提升用户在 OJ 平台上的编程体验。
对于普通的纯文本,Ace 支持多种语言语法高亮(如 C++、Python)、自动补全、主题切换
这个项目也考虑到了多进程,多线程的场景,其中网络库cpp-httplib就是多线程的,也进行了加锁解锁处理
MySQL C connect,为了将题目信息,用户记录存入mysql中
2. 架构与设计类
1. 项目的整体架构是怎样的?你是怎么参与设计的?
- 首先设计了项目的宏观结构,两个大模块,CompileServer模块提供了对提交的代码进行编译的服务,OJServer模块提供在线OJ服务,
- 我在设计CompileSever服务时,先设计编译模块,再设计运行模块,最后在进行了编译运行,其中引入了第三方库cpp-httplib,用来发起http和接受http请求,其中数据的发送和接受都使用了json进行序列化/反序列化
- 设计OjServer的时候,使用了MVC模块,其中oj_mode管理数据的存储,以及对外提供接口,oj_view引入了ctemplate第三方库进行网页渲染,本质就是key-value之前的替换,
oj_central是这个服务的核心,
负载均衡调度,就是加权遍历所有的主机,并找到最小负载主机,进行服务,它会提供3个接口,一个是得到所有题目,一个是得到某个题目的详细信息,最后就是判题功能 - 之后设计前端服务时,使用了第三方插件ACE一个编写代码的编译框,后用了JQuery获取html中的内容,构建json,通过类似信号槽机制借助ajax向后台发起基于http的json请求,并实现前后端交互
- 最后引入了mysql数据库,借助第三方工具Navicat 连接数据库,存储题目数据,替换之前文件版的存储数据
2. 模块之间如何解耦?怎么处理模块间通信?
模块解耦方式:
通过 HTTP 接口进行通信:
OJServer
作为主服务,通过 HTTP 请求调用CompileServer
暴露的编译接口(REST API)CompileServer
编译完成后返回标准化 JSON 数据(包括编译是否成功、错误信息、可执行文件路径等)- 这样设计,两个模块通过标准协议通信,无需强依赖对方内部结构,解耦良好。
- 我也定义了统一的数据结构(使用
jsoncpp
处理 JSON)(所有错误、状态都用统一的错误码和消息返回,方便主模块统一处理)
通信流程(举例)
- 用户提交代码后,
OJServer
将其打包为 JSON 请求,发送到CompileServer
。
CompileServer 编译后将结果(是否成功、错误信息、可执行路径)通过 HTTP 响应返回给 OJServer,OJServer 再进入执行与判题流程。
3. 用到了什么设计模式?为什么用?
单例模式
使用场景:
- 项目中的日志模块、配置加载模块使用了单例模式,确保全局只有一个实例,节省资源并统一管理。
为什么用:
- 日志系统或配置对象需要被多个模块频繁调用,但又不希望重复创建,单例模式能保证线程安全的同时节省资源
3. 测试与部署类
1. 项目是怎么测试的?有没有写自动化测试?
我在项目开发过程中有进行接口测试,主要使用 Apifox 工具对 OJServer
和 CompileServer
暴露的接口进行了自动化测试
- 提交不同语言、正确与错误的代码,验证编译结果是否准确返回
- 测试如代码为空、非法语言类型、编译失败等边界情况
- 用 Apifox 批量模拟多用户并发提交,观察系统响应和稳定性
为什么使用 Apifox:
- Apifox 支持接口文档、调试和自动化测试一体化
- 主要也有使用简单的特点
是否写了测试代码:
- 编译模块核心逻辑(如
CompileTask
)我也写了部分单元测试代码,验证编译参数拼接、错误码返回逻辑是否正确。
2. 怎么部署上线的?本地/服务器端用了什么环境?
项目最终部署在 Linux 服务器上运行,主要采用的是手动部署 + 可执行文件运行的方式。
使用 Makefile 编译生成
compile_server
和oj_server
可执行文件我在服务器上通过
./compile_server 8081
直接启动编译服务,监听 8081 端口
同理,./oj_server
8081
模块也监听一个独立端口,并启动在线OJ服务
为什么这样部署:
- 相较于复杂的容器部署,这种方式简单高效,便于调试与快速上线,同时也适合我的项目开发阶段。
4. 反思与改进类
1. 如果重新做一次,有什么地方你会改进?
使用 Docker替代手动项目部署
- 之前项目是通过 手动部署 + 可执行文件运行的方式
- 其实可以使用Docker 容器进行项目的部署,会更安全,
- 也会让每次提交自动创建临时容器执行代码,更安全、更易于资源回收和管理,
- 也方便后期支持多语言环境
增加前端用户体验优化
目前前端使用的是 Ace 编辑器,功能上满足需求,但界面较为基础简单。如果重做,我会:
加入代码运行状态的 loading 动画、历史提交记录
支持自动保存代码草稿、键盘快捷提交
整体 UI 使用 Vue 等前端框架提升交互体验
- 基于注册和登录的录题功能
- 业务扩展:比如自己写个论坛,接入在线OJ中
- 把编译服务部署在docker中
- 将cpphttplib改成rest_rpc库
- 判断一道题目正确之后,自动下一道题目
- 将导航栏中的功能一个一个的都实现一下
2. 从这个项目中你学到了什么?
- 我掌握了 Linux 下的系统编程,实践了 C++ 工程化开发,熟悉了模块化设计、Makefile 构建、jsoncpp 第三方库集成等内容
- 通过设计
CompileServer
和OJServer
两个模块,我学会了如何通过 HTTP + JSON 实现模块解耦与通信 - 从开发、测试(用 Apifox 自动化测试)、部署上线(Linux 环境部署)、日志调试,我第一次完整地实践了一个后端服务从设计到上线的全过程
5. 技术细节类
1. 你在项目中遇到过哪些技术难题?怎么解决的?
问题一:如何让这个OJ平台成为一个高性能的平台,支持用户的高并发访问
OJServer
和 CompileServer
是完全解耦的,通过 HTTP 接口通信,可以分布式部署在不同机器上,便于资源调度和独立扩展。
我将编译模块(CompileServer
)做成可水平扩展的独立服务,部署了多个后端编译节点,然后通过 负载均衡(如 Nginx / 自写调度逻辑) 将用户提交分发到不同主机进行编译,避免一个主机负载太高的情况
在我设计的这个负载均衡模块,oj_server应该选择后端的负载最低的那个compile_server,
- 具体的操作就是循环遍历,找到权重也就是负载最低的那个compile_server
如果要进一步提高效率的话,可以试试任务队列 + 线程池 ,实现异步处理用户提交,提升系统处理速度
问题二:测试运行模块时出现了死循环
- 一般的测试环境都是没什么问题的,
- 但后来发现是恶意程序死循环了,不停消耗CPU资源 ,
- 解决办法是:添加进一步的资源约束
- 在json string数据中添加上资源限制(setrlimit)的属性 ,资源不足或cpu超时,导致OS终止进程,直接发送对应的信号终止的
问题三:用户代码运行的安全隔离
用户提交的代码是我无法控制的,比如死循环、恶意访问文件系统等。如果直接运行在服务器上,非常不安全,容易造成系统崩溃或泄露敏感信息。
我采用了 fork + exec
启动子进程,让子进程进行判断,这样就不会影响原来的服务了,然后结合setrlimit()
控制 CPU 时间、内存、文件大小,主要目的就是代码在受限环境中运行
2. 性能优化做过吗?具体做了哪些优化?效果如何?
原先编译和输出结果都写入临时文件,I/O 开销较大。
优化:
改用内存管道或共享内存传递编译输出 / 执行结果
减少中间文件写入次数,仅必要时存入磁盘文件中,
3. 你个系统,最多支持多少并发访问量
在实际开发阶段,我用 Apifox 模拟了多用户并发提交代码的场景,虽然没有用专业压测工具,但结合测试结果和架构设计,我估算当前系统可以稳定支持 100~200 个并发请求。