news 2026/3/19 23:30:33

基于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/3/18 5:43:50

GLM-4V-9B GPU算力优化实践:4-bit加载显存降低65%,RTX4090实测流畅

GLM-4V-9B GPU算力优化实践&#xff1a;4-bit加载显存降低65%&#xff0c;RTX4090实测流畅 1. 为什么需要优化GLM-4V-9B的GPU占用&#xff1f; 你有没有试过在自己的电脑上跑多模态大模型&#xff1f;明明显卡是RTX 4090&#xff0c;32GB显存&#xff0c;结果一加载GLM-4V-9B…

作者头像 李华
网站建设 2026/3/15 11:39:27

网盘加速与下载优化:提升百度网盘下载速度的完整方案

网盘加速与下载优化&#xff1a;提升百度网盘下载速度的完整方案 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 在日常工作和学习中&#xff0c;网盘下载速度慢是许多用户面临…

作者头像 李华
网站建设 2026/3/15 13:51:27

GLM-4.7-Flash快速部署:阿里云/腾讯云GPU实例一键镜像部署脚本

GLM-4.7-Flash快速部署&#xff1a;阿里云/腾讯云GPU实例一键镜像部署脚本 1. 为什么你需要这个镜像 你是不是也遇到过这些情况&#xff1f; 下载模型权重要等一小时&#xff0c;配置vLLM参数调了三天还没跑通&#xff0c;Web界面反复报错找不到端口&#xff0c;想试试最新大…

作者头像 李华
网站建设 2026/3/14 16:00:46

DLSS版本切换终极攻略:从新手到专家的完全掌控指南

DLSS版本切换终极攻略&#xff1a;从新手到专家的完全掌控指南 【免费下载链接】dlss-swapper 项目地址: https://gitcode.com/GitHub_Trending/dl/dlss-swapper 你是否曾遇到这样的困境&#xff1a;明明RTX显卡性能强劲&#xff0c;却因游戏默认DLSS版本优化不佳&…

作者头像 李华
网站建设 2026/3/15 13:23:59

如何快速部署中文情感分析?试试这款带界面的StructBERT镜像

如何快速部署中文情感分析&#xff1f;试试这款带界面的StructBERT镜像 你是否遇到过这样的场景&#xff1a;运营同学需要批量判断用户评论的情绪倾向&#xff0c;客服主管想实时掌握客户反馈的整体情绪分布&#xff0c;产品经理想快速验证新功能上线后的用户口碑……但每次都…

作者头像 李华