2026/5/20 16:48:30
网站建设
项目流程
杭州做服装电商拿货的网站,做网站的步骤 优帮云,南通北京网站建设,专门做淘宝特价的网站餐饮系统毕业设计中的效率瓶颈与优化实践#xff1a;从单体架构到模块解耦 适合读者#xff1a;计算机专业本科生、刚接触高并发场景的应届开发者 关键词#xff1a;餐饮系统、毕业设计、效率优化、消息队列、缓存、幂等性 一、为什么“点餐”总卡#xff1f;——典型痛点拆…餐饮系统毕业设计中的效率瓶颈与优化实践从单体架构到模块解耦适合读者计算机专业本科生、刚接触高并发场景的应届开发者关键词餐饮系统、毕业设计、效率优化、消息队列、缓存、幂等性一、为什么“点餐”总卡——典型痛点拆解去年指导学弟做餐饮系统毕设演示当天 30 个同学同时扫码下单页面直接转圈 8 秒库存还超卖了 3 份麻辣小龙虾。事后复盘问题集中在下面 3 点订单与库存同库同表下单接口里SELECT → 业务计算 → UPDATE三步走事务行锁排队并发一上来就互相拖死。前端没做防抖用户狂点“提交”后端重复落库造成订单号重复、支付回调异常。页面轮询订单状态1 秒一次高峰期 200 个长连接把 Tomcat 默认 200 线程占满后续请求直接 502。一句话单体架构 同步调用 无幂等 高并发必翻车。二、技术选型对比——别让数据库扛下所有| 场景 | 传统做法 | 瓶颈 | 优化方案 | 理由 | |---|---|---|---|---|---|---| | 库存扣减 | MySQL 行级锁 | 并发 50 TPS 就开始锁等 | Redis Lua 脚本 | 单线程 原子指令轻松 2000 TPS | | 订单创建 | 同步调用支付、库存、优惠券 | 任一接口超时整体重试 | RabbitMQ 异步消息 | 下游故障上游无感可削峰填谷 | | 热数据读取 | 每次都查 DB | QPS 高时 DB CPU 飙红 | 本地 Caffeine Redis 二级缓存 | 命中率达 90%RT 从 120 ms 降到 8 ms |结论“缓存 消息队列”不是大公司的专利毕业设计同样玩得转。三、核心实现细节下面给出经过教学项目验证的 4 个关键代码片段全部基于 Spring Boot 2.7 MyBatis-Plus RabbitMQ Redis 6.2。3.1 订单幂等令牌——前端“提交”按钮只让点一次// OrderController.java PostMapping(/order) public IdResp create(RequestBody OrderDTO dto, HttpServletRequest req){ // 1. 从网关统一头里拿 userId Long userId UserContext.get(); // 2. 组装业务幂等 Key String idemKey order:uid:userId:sku:dto.getSkuId(); // 3. Redis SET NX EX 原子性放令牌10 s 过期 Boolean ok stringRedisTemplate.opsForValue() .setIfAbsent(idemKey,1,Duration.ofSeconds(10)); if (Boolean.FALSE.equals(ok)){ throw new BizException(订单提交中请勿重复点击); } // 4. 发送创建指令到队列立即返回 rabbitTemplate.convertAndSend(order.event, dto); return IdResp.accepted(); // 只返回 202不阻塞 }说明利用 Redis 单线程 过期时间窗解决“双击”导致的重复下单10 s 足够后端消息消费完。3.2 RedisLua 原子扣库存——把“超卖”扼杀在内存里-- stockDeduct.lua local key KEYS[1] -- 库存 Key local num tonumber(ARGV[1])-- 购买量 local left redis.call(GET, key) if not left then return -1 end if tonumber(left) num then return 0 end return redis.call(DECRBY, key, num)// StockService.java public boolean deduct(String skuId, int num){ Long left (Long) redisTemplate.execute( stockDeductScript, Collections.singletonList(stock:skuId), String.valueOf(num) ); return left!null left0; }把脚本提前SCRIPT LOAD缓存到 Redis调用时直接走 EVALSHART 控制在 2 ms 内。3.3 缓存与 DB 双写一致性——先删缓存再更新 DB延迟消息兜底下单成功后先删除缓存中的库存异步消费队列里再异步刷新缓存防止并发读写脏数据若缓存失效期间又有读请求短暂穿透到 DB利用互斥锁保证只回源一次。伪代码RabbitListener(queues stock.refresh) public void refreshStock(StockRefreshEvent evt){ Stock s stockMapper.selectById(evt.getSkuId()); redisTemplate.opsForValue().set(stock:evt.getSkuId(), s.getLeft()); }3.4 事务边界划分——本地事务只关心订单库存走最终一致Transactional(rollbackFor Exception.class) public void insertOrder(Order order){ orderMapper.insert(order); // 不远程调库存只发消息 rabbitTemplate.convertAndSend(stock.deduct, new StockDeductDTO(order)); }事务范围小锁时间短库存服务消费消息时失败可重试保证最终一致。四、完整可运行示例精简版项目结构com.example.food ├── controller ├── service │ └── impl │ └── OrderServiceImpl.java ├── mq │ └── StockDeductConsumer.java └── config └── RedisLuaConfig.java关键类已在前文给出仓库地址教学用已脱敏https://github.com/yourrepo/food-order-benchmarkclone 后docker-compose up -d即可拉起 MySQL、Redis、RabbitMQ一键运行FoodOrderApplication。五、性能测试 安全考量测试环境Mac M1 16 GDocker 限制 4 Core / 4 GJMeter 200 线程循环压测 60 s。指标优化前单体优化后缓存MQ平均 RT1100 ms95 ms峰值 QPS42510CPU 占用MySQL 90 %Redis 35 %安全点补充防刷单接口网关层限流 用户维度令牌桶Bucket4jSQL 注入MyBatis-Plus 内置预编译额外开启全局过滤器sqlInjectionFilter消息幂等消费端用“订单号 状态机”做唯一索引重复消息直接 ACK六、生产环境避坑指南冷启动延迟Spring Boot 3.x 原生编译后首次 RabbitMQ 连接可能 5 s提前spring-rabbit的ConnectionFactory预热。日志追踪缺失引入 Sleuth Zipkin消息头里强制带上traceId否则 MQ 一拆调用链就断。事务边界过大不要在Transactional里发 HTTP 调用极易因为超时导致 DB 连接池被打满。Redis 大 Key库存每天定时归档历史数据落到 MySQL热 Key 长度保持在 1 KB 以内。缓存雪崩给 Redis 设置随机 TTL±300 s防止同一时刻集体失效回源。上图是 JMeter 聚合报告蓝色为单体版本红色为优化版本RT 与错误率一目了然。七、留给读者的思考题毕设周期通常只有 12 周功能完整性和系统健壮性往往互斥如果导师要求“月底必须看到 App 原型”你会先写单体再重构还是直接上消息队列在资源受限的校园服务器里Redis 与 RabbitMQ 部署在同一台 2 Core 云主机如何防止竞争 CPU当缓存与 DB 出现短暂不一致业务上能否接受“用户看到库存为 0 但下单成功”这需要产品层面怎样兜底欢迎在评论区交换思路也祝大家的毕业设计都能“抗住 200 并发顺利过答辩”。文末小字示例代码仅作教学演示未覆盖灰度、熔断、多活等企业级特性切勿直接用于商业外卖平台。