springCloud
单体架构 ->
所有功能模块都在一个项目
优点:开发部署
缺点:无法应对高并发
域名,IP,节点,应用,数据库
集群架构 ->
解决大并发
问题1:模块化升级
订单功能经常升级:v1.0,v2.0
问题2:多语言团队分工协作
C++直播模块
副本,集群,路由,负载均衡,扩缩容
分布式架构
一个大型应用被拆分成很多小应用
分布部署
在各个机器;工作方式
微服务(自治)独立部署、数据隔离,副本,负载均衡,单点故障,远程调用(RPC),分布式事务,服务熔断,服务雪崩,,服务发现,服务注册,注册中心,配置中心,请求路由
微服务:SpringBoot
注册中心/配置中心:Spring Cloud Alibaba Nacos
网关:Spring Cloud Gateway
远程调用:Spring Cloud OpenFeign
服务熔断:Spring Cloud Alibaba Sentine
分布式事务:Spring Cloud Alibaba Seata
Nacos
- Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
- 官网:https://nacos.io/zh-cn/docs/v2/quickstart/quick-start.html
- 安装:
- 下载安装包【2.4.3】
- 启动命令: startup.cmd -m standalone
- 访问地址:http://localhost:8848/nacos/
注册中心
服务注册
- 启动微服务SpringBoot 微服务web项目启动
- 引入服务发现依赖
spring-cloud-starter-alibaba-nacos-discovery
- 配置Nacos地址
spring.cloud.nacos.server-addr=127.0.0.1:8848
- 查看注册中心效果访问 http://localhost:8848/nacos
- 集群模式启动测试单机情况下通过改变端口模拟微服务集群
服务发现
- 开启服务发现功能
@EnableDiscoveryClient
- 测试服务发现API
DiscoveryClient
OrNacosServiceDiscovery
远程调用
以下订单为例
基本流程
下单场景
实现步骤
- 引入负载均衡依赖
spring-cloud-starter-loadbalancer
- 测试负载均衡API
LoadBalancerClient
- 测试远程调用
RestTemplate
- 测试负载均衡调用
@LoadBalanced
思考:注册中心宕机,远程调用还能成功吗?
配置中心
基本使用
启动Nacos
引入依赖
1
2
3
4<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>application.properties配置
1
2
3spring.cloud.nacos.server-addr=127.0.0.1:8848
# 以逗号分隔可以导入多个配置文件
spring.config.import=nacos:service-order.properties创建data-id(数据集)
禁用导入检查
1
spring.cloud.nacos.config.import-check.enabled=false
动态刷新
@Value(“${xx}”)
获取配置 +@RefreshScope
实现自动刷新@ConfigurationProperties(prefix="配置项前缀")
无感自动刷新(批量绑定,驼峰),注意需要加上@Component
配置监听
NacosConfigManager
监听配置变化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ApplicationRunner applicationRunner(NacosConfigManager nacosConfigManager){
return args -> {
ConfigService configService = nacosConfigManager.getConfigService();
configService.addListener("service-order.properties",
"DEFAULT_GROUP", new Listener() {
public Executor getExecutor() {
return Executors.newFixedThreadPool(4);
}
public void receiveConfigInfo(String configInfo) {
System.out.println("变化的配置信息:"+configInfo);
System.out.println("邮件通知...");
}
});
System.out.println("=========");
};
}
思考: Nacos中的数据集 和 application.properties 有相同的 配置项,哪个生效?
以配置中心为准
先导入优先,外部优先
数据隔离
- 需求描述
- 项目有多套环境:dev,test,prod
- 每个微服务,同一种配置,在每套环境的值都不一样
- 如:database.properties
- 如:common.properties
- 项目可以通过切换环境,加载本环境的配置
- 难点
- 区分多套环境
- 区分多种微服务
- 区分多种配置
- 按需加载配置
namespace
动态切换
示例
1 | server: |
总结
注册中心
- 引入 spring-cloud-starter-alibaba-nacosdiscovery依赖,配置Nacos地址
- @EnableDiscoveryClient 开启服务发现功能
- 扩展:
- DiscoveryClient 获取服务实例列表
- LoadBalancerClient 负载均衡选择一个实例
(需要引入 spring-cloud-starter-loadbalancer) - RestTemplate 可以发起远程调用
配置中心
- 引入 spring-cloud-starter-alibaba-nacosconfig依赖,配置Nacos地址
- 添加 数据集(data-id),使用spring.config.import 导入数据集
- @Value + @RefreshScope 取值 + 自动刷新
- @ConfigurationProperties批量绑定自动刷新
- NacosConfigManager 监听配置变化
- 扩展:
- 配置优先级;namespace区分环境、group区分
- 微服务、data-id区分配置 实现 数据隔离+环境切换
OpenFeign
声明式REST客户端 @EnableFeignClients
开启Feign远程调用功能
注解驱动
- 指定
远程地址
:@FeignClient - 指定
请求方式
:@GetMapping、@PostMapping、@DeleteMapping … - 指定
携带数据
:@RequestHeader、@RequestParam、@RequestBody … - 指定
结果返回
:响应模型
1 | <dependency> |
远程调用
业务API
如果调用自己写的API,直接把对应的Controller签名复制过来即可
1 | // feign客户端 |
第三方API
1 |
|
思考:客户端负载均衡与服务端负载均衡区别
日志
1 | logging: |
1 |
|
超时控制
- connectTimeout 连接超时(默认10s)
- readTimeout 读取超时(默认60s)
1 | spring: |
重试机制
远程调用超时失败后,还可以进行多次尝试,如果某次成功返回ok,如果多次依然失败则结束调用,返回错误
默认NEVER.RETRY
超过了一秒就以一秒计
1 | retryer: feign.retryer.Default |
Or
1 |
|
拦截器
示例
1 | // 只要有RequestInterceptor,容器会自动应用,spring自动扫描,或者在yaml加上 |
Fallback
Fallback:兜底返回
注意:此功能需要整合 Sentinel 才能实现
1 | feign: |
1 | // feign客户端 |
Sentinel
服务保护(限流、熔断降级)
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Spring Cloud Alibaba Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性
- 定义资源:
- 主流框架自动适配(Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor);所有Web接口均为资源
- 编程式:SphU API
- 声明式:@SentinelResource
- 定义规则:
- 流量控制(FlowRule)
- 熔断降级(DegradeRule)
- 系统保护(SystemRule)
- 来源访问控制(AuthorityRule)
- 热点参数(ParamFlowRule)
1 | <dependency> |
1 | sentinel: |
声明资源
1 |
启动控制器
1 | java -jar sentinel-dashboard-1.8.8.jar |
1 | SphU.entry() |
异常处理
Web接口
model下的common
1 | import lombok.Data; |
自定义BlockExceptionHandler
1 |
|
@SentinelResource
1 |
|
OpenFeign
如果有fallback方法,则会invoke执行兜底回调,否则会抛给spring Boot全局异常处理
流控规则(FlowRule)
限制多余请求,从而保护系统资源不被耗尽
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 直接(根据资源本身) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
阈值类型
- QPS:
- 统计每秒请求数
- 并发线程数:
- 统计并发线程数
流控模式
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系;有了调用链路的统计信息,我们可以衍生出多种流量控制手段
直接
直接对某个资源进行流量控制
链路
举个例子,我们正常创建订单的时候不做限制,但是涉及到秒杀的时候就会进行流量控制
要链路控制,就需要关闭统一的默认上下文
1 | sentinel: |
关联
当写的请求量不大的时候,读不会受到限制;如果此时有大量写的请求,Sentinel就会限制读的请求,来将更多的资源分配给写
流控效果
直接 - 快速失败
如果流量符合流控规则,则交给业务处理;如果超出限制,则会抛出BlockException异常,对于这个异常我们可以自定义进行处理,比如封装成json返回
Warm Up
- 只适用于直接的流控模式
如果遇见高峰流量请求,会经过一段时间的冷启动,根据QPS(峰值)和Period,逐步上调当前QPS直到达到阈值
匀速排队(Leaky Bucket算法和虚拟队列等)
- 只适用于直接的流控模式
QPS=2时,每秒两个,则500ms放行一个,不支持QPS>1000(因为1s=1000ms,QPS大于1000后精度会失效)
每秒放行两个,多余的请求会排队,如果排队的时间达到了timeout的设定值,该请求会被丢弃
熔断规则- 熔断降级(DegradeRule)
- 切断不稳定调用
- 快速返回不积压
- 避免雪崩效应
熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
属性 | 说明 | 默认值 |
---|---|---|
resource | 资源名,必填 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
断路器
慢调用比例
- 最大RT(ms):只要超过设定值则会认为是慢请求
- 比例阈值:如果慢请求比例达到了设定值,则会开启熔断
- 统计时长,最小请求数:只有在统计时长内达到了最小请求数,才会开始统计
- 熔断时长
异常比例
异常数
热点规则
热点参数
需求1:每个用户秒杀 QPS 不得超过 1(秒杀下单 userId 级别)
效果:携带此参数的参与流控,不携带不流控
需求2:6号用户是vvip,不限制QPS(例外情况)
需求3:666号是下架商品,不允许访问
热点规则可以对请求参数做限制,也就是说当某一参数携带特定的值时,热点限流将起作用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Order seckill( Long userId,
{ Long productId)
Order order = orderService.createOrder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
// 如果兜底回调设置成fallback,接受异常设置成Throwable,则该兜底回调不仅可以服务业务异常也可以服务因热点限流导致的异常
public Order seckillFallback(Long userId,Long productId, Throwable exception){
System.out.println("seckillFallback....");
Order order = new Order();
order.setId(productId);
order.setUserId(userId);
order.setAddress("异常信息:"+exception.getClass());
return order;
}
Gateway
路由
需求
- 客户端发送 /api/order/** 转到 service-order
- 客户端发送 /api/product/** 转到 service-product
- 以上转发有负载均衡效果
1 | <dependency> |
application.yml
1 | spring: |
application-route.yml
1 | spring: |
断言
长短写法
1 | predicates: |
所有的断言都是RoutePredicateFactory
的实现,ctrl + H
Query
名 | 参数(个数/类型) | 作用 |
---|---|---|
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 |
按指定权重负载均衡 |
XForwarded RemoteAddr | 1/List<string> |
从X-Forwarded-For请求头中解析请求来源,并判断是否来源于指定网络域 |
自定义断言工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class VipRoutePredicateFactory extends AbstractRoutePredicateFactory<VipRoutePredicateFactory.Config> {
public VipRoutePredicateFactory() {
super(Config.class);
}
public Predicate<ServerWebExchange> apply(Config config) {
return new GatewayPredicate() {
public boolean test(ServerWebExchange serverWebExchange) {
ServerHttpRequest request = serverWebExchange.getRequest();
String first = request.getQueryParams().getFirst(config.param);
return StringUtils.hasText(first) && first.equals(config.value);
}
};
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("param", "value");
}
/**
* 可以配置的参数
*/
public static class Config {
private String param;
private String value;
public String getParam() {
return param;
}
public void setParam( { String param)
this.param = param;
}
public String getValue() {
return value;
}
public void setValue( { String value)
this.value = value;
}
}
}
过滤器
路径重写
1 | # /api/order/read -> /read |
GlobalFilter
对所有请求进行拦截,可以在请求到达微服务之前(前置逻辑)和返回给客户端之前(后置逻辑)执行自定义操作
常见用途:
- 日志记录
- 权限校验
- 请求修改(比如添加或修改请求头)
- 响应修改(比如统一包装返回格式)
- 限流
- 负载均衡
1 |
|
自定义过滤器工厂
1 |
|
1 | filters: |
全局跨域
单体服务
- 可以在每个Controller上加上
@CrossOrigin
注解 - 或者使用CorsFilter方法
Gateway
1 | spring: |
思考:微服务之间的调用经过网关吗?
1 | // feign客户端 |
Seata
解决分布式事务
TC (Transaction Coordinator)
- 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚
TM (Transaction Manager)
- 事务管理器:定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM (Resource Manager)
- 资源管理器:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状
态,并驱动分支事务提交或回滚
Seata Java Download | Apache Seata
1 | seata-server.bat |
配置file.conf
(每个服务)
1 | service { |
为每个分支事务配置好RM后,只需要在最大的入口配置全局事务注解即可@GlobalTransactional
二阶提交协议
四种事务模式
AT
AT 模式是 Seata 最常用的事务模式,适用于基于关系型数据库(如 MySQL、PostgreSQL)的业务场景
工作原理
- AT 模式基于 二阶段提交协议(2PC),但对数据库操作进行了无侵入增强
- 由Seata 代理业务SQL语句,在本地事务中自动维护回滚日志,实现自动补偿
执行流程
- 第一阶段(Try)—— 业务 SQL 执行,Seata 记录回滚日志
- 业务代码执行普通的
INSERT/UPDATE/DELETE
语句; - Seata 通过数据代理,在执行 SQL 之前,生成一份回滚日志,记录数据变更前的状态。
- SQL 语句执行完成后,事务暂不提交。
- 业务代码执行普通的
- 第二阶段(Confirm 或 Rollback)
- 提交:直接提交本地事务,无需额外操作。
- 回滚:Seata 通过回滚日志生成补偿 SQL,自动恢复数据。
- 无侵入
- 自动回滚
- 性能较优:相比 XA 模式,不锁住整个事务时间,只在本地数据库生效
TCC
TCC 模式是一种业务层面的二阶段提交,由开发者手动
实现补偿逻辑,适用于跨数据库、跨服务的分布式事务。
工作原理
TCC 也是基于二阶段提交协议(2PC):
- Try(尝试)
- 业务执行前,预留资源。
- Confirm(确认)
- 业务执行成功后,真正提交事务。
- Cancel(取消)
- 业务失败或事务回滚时,释放资源。
- 无数据库限制,适用于 NoSQL、RPC 等非关系型数据存储。
- 业务自定义控制,可以优化性能,减少数据库锁定时间。
Saga
Saga 是基于 长事务 的补偿机制,适用于长时间运行
的业务(如机票预订、订单支付)。
工作原理
Saga 采用 事务编排 或 事务悬挂 的方式,分多个子事务依次执行:
- 正向事务(S1 → S2 → S3):逐步执行业务操作
- 回滚事务(C3 → C2 → C1):当某个事务失败时,执行补偿操作(撤销之前的步骤)
执行流程
事务链执行
- 例如:用户下单(扣款) → 预定酒店 → 预定机票。
- 每个操作都是独立事务,彼此没有锁定关系。
事务回滚
如果某一步失败(如机票预订失败),则执行
反向补偿事务:
- 取消酒店预订
- 退还用户扣款
适用场景
- 长时间事务(如电商订单、机票酒店预订)。
- 可补偿的业务流程(每个子事务必须有对应的撤销逻辑)。
优点
没有全局锁,适用于长事务场景,性能较优。
适合业务编排,可以灵活控制事务流程。
XA
XA 是 强一致性 事务,基于 XA 协议(2PC),由数据库本身提供 分布式事务管理。
工作原理
XA 也是 二阶段提交:
- 第一阶段(Prepare)
- 事务管理器(TM)通知各数据库准备提交。
- 各数据库锁定资源,但
不提交
事务。
- 第二阶段(Commit 或 Rollback)
- Commit:所有数据库都正式提交事务。
- Rollback:某个数据库失败,则所有数据库回滚事务。
适用场景
- 适用于强一致性需求(如金融交易)。
- 适用于支持 XA 事务的数据库(如 MySQL InnoDB、PostgreSQL)。
优点
数据强一致性,适用于金融等高要求场景
缺点
性能低,因为数据库需要长时间锁定资源。
需要数据库支持 XA 事务,适用范围受限。