跳到主要内容
SpringCloud 微服务架构快速入门与实践 | 极客日志
Java java
SpringCloud 微服务架构快速入门与实践 SpringCloud 微服务架构的核心组件与实践。包括 Nacos 注册与配置中心、OpenFeign 远程调用、Sentinel 流控与熔断、Gateway 网关路由及 Seata 分布式事务。通过代码示例讲解服务治理、容错机制及数据一致性方案。
草莓泡芙 发布于 2026/3/29 更新于 2026/6/11 29 浏览springcloud 简介
springcloud 是分布式系统一站式解决方案 。
什么是分布式系统?
架构分:单体和分布式。集群只是一种物理形态,分布式是工作方式。
架构演进
单体架构
集群架构
分布式架构
定义
所有功能模块都在一个项目里
单体的多服务器版本
一个大型应用被拆分成很多小应用分布部署在各个机器;
优点
开发部署简单
解决大并发
解决了单体 + 集群的问题
缺点
无法应对高并发
问题 1:模块块升级 麻烦
问题 2:多语言团队 交互不通
基于自己的理解:
分布式架构(模拟用户访问)
通过网关来发送各个微服务的请求(请求路由)。用 gateway。网关需要对请求进行分发,所以要注册到注册中心。
将各微服务布置到各服务器,即微服务(自治)独立部署、数据隔离、语言无关,将不同模块部署到多个服务器,每个模块都要有副本服务器。不能让每个模块只部署到一个服务器,会出现单点故障问题:如果这个服务器崩了,那应用就不能提供完整服务了
如果模块跨服务器之间调用会遇到什么问题?远程调用 RPC。如果远程调用怎么让应用知道调用哪个服务器的微服务。此时就需要用到 nacos 注册中心和配置中心,注册中心有两个功能:服务注册(监控服务上下线)和服务发现(远程调用之前要发现对方在哪)。配置中心:统一管理配置文件 + 推送配置的变更。Nacos+OpenFeign
如果模块之间调用失败导致服务调用链整体阻塞甚至雪崩,怎么办?服务熔断(快速失败机制),及时释放资源,防止资源耗尽。Sentinal
如果有一个操作需要多个数据库合作,而不同数据库部署在不同服务器,这就需要用到分布式事务。Seata
前期准备
建 springcloud-demo 项目
先用手脚架快速搭建框架
导依赖
pom 父模块
注意:springboot, springcloud, springcloud-alibaba 的版本
<?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <parent > <groupId > org.springframework.boot</ > spring-boot-starter-parent 3.3.4 4.0.0 com.atguigu spring-cloud-demo 1.0-SNAPSHOT pom 17 17 UTF-8 2023.0.3 2023.0.3.2 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import com.alibaba.cloud spring-cloud-alibaba-dependencies ${spring-cloud-alibaba.version} pom import
groupId
<artifactId >
</artifactId >
<version >
</version >
<relativePath />
</parent >
<modelVersion >
</modelVersion >
<groupId >
</groupId >
<artifactId >
</artifactId >
<version >
</version >
<packaging >
</packaging >
<properties >
<maven.compiler.source >
</maven.compiler.source >
<maven.compiler.target >
</maven.compiler.target >
<project.build.sourceEncoding >
</project.build.sourceEncoding >
<spring-cloud.version >
</spring-cloud.version >
<spring-cloud-alibaba.version >
</spring-cloud-alibaba.version >
</properties >
<dependencyManagement >
<dependencies >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
<version >
</version >
<type >
</type >
<scope >
</scope >
</dependency >
<dependency >
<groupId >
</groupId >
<artifactId >
</artifactId >
<version >
</version >
<type >
</type >
<scope >
</scope >
</dependency >
</dependencies >
</dependencyManagement >
</project >
建 services 模块 services 模块作为管理所有 service-xxx 模块的父模块
导入依赖 在 service 中导入 nacos-discovery 依赖
<dependencies > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > </dependencies >
建 service-order/product 模块
nacos - 注册/配置中心
基础入门 这里要下载 nacos 的服务端:nacos server 账号密码都是 nacos
暂时版本用图中所示,用 docker 也行,参考文档
启动:startup.cmd -m standalone
注册中心
服务注册 service-order, service-product 都加依赖
<dependencies > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-web</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-test</artifactId > <scope > test</scope > </dependency > </dependencies >
service-order 模块 server.port=8000 spring.application.name=service-order spring.cloud.nacos.server-addr=127.0.0.1:8848
@SpringBootApplication public class OrderMainApplication { public static void main (String[] args) { SpringApplication.run(OrderMainApplication.class, args); } }
service-product 模块 server.port=9000 spring.application.name=service-product spring.cloud.nacos.server-addr=127.0.0.1:8848
@SpringBootApplication public class ProductMainApplication { public static void main (String[] args) { SpringApplication.run(ProductMainApplication.class, args); } }
启动集群 例如:service-order 启动两个,service-product 启动 3 个
product 端口:9000/9001/9002
服务发现 服务发现的作用是:服务间的远程调用通过 nacos 发现对方的服务,然后进行调用,后续不用手动调,这里只要加上注解,两个 API 作为了解。
@EnableDiscoveryClient @SpringBootApplication public class ProductMainApplication { public static void main (String[] args) { SpringApplication.run(ProductMainApplication.class, args); } }
@SpringBootTest public class ProductApplicationTest { @Autowired DiscoveryClient discoveryClient; @Autowired NacosDiscoveryClient nacosDiscoveryClient;
远程调用
新建 model 模块 <dependencies > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <scope > annotationProcessor</scope > </dependency > </dependencies >
@Data public class Order { private Long id; private BigDecimal totalAmount; private Long userId; private String nickName; private String address; private List<Product> productList; }
@Data public class Product { private Long id; private BigDecimal price; private String productName; private int num; }
在 services 的 pom 文件中导入 model,就可以用了
<dependency > <groupId > com.atguigu</groupId > <artifactId > model</artifactId > <version > 1.0-SNAPSHOT</version > </dependency >
回到 service 业务类
service-product @RestController public class ProductController { @Autowired ProductService productService; @GetMapping(value = "/productId/{id}") public Product getProductById (@PathVariable("id") Long productId) { Product product = productService.getProductById(productId); return product; } }
public interface ProductService { Product getProductById (Long productId) ; }
@Service public class ProductServiceImpl implements ProductService { @Override public Product getProductById (Long productId) { Product product = new Product (); product.setId(productId); product.setPrice(new BigDecimal ("99" )); product.setProductName("苹果-" + productId); product.setNum(11 ); return product; } }
service-order @RestController public class OrderController { @Autowired OrderService orderService; @GetMapping(value = "/create") public Order createOrder (@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) { Order order = orderService.createOrder(userId, productId); return order; } }
public interface OrderService { Order createOrder (Long userId, Long productId) ; }
此处需要对 service-product 服务进行远程调用,稍后处理,先测试
@Service public class OrderServiceImpl implements OrderService { @Override public Order createOrder (Long userId, Long productId) { Order order = new Order (); order.setId(1L );
可以自动生成 getter/setter 方法的 IDEA 插件
完善业务类中远程调用
service-order 将 RestTemplate 加入到 spring 容器
@Configuration public class OrderConfig { @Bean public RestTemplate restTemplate () { return new RestTemplate (); } }
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @Override public Order createOrder (Long userId, Long productId) { Product product = getProductFromRemote(productId); Order order = new Order (); order.setId(1L );
负载均衡
1.使用 loadBalancerClient <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency >
@SpringBootTest public class OrderApplicationTest { @Autowired LoadBalancerClient loadBalancerClient; @Test public void test () { ServiceInstance choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); choose = loadBalancerClient.choose("service-product" ); System.out.println("choose.getHost()+choose.getPort() = " + choose.getHost() + ":" + choose.getPort()); } }
改造 service-order 的 OrderServiceImpl 的远程调用 product 服务的方法
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @Autowired LoadBalancerClient loadBalancerClient; @Override public Order createOrder (Long userId, Long productId) {
2.使用@LoadBalanced 注解 @Configuration public class OrderConfig { @LoadBalanced
@RestController public class ProductController { @Autowired ProductService productService; @GetMapping(value = "/productId/{id}") public Product getProductById (@PathVariable("id") Long productId) { System.out.println("正在远程调用 service-product..." ); Product product = productService.getProductById(productId); return product; } }
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @Autowired LoadBalancerClient loadBalancerClient; @Override public Order createOrder (Long userId, Long productId) {
思考:注册中心宕机,远程调用还能成功吗? 结论:不能调用,因为远程调用由于 nacos 宕机找不到地址
结论:可以调用,因为缓存中有地址。但如果对方服务宕机则也调不通。
第一次远程调用要经过两个步骤:1.拿到 nacos 服务地址列表 2.给对方服务的某个地址发送请求。
第二次及后续:就会将步骤 1 省略,已经将地址列表放到缓存中了,即使 nacos 宕机也能远程调用,并且能负载均衡。
小结
使用 RestTemplate 可以获取到远程数据
必须精确指定地址和端口
如果远程宕机将不可用
配置中心
整合配置
services 导入依赖 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-config</artifactId > </dependency >
service-order 的配置文件 application.properties 导入了配置中心的依赖但是没设置(用到)配置中心就会报错,解决办法,关闭自动检查
server.port=8000 spring.application.name=service-order spring.cloud.nacos.server-addr=127.0.0.1:8848 spring.config.import=nacos:service-order.properties #暂未用到配置中心功能,需要关闭配置检查 spring.cloud.nacos.config.import-check.enabled=false
spring.application.name=service-product spring.cloud.nacos.server-addr=127.0.0.1:8848 #暂未用到配置中心功能,需要关闭配置检查 spring.cloud.nacos.config.import-check.enabled=false
在 nacos 服务端进行配置 @RestController public class OrderController { @Autowired OrderService orderService; @Value("${order.timeout}") String orderTimeout; @Value("${order.auto-confirm}") String orderAutoConfirm; @GetMapping("/config") public String getConfig () { return "OrderTimeout+OrderAutoConfirm = " + orderTimeout+" : " + orderAutoConfirm; } @GetMapping(value = "/create") public Order createOrder (@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) { Order order = orderService.createOrder(userId, productId); return order; } }
自动刷新
1. @Value("${xx}") 获取配置 + @RefreshScope 实现自动刷新 但是会产生一个问题:nacos 配置中修改后,不重启服务,发请求不能自动更新修改后的数据
实时更新配置显示的办法:在@RestController 上加@RefreshScope 即可
2. @ConfigurationProperties 无感自动刷新 加 com.atguigu.order.properties.OrderProperties
@Component @ConfigurationProperties(value = "order")
service-order: controller
3.NacosConfigManager 监听配置变化 在启动类中添加 ApplicationRunner 实例,是一个一次性任务,项目启动其他他就会执行
@SpringBootApplication public class OrderMainApplication { public static void main (String[] args) { SpringApplication.run(OrderMainApplication.class, args); }
思考:Nacos 中的数据集 和 application.properties 有相同的 配置项,哪个生效?
数据隔离 作用:配置中心基于项目激活哪个环境标识,动态指定名称空间,动态加载指定文件和配置
创建几个 namespace 和 Group 等方便测试
设置 application.yml 按需加载,设置 application.yml 配置文件,将 application.properties 注释掉
server:
port: 8000
spring:
profiles:
active: prod
application:
name: service-order
cloud:
nacos:
server-addr: 127.0 .0 .1 :8848
config:
import-check:
enabled: false
namespace: ${spring.profiles.active:public}
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
activate:
on-profile: dev
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:haha.properties?group=order
activate:
on-profile: test
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
- nacos:hehe.properties?group=order
activate:
on-profile: prod
代码层面 @Component @ConfigurationProperties(value = "order")
测试效果
小节
注册中心
服务注册
导 nacos-discovery 依赖
配置 naocs 地址
启动 nacos
启动微服务
服务发现
启动类加@EnableDiscoveryClient 注解
DiscoveryClient 用于发现 nacos 列表所有服务
配置类加@Bean (RestTemplate),与 DiscoveryClient 搭配即可远程调用
导 loadbalancer 依赖
方案一:用 loadBalancerClient.choose(服务名) 实习负载均衡
方案二:直接在@Bean (RestTemplate)加注解@LoadBalanced
配置中心
整合配置
导入依赖 nacos-config
配置 nacos 配置中心地址
spring.config.import=nacos:service-order.properties,nacos:common.properties #暂未用到配置中心功能,需要关闭配置检查 spring.cloud.nacos.config.import-check.enabled=false
在 nacos 服务端页面编写配置文件
用 @Value("${order.timeout}")代码中拿到配置信息
自动刷新
方案一:在有@Value 的类上加@RefreshScope 实现自动刷新
方案二:写一个 properties 配置类,加注解@ConfigurationProperties(value = "order")+@Component+@Data,用到配置文件的地方@Autowired 即可使用
数据隔离
这里主要编写 yml 配置文件,清楚怎么配置即可
server:
port: 8000
spring:
profiles:
active: dev
include: feign
application:
name: service-order
cloud:
nacos:
server-addr: 127.0 .0 .1 :8848
config:
import-check:
enabled: false
namespace: ${spring.profiles.active:public}
---
spring:
config:
import:
- nacos:common.properties?group=order
- nacos:database.properties?group=order
activate:
on-profile: dev
多环境(public, dev, test, prod)=》用 namespace 管理
多服务(order, product)=》group
多配置(xxx.properties)=>具体 xxx.properties
多配置项
openFeign 区别于:restTemplate 是编程式远程调用
基础使用
services 中导依赖 <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-openfeign</artifactId > </dependency >
启动类加注解 @EnableFeignClients(value = "com.atguigu.order.feign")
编写 FeignClient 接口 远程调用方法直接将调用地方的 controller 方法粘贴过来即可
@FeignClient(value = "service-product") public interface ProductFeignClient { @GetMapping(value = "/productId/{id}") public Product getProductById (@PathVariable("id") Long productId) ; }
改造 service-order OrderServiceImpl @Autowired ProductFeignClient productFeignClient; Product product = productFeignClient.getProductById(productId);
远程调用外部 API @FeignClient(value = "weather-client", url = "http://aliv18.data.moji.com") public interface WeatherFeignClient { @PostMapping("/whapi/json/alicityweather/condition") String getWeather (@RequestHeader("Authorization") String auth, @RequestParam("token") String token, @RequestParam("cityId") String cityId) ; }
@SpringBootTest public class WeatherTest { @Autowired WeatherFeignClient weatherFeignClient; @Test public void test () { String weather = weatherFeignClient.getWeather("自己的 AppCode" , "50b53ff8dd7d9fa320d3d3ca32cf8ed1" ,"2182" ); System.out.println("weather = " + weather); } }
客户端负载均衡:客户端在选择服务地址进行调用,例如 service-order 掉哟个 service-product
服务端负载均衡:服务端进行负载均衡,客户端只要发请求即可,例如调用墨迹天气 API
进阶配置
开启日志 说明:这行配置是 Spring Boot 的日志配置 ,用于设置 com.atguigu.order.feign 这个包下的 日志级别 为 DEBUG。
logging:
level:
com.atguigu.order.feign: debug
说明:这是 Feign 提供的日志级别配置,它控制 Feign 请求和响应的详细日志,Logger.Level.FULL 代表 打印所有请求和响应的详细信息
请求方法(GET、POST 等)
请求 URL
请求头
请求体
响应状态码
响应头
响应体
请求的执行时间
@Bean Logger.Level feignLoggerLevel () { return Logger.Level.FULL; }
超时控制 在 application.yml 引入 application-feign.yml
spring:
cloud:
openfeign:
client:
config:
default:
logger-level: full
connect-timeout: 1000
read-timeout: 2000
service-product:
logger-level: full
connect-timeout: 3000
read-timeout: 5000
在 service-product 中模拟超时,后续记得注销
try { TimeUnit.SECONDS.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); }
重试机制 在 service-order 的 OrderConfig 中加入重试机制
@Bean Retryer retryer () { return new Retryer .Default(); }
源码:默认重试 5 次,初始间隔 100 毫秒,后续每次乘 1.5,最多间隔 1 秒
拦截器
全局拦截器——拦截所有远程调用请求 在 service-order 的包下 com.atguigu.order.interceptor
@Component public class XTokenRequestIntercepter implements RequestInterceptor { @Override public void apply (RequestTemplate requestTemplate) { requestTemplate.header("X-Token" , UUID.randomUUID().toString()); } }
改造 service-product 的 ProductController
@RestController public class ProductController { @Autowired ProductService productService; @GetMapping(value = "/productId/{id}") public Product getProductById (@PathVariable("id") Long productId, HttpServletRequest httpServletRequest) { String XToken = httpServletRequest.getHeader("X-Token" ); System.out.println("XToken = " + XToken); System.out.println("正在远程调用 service-product..." );
局部拦截器
fallback - 兜底返回 <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-sentinel</artifactId > </dependency >
在 application-feign.yml 加配置
feign:
sentinel:
enabled: true
ProductFeignClient 加上回调实现
@FeignClient(value = "service-product", fallback = ProductFeignClientFallback.class) public interface ProductFeignClient { @GetMapping(value = "/productId/{id}") public Product getProductById (@PathVariable("id") Long productId) ; }
ProductFeignClientFallback
@Component public class ProductFeignClientFallback implements ProductFeignClient { @Override public Product getProductById (Long productId) { System.out.println("兜底回调...." ); Product product = new Product (); product.setId(productId); product.setPrice(new BigDecimal ("0" )); product.setProductName("未知商品" ); product.setNum(0 ); return product; } }
测试前先将重试机制关了,不然会一直重试,无法快速看到兜底结果。
在调用 service-product 的 controller 中加入一个错误。或者将 service-order 服务给停掉
小节
基础使用
导依赖 openfeign
启动类加注解@EnableFeignClients(value = "com.atguigu.order.feign")
写接口@FeignClient(value = "service-product"),再注入使用即可
进阶配置
开启日志
修改 yml + 配置类 Logger.Level feignLoggerLevel()
超时控制
加一个关于 feign 的配置,设置服务的连接时间 + 超时时间
重试机制
在配置类加 Bean: new Retryer.Default()
拦截器
全局拦截:写一个拦截类实现 RequestIntecepter 接口
局部拦截:在 feign 配置文件的某服务下加 request-interceptors 配置
fallback 回调
加 sentinel 依赖
yml 文件加 sentinel 配置
@FeignClent(中加入 fallback=XXX.class)
写@FeignClent 的实现类
sentinel
环境搭建
下载 sentinel dashboard
启动: cmd 输入:java -jar sentinel.jar
配置连接 + 热加载(服务启动就加载) spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true
说明:
主流框架自动适配(例如:web 请求)
声明式 Sphu API(不常用)
声明式:@SentinelResource
异常处理
Web 接口 出现异常
model 模块写异常类 @Data public class R { private Integer code; private String msg; private Object data; public static R ok () { R r = new R (); r.setCode(200 ); return r; } public static R ok (String msg,Object data) { R r = new R (); r.setCode(200 ); r.setMsg(msg); r.setData(data); return r; } public static R error () { R r = new R (); r.setCode(500 ); return r; } public static R error (Integer code,String msg) { R r = new R (); r.setCode(code); r.setMsg(msg); return r; } }
实现 BlockExceptionHandler 接口 在 service-order 的 com.atguigu.order.exception 包中写一个 BlockExceptionHandler 的实现类
@Component public class MyBlockExceptionHandler implements BlockExceptionHandler { private ObjectMapper objectMapper = new ObjectMapper (); @Override public void handle (HttpServletRequest request, HttpServletResponse response, String resourceName, BlockException e) throws Exception { response.setStatus(429 );
配置流控规则
效果 违反流控规则就走 MyBlockExceptionHandler 异常
@SentinelResource 出现异常
在目标方法加@SentinelResource 注解
编写 blockHandler 方法或者 fallback 方法处理异常 blockHandler="异常处理方法",这个方法中加参数:BlockException exception
fallback="异常处理方法",这个方法中加参数:Throwable exception
@Service @Slf4j public class OrderServiceImpl implements OrderService { @Autowired DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @Autowired LoadBalancerClient loadBalancerClient; @Autowired ProductFeignClient productFeignClient; @SentinelResource(value = "createOrder",blockHandler = "createOrderFallback") @Override public Order createOrder (Long userId, Long productId) {
测试
OpenFeign 调用 出现异常 走 openfeign 的 fallback 异常
SphU 硬编码 出现异常
规则 - 流量控制 在 application-feign.yml 中加配置
web-context-unify: false #在 sentinel 中不共用同一个上下文
阈值类型
集群
流控模式
流控模式——直接策略:
流控模式——关联策略: 在 OrderController 加方法,方便测试
@GetMapping("/writeDb") public String writeDb () { return "writeDb success..." ; }
@GetMapping("/readDb") public String readDb () { log.info("readDb success..." ); return "readDb success..." ; }
单独访问/readDb 或者/writeDb,多少请求都能成功
当/writeDb 请求量非常大的时候,突然访问/readDb,则会崩溃,走自定义 BlockExceptionHandler 的实现类
使用场景:当系统里出现资源竞争的时候,使用关联策略进行限制。例如这里只有写量特别大的时候才会限制读,其他不限制。
流控模式——链路策略: @GetMapping("/seckill") public Order seckill (@RequestParam("userId") Long userId, @RequestParam("productId") Long productId) { Order order = orderService.createOrder(userId, productId); order.setId(Long.MAX_VALUE); return order; }
特点:两个请求访问一个资源(随便选一个 creatOrder 资源,即加了@SentinelResource 的资源),但只限制 seckill 这个请求
流控效果
流控效果——快速失败 作用:处理不了的请求直接丢弃。交给 Web 接口异常处理 MyBlockExceptionHandler。
流控效果——Warm Up 例如我这里 QPS 是 10,需要经过 3 秒才能达到 10,前三秒是慢慢将能接收请求数量提上来。
流控效果——排队等待
注意!
规则 - 熔断降级
熔断策略——慢调用比例 举例:统计时长 5 秒,比例阈值 70% 的请求在最大 RT(最大反应时间)1 秒以上,则触发熔断降级 。
熔断时长:断开时间,时间到了则会发一个请求试探,有用则通,没用则再继续熔断。
此处:由于是在远程调用处设置了熔断策略,所以熔断之后走 openFeign 的 fallback 处理
熔断策略——异常比例 统计时长内发生异常的比例达到比例阈值,则发生熔断.
熔断策略——异常数 统计时长内发生异常的数量达到设置的异常数,则发生熔断.
此处发送了 10 个远程调用的请求发现都是异常的,直接熔断,30 秒内不会再进行远程调用
规则 - 热点参数 @GetMapping("/seckill") @SentinelResource(value = "seckill-order",fallback = "seckillFallback")
需求一 e.g: 带了 userId 流空规则生效,不带不生效。
需求二 这里在参数索引位置为 0 的参数中,设置了参数值=6 的特殊情况,它的限流阈值是 10000.
意味着,除了 userId=6 的请求,其他请求还是每秒不能超过 1
需求三 意味着在索引参数为 1 的参数上,其他请求阈值是 100000,但是参数值=666 的情况,则限流阈值=0,意味着参数值为 666 的商品,不让访问。
为什么这里没走 fallback 回调 因为用错了异常,不能用 BlockException,应该用 Throwable
小节
不同资源的异常处理
Web 接口 --> 写 BlockExceptionHandler 实现类
@SentinelResource --> 写 blockHandler 和 fallback 方法
openfeign --> 走自己的 fallback 方法
重点掌握流控控制 + 熔断降级规则,了解热点参数规则
gateway
环境配置
建 module: gateway 微服务
改 pom <dependencies > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-gateway</artifactId > </dependency > <dependency > <groupId > com.alibaba.cloud</groupId > <artifactId > spring-cloud-starter-alibaba-nacos-discovery</artifactId > </dependency > <dependency > <groupId > org.springframework.cloud</groupId > <artifactId > spring-cloud-starter-loadbalancer</artifactId > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <scope > annotationProcessor</scope > </dependency > </dependencies >
写 yml spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0 .0 .1 :8848
profiles:
include: route
server:
port: 80
spring:
cloud:
gateway:
routes:
- id: order
uri: lb://service-order
predicates:
- Path=/api/order/**
- id: product
uri: lb://service-product
predicates:
- Path=/api/product/**
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]' :
allowedOriginPatterns: '*'
allowedHeaders: '*'
allowedMethods: '*'
default-filters:
- AddResponseHeader=X-Response-Abc,123
routes:
- id: bing
uri: https://cn.bing.com/
predicates:
- name: Path
args:
patterns: /search
- name: Query
args:
param: q
regexp: haha
- name: Vip
args:
param: user
value: lei
order: 10
- id: order-route
uri: lb://service-order
predicates:
- name: Path
args:
patterns: /api/order/**
matchTrailingSlash: true
filters:
- RewritePath=/api/order/?(?<segment>.*), /$\{segment}
- OnceToken=X-Response-Token,jwt
order: 1
- id: product-route
uri: lb://service-product
predicates:
- Path=/api/product/**
filters:
- RewritePath=/api/product/?(?<segment>.*), /$\{segment}
order: 2
启动类 @SpringBootApplication @EnableDiscoveryClient public class GatewayMainApplication { public static void main (String[] args) { SpringApplication.run(GatewayMainApplication.class, args); } }
业务类
Predicate - 断言
短写法 这里根据不同服务的 predicates 条件,将请求执行不同服务的 uri
order:代表 predicates 执行顺序,越下越优先
spring:
cloud:
gateway:
routes:
- id: order
uri: lb://service-order
predicates:
- Path=/api/order/**
- id: product
uri: lb://service-product
predicates:
- Path=/api/product/**
长写法
predicates 可选列表 名
参数(个数 / 类型)
作用
After
1/datetime
在指定时间之后
Before
1/datetime
在指定时间之前
Between
2/datetime
在指定时间区间内
Cookie
2/string,regexp
包含 cookie 名且必须匹配指定值
Header
2/string,regexp
包含请求头且必须匹配指定值
Host
N/string
请求 host 必须是指定枚举值
Method
N/string
请求方式必须是指定枚举值
Path
2/List<String>,bool
请求路径满足规则,是否匹配最后的/
Query
2/string,regexp
包含指定请求参数
RemoteAddr
1/List<String>
请求来源于指定网络域 (CIDR 写法)
Weight
2/string,int
按指定权重负载均衡
XForwardedRemoteAddr
1/List<string>
从 X-Forwarded-For 请求头中解析请求来源,并判断是否来源于指定网络域
自定义 predicates 根据 QueryRoutePredicateFactory 仿写
@Component public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory <VipRoutePredicateFactory.Config> { public VipRoutePredicateFactory () { super (Config.class); }
Filter - 过滤器
写法
RewritePath=/api/order/?(?."), /${segment}
这条规则的作用是:将路径 /api/order/{任意内容} 重写为 /{任意内容} 。换句话说,它去掉了 /api/order/ 前缀,保留了后面的路径部分。
/api/order/123 会被重写为 /123
/api/order/abc/xyz 会被重写为 /abc/xyz
这类配置通常用于将请求从一个路径重定向到另一个路径,或者将路径中的某些部分移除。
在此处的作用是不用在各微服务加前缀,这里可以统一管理。
Filter 可选列表 名
参数(个数/类型)
作用
AddRequestHeader
2/string
添加请求头
AddRequestHeadersIfNotPresent
1/List
如果没有则添加请求头,key:value 方式
AddRequestParameter
2/string、string
添加请求参数
AddResponseHeader
2/string、string
添加响应头
CircuitBreaker
1/string
仅支持 forward:/inCaseOfFailureUseThis 方式进行熔断
CacheRequestBody
1/string
缓存请求体
DedupeResponseHeader
1/string
移除重复响应头,多个用空格分割
FallbackHeaders
1/string
设置 Fallback 头
JsonToGrpc
请求体 Json 转为 gRPC
LocalResponseCache
2/string
响应数据本地缓存
MapRequestHeader
2/string
把某个请求头名字变为另一个名字
ModifyRequestBody
仅 Java 代码方式
修改请求体
ModifyResponseBody
仅 Java 代码方式
修改响应体
PrefixPath
1/string
自动添加请求前缀路径
PreserveHostHeader
0
保护 Host 头
RedirectTo
3/string
重定向到指定位置
RemoveJsonAttributesResponseBody
1/string
移除响应体中的某些 Json 字段,多个用,分割
RemoveRequestHeader
1/string
移除请求头
RemoveRequestParameter
1/string
移除请求参数
RemoveResponseHeader
1/string
移除响应头
RequestHeaderSize
2/string
设置请求大小,超出则响应 431 状态码
RequestRateLimiter
1/string
请求限流
RewriteLocationResponseHeader
4/string
重写 Location 响应头
RewritePath
2/string
路径重写
RewriteRequestParameter
2/string
请求参数重写
RewriteResponseHeader
3/string
响应头重写
SaveSession
0
session 保存,配合 spring-session 框架
SecureHeaders
0
安全头设置
SetPath
1/string
路径修改
SetRequestHeader
2/string
请求头修改
SetResponseHeader
2/string
响应头修改
SetStatus
1/int
设置响应状态码
StripPrefix
1/int
路径层级拆除
Retry
7/string
请求重试设置
RequestSize
1/string
请求大小限定
SetRequestHostHeader
1/string
设置 Host 请求头
TokenRelay
1/string
OAuth2 的 token 转发
自定义 Filter @Component public class OnceTokenGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply (NameValueConfig config) { return new GatewayFilter () { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { return chain.filter(exchange).then(Mono.fromRunnable(()->{
设置默认 filter
CORS - 跨域处理 spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]' :
allowedOriginPatterns: '*'
allowedHeaders: '*'
allowedMethods: '*'
适用于所有路径([/**]) 。
允许任何来源(allowedOriginPatterns: '*') 访问 API。
允许所有 HTTP 头部(allowedHeaders: '*') 。
允许所有请求方法(allowedMethods: '*') ,包括 GET、POST、PUT、DELETE 等。
[/
*
*] 其中 /** 代表所有 API 路径
注意 :[/**]必须加方括号 [] ,否则 YAML 解析可能会出错。
GlobalFilter @Component @Slf4j public class RTGlobalFilter implements GlobalFilter , Ordered { @Override public Mono<Void> filter (ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); String uri = request.getURI().toString(); long startTime = System.currentTimeMillis(); log.info("请求【{}】开始,时间{}" , uri, startTime);
小节
导依赖:gateway,nacos-discovery,lombok(@Slf4j 用)
predicates 的 yml 配置,自定义断言类
filters 的 yml 配置,自定义 filter 类,设置默认 filter
CORS 跨域的 yml 配置
GlobalFilter 全局过滤器
seata seata 服务器的 web 界面的端口是 7091,而 8091 是 TC 协调者的 TCP 端口
是什么
seata 有服务器端和客户端,客户端要连上服务器才能使用。
TC(事务协调者)在服务器端 得官网下载: 全局事务的管理者 。用于维护全局和分支事务的状态,驱动 TM 的全局提交和回滚。TM 和 RM 通过 TC 注册分支和汇报状态。
TM(事务管理器)在客户端:发起全局事务 ,定义全局事务的范围,操作全局事务的提交和回滚。
RM(资源管理器)在客户端:操作自己分支的事务提交和回滚 。
注意:seata 的稳定性非常重要,如果 TC 崩了,那所有的事务管控都失效。
怎么用
环境搭建
微服务
SQL CREATE DATABASE IF NOT EXISTS `storage_db`;
USE `storage_db`;
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int (11 ) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar (255 ) DEFAULT NULL ,
`count` int (11 ) DEFAULT 0 ,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE= InnoDB DEFAULT CHARSET= utf8;
INSERT INTO storage_tbl (commodity_code, count) VALUES ('P0001' , 100 );
INSERT INTO storage_tbl (commodity_code, count) VALUES ('B1234' , 10 );
DROP TABLE IF EXISTS `undo_log`;
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 ,
`ext` varchar (100 ) DEFAULT NULL ,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE= InnoDB AUTO_INCREMENT= 1 DEFAULT CHARSET= utf8;
CREATE DATABASE IF NOT EXISTS `order_db`;
USE `order_db`;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int (11 ) NOT NULL AUTO_INCREMENT,
`user_id` varchar (255 ) DEFAULT NULL ,
`commodity_code` varchar (255 ) DEFAULT NULL ,
`count` int (11 ) DEFAULT 0 ,
`money` int (11 ) DEFAULT 0 ,
PRIMARY KEY (`id`)
) ENGINE= InnoDB DEFAULT CHARSET= utf8;
DROP TABLE IF EXISTS `undo_log`;
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 ,
`ext` varchar (100 ) DEFAULT NULL ,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE= InnoDB AUTO_INCREMENT= 1 DEFAULT CHARSET= utf8;
CREATE DATABASE IF NOT EXISTS `account_db`;
USE `account_db`;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int (11 ) NOT NULL AUTO_INCREMENT,
`user_id` varchar (255 ) DEFAULT NULL ,
`money` int (11 ) DEFAULT 0 ,
PRIMARY KEY (`id`)
) ENGINE= InnoDB DEFAULT CHARSET= utf8;
INSERT INTO account_tbl (user_id, money) VALUES ('1' , 10000 );
DROP TABLE IF EXISTS `undo_log`;
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 ,
`ext` varchar (100 ) DEFAULT NULL ,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE= InnoDB AUTO_INCREMENT= 1 DEFAULT CHARSET= utf8;
使用步骤 service {
#transaction service group mapping
vgroupMapping.default_tx_group = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#degrade, current not support
enableDegrade = false
#disable seata
disableGlobalTransaction = false
}
在 RM 分支事务微服务中启动类上加入@EnableTransactionManagement,在具体事务 service 方法上加@Transactional
在 TM 全局事务微服务中启动类上不加 @EnableTransactionManagement,但在事务 service 方法上要加上**@GlobalTransactional。**这个注解是核心
测试结果
测试本地事务
没有@EnableTransactionManagement,@Transactional 两注解
有@Transactional,但没有@EnableTransactionManagement 会产生一个问题:没有@EnableTransactionManagement,异常数据也能回滚,为什么?
在 Spring Boot 中,不需要 @EnableTransactionManagement 也能回滚数据,因为 Spring Boot 默认开启了事务管理 。如果你在 Spring 传统项目里,可能就需要手动启用事务管理。
所以还是启动类加上@EnableTransactionManagement
有@EnableTransactionManagement,@Transactional 两注解 seata-account, seata-storage 单个本地事务是有效的,但是对于 seata-order 要远程调用 seata-account,有异常订单不会创建,但是远程调用的余额扣减却成功了。
修改 seata-order 去掉@EnableTransactionManagement
将@Transactional 改成@GlobalTransactional
效果:seata-order 出现异常,订单没创建,远程调用账户扣减数据能回滚
再测试有@EnableTransactionManagement,有@GlobalTransactional
效果:和上面一样。再一次证明 springboot 默认开启事务
测试分布式事务——模拟情景 采购服务要远程调用 扣库存和下订单服务,订单服务又要远程调用扣减余额服务,怎么保证分布式事务的数据一致性?
先在各个模块启动类上加入@EnableTransactionManagement,全局事务(采购模块)除外。
各分支事务加@Transactional,全局事务加**@GlobalTransactional**
在订单模块模拟错误进行测试
情景:结算服务中事务上没有加@GlobalTransactional 情况,远程调用扣减库存服务和创建订单服务(订单服务要远程调用扣减余额服务)。模拟订单服务出错,看扣减余额远程调用能否回滚
结果:库存扣减,余额扣减,订单没有生成;只有订单服务回滚,其他皆无效。
其他 如果导入了 seata 的依赖:spring-cloud-starter-alibaba-seata
所在模块的 file.conf 配置文件先自动配置,不行再将格式改成 properties
seata 各模式原理 广义事务:业务的事务(包括数据库操作),举例:事务中需要发邮件和发短信,这种不能撤回的情况,AT 和 XA 模式不管用了。
AT 模式 图片里是 AT 模式的二阶段提交协议,其他模式也是二阶段提交(一阶段:本地事务 二阶段:提交/回滚 )
AT:系统默认使用,各分支事务要经过两阶段提交协议。
第一阶段:本地提交
生成前镜像:将要操作的数据记录下来
执行 SQL 操作数据 (期间启动 MySQL 的行锁,先对要操作的数据 select ...for update(此时数据被锁住,普通的 select 可以读取,要看隔离级别。还有如果读操作的 select..for update 这种想加锁的读是不可以的),再执行后续 sql,执行完则释放行锁)
生成后镜像:将操作后的数据记录下
前镜像和后镜像等待保存到 uodo_log 日志表中
向 TC 注册分支,在 TC 中申请一个全局锁,锁定操作的数据防止其他人操作,读同上,有锁才能操作 。注意这里 TC 的全局锁不是 MySQL 的全局锁,MySQL 的全局锁是锁整个数据库,而 TC 的全局锁相当于 MySQL 的行级别锁,只锁操作的数据。
本地事务提交;将业务数据和 uodo_log 日志表数据一起保存到当前事务的的表中
和 TC 汇报事务执行状态
第二阶段:
若各分支事务都成功:删除 uodo_log 记录
TC 能感知到每个事务的状态,通知他们进行提交
给异步任务对列添加异步任务,异步 + 批量删除对应的 uodo_log 日志表的记录
若某个事务失败,TC 会通知所有分支事务回滚:拿到前镜像,数据恢复,删除 uodo_log 记录
先找到 uodo_log 记录(通过 XID,BranchID)
数据校验,后镜像和当前数据是否一致,一致就 ok 执行回滚;不一致说明当前数据被其他操作给篡改了,需要配置相应的策略(怎么处理这个脏数据,忽略还是人工处理?之类)
若一致,则回滚数据到前镜像的内容,完成后删除 uodo_log 记录
只要有分支事务没处理完,全局锁会一直存在。但是第一阶段执行事务是真正提交了的,不会在第二阶段一直阻塞数据库。
XA 模式: 第一阶段不会提交数据,阻塞事务请求,在第二阶段确认提交再提交或者回滚。全局锁+MySQL 行锁在第一阶段就开启,事务一开始就用阻塞模式,性能差。AT 和 XA 区别是 AT 第一阶段执行完 SQL 释放行锁,XA 是到第二阶段才提交 SQL 导致行锁从开始到最后,阻塞时间长性能差。但二者都是一直持有 seata 的全局锁的。
TCC 模式: 主要是广义上的事务,需要写侵入式的代码 。举例业务需要三个事务,一个事务改数据库,一个发短信,一个发邮件,这就用 AT 和 XA 行不通了,无法回滚,如果全局事务失败,只能进行补偿性操作,例如再发邮件和短信提醒对方扣款失败或者订单失败等。
saga 模式: 用于长事务,一时半会执行不完的事务 。例如请假审批,其他模式都用了锁,如果长期锁在那是对系统是非常大的阻塞。saga 是基于消息队列做的,后续有替代方案,所以这个几乎不用。
debug 查看 seata 执行流程
小节
使用。引入了 alibaba-seata 依赖,只要在分布式事务的方法加@GlobalTransactional,在其他本地事务加@EnableTransactionManagement,@Transactional 即可。这样分布式事务中无论哪个环节异常,都会回滚。
原理,以及四种模式的特点
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online