2024年毕设系列:基于微服务架构的毕业设计实战与避坑指南
关键词:毕设、微服务、Spring Cloud Alibaba、Docker Compose、服务拆分
一、为什么又把单体拆“碎”了?——毕设三大常见坑
先别急着把“微服务”写进开题报告,看看下面几条有没有中枪:
- 过度拆分:把“用户登录”和“用户头像上传”硬拆成两个服务,结果本地跑一遍要开 8 个端口,答辩现场电脑风扇直接起飞。
- 无熔断机制:A 服务调 B 服务,B 调 C 服务,C 一挂整条调用链雪崩,日志里全是
ConnectTimeout,老师一句“健壮性呢?”直接问懵。 - 本地 vs 线上两张皮:IDEA 里跑得好好的,上服务器就“找不到服务”,一查发现注册中心地址还是
localhost:8848。
如果你也踩过类似的坑,下面的实战复盘或许能救你一命。
二、技术选型:Spring Cloud Alibaba vs Dubbo 轻量级对决
| 维度 | Spring Cloud Alibaba(SCA) | Dubbo |
|---|---|---|
| 学习曲线 | 与 Spring Boot 无缝衔接,注解丰富 | 额外掌握dubbo配置、SPI 机制 |
| 注册中心 | Nacos(自带控制台,可视化佳) | Zookeeper/Nacos,控制台需额外搭 |
| 通信协议 | HTTP(OpenFeign),调试抓包一目了然 | 默认 dubbo 协议,二进制包抓包需解码 |
| 毕业场景 | 演示、答辩、本地一键启动 | 高并发压测更优,但毕设往往用不上 |
结论:时间紧、人手少、答辩优先,选 SCA;如果团队里有大神想玩高并发,再考虑 Dubbo。
三、核心实现:把“用户服务”与“资源服务”真正解耦
3.1 业务背景
校园资源共享平台,允许学生发布二手教材、闲置电子设备。两个核心子域:
- 用户域:注册、登录、JWT 签发。
- 资源域:发布商品、查询列表、库存扣减。
3.2 服务拆分原则
- 同一业务动词放一起:用户相关动词全进
user-service。 - 数据主权限清晰:
user表只在user-service,resource表只在resource-service。 - 单向依赖:资源服务可查询用户,但用户服务不感知资源,避免循环引用。
3.3 交互时序图图
- 网关统一鉴权,把
userId写进请求头。 resource-service通过 OpenFeign 调用user-service拿到用户摘要,本地缓存 30 s,降低并发重复查询。
3.4 并发竞争下的“库存扣减”怎么做?
场景:两同学同时下单同一本二手书。
解决步骤:
- 数据库层加乐观锁:
version字段。 - 业务层加 Redis 分布式锁(基于
spring-integration-redis),锁 key 为resource:{id},持有时间 3 s。 - OpenFeign 调用失败重试:配置
Retryer两次、间隔 500 ms,防止网络抖动误判为库存不足。
四、代码片段:用户服务 & 资源服务关键实现
以下代码均基于 Spring Boot 3.2 + Spring Cloud 2022.0.0,端口分别为8081、8082,注册中心 Nacos 本地8848。
4.1 user-service:提供内网接口/inner/{userId}
// UserController.java @RestController @RequestMapping("/inner") @RequiredArgsConstructor public class UserInnerController { private final UserRepository repo; @GetMapping("/{userId}") public UserDTO get(@PathVariable Long userId){ return repo.findById(userId) .map(u -> new UserDTO(u.getId(), u.getNickname())) .orElseThrow(()-> new BizException("USER_NOT_FOUND")); } }4.2 resource-service:OpenFeign 客户端 + 熔断
// UserClient.java @FeignClient(value = "user-service", fallback = UserClientFallback.class) public interface UserClient { @GetMapping("/inner/{userId}") UserDTO getUser(@PathVariable Long userId); } @Component class UserClientFallback implements UserClient { @Override public UserDTO getUser(Long userId){ // 返回空对象,前端展示“用户已注销” return new UserDTO(null, "匿名用户"); } }4.3 资源发布接口(含幂等性)
@PostMapping public ApiResp<String> publish(@RequestBody PublishReq req, @RequestHeader(HttpHeaders.AUTHORIZATION) String token){ // 1. 验证 JWT Long userId = JwtHolder.parse(token); // 2. 幂等性:使用 token + 请求体 MD5 作为 key,Redis 缓存 60 s String idemKey = "publish:" + userId + ":" + DigestUtils.md5DigestAsHex(req.toString().getBytes()); if (Boolean.TRUE.equals(redisTemplate.hasKey(idemKey))) { return ApiResp.ok("重复提交,已忽略"); } redisTemplate.opsForValue().set(idemKey, "1", Duration.ofSeconds(60)); // 3. 业务处理 resourceService.publish(userId, req); return ApiResp.ok("发布成功"); }五、性能与安全:JWT、幂等、冷启动
JWT 令牌
- 过期时间 30 min,刷新令牌 7 天,存 Redis 可踢人下线。
- 网关层统一鉴权,下游服务只认
userId请求头,不解析 token,减少重复计算。
接口幂等
- 除“发布”外,“下单”、“支付”均使用相同策略,防止快速点击或重试风暴。
冷启动延迟
- Spring Boot 3 原生 AOT 编译效果有限,毕设机器 4C8G 实测空载启动 6 s。
- 采用
spring-cloud-starter-bootstrap提前加载 Nacos 配置,把初始化耗时从 2.3 s 降到 1.1 s。 - Docker 镜像用
paketobuildpacks打出来的 120 M,比官方 openjdk 瘦身 40%,启动再快 500 ms。
六、生产环境避坑速查表
| 现象 | 根因 | 解决 |
|---|---|---|
| Nacos 配置热更新失效 | @RefreshScope没加、或配置类是内部类 | 把@Configuration单独建文件,并加@RefreshScope |
| Docker 容器互相 ping 不通 | 默认 bridge 网络隔离 | 建自定义网络docker network create campus,所有服务networks: - campus |
Feign 调用报404 | 路径拼写错 / 网关 stripPrefix=1 误删 | 本地用feign.Logger.Level=FULL打印 URL 对照 |
| 日志时间差 8 h | 容器缺 locale | Dockerfile 加ENV TZ=Asia/Shanghai并RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime |
| 压测时 Hystrix 线程池爆满 | 毕设根本用不到高并发,把超时设短一点即可,或者直接 Sentinel 限流 |
七、一键启动脚本:Docker Compose 模板
version: "3.9" services: nacos: image: nacos/nacos-server:v2.3.0 environment: MODE: standalone ports: ["8848:8848"] user-service: build: ./user-service networks: [campus] depends_on: [nacos] resource-service: build: ./resource-service networks: [campus] depends_on: [nacos] gateway: build: ./gateway networks: [campus] ports: ["80:80"] depends_on: [user-service, resource-service] networks: campus:本地测试:
docker compose up -d浏览器访问http://localhost/api/resource即可。
八、小结:算力有限,粒度怎么切?
把微服务写进毕设,最怕“为了拆而拆”。下面三句话送给还在纠结的你:
- 先跑通单体,再识别真正瓶颈域,按“修改频率”与“并发压力”两个维度拆。
- 每多一个容器,就多一份内存 + 注册中心心跳,本地 8 G 笔记本扛不住 10 个服务同时开。
- 答辩完就把镜像推到云服务器,按量计费 1C2G 也能跑,但记得合并日志、集中监控,别让老师 SSH 到服务器
docker logs翻半天。
动手复现,把仓库跑通,再思考“如果只有两台 2C4G 轻量云,如何既保证高内聚,又省运维成本?”——这或许比“微服务”本身,更像一份合格的毕业答卷。