1. 项目概述:微服务架构下的救援物资管理系统
这个基于SpringBoot+Vue+SpringCloud的救援物资管理系统,是我去年参与开发的一个公益性质项目。当时河南洪灾刚过,我们团队发现很多救援组织还在用Excel表格管理物资分发,效率低下且容易出错。于是我们决定开发这套系统,用技术手段解决救灾物资管理的痛点。
系统采用微服务架构设计,将传统单体应用拆分为多个独立服务。比如物资管理、调度追踪、权限控制等功能都是独立部署的服务,通过SpringCloud进行服务治理。这种架构最大的优势是弹性扩展——当某个服务(比如物资申领)访问量激增时,可以单独扩容该服务,而不需要整体扩容,这在救灾场景下特别实用。
提示:微服务架构虽然灵活,但也带来了分布式事务、服务发现等新挑战。我们在开发中采用了SpringCloud Alibaba的Seata组件处理分布式事务,用Nacos做服务注册中心,这些都是经过生产验证的方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能模块详解
2.1 物资管理子系统
物资管理是整个系统的核心,我们设计了多级分类体系:
- 一级分类:食品、药品、日用品等
- 二级分类:食品下分即食食品、饮用水等
- 三级分类:即食食品下分饼干、方便面等
数据库采用MySQL分库分表设计:
sql复制-- 物资主表(按物资类型分库)
CREATE TABLE `goods_%s` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '物资名称',
`category_id` int NOT NULL COMMENT '分类ID',
`quantity` int NOT NULL DEFAULT '0' COMMENT '库存数量',
`shelf_life` date DEFAULT NULL COMMENT '保质期',
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 物资分类表(全局表)
CREATE TABLE `goods_category` (
`id` int NOT NULL AUTO_INCREMENT,
`parent_id` int DEFAULT '0' COMMENT '父分类ID',
`level` tinyint NOT NULL COMMENT '分类层级',
`name` varchar(50) NOT NULL COMMENT '分类名称',
PRIMARY KEY (`id`),
KEY `idx_parent` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.2 调度追踪系统
调度模块接入了高德地图API,实现以下功能:
- 物资运输路径规划
- 实时位置追踪(每5秒更新一次)
- 预计到达时间计算
我们使用Redis的GEO数据类型存储车辆位置信息:
java复制// 更新车辆位置
public void updateVehiclePosition(String vehicleId, double lng, double lat) {
String key = "vehicle:position";
redisTemplate.opsForGeo().add(key, new Point(lng, lat), vehicleId);
// 同时写入时间戳
String tsKey = "vehicle:position:ts";
redisTemplate.opsForHash().put(tsKey, vehicleId, System.currentTimeMillis());
}
2.3 权限控制系统
采用RBAC(基于角色的访问控制)模型,结合OAuth2.0实现:
- 角色分为:超级管理员、政府管理员、NGO管理员、志愿者
- 权限粒度控制到按钮级别
- JWT token有效期2小时,支持refresh token续期
权限判断的AOP实现示例:
java复制@Aspect
@Component
public class PermissionAspect {
@Before("@annotation(requiredPermission)")
public void checkPermission(JoinPoint joinPoint, RequiredPermission requiredPermission) {
String permission = requiredPermission.value();
User user = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(!user.getPermissions().contains(permission)) {
throw new AccessDeniedException("无权限操作");
}
}
}
3. 技术架构深度解析
3.1 微服务拆分方案
我们将系统拆分为以下服务:
- 用户服务(user-service):处理用户认证、权限管理
- 物资服务(goods-service):物资CRUD、库存管理
- 调度服务(dispatch-service):运输路线规划、追踪
- 订单服务(order-service):物资申领、分配
- 通知服务(notice-service):短信、邮件通知
服务间调用采用FeignClient,并添加熔断保护:
java复制@FeignClient(name = "user-service", fallback = UserServiceFallback.class)
public interface UserServiceClient {
@GetMapping("/users/{id}")
User getUserById(@PathVariable Long id);
}
@Component
public class UserServiceFallback implements UserServiceClient {
@Override
public User getUserById(Long id) {
User user = new User();
user.setId(-1L);
user.setName("默认用户");
return user;
}
}
3.2 性能优化实践
-
缓存策略:
- 一级缓存:本地Caffeine缓存(有效期5分钟)
- 二级缓存:Redis集群(有效期30分钟)
- 缓存键设计:
业务前缀:业务ID:版本号,如goods:123:v1
-
异步处理:
使用RabbitMQ实现物资申领的异步处理:java复制@RabbitListener(queues = "goods.request.queue") public void handleGoodsRequest(GoodsRequest request) { try { goodsService.processRequest(request); } catch (Exception e) { // 失败后进入重试队列 rabbitTemplate.convertAndSend("goods.request.retry.queue", request); } } -
数据库优化:
- 读写分离:写主库,读从库
- 索引优化:为所有查询条件建立合适索引
- SQL监控:接入Druid监控慢查询
4. 部署与运维方案
4.1 容器化部署
我们采用Docker + Kubernetes的方案:
dockerfile复制# 以用户服务为例的Dockerfile
FROM openjdk:11-jre
WORKDIR /app
COPY target/user-service.jar .
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "user-service.jar"]
Kubernetes部署文件示例:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: registry.example.com/user-service:1.0.0
ports:
- containerPort: 8080
resources:
limits:
cpu: "1"
memory: 1Gi
requests:
cpu: "0.5"
memory: 512Mi
4.2 监控方案
-
指标监控:Prometheus + Grafana
- JVM指标
- 接口响应时间
- 数据库连接池状态
-
日志收集:ELK栈
- Filebeat收集日志
- Logstash处理日志
- Elasticsearch存储
- Kibana展示
-
链路追踪:SkyWalking
- 服务调用链路可视化
- 慢请求分析
- 依赖服务拓扑图
5. 开发中的经验与教训
5.1 分布式事务处理
在物资申领流程中,需要同时更新库存和创建订单,这涉及到分布式事务。我们尝试了以下方案:
-
本地消息表:
- 实现简单但业务侵入性强
- 需要额外开发消息补偿机制
-
Seata AT模式:
- 自动处理回滚
- 需要数据库支持undo_log表
- 最终采用了此方案
Seata配置示例:
properties复制# seata配置
seata.tx-service-group=my_test_tx_group
seata.service.vgroup-mapping.my_test_tx_group=default
seata.service.grouplist.default=127.0.0.1:8091
5.2 缓存一致性解决方案
物资库存是个高频访问的数据,我们采用"先更新数据库,再删除缓存"的策略,并添加了以下保障措施:
- 缓存设置合理的过期时间(即使删除失败,数据也会最终一致)
- 使用Redisson的分布式锁,防止并发更新导致的数据不一致
- 通过canal监听binlog,作为缓存删除的兜底方案
java复制public void updateGoodsStock(Long goodsId, int delta) {
// 获取分布式锁
RLock lock = redissonClient.getLock("lock:goods:" + goodsId);
try {
lock.lock();
// 更新数据库
goodsMapper.updateStock(goodsId, delta);
// 删除缓存
redisTemplate.delete("goods:" + goodsId);
} finally {
lock.unlock();
}
}
5.3 高并发场景下的优化
在灾情高峰期,系统面临大量并发请求。我们通过以下手段提升系统吞吐量:
-
接口限流:
- 使用Sentinel实现QPS限流
- 关键接口设置熔断规则
-
库存扣减优化:
采用乐观锁避免超卖:sql复制UPDATE goods SET stock = stock - #{num} WHERE id = #{id} AND stock >= #{num} -
请求合并:
对获取物资列表等高频查询,使用Hystrix请求合并:java复制@HystrixCollapser(batchMethod = "batchGetGoods", collapserProperties = { @HystrixProperty(name = "timerDelayInMilliseconds", value = "100") }) public Future<Goods> getGoodsById(Long id) { return null; // 实际由batchGetGoods处理 } public List<Goods> batchGetGoods(List<Long> ids) { return goodsMapper.batchSelect(ids); }
6. 系统实际应用效果
这套系统在河南洪灾期间实际部署使用,取得了以下效果:
-
效率提升:
- 物资分发周期从平均5天缩短到2天
- 物资匹配准确率达到98%(之前约80%)
-
资源利用率:
- 通过智能调度,运输成本降低35%
- 物资浪费率从15%降至5%
-
系统稳定性:
- 最高支撑10万QPS
- 平均响应时间<200ms
- 服务可用性99.99%
注意:在救灾场景下,系统的鲁棒性比功能丰富度更重要。我们做了大量异常处理和数据校验,确保即使在网络不稳定等恶劣条件下,系统也能保持基本功能可用。
