微服务是一种将应用划分为多个独立服务的架构风格,服务通过轻量级通信协议互相协作,每个服务负责单一功能,可独立开发、部署和运行。
微服务架构的优缺点
单体架构向微服务架构的演进
bootstrap.yml
文件(1)什么是 bootstrap.yml
?
Spring Cloud 的引导配置文件:bootstrap.yml
(或 bootstrap.properties
)是 Spring Cloud 项目的特殊配置文件,它在应用启动时优先于 application.yml
加载。
核心作用:用于配置与分布式系统基础设施相关的参数,例如:
配置中心(Nacos、Consul、Spring Cloud Config Server)
服务注册与发现(Nacos、Eureka)
加密配置(如 Vault)
优先级规则:bootstrap.yml
> application.yml
,因此它能确保关键基础设施配置在应用启动初期就被加载。
(2)是否需要自己创建?放在哪里?
必须手动创建:Spring Boot 默认不会自动生成该文件,需要开发者自行在以下位置创建:
src/main/resources/bootstrap.yml
文件命名规范:支持 YAML(.yml
)和 Properties(.properties
)两种格式,建议使用 YAML 格式以获得更好的可读性。
(3)使用 Nacos 时需要配置哪些内容?
典型配置项(YAML 格式):
# ===== 基础配置 =====
spring:
application:
name: order-service # 应用名称(决定Data ID)
profiles:
active: dev
cloud:
nacos:
# ===== 配置中心 =====
config:
server-addr: 192.168.1.100:8848 # Nacos服务器地址
namespace: dev-7e3d # 命名空间ID(环境隔离)
group: PAYMENT_GROUP # 配置分组
file-extension: yaml # 配置文件格式
refresh-enabled: true # 启用自动刷新
shared-configs: # 共享配置
- data-id: common-db.yaml
group: COMMON_GROUP
refresh: true
# ===== 服务注册与发现 =====
discovery:
server-addr: ${spring.cloud.nacos.config.server-addr} # 复用配置中心地址
namespace: ${spring.cloud.nacos.config.namespace} # 复用命名空间
ephemeral: true
关键配置说明:
spring.application.name
:决定 Nacos 配置中心的 Data ID(如 order-service.yaml
)
spring.cloud.nacos.config
:所有与配置中心相关的参数
spring.cloud.nacos.discovery
:所有与服务注册发现相关的参数
共享配置:多个微服务共用的配置(如数据库连接信息),通过 shared-configs
指定
ephemeral
是 Nacos 中注册实例的生命周期设置:true
(默认):临时实例,心跳断开后自动删除;false:持久实例,即使服务宕机,也不会从注册中心自动移除,必须手动下线如果你使用的是 Kubernetes、Docker 等容器环境,推荐用临时实例(ephemeral: true
),自动上下线更方便;
如果你希望服务下线必须人为控制,或者用于 Dubbo RPC 注册中心这类对实例稳定性要求高的场景,可使用 ephemeral: false
。
(4)为什么必须用 bootstrap.yml
配置?
生命周期差异:
阶段 | bootstrap.yml |
application.yml |
---|---|---|
加载时机 | 应用启动的最早期 | 主上下文初始化时 |
典型用途 | 连接配置中心、注册中心等基础设施 | 业务相关配置 |
能否访问远程配置 | ✅ 是 | ❌ 否(此时远程配置尚未加载) |
依赖关系:Spring Cloud 应用启动时,需要先获取配置中心的地址,才能拉取远程配置。如果将这些配置放在 application.yml
中,会导致循环依赖问题(先有鸡还是先有蛋)。
(5)注意事项
版本兼容性:若使用 Spring Cloud 2020.0.0(含)以上版本,需额外添加依赖以启用引导上下文:
org.springframework.cloud
spring-cloud-starter-bootstrap
配置覆盖规则:bootstrap.yml
→ bootstrap-{profile}.yml
→ application.yml
→ application-{profile}.yml
每个微服务都需要独立的
bootstrap.yml
文件,但配置内容可以部分共享。
关键点:独立性:每个微服务通过
spring.application.name
定义自己的唯一标识(决定 Nacos 中的 Data ID)。共享配置:公共配置(如 Nacos 服务器地址、命名空间等)可以通过 环境变量、Maven/Gradle 多环境配置 或 外部化配置 统一管理,避免重复。
服务注册与发现是微服务架构的基础组件,用于管理和定位服务实例,帮助实现服务之间的动态通信和解耦。通过注册中心,服务提供者可以注册自身信息,服务消费者可以根据服务名动态发现目标服务,从而避免硬编码服务地址的问题。
Nacos 是由阿里巴巴开源的一个易用的动态服务发现、配置管理和服务治理平台。相较于其他注册中心(Eureka、Consul),具有明显的配置管理与动态扩展方面的优势。
它是一个专门用于服务注册和发现的程序,同时具备配置管理功能。在微服务架构中,它类似于一个“电话簿”,服务提供者将自己的信息注册到 Nacos,服务消费者通过 Nacos 动态发现目标服务,而无需硬编码服务的地址。
服务提供者注册
服务提供者启动后,会通过 Nacos 客户端向 Nacos 注册自己的服务实例信息,包括:
- 服务名称:如
user-service
。- 实例信息:包括实例的 IP 和端口。
- 元数据:如服务版本、环境信息。
注册的信息存储在 Nacos 的注册表中。
服务消费者发现
服务消费者调用其他服务时,会通过 Nacos 客户端向注册中心查询目标服务的信息,动态获取服务实例的地址列表,进行负载均衡调用。动态维护
- 心跳机制:服务实例定期向 Nacos 发送心跳,证明自己还在运行。
- 服务下线:如果服务实例长时间未发送心跳,Nacos 会将其标记为不可用并从注册表中移除。
服务注册是指微服务实例启动时,将自己的地址(IP 和端口)、服务名及元数据注册到注册中心,供其他服务发现和调用。服务注册是服务通信的基础,确保微服务可以通过服务名动态定位。
1.服务启动时注册: 服务实例在启动时,通过注册客户端(如 nacos-client
或 eureka-client
)向注册中心发送注册请求,包含以下信息:
user-service
)。2.注册中心保存信息: 注册中心将这些信息存储在注册表中,以供其他服务发现。
3.动态更新和维护:
(1)安装 Nacos 注册中心
下载 Nacos:发布历史 | Nacos 官网。
启动 Nacos:
.\startup.cmd -m standalone
http://localhost:8848/nacos
。(2)Spring Boot 项目集成 Nacos
引入依赖: 在 pom.xml
文件中添加以下依赖:
SpringCloud 2021.* 版本把bootstrap禁用了,导致在读取文件的时候读取不到而报空指针
解决:我们只要把spring-cloud-starter-bootstrap导入进来就会生效了。 (一定要导入)
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
2022.0.0.0-RC1
org.springframework.cloud
spring-cloud-starter-bootstrap
配置服务注册信息: 在 application.yml
文件中,添加如下配置:
server:
port: 8082
spring:
application:
name: order-server
profiles:
active: dev #测试环境
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: 你自定义的命名空间 # 这里需要单独为discovery配置命名空间
config:
server-addr: localhost:8848
namespace: 你自定义的命名空间
file-extension: yaml
启用服务注册: 在主类中添加 @EnableDiscoveryClient
注解,启用服务注册功能:
@SpringBootApplication
@EnableDiscoveryClient // 启用服务注册功能
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
启动服务并验证:
user-service
项目。http://localhost:8848/nacos
),在 服务管理 -> 服务列表 中,可以看到 user-service
已成功注册。服务发现是指服务消费者通过注册中心动态获取目标服务的实例信息(IP 地址、端口等),并发起调用。
通过服务发现,服务之间无需提前配置地址,解决了服务动态扩缩容和部署复杂性问题。
product-service
)。(1)创建服务提供者
新建服务 product-service
: 提供一个简单的商品查询接口:
@RestController
@RequestMapping("/product")
public class ProductController {
@GetMapping("/{id}")
public String getProduct(@PathVariable String id) {
return "Product ID: " + id;
}
}
配置注册信息: 在 application.properties
中:
spring.application.name=product-service # 服务名
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 # 注册中心地址
server.port=8081 # 服务端口
(2)创建服务消费者
新建服务 user-service
: 消费者服务调用 product-service
。
引入 Feign 依赖: 在 pom.xml
文件中添加:
org.springframework.cloud
spring-cloud-starter-openfeign
定义 Feign 客户端: 使用 @FeignClient
调用远程服务:
@FeignClient("product-service") // 指定调用的目标服务
public interface ProductClient {
@GetMapping("/product/{id}")
String getProduct(@PathVariable("id") String id);
}
消费者 Controller: 调用 product-service
的接口:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private ProductClient productClient;
@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
return productClient.getProduct(id);
}
}
配置注册信息: 在 application.properties
中:
spring.application.name=user-service # 服务名
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 # 注册中心地址
server.port=8082 # 服务端口
启动服务并验证:
product-service
和 user-service
。http://localhost:8082/user/product/123
。Product ID: 123
服务健康检查用于检测服务实例的运行状态,确保注册表中的服务是健康可用的。如果某服务实例不可用,注册中心会将其从注册表中移除,避免请求失败。
主动心跳检测:
被动健康检查:
product-service
。product-service
的实例状态变为不健康,并被移除。服务间通信是微服务架构中的核心环节,用于实现服务之间的协作。在微服务中,服务之间通常通过 HTTP 通信来实现数据交互。Spring Cloud 提供了多种方式来实现服务间通信,包括 Feign 和 RestTemplate,同时结合 Spring Cloud LoadBalancer 实现负载均衡。
Feign 是 Spring Cloud 提供的声明式服务调用工具,它将 HTTP 请求抽象成 Java 接口,开发者只需定义接口方法和注解,就可以调用远程服务,无需手动构造 HTTP 请求。
@FeignClient
注解标注目标服务名称。@GetMapping
、@PostMapping
)指定 HTTP 请求的路径和参数。(1)引入依赖
在 pom.xml
中添加 Feign 依赖:
org.springframework.cloud
spring-cloud-starter-openfeign
(2)配置 Feign
在 application.properties
中:
spring.application.name=user-service
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 # Nacos 地址
server.port=8080
(3)定义 Feign 客户端
创建一个接口,用于调用远程服务:
@FeignClient("product-service") // 指定目标服务名称
public interface ProductClient {
@GetMapping("/product/{id}")
String getProduct(@PathVariable("id") String id);
}
(4)使用 Feign 客户端
在 Controller 中注入并使用 Feign 客户端:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private ProductClient productClient;
@GetMapping("/product/{id}")
public String getProduct(@PathVariable String id) {
return productClient.getProduct(id); // 通过 Feign 调用远程服务
}
}
(5)启用 Feign
在主类上添加 @EnableFeignClients
注解:
@SpringBootApplication
@EnableFeignClients // 启用 Feign 功能
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
(6)测试服务调用
启动 product-service
和 user-service
后,访问:
http://localhost:8080/user/product/123
返回结果:
Product ID: 123
在远程调用中,为了防止请求阻塞,可以设置超时时间和重试策略。
(1)超时配置: 在 application.properties
中:
feign.client.config.default.connectTimeout=5000 # 连接超时(毫秒)
feign.client.config.default.readTimeout=10000 # 读取超时(毫秒)
(2)重试配置: 通过 Feign 的配置启用重试:
feign.client.config.default.retryer=Retryer.Default
feign.client.config.default.maxAttempts=3 # 最大重试次数
feign.client.config.default.backoff=2000 # 重试间隔(毫秒)
Feign 提供了日志功能,可以记录请求和响应的详细信息,便于调试。
(1)启用 Feign 日志: 在 application.properties
中:
logging.level.feign=DEBUG # 将 Feign 日志级别设置为 DEBUG
(2)自定义日志级别: 通过配置类设置日志级别:
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // FULL 表示记录完整的请求和响应数据
}
}
通过拦截器从当前请求的上下文中获取 X-User-Id
,并添加到 Feign 请求头中。
@Component
public class FeignUserInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 从当前请求的上下文(如ThreadLocal)获取用户ID
String userId = UserContext.getUserId();
if (userId != null) {
template.header("X-User-Id", userId);
}
}
}
RestTemplate 是 Spring 提供的同步 HTTP 客户端工具,用于构造和发送 HTTP 请求。虽然 RestTemplate 功能强大,但需要手动处理服务地址,代码复杂度较高。
(1)引入依赖
Spring Boot 默认包含 RestTemplate 的依赖,无需额外引入。
(2)定义 RestTemplate Bean
在配置类中定义 RestTemplate Bean:
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 启用负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
(3)使用 RestTemplate
在代码中调用远程服务:
@Autowired
private RestTemplate restTemplate;
@GetMapping("/user/product/{id}")
public String getProduct(@PathVariable String id) {
String url = "http://product-service/product/" + id; // 服务名替代硬编码地址
return restTemplate.getForObject(url, String.class);
}
Spring Cloud LoadBalancer 是 Spring Cloud 提供的客户端负载均衡器,支持根据服务名动态解析服务实例列表,并在请求时自动选择一个实例。
product-service
)向 Spring Cloud LoadBalancer 发起请求。在 application.properties
中无需额外配置,默认启用。
Spring Cloud LoadBalancer 默认使用 轮询策略(Round Robin)。
修改为随机策略
创建配置类: 自定义负载均衡策略为随机(Random):
@Configuration
public class LoadBalancerConfig {
@Bean
public ReactorLoadBalancer randomLoadBalancer(Environment environment,
LoadBalancerClientFactory clientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(clientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
API 网关是微服务架构中的入口组件,用于实现路由转发、统一认证、流量控制等功能。Spring Cloud Gateway 是目前推荐的高性能网关解决方案,基于非阻塞的 WebFlux,提供灵活的路由和过滤器机制。
API 网关的核心功能:
在 pom.xml
文件中添加以下依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
2022.0.0.0-RC1
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
2022.0.0.0-RC1
org.springframework.cloud
spring-cloud-starter-bootstrap
org.springframework.cloud
spring-cloud-starter-gateway
org.springframework.cloud
spring-cloud-starter-loadbalancer
spring-cloud-starter-gateway
:提供网关的核心功能。spring-boot-starter-data-redis
:为限流功能提供支持。在 application.yml
文件中配置简单的路由规则:
server:
port: 9000
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 启用注册中心路由功能
lower-case-service-id: true # 将服务名转小写(URL美观)
globalcors: # 跨域配置
corsConfigurations:
'[/**]':
allowedOriginPatterns: "*"
allowedMethods: "*"
allowedHeaders: "*"
allowCredentials: true
routes: # 这一行必须和 discovery/globalcors 同级!
- id: order-server # 路由ID,没有固定要求,但要求保证唯一,建议配合服务名
uri: lb://order-server # 使用注册中心中的服务名,lb:// 表示使用负载均衡
predicates:
- Path=/order/** #当路径匹配/order/** 时,将请求转发到order-server服务
- id: resource-server
uri: lb://resource-server
predicates:
- Path=/resource/**
id
:路由规则的唯一标识。uri
:目标服务的地址,可以是具体的 http://localhost:8081
或服务名(结合服务注册中心使用)。predicates
:路由的条件,Path
表示按路径规则转发请求。启动 user-service
(8081)和 product-service
(8082),访问:
http://localhost:8080/user/123
转发到 http://localhost:8081/user/123
http://localhost:8080/product/456
转发到 http://localhost:8082/product/456
Spring Cloud Gateway 可以结合服务注册中心(如 Nacos、Eureka)实现动态路由。动态路由会根据注册的服务名生成路由规则,避免手动配置。
在 application.yml
中启用服务发现:
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 启用服务发现
lower-case-service-id: true # 服务名自动转换为小写
http://localhost:8080/user-service/**
会自动转发到 user-service
的实例。http://localhost:8080/product-service/**
会自动转发到 product-service
的实例。http://localhost:8080/user-service/user/123
http://localhost:8080/product-service/product/456
过滤器是 Gateway 的核心组件,可用于拦截和修改请求或响应,实现日志记录、鉴权等功能。
全局过滤器示例
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 日志记录示例
String path = exchange.getRequest().getPath().toString();
System.out.println("Request Path: " + path);
// 添加自定义请求头
exchange.getRequest().mutate().header("X-Custom-Header", "CustomValue");
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; // 优先级,值越小优先级越高
}
}
功能解析
测试过滤器 启动网关服务,访问任意路由地址,例如 /user/123
,在控制台可以看到日志输出。
限流功能通过 Redis 实现,使用 RequestRateLimiter
过滤器限制请求速率。
在 application.yml
文件中:
spring:
cloud:
gateway:
routes:
- id: user-service-route
uri: http://localhost:8081
predicates:
- Path=/user/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 每秒生成的令牌数
redis-rate-limiter.burstCapacity: 20 # 最大令牌数
key-resolver: "#{@remoteAddrKeyResolver}" # 限流的唯一标识(按 IP)
redis-rate-limiter.replenishRate
:每秒生成的令牌数(允许的请求数)。redis-rate-limiter.burstCapacity
:令牌桶的最大容量(允许的瞬时请求量)。key-resolver
:限流依据,这里使用客户端 IP 作为唯一标识。在配置类中定义限流的 KeyResolver:
@Configuration
public class RateLimiterConfig {
@Bean
public KeyResolver remoteAddrKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
}
}
在微服务架构中,随着服务数量的增加,不同服务需要管理大量的配置信息,例如数据库连接信息、服务端口、日志级别等。这些配置可能因环境(开发、测试、生产)不同而有所变化。配置管理的目标是集中化、动态化地管理这些配置文件,提升维护效率和系统灵活性。
推荐方案:Nacos 配置中心
集成方便、功能丰富,推荐在 Spring Cloud 项目中作为配置管理工具。
配合动态刷新和命名空间,能够满足多环境配置的需求。
备选方案:Spring Cloud Config
配置存储依赖 Git 或 SVN,更适合需要版本控制的场景。
配置刷新需要结合消息队列(如 RabbitMQ、Kafka)才能实现动态更新。
配置中心是一个专门用来集中管理微服务配置的服务,能够提供以下功能:
Nacos 是阿里巴巴开源的微服务解决方案,既能充当服务注册中心,又可以作为配置中心使用,推荐在 Spring Cloud 微服务中使用。
(1)安装和启动 Nacos
下载 Nacos:下载链接。
启动 Nacos:
sh startup.sh -m standalone
访问 Nacos 控制台:http://localhost:8848/nacos
。
(2)Spring Boot 集成 Nacos 配置
引入依赖 在 pom.xml
中添加 Nacos 的配置依赖:
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config
2022.0.0.0-RC1
配置 bootstrap.yml
配置与 Nacos 的连接信息:
server:
port: 8082
spring:
application:
name: order-server
profiles:
active: dev #测试环境
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: 你自定义的命名空间 # 这里需要单独为discovery配置命名空间
config:
server-addr: localhost:8848
namespace: 你自定义的命名空间
file-extension: yaml
创建 Nacos 配置文件 在 Nacos 控制台添加配置:
config-demo.yaml
(对应服务名称加后缀)DEFAULT_GROUP
(默认分组,可自定义)custom:
message: Hello from Nacos! # 自定义配置
在代码中读取配置 使用 @Value
或 @ConfigurationProperties
注解读取配置:
@RestController
@RequestMapping("/config")
public class ConfigController {
@Value("${custom.message}")
private String message;
@GetMapping("/message")
public String getMessage() {
return message;
}
}
测试配置 启动服务后,访问 http://localhost:8080/config/message
,输出:
Hello from Nacos!
编写配置类并使用 @RefreshScope 注解
@Component
@RefreshScope // 允许配置刷新
public class MyConfig {
@Value("${my.name}")
private String name;
@Value("${my.age}")
private int age;
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
测试动态刷新
custom.message
内容。http://localhost:8080/config/message
,即可看到实时更新的内容。Spring Cloud Config 是另一种配置管理方案,支持将配置文件存储在 Git、SVN 等版本管理工具中,并通过 Config Server 提供统一的访问接口。
架构图
(1)创建 Config Server
引入依赖 在 pom.xml
中添加 Config Server 依赖:
org.springframework.cloud
spring-cloud-config-server
配置 application.yml
配置 Config Server 的 Git 仓库地址:
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/your-repo/config-repo.git # Git 仓库地址
search-paths: config # 配置文件所在目录
server:
port: 8888
启动类 启用 Config Server 功能:
@SpringBootApplication
@EnableConfigServer // 启用 Config Server
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
(2)创建 Config Client
引入依赖 在 pom.xml
中添加 Config Client 依赖:
org.springframework.cloud
spring-cloud-starter-config
配置 bootstrap.yml
配置客户端与 Config Server 的连接:
spring:
application:
name: client-demo
cloud:
config:
uri: http://localhost:8888 # Config Server 地址
读取配置 在代码中读取配置内容:
@RestController
@RequestMapping("/config")
public class ConfigController {
@Value("${custom.message}")
private String message;
@GetMapping("/message")
public String getMessage() {
return message;
}
}
引入依赖 在 pom.xml
中添加 Spring Cloud Bus 的依赖:
org.springframework.cloud
spring-cloud-starter-bus-amqp
org.springframework.boot
spring-boot-starter-actuator
配置 application.yml
配置消息队列(如 RabbitMQ):
spring:
cloud:
bus:
enabled: true
rabbitmq:
host: localhost
username: guest
password: guest
刷新配置
/actuator/bus-refresh
发送 POST 请求:
curl -X POST http://localhost:8080/actuator/bus-refresh
功能 | Nacos 配置中心 | Spring Cloud Config |
---|---|---|
配置存储 | 内置存储,支持多种格式(YAML、JSON 等) | 基于 Git 或其他版本管理工具 |
动态刷新 | 原生支持动态刷新 | 需结合 Spring Cloud Bus 实现 |
多环境支持 | 支持命名空间、分组管理环境 | 支持环境的配置文件分层(如 dev 、prod ) |
操作界面 | 提供友好的 Web 管理界面 | 无界面,仅支持 Git/SVN 管理 |
服务容错与熔断的目的是在微服务系统中保证部分功能可用,避免服务雪崩效应。当某些服务不可用时,系统通过熔断、降级、限流等方式保障服务的整体稳定性。
- 熔断、降级、重试:推荐使用 Resilience4j,简单轻量,功能满足大部分需求。
- 限流与监控:推荐使用 Sentinel,支持复杂规则,且提供完善的监控界面。
服务容错
服务容错是指在服务调用失败或超时时,提供备用处理逻辑,保证系统的部分功能仍然可用。
熔断
熔断是一种保护机制,当某服务的失败率达到一定阈值时,短路请求,不再调用该服务。熔断器一段时间后会进入“半开”状态,尝试恢复调用。
服务降级
服务降级是在目标服务不可用或超时时,为调用方提供备用处理逻辑,避免用户体验过度受影响。
限流
限流是通过限制接口的访问频率或并发量,防止服务因流量过大而崩溃。
Resilience4j 是一款轻量级的服务容错库,支持熔断器、限流、重试等功能。
熔断器用于检测服务调用的健康状况,并在服务连续失败时短路请求。Resilience4j 的熔断器支持三种状态:
配置熔断器
引入依赖 在 pom.xml
文件中添加 Resilience4j 的依赖:
io.github.resilience4j
resilience4j-spring-boot2
在 application.yml
文件中配置熔断规则
resilience4j:
circuitbreaker:
instances:
demoCircuitBreaker: # 熔断器名称
sliding-window-size: 10 # 滑动窗口的大小
failure-rate-threshold: 50 # 失败率阈值(%)
wait-duration-in-open-state: 5000ms # 熔断后等待时间(毫秒)
permitted-number-of-calls-in-half-open-state: 3 # 半开状态时允许的调用数量
解释:
sliding-window-size
:统计最近多少次请求(窗口大小)。failure-rate-threshold
:当请求失败率超过 50% 时触发熔断。wait-duration-in-open-state
:熔断器打开后的等待时间,时间到后进入半开状态。permitted-number-of-calls-in-half-open-state
:半开状态允许的调用次数,用于测试服务恢复情况。在代码中使用熔断器
@RestController
public class DemoController {
@Autowired
private RestTemplate restTemplate;
// 通过注解使用熔断器
@CircuitBreaker(name = "demoCircuitBreaker", fallbackMethod = "fallback")
@GetMapping("/api/demo")
public String callExternalService() {
// 模拟调用外部服务
return restTemplate.getForObject("http://external-service/api", String.class);
}
// 熔断后触发的降级逻辑
public String fallback(Throwable t) {
return "Service is unavailable. Please try again later.";
}
}
代码解释:
@CircuitBreaker(name = "demoCircuitBreaker")
:
demoCircuitBreaker
。application.yml
中读取。fallbackMethod
:
fallback
方法的参数类型为 Throwable
,用于捕获异常信息。RestTemplate.getForObject()
:
http://external-service/api
。测试熔断器
external-service
服务连续失败。/api/demo
,观察是否返回降级响应 Service is unavailable.
。降级逻辑是在目标服务不可用时返回备用响应,保证系统部分功能可用。
降级代码实现
降级逻辑已通过 fallbackMethod
实现,下面补充其工作机制:
// 原服务调用方法
@CircuitBreaker(name = "demoCircuitBreaker", fallbackMethod = "fallback")
public String callExternalService() {
return restTemplate.getForObject("http://external-service/api", String.class);
}
// 降级方法
public String fallback(Throwable t) {
return "Fallback response: external service is down.";
}
详细解析:
http://external-service/api
时发生以下情况之一时,触发 fallback
方法:
重试机制用于对失败的调用按照设定的规则重新尝试。
配置重试规则
在 application.yml
文件中:
resilience4j:
retry:
instances:
demoRetry: # 重试策略的名称
max-attempts: 3 # 最大重试次数
wait-duration: 1000ms # 每次重试间隔
代码实现重试
@RestController
public class RetryController {
@Retry(name = "demoRetry", fallbackMethod = "retryFallback")
@GetMapping("/api/retry")
public String callWithRetry() {
// 模拟调用外部服务
return restTemplate.getForObject("http://external-service/api", String.class);
}
// 重试失败后的降级逻辑
public String retryFallback(Throwable t) {
return "Retry failed after multiple attempts.";
}
}
详细解析:
@Retry(name = "demoRetry")
:
demoRetry
。max-attempts
和 wait-duration
控制重试行为。retryFallback
:
Sentinel 支持多种限流方式,如 QPS 限制、并发线程限制。
配置 Sentinel
引入依赖 在 pom.xml
中添加:
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
在 application.yml
中配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # Sentinel Dashboard 地址
定义限流规则 使用 @SentinelResource
注解:
@RestController
public class SentinelDemoController {
@GetMapping("/api/sentinel")
@SentinelResource(value = "sentinelDemo", blockHandler = "blockHandler")
public String sentinelDemo() {
return "Request processed.";
}
// 限流触发时的逻辑
public String blockHandler(BlockException ex) {
return "Request has been limited. Please try again later.";
}
}
代码解释:
@SentinelResource(value = "sentinelDemo")
:
sentinelDemo
。blockHandler
:
BlockException
,表示限流异常。测试限流
java -jar sentinel-dashboard.jar
)。sentinelDemo
配置 QPS 限制为 5。启动 Sentinel Dashboard 下载并启动 Sentinel Dashboard:
java -jar sentinel-dashboard.jar
连接到 Dashboard 服务启动后,Sentinel 会自动将实例信息注册到 Dashboard,可以在界面上查看限流规则和流量统计。
功能 | Resilience4j | Sentinel |
---|---|---|
熔断降级 | 通过 @CircuitBreaker 实现,支持多种配置规则 |
提供丰富的熔断策略,结合 Dashboard 实时监控 |
限流 | 支持基本限流功能 | 提供多种限流方式(QPS、线程数),支持动态规则调整 |
重试 | 使用 @Retry 实现,配置灵活 |
不提供原生重试功能 |
监控界面 | 无原生监控界面,需结合 Prometheus 或 Micrometer 实现 | 提供完整的 Dashboard,可实时查看规则和流量数据 |
在微服务架构中,服务之间存在复杂的调用关系。排查问题时,单个服务的日志通常无法反映全局问题。分布式追踪通过记录请求在各服务中的流转过程,让开发者可以全面了解请求的执行路径和性能瓶颈。
- 使用 Spring Cloud Sleuth 配合 Zipkin,实现分布式追踪的基础功能。
- 在需要复杂性能分析和扩展性的场景下,可选择 Jaeger。
分布式追踪是一种记录请求跨服务调用行为的方法。通过为每个请求生成唯一标识,记录请求经过的服务、操作和耗时,从而提供一条完整的调用链。
Spring Cloud Sleuth 是 Spring 提供的分布式追踪工具,与 Zipkin、Jaeger 等工具无缝集成,用于生成和传递 TraceId 和 SpanId。
(1)引入依赖
在 pom.xml
中添加 Sleuth 和日志依赖:
org.springframework.cloud
spring-cloud-starter-sleuth
org.springframework.cloud
spring-cloud-starter-zipkin
(2)配置 Sleuth
在 application.yml
中添加以下配置:
spring:
application:
name: order-service # 服务名称
sleuth:
sampler:
probability: 1.0 # 采样率(1.0 表示记录所有请求)
zipkin:
base-url: http://localhost:9411 # Zipkin 服务地址
enabled: true # 启用 Zipkin
解释:
spring.application.name
:服务的名称,用于区分调用链中的服务。
sleuth.sampler.probability
:采样率,1.0
表示记录所有请求,0.5
表示记录 50% 的请求。
zipkin.base-url
:Zipkin 的服务地址,Sleuth 会自动将追踪数据发送到 Zipkin。
(3)日志集成
Sleuth 会自动将 TraceId 和 SpanId 添加到日志中,无需额外配置。示例日志输出格式:
2024-11-15 12:00:00.123 INFO [order-service,abc123,xyz456] 12 --- [nio-8080-exec-1] c.e.OrderController : Processing order
[order-service,abc123,xyz456]
:
order-service
:当前服务名。
abc123
:TraceId,标识整个请求。
xyz456
:SpanId,标识当前操作。
(4)模拟服务间调用
假设有两个服务:order-service
和 payment-service
。
在 order-service
中通过 RestTemplate
调用 payment-service
。
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/{id}")
public String createOrder(@PathVariable String id) {
// 调用 payment-service
String paymentStatus = restTemplate.getForObject("http://payment-service/payment/" + id, String.class);
return "Order created with Payment Status: " + paymentStatus;
}
}
配置 RestTemplate
:
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced // 支持服务发现
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在日志中可以看到完整的调用链信息,包括 TraceId 和 SpanId。
Zipkin 是一个分布式追踪系统,用于收集、存储和可视化追踪数据。它与 Spring Cloud Sleuth 集成,可以展示请求的调用链路和每个操作的耗时。
(1)安装 Zipkin
下载 Zipkin 的可执行 Jar 包:下载地址。
启动 Zipkin:
java -jar zipkin-server-2.23.2-exec.jar
访问 Zipkin 控制台:http://localhost:9411
(2)配置 Sleuth 发送数据到 Zipkin
在 application.yml
中添加:
spring:
zipkin:
base-url: http://localhost:9411 # Zipkin 地址
enabled: true # 启用 Zipkin
(3)调用链展示
启动 order-service
和 payment-service
。
调用 http://localhost:8080/order/123
。
打开 Zipkin 控制台,查询 TraceId
,可以看到完整的调用链:
Order-Service --> Payment-Service
每个服务的耗时会以图形化的形式展示。
Jaeger 是另一种分布式追踪系统,与 Zipkin 类似,提供分布式调用链的可视化功能。它更注重性能监控和扩展性,适合大规模系统。
安装 Jaeger
使用 Docker 启动 Jaeger:
docker run -d --name jaeger \
-e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
-p 5775:5775/udp \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 14268:14268 \
-p 14250:14250 \
-p 9411:9411 \
jaegertracing/all-in-one:1.33
访问 Jaeger 控制台:http://localhost:16686
集成 Jaeger
与 Zipkin 集成类似,将 Zipkin 的 base-url
替换为 Jaeger 的地址。
分布式追踪的一个重要功能是性能分析,通过展示每个服务的耗时帮助开发者优化系统。
示例分析
在 Zipkin 中查看某个 Trace。
点击某个服务(如 payment-service
),查看其详细耗时:
如果某个服务的耗时明显较长,可以深入分析具体原因(如数据库查询慢、依赖服务超时)。
功能 | Spring Cloud Sleuth | Zipkin | Jaeger |
---|---|---|---|
核心功能 | 生成并传递 TraceId 和 SpanId | 可视化调用链和性能数据 | 更强的扩展性和性能分析功能 |
日志集成 | 自动将 TraceId 和 SpanId 注入日志 | 无日志功能 | 无日志功能 |
界面 | 无可视化界面,需要结合 Zipkin 或 Jaeger | 提供简单直观的调用链可视化界面 | 提供调用链和性能分析界面,支持大规模分布式系统 |
适用场景 | 微服务系统的基础追踪 | 中小型系统,便于快速定位调用链问题 | 大型分布式系统,适合深入性能优化 |
在微服务架构中,服务之间的异步通信是解耦服务、提高系统弹性的重要手段。通过消息队列,可以在不同服务之间传递数据,实现松耦合,同时支持削峰填谷、流量控制等功能。
消息驱动是一种微服务之间的异步通信模型,通过消息中间件(如 RabbitMQ、Kafka 等)实现服务之间的解耦和消息传递。消息驱动使得微服务系统在高并发和高可靠性场景下更加灵活和高效。
Spring Cloud Stream 是 Spring 提供的消息驱动框架,用于统一处理消息的生产与消费。它支持多种消息中间件(如 RabbitMQ、Kafka),通过抽象化的 API 隐藏了底层消息队列的具体实现,降低了开发复杂度。
Binding(绑定):
Source
)和消费者(Sink
)。Binder(绑定器):
消息通道:
output
):生产者通过输出通道发送消息。input
):消费者通过输入通道接收消息。Stream API:
(1)引入依赖
以 RabbitMQ 为例,在 pom.xml
中添加以下依赖:
org.springframework.cloud
spring-cloud-starter-stream-rabbit
(2)配置 application.yml
spring:
application:
name: message-service # 服务名称
cloud:
stream:
bindings:
output: # 定义生产者通道
destination: my-topic # 指定目标队列/主题
content-type: application/json # 消息内容类型
input: # 定义消费者通道
destination: my-topic # 指定监听的队列/主题
group: message-group # 消费者组(Kafka 使用)
rabbit:
bindings:
input:
consumer:
acknowledge-mode: MANUAL # 手动确认消息
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
配置解析:
destination
:绑定的消息队列或主题。group
:消费者组(Kafka 场景)。acknowledge-mode
:消费者手动确认消息的模式,避免消息丢失。(1)消息生产
使用 MessageChannel
发送消息。
@EnableBinding(Source.class) // 启用消息绑定
@RestController
@RequestMapping("/message")
public class MessageProducer {
@Autowired
private MessageChannel output; // 自动绑定到配置中的 output 通道
@PostMapping("/send")
public String sendMessage(@RequestBody String payload) {
// 使用 MessageBuilder 构建消息并发送
output.send(MessageBuilder.withPayload(payload).build());
return "Message sent: " + payload;
}
}
代码解析:
@EnableBinding(Source.class)
:
启用生产者绑定,将 output
通道与 my-topic
队列绑定。
MessageChannel
:
Spring Cloud Stream 提供的消息通道接口,用于发送消息。
MessageBuilder
:
构建消息对象,包含消息内容和元数据。
(2)消息消费
使用 @StreamListener
监听消息。
@EnableBinding(Sink.class) // 启用消费者绑定
public class MessageConsumer {
@StreamListener(Sink.INPUT) // 监听 input 通道
public void handleMessage(String message) {
System.out.println("Received message: " + message);
}
}
代码解析:
@EnableBinding(Sink.class)
:
input
通道与 my-topic
队列绑定。@StreamListener(Sink.INPUT)
:
input
通道,处理接收到的消息。启动 RabbitMQ 服务。
启动 message-service
。
通过 Postman 或其他工具发送请求:
POST http://localhost:8080/message/send
Body: "Hello, RabbitMQ!"
消费端打印:
Received message: Hello, RabbitMQ!
RabbitMQ 是基于 AMQP(Advanced Message Queuing Protocol)协议的消息中间件,支持多种消息模型(如点对点、发布订阅)。它以可靠性和灵活性著称,适合中小型系统的消息驱动场景。
Producer(生产者)
Consumer(消费者)
Queue(队列)
Exchange(交换机)
Binding(绑定)
Message(消息)
(1)点对点模型
消息被发送到一个队列,且每条消息只能被一个消费者处理。
场景:订单服务发送订单创建消息到队列,库存服务从队列消费消息。
(2)发布订阅模型
消息通过 Fanout 类型的交换机广播到所有绑定的队列,每个队列的消费者都能收到消息。
场景:用户注册后,发送消息通知多个服务(如发送邮件、推送消息)。
(3)路由模型
消息通过 Direct 类型的交换机根据路由键分发到匹配的队列。
场景:日志系统按日志级别(如 INFO、ERROR)将日志消息分发到不同的队列。
(4)主题模型
消息通过 Topic 类型的交换机按路由键模式匹配分发到队列,支持模糊匹配。
order.payment.success
)分发订单状态更新消息。(1)安装 RabbitMQ
下载并安装 RabbitMQ:RabbitMQ 下载。
启动 RabbitMQ:
rabbitmq-server
启用管理插件:
rabbitmq-plugins enable rabbitmq_management
访问管理界面:http://localhost:15672
默认用户名和密码:guest
/guest
。
(2)Spring Boot 集成 RabbitMQ
引入依赖
在 pom.xml
中添加 RabbitMQ 的依赖:
org.springframework.boot
spring-boot-starter-amqp
配置 application.yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
(3)实现消息生产与消费
消息生产者
生产者将消息发送到队列。
@RestController
@RequestMapping("/producer")
public class ProducerController {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostMapping("/send")
public String sendMessage(@RequestBody String message) {
// 发送消息到默认交换机和队列
rabbitTemplate.convertAndSend("my-exchange", "my-routing-key", message);
return "Message sent: " + message;
}
}
代码解释:
RabbitTemplate
:Spring 提供的 RabbitMQ 操作模板。
convertAndSend
:
第一个参数是交换机名称。
第二个参数是路由键。
第三个参数是消息内容。
消息消费者
消费者从队列中接收消息。
@Component
public class Consumer {
@RabbitListener(queues = "my-queue")
public void receiveMessage(String message) {
System.out.println("Received message: " + message);
}
}
代码解释:
@RabbitListener
:声明消费者监听的队列名称。
receiveMessage
:当队列中有新消息时自动触发处理逻辑。
(4)完整配置队列与交换机
RabbitMQ 需要提前声明队列、交换机和绑定关系。Spring 提供了 @Bean
注解的方式进行配置。
@Configuration
public class RabbitConfig {
@Bean
public Queue queue() {
return new Queue("my-queue", true); // 队列名称,是否持久化
}
@Bean
public DirectExchange exchange() {
return new DirectExchange("my-exchange"); // 交换机名称
}
@Bean
public Binding binding(Queue queue, DirectExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("my-routing-key");
}
}
代码解释:
Queue
:声明队列 my-queue
,并设置持久化。
DirectExchange
:声明 Direct 类型的交换机 my-exchange
。
Binding
:将队列和交换机通过路由键 my-routing-key
绑定。
RabbitMQ 提供三种消息确认机制,确保消息可靠传递:
(1)生产者确认
生产者发送消息到交换机后,交换机会返回确认。
配置生产者确认:
spring:
rabbitmq:
publisher-confirm-type: correlated # 启用发布确认
示例代码:
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
System.out.println("Message delivered successfully.");
} else {
System.out.println("Message delivery failed: " + cause);
}
});
(2)消费者确认
消费者接收到消息后,需要手动确认消息已处理。
配置消费者手动确认:
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 手动确认
示例代码:
@RabbitListener(queues = "my-queue")
public void receiveMessage(String message, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
try {
System.out.println("Processing message: " + message);
channel.basicAck(deliveryTag, false); // 手动确认
} catch (Exception e) {
channel.basicNack(deliveryTag, false, true); // 拒绝并重新投递
}
}
(1)消息重试
在消息消费失败时,RabbitMQ 可以自动重试。
配置重试策略:
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true
initial-interval: 1000 # 初始重试间隔
max-attempts: 3 # 最大重试次数
multiplier: 2.0 # 间隔倍增
(2)幂等处理
为避免消息重复消费,可引入唯一标识(如消息 ID),并记录到数据库进行去重。
示例代码:
@RabbitListener(queues = "my-queue")
public void handleMessage(String message) {
String messageId = extractMessageId(message);
if (!isProcessed(messageId)) {
processMessage(message);
markAsProcessed(messageId); // 标记消息已处理
} else {
System.out.println("Duplicate message ignored: " + messageId);
}
}
Kafka 是一个分布式流处理平台,最初由 LinkedIn 开发,后开源成为 Apache 项目。它以高吞吐量、高可用性和持久化特性著称,广泛用于日志收集、实时数据流处理等场景。
1. Producer(生产者)
生产者负责将消息发送到 Kafka 的指定主题(Topic)。生产者可以自定义分区策略,将消息分发到不同的分区。
2. Consumer(消费者)
消费者从 Kafka 的主题中拉取消息。一个主题可以有多个消费者,消费者可以组成消费者组(Consumer Group)。
3. Topic(主题)
主题是 Kafka 中消息的逻辑分类,每条消息必须归属于一个主题。主题可以有多个分区。
4. Partition(分区)
每个主题可以有多个分区,消息会分布到不同的分区中。
分区的好处:
提高并发处理能力。
实现消息的有序性(分区内有序)。
支持数据分布在不同的节点上。
5. Offset(偏移量)
Kafka 为每条消息分配一个唯一的 Offset,消费者通过 Offset 记录读取位置。
6. Broker
Kafka 集群中的节点称为 Broker,负责存储和管理消息。
7. ZooKeeper
Kafka 使用 ZooKeeper 管理集群的元数据(如分区、Broker 信息)。新版 Kafka 提供了 ZooKeeper-less 模式,支持无需 ZooKeeper 的运行。
Kafka 的消息模型支持以下场景:
1. 点对点模式
消息被发送到一个主题,由一个消费者组中的某个消费者接收。
应用场景:订单服务发送消息,物流服务处理消息。
2. 发布/订阅模式
消息被发送到一个主题,多个消费者组可以同时接收消息。
应用场景:用户注册后发送消息,多个服务(如邮件、通知服务)订阅同一主题。
3. 分区消费
一个主题分成多个分区,消费者组中的消费者会分摊处理这些分区。
应用场景:高并发日志收集,每个消费者处理一部分日志。
1. 安装 Kafka
下载 Kafka:Kafka 官网。
解压后,启动 ZooKeeper:
bin/zookeeper-server-start.sh config/zookeeper.properties
启动 Kafka:
bin/kafka-server-start.sh config/server.properties
2. 创建主题
bin/kafka-topics.sh --create --topic my-topic --bootstrap-server localhost:9092 --partitions 3 --replication-factor 1
参数解析:
--topic
:主题名称。
--partitions
:分区数。
--replication-factor
:副本因子(推荐 >=2)。
3. 验证 Kafka
生产消息:
bin/kafka-console-producer.sh --topic my-topic --bootstrap-server localhost:9092
输入消息内容后按 Enter 发送。
消费消息:
bin/kafka-console-consumer.sh --topic my-topic --from-beginning --bootstrap-server localhost:9092
查看从主题 my-topic
中读取的消息。
1. 引入依赖
在 pom.xml
中添加 Kafka 相关依赖:
org.springframework.boot
spring-boot-starter
org.springframework.kafka
spring-kafka
2. 配置 Kafka
在 application.yml
文件中添加 Kafka 的基本配置:
spring:
kafka:
bootstrap-servers: localhost:9092 # Kafka 集群地址
consumer:
group-id: my-group # 消费者组 ID
auto-offset-reset: earliest # 从最早的消息开始消费
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
producer:
key-serializer: org.apache.kafka.common.serialization.StringSerializer
value-serializer: org.apache.kafka.common.serialization.StringSerializer
3. 消息生产者
创建 Kafka 消息生产者:
@RestController
@RequestMapping("/producer")
public class KafkaProducerController {
@Autowired
private KafkaTemplate kafkaTemplate;
@PostMapping("/send")
public String sendMessage(@RequestParam String topic, @RequestBody String message) {
kafkaTemplate.send(topic, message); // 发送消息到指定主题
return "Message sent to topic: " + topic;
}
}
代码解释:
KafkaTemplate
:Spring 提供的 Kafka 消息发送模板。
send()
:向指定的主题发送消息。
4. 消息消费者
创建 Kafka 消息消费者:
@Component
public class KafkaConsumer {
@KafkaListener(topics = "my-topic", groupId = "my-group")
public void consumeMessage(String message) {
System.out.println("Received message: " + message);
}
}
代码解释:
@KafkaListener
:监听指定主题的消息。
topics
:指定要监听的主题。
groupId
:消费者组 ID,确保分区内消息只被同组中的一个消费者消费。
5. 测试 Kafka 集成
启动 Kafka 服务。
启动 Spring Boot 应用。
使用 Postman 发送 POST 请求:
POST http://localhost:8080/producer/send?topic=my-topic
Body: "Hello, Kafka!"
在控制台查看消费者输出:
Received message: Hello, Kafka!
Kafka 提供两种确认机制:
生产者确认:
acks
参数:
spring:
kafka:
producer:
properties:
acks: all # 等待所有副本确认
消费者确认:
@KafkaListener(topics = "my-topic", groupId = "my-group")
public void consumeMessage(ConsumerRecord record, Acknowledgment acknowledgment) {
System.out.println("Processing message: " + record.value());
acknowledgment.acknowledge(); // 手动提交 Offset
}
1. 消息重试
在消费消息失败时,Kafka 可以自动重新投递消息。
配置重试策略:
spring:
kafka:
listener:
retry:
max-attempts: 3 # 最大重试次数
backoff: 1000 # 重试间隔(毫秒)
2. 幂等处理
为避免消息重复消费,可使用消息 ID 进行去重。
示例代码:
@KafkaListener(topics = "my-topic", groupId = "my-group")
public void handleMessage(ConsumerRecord record) {
String messageId = record.key(); // 消息唯一 ID
if (!isProcessed(messageId)) {
processMessage(record.value());
markAsProcessed(messageId); // 记录消息已处理
} else {
System.out.println("Duplicate message ignored: " + messageId);
}
}
功能 | Spring Cloud Stream | RabbitMQ | Kafka |
---|---|---|---|
消息模型 | 抽象化消息生产和消费,适配多种中间件 | 支持点对点、发布订阅等模式 | 高吞吐量,支持日志存储和流式处理 |
消息确认机制 | 支持手动确认 | 提供自动和手动确认模式 | 提供 offset 手动提交机制 |
消息重试与幂等处理 | 自动支持重试逻辑,需手动处理幂等 | 配置重试策略,需额外实现幂等逻辑 | 支持 offset 去重逻辑 |
推荐场景 | 需要适配多种消息中间件场景 | 中小规模消息传递,推荐作为入门学习中间件 | 大数据、高吞吐场景,推荐用于日志处理与实时分析 |
分布式事务是解决微服务架构中跨多个服务或数据库操作时数据一致性问题的重要手段。Seata 是阿里巴巴开源的分布式事务框架,其核心特性包括分布式事务协调、异常传播、事务日志记录等。
1. AT 模式
Seata AT 模式的工作原理
2. TCC 模式
Seata TCC 模式工作原理
TCC(Try-Confirm-Cancel)是一种分布式事务实现方式。开发者需要显式实现三个阶段:
分布式事务保证跨多个服务的操作要么全部成功,要么全部回滚。它依赖于以下三大关键机制:
出现异常必须用throw显式地抛出异常。
涉及数据库操作的子方法
@Transactional
,以确保数据库操作的原子性。不涉及数据库操作的子方法
@Transactional
,但需要确保异常能够正确传播到全局事务。示例:
public void validateOrder(String userId, int count) {
if (count <= 0) {
throw new IllegalArgumentException("Invalid count");
}
}
@GlobalTransactional
方法中。@GlobalTransactional
启动全局事务。AT 模式和 TCC 模式对比与选择建议
特性 AT 模式 TCC 模式 侵入性 无侵入 需要实现三个接口 适用场景 基于关系型数据库的简单操作 复杂业务逻辑/非关系型数据库 性能 较高(自动提交) 较低(需要多次交互) 锁机制 全局锁 无锁(业务自己控制) 实现复杂度 简单 复杂 补偿机制 自动生成回滚SQL 需要手动实现补偿逻辑
AT模式优先用于简单CRUD场景
TCC模式适用于需要:
与非关系型数据库交互(Redis/MongoDB)
调用第三方支付接口
需要人工审核的长时间事务
一阶段(Phase 1):
在本地事务中执行业务SQL
生成反向回滚SQL(undo_log
快照)
提交本地事务时同时提交undo_log
二阶段(Phase 2):
全局事务成功:异步删除所有undo_log
记录(无补偿操作)
全局事务失败:通过undo_log
生成反向SQL执行回滚
io.seata
seata-spring-boot-starter
1.5.2
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
2021.0.5.0
# application.yml
seata:
enabled: true
application-id: ${spring.application.name}
tx-service-group: my_tx_group
service:
vgroup-mapping:
my_tx_group: default
config:
type: nacos
nacos:
server-addr: 127.0.0.1:8848
namespace:
group: SEATA_GROUP
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace:
每个业务数据库需要创建 undo_log
表:
关键点:
每个业务数据库实例需要创建一个undo_log
表(不是每个业务表)
与AT模式的关系:
数据修改前会记录beforeImage
快照
数据修改后会记录afterImage
快照
事务回滚时自动用beforeImage
恢复数据
表结构设计原理:
字段 | 作用 |
---|---|
xid | 全局事务ID |
branch_id | 分支事务ID |
rollback_info | 序列化的回滚信息(包含前后镜像) |
log_status | 日志状态(0-正常,1-已回滚) |
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Bean
public DataSource dataSource(DataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}
DataSourceProxy的作用
SQL解析:解析业务SQL生成undo日志
全局锁管理:检查数据修改是否与其他全局事务冲突
连接管理:绑定XID到数据库连接
必要性:必须配置才能让Seata正常工作
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@GlobalTransactional // 开启全局事务
@Override
public void createOrder(Order order) {
// 本地事务操作
orderMapper.insert(order);
// 远程调用(通过Feign)
storageFeignService.deduct(order.getProductId(), order.getCount());
// 模拟异常
// int i = 1/0;
}
}
实现原理:
事务发起者生成全局事务ID(XID)
通过Feign调用时,XID会通过请求头传递:
// Seata的XID传播拦截器
public class SeataFeignInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
String xid = RootContext.getXID();
if (StringUtils.isNotBlank(xid)) {
template.header(RootContext.KEY_XID, xid);
}
}
}
被调用服务通过GlobalTransactionalInterceptor
检测XID,加入全局事务
XID 传播拦截器的自动配置
自动生效条件:当项目中同时存在以下依赖时,Seata 会自动配置
SeataFeignInterceptor
:
com.alibaba.cloud spring-cloud-starter-alibaba-seata org.springframework.cloud spring-cloud-starter-openfeign 验证方式:在 Feign 调用时,检查请求头中是否包含
TX_XID
字段(可通过抓包或日志查看)。步骤 1:在服务调用链的入口方法添加日志:
@GlobalTransactional public void businessMethod() { log.info("XID in caller: {}", RootContext.getXID()); feignClient.callRemote(); }
- 步骤 2:在被调用服务中添加日志:
@RestController public class RemoteController { @PostMapping("/api") public void handleRequest() { log.info("XID in callee: {}", RootContext.getXID()); } }
XID 未传递的可能原因
问题原因 解决方案 Feign 版本冲突 确保使用 Spring Cloud 2020.0.x 以上版本 自定义拦截器覆盖了 Seata 的 检查是否有其他 RequestInterceptor
清除了请求头跨线程调用未传递上下文 使用 RootContext.bind(xid)
手动传递GlobalTransactionalInterceptor 的自动管理
自动生效机制:通过
seata-spring-boot-starter
自动注册该拦截器。拦截范围:所有被
@GlobalTransactional
或@GlobalLock
注解的方法。核心逻辑:
public class GlobalTransactionalInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) { // 1. 检查是否已有全局事务 // 2. 开启新事务(若无)或加入已有事务 // 3. 处理事务传播 } }
回滚能力:
只要所有服务都接入Seata
所有数据源都配置了DataSourceProxy
整个调用链的数据库操作都会回滚
必须使用代理数据源(DataSourceProxy)
需要支持本地事务的关系型数据库
业务SQL必须为单表操作(不支持多表关联更新)无法生成准确的回滚SQL
为什么要用 TCC 模式?
因为很多系统不能像传统数据库一样支持本地事务回滚:
✅ Redis:没有事务回滚机制
✅ MongoDB:事务支持有限,且分布式事务很难实现
✅ 第三方支付接口:一旦调用成功,没法撤回(如微信/支付宝支付)
✅ 外部接口/服务:失败了也不能自动撤销
➡️ 所以必须“提前做准备”,等确定无误后再“真正提交”,否则就“取消操作”。
Try(资源预留):
MySQL:插入预创建订单(status=预创建
),本地事务提交。
Redis:冻结库存(如 DECRBY stock_frozen 1
),记录预留资源。
支付接口:调用预授权接口,冻结用户账户资金。
目标:所有操作均为资源预留,确保后续阶段可提交或回滚。
Confirm(提交确认):
MySQL:更新订单状态为 已确认
。
Redis:扣减真实库存(DECRBY stock_real 1
),释放冻结资源。
支付接口:执行实际扣款,完成支付。
目标:将预留资源转为真实数据,保证业务最终生效。
Cancel(回滚补偿):
MySQL:删除预创建订单。
Redis:解冻库存(INCRBY stock_frozen 1
)。
支付接口:调用取消预授权接口,释放冻结资金。
目标:释放所有预留资源,确保数据回滚一致。
io.seata
seata-spring-boot-starter
1.6.1
com.alibaba.cloud
spring-cloud-starter-alibaba-seata
2021.0.5.0
# 开启TCC模式
seata.tcc.mode=api
seata.tcc.fence.enable=true # 开启防悬挂
// 订单服务接口
@LocalTCC
public interface OrderService {
@TwoPhaseBusinessAction(
name = "createOrder",
commitMethod = "confirm",
rollbackMethod = "cancel"
)
boolean tryCreate(Order order);
boolean confirm(BusinessActionContext context);
boolean cancel(BusinessActionContext context);
}
// 库存服务接口
@LocalTCC
public interface StockService {
@TwoPhaseBusinessAction(
name = "deductStock",
commitMethod = "confirmDeduct",
rollbackMethod = "cancelDeduct"
)
boolean tryDeduct(Long productId, Integer quantity);
boolean confirmDeduct(BusinessActionContext context);
boolean cancelDeduct(BusinessActionContext context);
}
@TwoPhaseBusinessAction 注解必要性详解
注解的核心作用
@TwoPhaseBusinessAction(
name = "actionName", // 事务动作全局唯一标识
commitMethod = "commit", // 二阶段提交方法名(必须存在)
rollbackMethod = "rollback", // 二阶段回滚方法名(必须存在)
useTCCFence = true // 是否启用防悬挂(1.5+版本)
)
必要性体现:
场景 | 无注解 | 有注解 |
---|---|---|
事务识别 | Seata 无法识别 TCC 服务 | Seata 自动注册事务动作 |
参数传递 | 无法自动传递事务上下文参数 | 通过 @BusinessActionContextParameter 自动序列化参数 |
方法映射 | 需手动维护 Try-Confirm-Cancel 映射 | 自动关联三阶段方法 |
悬挂防护 | 需自行实现防悬挂逻辑 | 开启 useTCCFence 后自动处理 |
注解工作原理
服务启动时:
// Seata 自动扫描带 @TwoPhaseBusinessAction 的接口
TCCBeanParserUtils.processTCCAnnotation(TccBeanParserUtils.java:112)
调用阶段:
// 代理拦截(TccActionInterceptor)
public Object invoke(MethodInvocation invocation) {
// 1. 创建 ActionContext
// 2. 注册分支事务
// 3. 调用 try 方法
}
事务协调:
// Seata Server 根据注解配置找到对应方法
DefaultCore.commit() -> TCCResourceManager.commit()
@Service
public class OrderTccServiceImpl implements OrderTccService {
@Autowired
private OrderMapper orderMapper;
@Override
@Transactional
public boolean prepare(BusinessActionContext actionContext, Order order) {
// Try阶段:资源预留
order.setStatus(0); // 0-处理中
orderMapper.insert(order);
return true;
}
@Override
@Transactional
public boolean commit(BusinessActionContext actionContext) {
// Confirm阶段:确认提交
Order order = (Order) actionContext.getActionContext("order");
order.setStatus(1); // 1-已完成
orderMapper.updateById(order);
return true;
}
@Override
@Transactional
public boolean rollback(BusinessActionContext actionContext) {
// Cancel阶段:回滚补偿
Order order = (Order) actionContext.getActionContext("order");
orderMapper.deleteById(order.getId());
return true;
}
}
@Service
public class BusinessService {
@Autowired
private OrderTccService orderTccService;
@GlobalTransactional
public void createOrder(Order order) {
// TCC第一阶段
orderTccService.prepare(null, order);
// 调用其他服务的TCC接口
storageTccService.prepare(...);
// 当所有Try成功时,自动执行Confirm
// 出现异常时自动执行Cancel
}
}
① 幂等处理:
// 使用防重表
@Transactional
public boolean commit(BusinessActionContext context) {
if(select count(*) from tcc_record where xid=context.getXid()) > 0){
return true; // 已处理过
}
// 业务逻辑
insert into tcc_record(xid) values(context.getXid());
}
② 空回滚处理:
@Transactional
public boolean rollback(BusinessActionContext context) {
// 检查Try是否执行
if(!checkTryExecuted(context.getXid())){
log.warn("空回滚: {}", context.getXid());
return true;
}
// 正常回滚逻辑
}
③ 防悬挂方案:
// 在Try阶段先插入状态记录
@Transactional
public boolean prepare(...) {
insert into tcc_status(xid, status) values(xid, 'TRYING');
// 业务操作
}
// Cancel阶段检查状态
public boolean rollback(...) {
if(select status from tcc_status where xid=... == 'CANCELED'){
return true; // 已经处理过
}
}
④ 业务数据设计示例:
public class Order {
private Long id;
private Integer status; // 0-预创建,1-已确认,2-已取消
private LocalDateTime freezeTime; // 预留字段
}
在生产环境中,可以将 Actuator、Prometheus + Grafana 和 ELK Stack 结合使用,以实现从性能监控到日志分析的全方位监控体系。
1. Actuator 的角色
/health
)和性能指标暴露(如 /metrics
)。2. Prometheus + Grafana 的角色
/metrics
接口,采集服务性能数据。3. ELK Stack 的角色
Spring Boot Actuator 是 Spring 提供的一套开箱即用的工具,用于监控和管理服务。它的主要功能是:
1. 健康检查
/actuator/health
示例:
{
"status": "UP",
"components": {
"db": { "status": "UP", "details": { "database": "MySQL", "result": "ok" } },
"diskSpace": { "status": "UP", "details": { "total": 50000000000, "free": 20000000000 } }
}
}
解读:
status: UP
表示服务正常。components
显示健康检查的各项子系统状态,比如数据库连接和磁盘空间。2. 核心指标
/actuator/metrics
示例:
{
"names": [
"jvm.memory.used",
"system.cpu.usage",
"http.server.requests"
]
}
解读:
jvm.memory.used
)、CPU 使用率(system.cpu.usage
)等指标。3. 自定义健康检查
开发者可以根据业务需求定义自定义的健康检查逻辑。
示例代码:
@Component
public class CustomHealthIndicator implements HealthIndicator {
@Override
public Health health() {
boolean dbConnectionOk = checkDatabase();
if (dbConnectionOk) {
return Health.up().withDetail("Database", "Connected").build();
}
return Health.down().withDetail("Database", "Disconnected").build();
}
private boolean checkDatabase() {
// 自定义检查逻辑,比如尝试连接数据库
return true;
}
}
功能:
/health
会返回异常状态,方便及时发现问题。1. 数据采集与存储(Prometheus)
Prometheus 定期访问 Actuator 暴露的 /metrics
接口,采集服务性能数据并存储。
2. 数据可视化(Grafana)
Grafana 从 Prometheus 获取数据,并以图表、仪表盘形式展示服务性能。例如:
3. 实时告警
Prometheus 支持基于指标配置告警规则,例如:
1. Prometheus 配置
Prometheus 需要配置一个 prometheus.yml
文件,用于指定要监控的服务:
scrape_configs:
- job_name: 'spring-boot'
static_configs:
- targets: ['localhost:8080'] # 采集 Actuator 暴露的指标
2. Spring Boot 集成 Micrometer
添加依赖:
io.micrometer
micrometer-registry-prometheus
在 application.properties
中启用 Prometheus 端点:
management.endpoints.web.exposure.include=prometheus
Actuator 的 /actuator/prometheus
接口会返回 Prometheus 格式的数据。
3. Grafana 配置
安装并启动 Grafana。
添加 Prometheus 数据源:
http://localhost:9090
(Prometheus 地址)。创建仪表盘:
rate(http_server_requests_seconds_sum[1m])
展示服务请求速率、错误率等数据。
ELK 是 Elasticsearch、Logstash 和 Kibana 的组合,主要用于 日志收集、存储和分析。
创建 logstash.conf
:
input {
file {
path => "/var/log/my-service.log" # 日志文件路径
start_position => "beginning"
}
}
output {
elasticsearch {
hosts => ["localhost:9200"] # Elasticsearch 地址
}
}
启动 Logstash:
./logstash -f logstash.conf
启动 Elasticsearch:
./elasticsearch
默认运行在 http://localhost:9200
。
启动 Kibana:
./kibana
通过浏览器访问 http://localhost:5601
。
配置日志索引(如 logstash-*
),并创建仪表盘。
微服务架构强调快速部署与扩展,而容器化技术(如 Docker)是实现这一目标的关键工具。Docker 提供了轻量级、可移植的环境,可以轻松将应用打包成镜像,运行在任何支持 Docker 的平台上。同时,通过 Docker Compose 可以实现多个微服务的协调部署。
Docker 是一种容器化技术,可以将应用及其依赖打包到一个标准化的单元中,便于跨环境运行。Docker 的主要特点:
创建 Dockerfile
Dockerfile 是一个脚本文件,定义了如何构建 Docker 镜像的步骤。以下是容器化 Spring Boot 项目的 Dockerfile 示例:
# 使用官方的 Java 运行环境镜像
FROM openjdk:17-jdk-alpine
# 设置工作目录
WORKDIR /app
# 将 Spring Boot 项目的 JAR 文件复制到容器中
COPY target/my-app-1.0.jar app.jar
# 暴露服务端口
EXPOSE 8080
# 定义容器启动时的命令
ENTRYPOINT ["java", "-jar", "app.jar"]
代码解析:
FROM
:指定基础镜像,openjdk:17-jdk-alpine
是一个轻量级的 Java 运行环境。WORKDIR
:设置容器的工作目录为 /app
。COPY
:将编译好的 JAR 文件复制到容器中。EXPOSE
:声明容器对外暴露的端口(与 Spring Boot 应用的端口一致)。ENTRYPOINT
:定义容器启动时的命令,运行 Spring Boot 项目的 JAR 文件。构建 Docker 镜像
打包 Spring Boot 项目:
mvn clean package
输出的 JAR 文件通常位于 target
目录下。
构建 Docker 镜像:
docker build -t my-app:1.0 .
命令解析:
-t my-app:1.0
:为镜像指定名称和版本。.
:指向 Dockerfile 所在的目录。查看生成的镜像:
docker images
示例输出:
REPOSITORY TAG IMAGE ID CREATED SIZE
my-app 1.0 abc12345 10 seconds ago 500MB
运行 Docker 容器
启动容器:
docker run -d -p 8080:8080 --name my-app-container my-app:1.0
命令解析:
-d
:后台运行容器。-p 8080:8080
:将宿主机的 8080 端口映射到容器的 8080 端口。--name my-app-container
:为容器指定名称。查看容器状态:
docker ps
示例输出:
CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES
12345abcde my-app:1.0 "java -jar app.jar" Up 2 minutes 0.0.0.0:8080->8080/tcp my-app-container
访问服务: 在浏览器或 Postman 中访问 http://localhost:8080
,验证服务是否正常运行。
Docker Compose 是一个工具,用于定义和运行多容器 Docker 应用。通过一个 YAML 文件,可以配置多个服务的部署参数,实现微服务的快速部署。
以下是一个示例 docker-compose.yml
文件,定义了两个服务(app
和 db
)的部署。
version: '3.8'
services:
app:
image: my-app:1.0
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: mydb
MYSQL_USER: user
MYSQL_PASSWORD: password
ports:
- "3306:3306"
文件解析
version
:
3.8
。services
:
app
:表示 Spring Boot 服务。db
:表示 MySQL 数据库服务。build
:
app
服务的镜像来源,可以通过 Dockerfile
构建镜像。ports
:
depends_on
:
app
服务依赖于 db
服务。environment
:
创建 docker-compose.yml
文件,并放置在项目根目录。
启动服务:
docker-compose up -d
命令解析:
up
:启动 Compose 定义的服务。-d
:后台运行。查看服务状态:
docker-compose ps
停止服务:
docker-compose down
1. 基本流程
2. 容器化的好处