开源在线教育系统的架构设计与性能优化实践

最近开源了一个轻量级在线教育系统的核心模块,GitHub上收到不少Issue反馈性能问题。这篇分享架构设计思路和优化过程,希望能给同类项目参考。

项目背景

系统定位是中小机构的轻量化方案,非企业级重平台。核心约束:

部署成本低(单机或2-3台服务器可跑)

支持直播+录播+简单教务

多端覆盖但不做过度优化

技术栈选择:Laravel(业务层)+ Vue3(前端)+ WebRTC(直播)+ MySQL + Redis。

一、数据库设计的取舍

教务系统的数据库设计容易过度复杂。我们坚持反范式优先,用空间换时间:

sql

-- 课程表冗余存储教师姓名,避免JOINCREATE TABLE courses (

id BIGINT PRIMARY KEY,

teacher_id BIGINT,

teacher_name VARCHAR(64), -- 冗余字段

title VARCHAR(128),

...);

-- 排课表用JSON存储不规则时间(支持单双周、跳周)CREATE TABLE schedules (

id BIGINT PRIMARY KEY,

course_id BIGINT,

time_rules JSON, -- {day:1, start:"19:00", end:"20:30"}, ...

timezone VARCHAR(32),

...);

争议点:JSON字段在MySQL 5.7性能一般,升级到8.0后用JSON索引解决查询问题。排课冲突检测在应用层做,而非数据库约束,灵活性更高。

二、直播模块的简化实现

没有自研SFU,基于开源方案二次开发:

信令服务:用Socket.io实现房间管理,Redis存储房间状态

媒体转发:Mediasoup(Node.js版本),单核支撑50路视频转发

录制:GStreamer管道,WebM格式直接存对象存储

关键优化:学生端上行视频默认关闭,教师端强制开启。大班课(>20人)自动切换为"教师主讲+文字互动"模式,降低Mediasoup转发压力。

三、缓存策略的分层设计

php

// 课时余额查询,三级缓存public function getBalance(KaTeX parse error: Expected '}', got 'EOF' at end of input: ...u缓存,1秒 if (cache = apcu_fetch("balance:$studentId")) {

return $cache;

}

复制代码
// L2: Redis缓存,5分钟
if ($cache = Redis::get("balance:$studentId")) {
    apcu_store("balance:$studentId", $cache, 1);
    return $cache;
}

// L3: 数据库查询
$balance = DB::table('student_balances')
    ->where('student_id', $studentId)
    ->value('balance');

Redis::setex("balance:$studentId", 300, $balance);
apcu_store("balance:$studentId", $balance, 1);
return $balance;}

坑点:APCu在FPM多进程环境下共享问题,改用文件锁做本地缓存同步,性能损失可接受。

四、多时区处理的工程实践

开源社区问最多的问题:如何支持多时区?

方案选择存储统一UTC,展示本地化:

php

// 模型自动转换class Schedule extends Model {

protected $dates = 'start_time', 'end_time';

复制代码
public function getStartTimeAttribute($value) {
    return Carbon::parse($value, 'UTC')
        ->setTimezone($this->timezone);
}

public function setStartTimeAttribute($value) {
    $this->attributes['start_time'] = Carbon::parse($value, $this->timezone)
        ->setTimezone('UTC');
}}

教师创建课程时选择本地时区,存储自动转UTC。查询时根据用户时区转换。夏令时处理依赖Carbon的IANA时区库,无需手动维护。

五、性能瓶颈的真实案例

案例1:排课冲突检测慢

早期实现:查询所有冲突时间段,PHP循环判断。教师课表量大时,3秒+响应。

优化后:用MySQL空间索引(SPATIAL INDEX)存储时间段,SQL直接判断交集:

sql

-- 时间段用LINESTRING表示(开始时间, 结束时间)SELECT * FROM schedules

WHERE MBRIntersects(

time_range,

LineString(Point(UNIX_TIMESTAMP('2024-01-01 19:00'), 0), Point(UNIX_TIMESTAMP('2024-01-01 20:30'), 0)));

案例2:直播房间列表卡顿

首页展示"正在进行"的直播房间,早期用WHERE + ORDER,数据量上万后慢查询。

优化:用物化视图(Materialized View)或定时任务刷缓存表,而非实时计算。允许1分钟延迟,换取查询性能。

六、开源维护的经验

Issue模板:强制要求提供环境信息(PHP版本、MySQL版本、是否Docker部署),减少无效沟通

性能基准:提供压测脚本(Artisan命令),用户可自行验证性能

文档优先:复杂功能(如直播配置、支付回调)必须配流程图,文字描述歧义太多

关于商业与开源的平衡

核心教务功能完全开源,但直播转码、跨境支付等模块用插件形式闭源。既保证社区能用起来,又能持续维护项目。

开源地址:

https://github.com/xueye-admin/xueye

相关推荐
冬奇Lab16 小时前
每日一个开源项目(第135篇):codebase-memory-mcp - 给 AI Agent 一张代码库的知识图谱
人工智能·开源·llm
uniquejing1 天前
《每次 API 调用前扔掉 43% Token,我开源了一个 AI 提示词瘦身工具》
开源
jump_jump2 天前
流式 HTML:从 htmx 片段装配到浏览器原生增量渲染
javascript·性能优化·前端工程化
文心快码BaiduComate2 天前
Comate 搭载GLM-5.2:百万上下文,稳定支撑长程任务
前端·程序员·开源
冬奇Lab3 天前
每日一个开源项目(第133篇):EchoBird - 把 AI 工具的安装和部署做成傻瓜操作
人工智能·开源·资讯
小小工匠3 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
下班走回家3 天前
DeepSeek 开源模型的突破与思考:从技术到生态的全面进化
人工智能·开源
ApacheSeaTunnel3 天前
实战演示 | 基于 Apache SeaTunnel 与 Apache DolphinScheduler 实现 MySQL 到 Doris 离线定时增量同步
大数据·mysql·开源·doris·数据集成·seatunnel·数据同步
国产化创客3 天前
ESP32 CameraWebServer 原生摄像头项目全解析
物联网·开源·嵌入式·实时音视频·智能硬件
ofoxcoding3 天前
GLM 5.2 使用教程:API 接入配置、价格说明及 MIT 开源权重发布计划
ai·开源