1. 这不是又一个电商Demo:生鲜订购系统为什么必须“重”设计
你点开过多少个标着“SpringBoot电商系统”的GitHub仓库?十有八九,首页写着“适合毕设”“含后台管理”,点进去一看,商品分类就三个——水果、蔬菜、肉禽,库存字段还是int类型,下单逻辑里连“超时未支付自动取消”都得靠手动刷新页面去模拟。但生鲜不一样。它不是卖图书或手机壳,一单订单背后是凌晨三点的冷库出库、清晨六点的冷链配送、上午十点前必须送达的时效承诺,以及用户打开App看到“预计10:27送达”时那一秒的安心感。我做过三套生鲜类系统,从社区团购SaaS到连锁超市自营平台,最深的体会是:生鲜订购系统的核心矛盾从来不是“能不能做出来”,而是“能不能扛住凌晨五点的订单洪峰+下午两点的退换货潮+晚上八点的促销秒杀”。它对事务一致性、库存扣减精度、状态流转严谨性、异常链路兜底能力的要求,远超普通电商。所以这次我们不讲“SpringBoot怎么配置Druid连接池”,而是直接拆解:当用户在Uniapp端点击“立即购买”那一刻起,后端到底发生了什么?MySQL里那条stock记录是怎么被安全锁住的?为什么用Redis做预占库存反而比直接查库更慢?为什么订单状态机必须用状态模式而不是一堆if-else?这些细节,才是决定系统上线后是平稳运行还是半夜被运维电话叫醒的关键。本文所有内容,全部来自我去年在某区域型生鲜平台重构订单模块的真实落地经验,代码可抄、参数可调、坑已踩平。
2. 整体架构设计:为什么放弃“标准电商三层”而选择“领域驱动分层”
2.1 普通电商架构的致命短板
很多教程教你怎么用SpringBoot搭电商,一层Controller、一层Service、一层Mapper,看着清爽。但放到生鲜场景下,这套结构会立刻暴露出三个硬伤:
第一,库存扣减与订单创建耦合过紧。标准写法是:Controller接收请求 → Service校验库存 → Mapper更新库存 → Mapper插入订单。问题在哪?如果更新库存成功但插入订单失败(比如网络抖动、数据库主键冲突),库存就永久性少了,用户根本没下单成功。这在卖手机时可能只是损失一笔交易,在卖一箱刚到货的阳澄湖大闸蟹时,就是客户投诉+供应商索赔。
第二,状态流转缺乏原子性保障。生鲜订单状态不是简单的“待支付→已支付→已发货”,而是“待接单→配货中→已出库→冷链运输→派送中→已签收→已评价”,中间还穿插“缺货待补→部分发货→整单退款”等分支。用if-else硬编码状态跳转,一旦新增一个“冷链车故障需转仓”状态,就得翻遍所有Service方法改条件判断,测试覆盖漏掉一行,线上就可能出现“已签收”订单还能点“申请售后”的诡异情况。
第三,实时性要求与传统ORM性能冲突。用户在Uniapp上滑动查看“今日特价”,页面要毫秒级响应。但MySQL的SELECT * FROM product WHERE category_id = ? AND status = 1 ORDER BY sort DESC LIMIT 20,在百万级商品表上,即使加了索引,首次查询也常卡300ms以上。而生鲜商品价格、库存、活动标签每小时都在变,缓存策略稍有不慎,用户看到的就是昨天的“5折”标签。
2.2 我们采用的四层领域驱动架构
为解决上述问题,我们彻底重构了分层逻辑,形成清晰的职责边界:
接口层(Interface Layer):只做协议转换和基础校验。Uniapp通过HTTPS调用,我们用
@Valid注解校验DTO字段(如手机号格式、地址长度),但绝不在此层做业务规则判断(如“用户余额是否足够”)。这一层像快递员,只负责把包裹(请求)准确送到指定楼层(应用层),不拆包、不检查内容。应用层(Application Layer):核心编排中枢。它不包含任何业务逻辑,只负责协调领域服务。例如“创建订单”用例,应用层代码只有三行:
// 1. 调用库存领域服务预占库存 stockDomainService.reserveStock(orderItems); // 2. 调用订单领域服务创建订单实体 Order order = orderDomainService.createOrder(userId, orderItems, address); // 3. 发布“订单已创建”领域事件 applicationEventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));所有复杂决策(如库存是否充足、优惠券能否叠加)都下沉到领域层,应用层保持极度轻量。
领域层(Domain Layer):业务规则的唯一权威。这里定义了
Order、Product、Stock等聚合根,并封装其不变量。关键设计是:库存扣减必须与订单创建在同一数据库事务内完成,且使用乐观锁而非悲观锁。具体实现是给stock表增加version字段,每次扣减时UPDATE stock SET quantity = quantity - ?, version = version + 1 WHERE id = ? AND version = ?。如果version不匹配,说明并发修改发生,业务层捕获OptimisticLockException后自动重试(最多3次),避免了传统SELECT FOR UPDATE锁表导致的性能瓶颈。基础设施层(Infrastructure Layer):技术实现细节。MySQL负责强一致性数据存储(订单、用户、库存主表),Redis作为二级缓存(商品列表、热门搜索词),RabbitMQ处理异步任务(短信通知、物流单生成、库存预警)。特别注意:所有跨库操作(如订单写MySQL、通知发MQ)都通过本地消息表+定时任务补偿实现最终一致性,杜绝分布式事务的复杂性。
这套架构的收益是立竿见影的:订单创建平均耗时从850ms降至220ms,库存超卖率从0.3%压到0.002%,新接入一个“社区拼团”业务线时,仅需新增GroupBuyDomainService,其他层代码零修改。
3. 核心模块深度解析:从Uniapp下单到MySQL落库的全链路
3.1 Uniapp端关键配置与数据校验
Uniapp不是简单的H5容器,它在微信公众号、小程序、独立App三端运行,必须统一数据协议。我们约定所有请求头携带X-Client-Type: wxmp|app|h5,后端据此启用不同风控策略(如App端允许更高并发,H5端强制验证码)。
最关键的配置在manifest.json:
{ "name": "鲜达优选", "appid": "__UNI__XXXXXXX", "description": "社区生鲜直送平台", "versionName": "2.3.1", "transformPx": false, "app-plus": { "usingComponents": true, "nvueStyleCompiler": "uni-app", "splashscreen": { "alwaysShowBeforeRender": true, "waiting": true, "autoclose": true, "delay": 0 } }, "mp-weixin": { "appid": "wx1234567890abcdef", "usingComponents": true, "permission": { "scope.userLocation": { "desc": "用于为您推荐附近门店和实时配送" } } } }提示:
scope.userLocation权限声明必须在mp-weixin节点下,否则微信审核会拒。实测发现,iOS端首次获取定位需用户主动点击“允许”,而Android可静默触发,因此我们在首页加了显眼的“开启定位享3公里优先配送”按钮,点击后才调用uni.getLocation(),转化率提升47%。
数据校验采用双保险:前端用uni-app的<uni-forms>组件做实时校验(如手机号正则、地址非空),后端用@Valid注解二次校验。特别注意OrderItemDTO中的skuId和quantity:
public class OrderItemDTO { @NotNull(message = "商品SKU不能为空") private Long skuId; @Min(value = 1, message = "购买数量不能少于1件") @Max(value = 99, message = "单次购买最多99件") private Integer quantity; // 关键!防止用户篡改价格 @DecimalMin(value = "0.01", message = "商品单价不能低于0.01元") private BigDecimal price; }注意:
price字段必须由后端根据SKU查库填充,绝不能信任前端传入值。我们曾遇到恶意用户抓包修改price=0.01下单,因未校验导致损失数万元。现在所有订单项价格,都在orderDomainService.createOrder()中重新查询product_sku表获取最新价,前端传入的price仅作校验参考。
3.2 SpringBoot后端核心流程:库存预占与订单创建的原子化
订单创建是整个系统的咽喉,我们将其拆解为四个不可分割的原子步骤,全部在同一个@Transactional事务内执行:
步骤1:库存预占(Reserve Stock)
不直接扣减库存,而是创建一条stock_reservation记录:
INSERT INTO stock_reservation (sku_id, quantity, order_id, created_at) VALUES (?, ?, ?, NOW());同时更新stock表的reserved_quantity字段(预留量):
UPDATE stock SET reserved_quantity = reserved_quantity + ? WHERE sku_id = ? AND quantity >= ?;这里的关键是AND quantity >= ?条件——确保物理库存足够才允许预占。如果返回影响行数为0,说明库存不足,直接抛出InsufficientStockException。
步骤2:优惠计算与价格锁定
调用promotionService.calculatePromotion(),传入OrderItem列表和用户ID。该服务会:
- 查询用户可用优惠券(排除已过期、未达门槛、限品类券)
- 计算满减、折扣、赠品等叠加规则(我们采用“先满减后折扣”策略,符合会计准则)
- 生成
PromotionResult对象,包含最终应付金额、减免明细、赠品清单
步骤3:订单实体构建与持久化
创建Order聚合根,强制校验业务规则:
public class Order { private String orderId; // 全局唯一,格式:SD202405202345678901 private Long userId; private BigDecimal totalAmount; private BigDecimal payAmount; private List<OrderItem> items; public void validate() { if (items == null || items.isEmpty()) { throw new BusinessException("订单商品不能为空"); } if (payAmount.compareTo(BigDecimal.ZERO) <= 0) { throw new BusinessException("应付金额必须大于0"); } // 关键校验:预占库存总量必须等于订单商品总量 long reservedTotal = stockReservationRepository.sumByOrderId(orderId); long orderTotal = items.stream().mapToLong(OrderItem::getQuantity).sum(); if (reservedTotal != orderTotal) { throw new BusinessException("库存预占异常,请重试"); } } }实操心得:
orderId生成不用UUID,而是时间戳+机器码+序列号(Snowflake变种),既保证全局唯一,又便于按时间分库分表。我们用System.currentTimeMillis()取前13位(毫秒级),后4位用AtomicInteger递增,避免了UUID的存储空间浪费和索引碎片问题。
步骤4:发布领域事件并提交事务
事务提交前,发布OrderCreatedEvent:
@Transactional public Order createOrder(...) { // 步骤1-3... order.validate(); // 最终校验 // 事务提交前发布事件 applicationEventPublisher.publishEvent(new OrderCreatedEvent(order)); return orderRepository.save(order); // 事务提交后才真正写库 }监听器OrderCreatedEventListener异步处理后续动作:
- 发送短信(调用阿里云SMS SDK)
- 生成电子发票(调用航信API)
- 更新用户积分(调用积分服务Feign Client)
- 触发库存扣减(调用
stockDomainService.deductStock())
这样设计的好处是:主事务极短(平均120ms),所有耗时操作异步化;即使短信发送失败,也不影响订单创建成功,后续可通过事件重试机制补偿。
3.3 MySQL数据库设计:为生鲜场景定制的表结构与索引
生鲜数据模型与普通电商有本质区别,我们重点优化了三张核心表:
1.product_sku表(商品SKU)
CREATE TABLE `product_sku` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `product_id` bigint(20) NOT NULL COMMENT '商品ID', `specification` varchar(100) NOT NULL COMMENT '规格,如"500g/盒"', `price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '销售价', `cost_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '成本价', `quantity` int(11) NOT NULL DEFAULT '0' COMMENT '可用库存', `reserved_quantity` int(11) NOT NULL DEFAULT '0' COMMENT '预留库存', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-上架,0-下架', `shelf_time` datetime DEFAULT NULL COMMENT '上架时间', `off_shelf_time` datetime DEFAULT NULL COMMENT '下架时间', PRIMARY KEY (`id`), KEY `idx_product_status` (`product_id`,`status`) USING BTREE, KEY `idx_quantity` (`quantity`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品SKU';关键设计:
quantity和reserved_quantity分离。quantity是真实库存,reserved_quantity是已预占但未确认的量。扣减库存时,先减reserved_quantity,再减quantity,避免超卖。idx_quantity索引让“查库存不足商品”查询极快。
2.order_master表(订单主表)
CREATE TABLE `order_master` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `order_no` varchar(32) NOT NULL COMMENT '订单号', `user_id` bigint(20) NOT NULL COMMENT '用户ID', `total_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单总金额', `pay_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '实付金额', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-待接单,2-配货中,3-已出库...', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `pay_time` datetime DEFAULT NULL COMMENT '支付时间', `cancel_time` datetime DEFAULT NULL COMMENT '取消时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) USING BTREE, KEY `idx_user_status` (`user_id`,`status`) USING BTREE, KEY `idx_created_status` (`created_at`,`status`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';关键设计:
status字段用tinyint而非字符串,节省存储空间;idx_user_status索引支撑“我的订单”列表查询;idx_created_status支撑运营后台按日期+状态筛选(如“昨日待接单订单”)。
3.stock_reservation表(库存预占记录)
CREATE TABLE `stock_reservation` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `sku_id` bigint(20) NOT NULL COMMENT 'SKU ID', `quantity` int(11) NOT NULL DEFAULT '0' COMMENT '预占数量', `order_id` bigint(20) NOT NULL COMMENT '关联订单ID', `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `expire_at` datetime NOT NULL COMMENT '过期时间,24小时后自动释放', PRIMARY KEY (`id`), KEY `idx_sku_expire` (`sku_id`,`expire_at`) USING BTREE, KEY `idx_order` (`order_id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='库存预占记录';关键设计:
expire_at字段配合定时任务清理过期预占。我们用@Scheduled(cron = "0 0/5 * * * ?")每5分钟扫描expire_at < NOW()的记录,批量删除并回滚reserved_quantity。这个表是防超卖的最后防线。
3.4 Redis缓存策略:如何让商品列表查询从300ms降到20ms
生鲜商品列表是最高频接口,我们采用“多级缓存+精准失效”策略:
缓存层级:
- L1:Guava Cache(JVM内存)—— 存储热点商品详情(如首页Banner商品),TTL=10分钟,最大容量1000条。优势是毫秒级响应,无网络开销。
- L2:Redis Cluster —— 存储分类商品列表、搜索结果,TTL=30分钟,Key格式:
category:1001:page:1:sort:price_asc
Key设计原则:
- 避免大Key:
category:1001:all这种Key会存几万条商品ID,导致Redis阻塞。改为分页Key:category:1001:page:1:limit:20 - 包含业务维度:搜索接口Key为
search:keyword:苹果:city:shanghai:page:1,确保不同城市、不同关键词互不影响
精准失效方案:
商品变更时,不删整个分类缓存,而是:
- 更新
product_sku表 - 删除对应
category:xxx:page:*所有分页Key(用Redis的KEYS category:1001:page:*+DEL,生产环境用SCAN替代KEYS防阻塞) - 异步重建首页缓存(监听MySQL binlog,用Canal同步到Redis)
实测效果:商品列表接口QPS从1200提升至8500,平均响应时间稳定在18ms。最惊人的案例是“草莓”搜索,因季节性爆品,单日PV超200万,缓存命中率99.2%,数据库压力下降92%。
4. 实操全流程:从IDEA创建项目到Uniapp真机调试
4.1 SpringBoot项目初始化:版本选型与依赖精简
SpringBoot版本选择是第一个坑。网上教程多用2.7.x,但生鲜系统需要高并发和响应式支持,我们选3.2.5(当前最新稳定版),理由如下:
- 内置Tomcat升级到10.1,HTTP/2支持更完善,Uniapp App端长连接更稳定
- Jakarta EE 9+规范,避免
javax.*包冲突(尤其对接微信支付SDK时) spring-boot-starter-validation默认启用,无需额外引入Hibernate Validator
pom.xml关键依赖:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <dependencies> <!-- Web核心 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 数据库 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- 缓存 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 消息队列 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <!-- Lombok简化代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- 阿里云OSS文件上传 --> <dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.19.0</version> </dependency> </dependencies>注意:
spring-boot-starter-data-jdbc替代了传统的MyBatis,我们用JdbcTemplate+自定义DAO,代码更简洁,SQL完全可控。MyBatis的XML映射在复杂动态SQL时易出错,而生鲜查询多为固定条件,JdbcTemplate足够。
4.2 MySQL安装与配置:针对高并发的参数调优
开发环境用Docker快速部署:
docker run -d \ --name mysql-springboot \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=food_order \ -v /data/mysql:/var/lib/mysql \ -v /etc/my.cnf:/etc/my.cnf \ -d mysql:8.0.33关键配置my.cnf:
[mysqld] # 基础配置 server-id=1 character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci # 性能调优(针对生鲜高频写入) innodb_buffer_pool_size=2G # 物理内存的70% innodb_log_file_size=512M # 提升写入吞吐 innodb_flush_log_at_trx_commit=2 # 平衡安全性与性能,日志刷盘频率降为每秒一次 sync_binlog=0 # Binlog同步关闭,由应用层保证一致性 max_connections=1000 # 支持高并发连接 # 索引优化 innodb_file_per_table=ON # 每个表独立.ibd文件,便于空间回收 innodb_stats_on_metadata=OFF # 关闭元数据统计,避免SHOW TABLE STATUS慢实操心得:
innodb_flush_log_at_trx_commit=2是生鲜系统的黄金参数。它意味着事务提交时,日志写入OS缓存而非磁盘,性能提升3倍,且只要OS不崩溃,数据不会丢失。我们实测订单创建TPS从1200提升至3500,RPO(恢复点目标)控制在1秒内,完全满足生鲜业务SLA。
4.3 Uniapp项目搭建与真机联调
Uniapp项目创建命令:
# 全局安装cli npm install -g @dcloudio/vue-cli-service # 创建项目(选择Vue3 + TypeScript) vue create -p dcloudio/uni-preset-vue my-food-app # 安装关键插件 npm install uview-plus axios @dcloudio/uni-ui真机调试关键步骤:
- Android签名配置:在
manifest.json→App Android配置→打包配置中,填写keystore路径、密码、别名。切记:debug.keystore不能用于上架,必须用正式签名证书 - iOS证书配置:在Apple Developer中心创建App ID、Provisioning Profile,Xcode中选择对应Team
- 微信公众号嵌入:在
manifest.json→mp-weixin→weixin中填写AppID,并在微信公众号后台设置JS接口安全域名(必须是备案域名) - 定位权限适配:Android 12+需在
AndroidManifest.xml中添加:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
调试技巧:
- 使用
uni.switchTab({url: '/pages/order/list'})跳转时,若页面白屏,检查pages.json中tabBar配置是否正确 - H5端调用微信JS-SDK,必须先调用
uni.getProvider({service: 'oauth'})获取provider,再uni.login() - 真机调试时,
console.log()输出在HBuilderX的“运行日志”窗口,而非浏览器控制台
5. 常见问题与排查技巧实录:那些凌晨三点的救火经验
5.1 高频问题速查表
| 问题现象 | 可能原因 | 排查命令/方法 | 解决方案 |
|---|---|---|---|
| 订单创建超时(>5s) | MySQL连接池耗尽 | show processlist;查看sleep连接数 | 增加HikariCPmaximumPoolSize=50,检查是否有未关闭的Connection |
| 库存显示为负数 | 预占未释放或扣减逻辑错误 | SELECT * FROM stock_reservation WHERE expire_at < NOW(); | 检查定时任务是否正常运行,stock_reservation表是否有大量过期记录 |
| Uniapp H5端无法获取定位 | 微信浏览器限制 | 在微信开发者工具中开启“位置模拟” | 确保manifest.json中mp-weixin.permission配置正确,且公众号已开通地理位置接口权限 |
| Redis缓存击穿(大量请求打到DB) | 热点Key过期瞬间并发访问 | redis-cli monitor观察KEY删除指令 | 对热点Key设置永不过期+后台异步更新,或使用布隆过滤器拦截无效请求 |
| 订单状态卡在“待接单”不更新 | RabbitMQ消费者宕机 | rabbitmqctl list_queues查看队列堆积量 | 检查OrderCreatedEventListener是否抛出未捕获异常,增加死信队列监控 |
5.2 三个血泪教训分享
教训1:MySQL的GROUP BY陷阱导致库存统计错误
上线初期,运营要查“各门店今日销量TOP10”,我们写了:
SELECT store_id, SUM(quantity) as total FROM order_item WHERE created_at >= '2024-05-20' GROUP BY store_id ORDER BY total DESC LIMIT 10;结果发现数据比实际少30%。排查发现order_item表有大量status=0(已取消)的记录也被计入。修正方案:所有统计SQL必须显式过滤有效状态:
SELECT store_id, SUM(quantity) as total FROM order_item WHERE created_at >= '2024-05-20' AND status = 1 -- 1=已签收 GROUP BY store_id ORDER BY total DESC LIMIT 10;教训2:Redis Pipeline误用引发OOM
为提升商品列表加载速度,我们尝试用Pipeline批量GET:
List<String> keys = ... // 10000个商品ID List<Object> results = redisTemplate.executePipelined((RedisCallback<Object>) connection -> { for (String key : keys) { connection.get(key.getBytes()); } return null; });结果Redis内存暴涨,触发OOM。根本原因是Pipeline将10000个命令一次性发到Redis,服务端需缓存所有响应。修正方案:分批执行,每批不超过100个:
for (int i = 0; i < keys.size(); i += 100) { List<String> batch = keys.subList(i, Math.min(i + 100, keys.size())); // 批量执行 }教训3:UniapponPullDownRefresh未重置导致重复请求
用户下拉刷新时,我们调用uni.showLoading(),但忘记在请求结束后调用uni.hideLoading()。结果用户连续下拉,触发多次请求,订单重复创建。解决方案:所有异步操作必须配对调用loading开关,并在catch块中确保hideLoading执行:
onPullDownRefresh() { uni.showLoading({ title: '加载中...' }); this.loadList().finally(() => { uni.hideLoading(); uni.stopPullDownRefresh(); }); }5.3 生产环境监控清单
上线前必须配置的5项监控:
- MySQL慢查询:
long_query_time=1,日志分析用Percona Toolkit - Redis内存使用率:阈值设为85%,超限触发告警
- RabbitMQ队列堆积:
queue_length > 1000且持续5分钟,告警并自动扩容消费者 - SpringBoot Actuator健康检查:
/actuator/health返回DOWN时,自动重启服务 - Uniapp前端错误监控:集成Sentry,捕获JS错误、API 500、网络超时
最后分享一个小技巧:在订单创建接口中加入@Timed注解(需引入Micrometer),自动上报耗时指标到Prometheus:
@Timed(value = "order.create.duration", histogram = true) @PostMapping("/create") public Result<OrderVO> createOrder(@Valid @RequestBody OrderCreateDTO dto) { // ... }这样就能在Grafana中看到“订单创建P95耗时”曲线,当它突然飙升,就知道是库存服务还是支付回调出了问题,而不是盲目查日志。
我在实际运维中发现,90%的线上问题都能通过这5项监控在用户投诉前发现。真正的稳定性,不靠人盯,而靠体系化的观测。