news 2026/6/10 11:36:45

基于Python的毕设选题系统实战:从需求建模到高并发选题冲突处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python的毕设选题系统实战:从需求建模到高并发选题冲突处理


背景痛点:抢选题就像春运抢票

高校毕设季,几千名学生同时登录教务系统,热门课题几秒被抢光,冷门课题无人问津。传统做法往往只有一张student_topic表,状态字段status=0/1,并发时大量UPDATE撞车,导致:

  1. 超卖:同一课题被 N 个同学“成功”写入,数据库最后只落一条记录,其余全部丢失。
  2. 脏读:学生 A 看到“可选”,点击确定瞬间被 B 抢走,前端仍提示“成功”,刷新后却消失。
  3. 用户体验差:页面转圈 5s 返回“系统繁忙”,再刷新课题已被抢光。

一句话:没有互斥、没有幂等、没有实时反馈

技术选型:为什么不是 Django/FastAPI?

维度FlaskDjangoFastAPI
学习/改造成本低,单文件即可启动高,ORM+Admin 全家桶中,依赖 Pydantic 类型体操
生态灵活度高,自由组合 SQLAlchemy、Redis中,Django ORM 深度绑定高,但异步驱动需全链路 async
并发模型同步+gevent 即可满足 1k QPS同步异步
教学场景落地速度最快,毕设周期 2-3 周

结论:教学管理系统业务简单、流量突发、交付周期短,Flask 足够且最省时间。
Redis 作为单线程原子性的内存数据库,天然适合“分布式抢锁”场景,后续横向扩展也无需改代码。

系统架构速览

  • Nginx 反向代理 + Gunicorn(gevent)
  • Flask 无状态服务,水平扩容
  • MySQL 8.0 存储业务数据
  • Redis 6.2 负责分布式锁、选题热度计数、接口防刷令牌桶

核心实现

1. 数据模型(SQLAlchemy)

class Topic(db.Model): __tablename__ = 'topic' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(120)) teacher = db.Column(db.String(40)) quota = db.Column(db.SmallInteger, default=1) # 名额 picked = db.Column(db.SmallInteger, default=0) # 已选 status = db.Column(db.String(10), default='open') # open/close __table_args__ = ( db.Check.SQLOnConflict('quota', 'picked'), # 业务层兜底 )

2. 幂等性设计

利用学生+课题唯一索引,保证同一学生重复点击只产生一条记录。

class Choice(db.Model): __tablename__ = 'choice' id = db.Column(db.Integer, primary_key=True) student_id = db.Column(db.Integer, nullable=False) topic_id = db.Column(db.Integer # 外键 ctime = db.Column(db.DateTime, server_default=func.now()) __table_args__ = ( db.UniqueConstraint('student_id', 'topic_id', name='uk_student_topic'), )

接口层返回相同结果,前端无需额外提示。

3. 分布式锁(Redis Lua 脚本)

import redis, uuid, time r = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True) def acquire_lock(key: str, expire: int = 5) -> str: """返回 token,失败返回空字符串""" token = str(uuid.uuid4()) ok = r.set(key, token, nx=True, ex=expire) return token if ok else '' def release_lock(key: str, token: str) -> bool: lua = """ if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end """ return bool(r.eval(lua, 1, key, token))

4. 选题提交接口(含事务隔离)

@bp.post('/choose') def choose(): student_id = g.user.id topic_id = request.json['topic_id'] lock_key = f"lock:topic:{topic_id}" token = acquire_lock(lock_key) if not token: return {'ok': False, 'msg': '系统繁忙,请重试'}, 409 try: # 可重复读,防止幻读 with db.session.begin(): topic = (db.session.query(Topic) .filter_by(id=topic_id) .with_for_update() .first()) if not topic or topic.status != 'open': return {'ok': False, 'msg': '课题已关闭'} if topic.picked >= topic.quota: return {'ok': False, 'msg': '名额已满'} try: db.session.add(Choice(student_id=student_id, topic_id=topic_id)) topic.picked += 1 if topic.picked >= topic.quota: topic.status = 'close' db.session.flush() except IntegrityError: # 唯一索引冲突,幂等返回成功 return {'ok': True, 'msg': '已选过'} finally: release_lock(lock_key, token) return {'ok': True, 'msg': '选题成功'}

关键点:

  1. with_for_update()把行锁与 Redis 分布式锁双保险,即使锁超时仍有数据库兜底。
  2. 事务隔离级别REPEATABLE READ(MySQL 默认),避免幻读。
  3. 捕获IntegrityError实现幂等,重复点击不报错。

性能与安全

冷启动延迟

Flask 默认懒加载,首次请求 import 全部模型导致 300~500 ms。使用gunicorn --preload预加载,延迟降到 50 ms 内。

防刷机制

  1. 接口令牌桶(Redis + Lua):
def allow(uid: str, rate: int = 5) -> bool: key = f"rate:{uid}" curr = r.incr(key) if curr == 1: r.expire(key, 1) # 1 秒窗口 return curr <= rate
  1. 选题接口限流 5 次/秒,超过直接返回 429,保护下游 MySQL。

SQL 注入

SQLAlchemy ORM 已参数化,拒绝原生拼接;额外开启 MySQLsql_mode=STRICT_TRANS_TABLES防隐式转换。

生产避坑指南

  1. 锁超时:

    • 默认 5 s,接口 RT 99 线 200 ms 内足够;
    • 若 GC 或网络抖动导致超时,需配合with_for_update()兜底。
  2. 回滚策略:

    • 任何异常退出先释放锁,再抛异常,防止死锁
    • 利用try/finally保证锁一定被删掉。
  3. 日志追踪:

    • 在锁 key 中加入trace_id,通过 ELK 聚合,可快速定位哪一步 RT 过高。
    • 记录student_id+topic_id+result,方便审计。
  4. 监控:

    • Prometheus + Grafana 采集picked/quota比例,提前发现“超卖”风险。
    • Redis 内存 >80% 自动扩容,否则set nx失败率飙升。

可扩展方向

  1. 多轮志愿:
    Choice表加round字段,定时任务按志愿序+权重撮合,解锁未被命中课题。
  2. 热度排行榜:
    用 RedisZINCRBY topic:hot 1 <topic_id>,实时展示 Top20,前端 WebSocket 推送。
  3. 教师确认:
    增加teacher_confirm状态,支持导师“反选”,流程更贴合实际。

写在最后

整个系统 3 周完成,压测 1 k 并发、5 k 选题无超卖,代码行数不到 1 k。把并发冲突拆成“分布式锁 + 数据库行锁 + 唯一索引”三层,层层兜底,既保证安全又留足扩展空间。下一步我准备把热度排行榜做成实时弹幕,让学生像看直播一样刷选题。如果你也做过类似系统,欢迎留言交流更优雅的实现。


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/9 23:24:34

学长亲荐8个一键生成论文工具,继续教育学生轻松搞定毕业论文!

学长亲荐8个一键生成论文工具&#xff0c;继续教育学生轻松搞定毕业论文&#xff01; 论文写作新革命&#xff1a;AI 工具如何改变你的学术之路 在当今快速发展的学术环境中&#xff0c;继续教育学生面临着越来越高的论文写作要求。无论是本科、硕士还是博士阶段&#xff0c;撰…

作者头像 李华
网站建设 2026/6/10 11:27:36

基于CosyVoice Paraformer的语音识别效率优化实战

基于CosyVoice Paraformer的语音识别效率优化实战 1. 背景痛点&#xff1a;高并发 ASR 的“三座大山” 去年双十一&#xff0c;公司把客服机器人从“按键菜单”升级成“直接说”&#xff0c;结果流量一冲上来&#xff0c;ASR 服务直接三连跪&#xff1a; P99 延迟飙到 1.8 s&…

作者头像 李华
网站建设 2026/6/9 18:58:52

4×24GB显卡怎么跑?Live Avatar多GPU配置详解

424GB显卡怎么跑&#xff1f;Live Avatar多GPU配置详解 1. 现实困境&#xff1a;为什么424GB显卡跑不动Live Avatar&#xff1f; 你可能已经试过——把四张RTX 4090插进服务器&#xff0c;满怀期待地运行./run_4gpu_tpp.sh&#xff0c;结果却在启动瞬间遭遇CUDA Out of Memor…

作者头像 李华
网站建设 2026/6/10 11:25:21

无需专业显卡!Kook Zimage在普通GPU上的幻想风格创作体验

无需专业显卡&#xff01;Kook Zimage在普通GPU上的幻想风格创作体验 1. 为什么普通人也能玩转幻想风AI绘画&#xff1f; 你是不是也经历过这样的时刻&#xff1a;看到别人生成的梦幻人像——柔光漫溢的精灵少女、悬浮于星云之中的银发法师、雾气缭绕的古堡庭院——心动不已&…

作者头像 李华
网站建设 2026/5/25 8:21:36

Chainlit调用ERNIE-4.5-0.3B-PT效果展示:中文诗歌创作与押韵控制能力

Chainlit调用ERNIE-4.5-0.3B-PT效果展示&#xff1a;中文诗歌创作与押韵控制能力 1. 为什么选这个组合来写诗&#xff1f; 你有没有试过让AI写一首真正像样的中文诗&#xff1f;不是堆砌辞藻的“伪古风”&#xff0c;而是有平仄、讲押韵、懂意象、能传情的那种&#xff1f;很…

作者头像 李华