SpringBoot微服务实战指南

Spring Boot 微服务实战指南

CSDN 上 Java 相关内容常年占据技术社区流量榜首,而 Spring Boot 是 Java 后端的事实标准。本文从零开始,带你把一个 Spring Boot 单体项目演进为微服务架构,覆盖核心原理、常用组件、微服务拆分、注册中心、配置中心、网关、熔断等完整链路。


一、Spring Boot 快速上手

1.1 为什么是 Spring Boot

过去用 Spring 开发,要手动配置 XML、配置数据源、配置事务、配置 MVC------每个项目几百行配置。Spring Boot 的核心哲学是约定大于配置:

  • 自动配置:根据依赖自动装配 Bean;
  • 内嵌服务器:Tomcat/Jetty 打包进 jar,不用单独装;
  • Starter 机制:一个依赖引入一组能力。

对比传统 Spring:

维度 传统 Spring Spring Boot
配置 XML/注解,量大 自动配置,少量 application.yml
部署 打 war 扔 Tomcat 打 jar 直接 java -jar
依赖 手动管理版本 Starter + 版本管理
监控 无 Actuator 自带

1.2 创建项目

推荐用 Spring Initializr(start.spring.io)或 IDEA 内置:

bash 复制代码
# 或命令行
curl https://start.spring.io/starter.zip \
  -d dependencies=web,data-jpa,mysql,validation \
  -d groupId=com.example -d artifactId=demo -o demo.zip

核心依赖:

xml 复制代码
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.5</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

1.3 第一个接口

java 复制代码
@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        return Result.ok(userService.getById(id));
    }
}

注意 :Spring Boot 3.x 用 jakarta.* 包名,不再是 javax.*------很多老教程会踩这个坑。

1.4 配置体系

yaml 复制代码
spring:
  application:
    name: user-service
  datasource:
    url: jdbc:mysql://localhost:3306/mall?useUnicode=true&characterEncoding=utf8mb4
    username: root
    password: 123456
server:
  port: 8081

配置优先级:命令行参数 > application-{profile}.yml > application.yml > 默认值。

多环境配置:

yaml 复制代码
# application-dev.yml
# application-prod.yml
# 启动时指定
java -jar app.jar --spring.profiles.active=prod

二、核心原理:自动配置

2.1 自动配置是怎么工作的

@SpringBootApplication 是三个注解的合成:

java 复制代码
@SpringBootConfiguration   // 标记为配置类
@EnableAutoConfiguration   // 开启自动配置
@ComponentScan             // 扫描当前包及子包

@EnableAutoConfiguration 会读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,把里面的自动配置类全部加载,但条件化生效:

java 复制代码
@Configuration
@ConditionalOnClass(DataSource.class)           // 类在 classpath 才生效
@ConditionalOnMissingBean(DataSource.class)      // 用户没自定义才生效
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration { ... }

核心:条件注解决定"什么时候生效"。这也是为什么引入了 spring-boot-starter-data-jpa 就自动配好了数据源。

2.2 常用条件注解

  • @ConditionalOnClass:classpath 有某个类;
  • @ConditionalOnMissingBean:容器里没有某个 Bean;
  • @ConditionalOnProperty:配置项存在/等于某值;
  • @ConditionalOnExpression:SpEL 表达式。

2.3 自定义 Starter

公司内部公共组件(如统一日志、统一异常处理)可以打成 Starter:

复制代码
my-common-starter/
├── pom.xml
└── src/main/java/com/example/common/
    ├── MyAutoConfiguration.java
    └── META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
java 复制代码
@AutoConfiguration
@ConditionalOnClass(LogUtil.class)
public class MyAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public LogFilter logFilter() {
        return new LogFilter();
    }
}

三、Spring Boot 实战要点

3.1 统一异常处理

生产环境不能让用户看到堆栈。用 @RestControllerAdvice:

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result<?> handleBusiness(BusinessException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<?> handleValid(MethodArgumentNotValidException e) {
        String msg = e.getBindingResult().getFieldErrors()
            .stream().map(FieldError::getDefaultMessage)
            .collect(Collectors.joining(";"));
        return Result.error(400, msg);
    }

    @ExceptionHandler(Exception.class)
    public Result<?> handleUnknown(Exception e) {
        log.error("系统异常", e);
        return Result.error(500, "系统繁忙");
    }
}

原则:已知业务异常返回业务码,未知异常只记日志不暴露堆栈。

3.2 参数校验

java 复制代码
public class CreateUserRequest {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Email(message = "邮箱格式错误")
    private String email;

    @Min(value = 1, message = "年龄最小为1")
    private Integer age;
}

Controller 里加 @Valid:

java 复制代码
@PostMapping
public Result<User> create(@Valid @RequestBody CreateUserRequest req) { ... }

3.3 日志

xml 复制代码
<!-- logback-spring.xml -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
        <fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
        <maxHistory>30</maxHistory>
    </rollingPolicy>
</appender>

规范:

  • 用 {} 占位,不要字符串拼接;
  • 异常要打完整堆栈 log.error("xxx", e);
  • 日志分级:DEBUG 调试、INFO 业务、WARN 可恢复问题、ERROR 异常。

3.4 异步任务

java 复制代码
@EnableAsync
@Configuration
public class AsyncConfig { ... }

@Service
public class NotifyService {
    @Async
    public void sendSms(String phone) {
        // 异步发短信,不阻塞主流程
    }
}

坑 :@Async 失效的常见原因------同一个类内自调用(this 调用不经过代理);用 Spring 容器注入的 bean 调才会走代理。


四、为什么需要微服务

4.1 单体应用的问题

单体项目(一个 war 包包含所有模块)发展到一定规模会遇到:

  • 构建慢:几百万行代码,编译 10 分钟;
  • 发布难:改一行也要全量发布,风险大;
  • 团队冲突:100 人改同一个仓库,合并冲突频繁;
  • 扩展不均衡:订单模块压力大要加机器,但用户模块也要一起扩;
  • 技术绑定:想给报表模块用 Python 重写?不可能,都在一个包里。

4.2 微服务的思路

按业务边界拆成独立服务:

复制代码
mall/
├── user-service     用户服务(端口 8081)
├── order-service    订单服务(8082)
├── product-service  商品服务(8083)
├── payment-service  支付服务(8084)
└── gateway-service  网关(8080)

每个服务:

  • 独立仓库、独立部署;
  • 独立数据库(数据隔离);
  • 独立扩容;
  • 通过 HTTP/RPC 通信。

4.3 微服务的代价

不要神化微服务------它的代价是实打实的:

  • 运维复杂:20 个服务要监控 20 个进程;
  • 链路排查难:一次请求跨 5 个服务,出问题要全链路查;
  • 分布式事务:跨服务下单+扣库存,本地事务解决不了;
  • 网络开销:本地方法调用变远程调用,慢 100 倍。

判断标准:团队 < 50 人、模块 < 10 个,先用单体+模块化,别硬上微服务。


五、微服务核心组件

5.1 注册中心:Nacos

服务要互相发现,注册中心是微服务的地基。国内主流是 Nacos(阿里开源,支持注册+配置二合一)。

yaml 复制代码
spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

服务启动后自动注册,其他服务通过服务名调用:

java 复制代码
// 订单服务调用用户服务
@FeignClient("user-service")
public interface UserClient {
    @GetMapping("/api/users/{id}")
    Result<User> getUser(@PathVariable Long id);
}

Feign 声明式调用:像调本地方法一样调远程服务,底层是 HTTP。

5.2 配置中心:Nacos Config

配置集中管理,改配置不用重启:

yaml 复制代码
spring:
  config:
    import: optional:nacos:user-service.yml
java 复制代码
@RefreshScope
@Component
@ConfigurationProperties(prefix = "mall")
public class MallConfig {
    private String welcomeMsg;
    // getter/setter
}

改了 Nacos 里的配置,@RefreshScope 的 Bean 自动刷新。

5.3 网关:Spring Cloud Gateway

所有请求先进网关,做统一入口:

  • 路由转发;
  • 鉴权;
  • 限流;
  • 日志。
yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
        - id: order-route
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**

网关里做统一鉴权:

java 复制代码
@Component
public class AuthFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (token == null || !JwtUtil.verify(token)) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }
}

5.4 熔断降级:Sentinel

防止"一个服务挂了拖垮整个系统"------下单调库存,库存超时,不能一直等。

java 复制代码
@SentinelResource(value = "createOrder", fallback = "orderFallback")
public Order createOrder(OrderDTO dto) {
    return stockClient.deduct(dto);  // 可能超时
}

public Order orderFallback(OrderDTO dto, Throwable e) {
    log.error("下单降级: {}", e.getMessage());
    return Order.degraded();  // 返回降级结果
}

Sentinel 核心概念:

  • 熔断:错误率超阈值,直接拒绝一段时间;
  • 降级:熔断后走 fallback 逻辑;
  • 限流:QPS 超阈值丢弃请求。

5.5 链路追踪:SkyWalking

一次请求跨 5 个服务,出问题怎么定位?链路追踪记录每个环节耗时:

复制代码
GET /api/orders/123
├── gateway: 5ms
├── order-service: 40ms
│   ├── DB 查询: 15ms
│   └── user-service 调用: 20ms
├── user-service: 18ms

SkyWalking 通过 Agent 无侵入接入,不用改代码。


六、实战:单体拆微服务

6.1 拆分步骤

  1. 找边界:按业务能力拆(用户/订单/商品/支付),不是按技术层拆(controller/service/dao 各拆一个服务是错的);
  2. 抽公共模块:统一返回、统一异常、工具类打成 common 包;
  3. 数据库拆分:每个服务独立库,用 Feign 调用代替跨库 JOIN;
  4. 改造调用:Service 间调用改成 HTTP;
  5. 加网关和鉴权;
  6. 加熔断、链路追踪、监控。

6.2 数据一致性

跨服务操作(下单:订单服务+扣库存+减积分)怎么保证?

  • 本地事务 + 重试:先写订单,异步通知库存,失败重试;
  • 事务消息(RocketMQ):本地事务提交后发消息;
  • Seata:分布式事务框架,但性能损耗大,慎用。

经验:90% 的场景用"本地事务 + 消息 + 最终一致"就够,别一上来就 Seata。

6.3 一次完整下单链路

复制代码
用户请求 POST /api/orders  (网关)
  → gateway 鉴权
  → order-service 创建订单(本地事务)
  → 发 MQ 消息"订单已创建"
  → stock-service 消费消息,扣库存(幂等)
  → payment-service 处理支付
  → 用户轮询/推送查状态

七、Spring Boot 3 新特性

  • Jakarta EE 9+:javax 改 jakarta;
  • GraalVM 原生镜像:启动毫秒级、内存低,但反射受限;
  • 虚拟线程(Java 21):百万级并发线程;
  • Observability:Micrometer + OpenTelemetry 内置支持。

迁移注意 :Spring Boot 2 → 3 不是改版本号那么简单------javax.* 全部改 jakarta.*、spring.factories 改 imports 文件、一些 API 废弃。


八、面试高频题速览

  1. Spring Boot 自动配置原理 :@EnableAutoConfiguration + 条件注解 + imports 文件;
  2. @Autowired 和 @Resource 区别:前者按类型,后者按名称;
  3. Bean 生命周期:实例化 → 属性填充 → Aware → BeanPostProcessor → init → 使用 → destroy;
  4. 循环依赖怎么解决:三级缓存(Spring 默认单例支持,构造器注入除外);
  5. 微服务拆分原则:高内聚低耦合、按业务边界、数据独立;
  6. 分布式事务方案:2PC/3PC、TCC、本地消息表、事务消息、Seata;
  7. 服务雪崩怎么防:超时、熔断(Sentinel/Hystrix)、限流、隔离、降级。

本章小结

Spring Boot 降低的是"写代码"的成本,微服务解决的是"扩展与协作"的问题,但两者都带来新的复杂度。工程上务实的路径是:单体起步 → 模块化 → 按需拆服务 → 配套治理组件。

下一篇讲 Redis------缓存、分布式锁、持久化,后端面试和实战的双热点。

(全文完)

相关推荐
FfHUCisI1 小时前
sync.Once 与 sync.Cond 源码与并发控制陷阱
服务器·开发语言·后端·golang
montEvergreen1 小时前
位运算不是魔法——AV1 配置解析实战
后端
程序猿乐锅2 小时前
【黑马点评 | 第九篇】秒杀优化-异步实现
java·spring boot·redis·后端·spring·中间件·mybatis
Wx-bishekaifayuan2 小时前
springboot户外登山社交小程序19787-计算机课程设计、毕业设计
spring boot·后端·python·spring·elasticsearch·django·课程设计
+VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue外卖点餐系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
FYKJ_20102 小时前
springboot刑事案件管理系统03047-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
小辰爱喝汤3 小时前
新闻管理系统|SpringBoot + Vue 毕业设计完整方案
spring boot·毕业设计·课程设计
FYKJ_20103 小时前
springboot助农产品销售商城05829-计算机课程设计、毕业设计
java·spring boot·python·mysql·spark·django
Ticnix4 小时前
RAG 烂大街?烂大街的只是那条流水线——真正的分水岭在这五处
后端·python·agent