【Spring Cloud Alibaba系列】快速入门Spring Cloud Alibaba(整合Nacos、Sentinel、Seata、Gateway、OpenFeign、Loadbalancer)
本内容适用对应Spring Cloud Alibaba的快速入门,且所有需要部署的服务均在服务器进行部署。
参考视频:吃透微服务Spring Cloud Alibaba快速入门到精通全套教程
前置条件:SpringBoot(必须)、分布式事务原理(可选)
Spring Cloud Alibaba组件:Nacos、Sentinel、Seata
Spring Cloud组件:Gateway、OpenFeign
版本说明
| Spring Cloud Alibaba | 2023.0.1.0 |
| Sentinel | 1.8.6 |
| Nacos | 2.3.2 |
| RocketMQ | 5.1.4 |
| Seata | 2.0.0 |
| Spring Cloud | 2023.0.1 |
| Spring Boot | 3.2.4 |
服务划分
| 网关服务 (gateway) | 统一入口、路由转发、请求过滤、跨域处理、限流熔断 |
| 认证授权服务 (auth-service) | 用户登录、Token 颁发与校验、OAuth2 / 第三方登录入口 |
| 用户服务 (user-service) | 用户基本信息、角色权限、关注粉丝、封禁、第三方登录配置 |
| 文件服务 (file-service) | 文件上传、下载、元数据管理、预览地址生成 |
| 内容服务 (content-service) | 文章、资源、AI 工具、分类、点赞收藏 |
| 评论服务 (comment-service) | 评论及回复、评论状态管理 |
| 审核服务 (audit-service) | 违禁词管理、内容审核策略 |
| 系统配置服务 (system-service) | 系统配置、AI 配置、前端路由管理 |
注意:除Gateway外,其他服务都需要添加springboot的web依赖。 服务划分将在项目实战部分使用,当前阶段仅做介绍。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
服务注册与发现-Nacos Discovery
管理项目的服务
下载对应版本的 Nacos Server 2.3.2 下载地址
修改启动脚本:
win系统:修改startup.cmd->set MODE="cluster"中的cluster为standalone即集群模式修改为单机模式。
linux系统:修改startup.sh-> export MODE=“cluster” 的同样字段内容。
单机建议使用内存,集群建议使用数据库,在配置文件进行修改。
默认端口:8848
启动后访问地址: http://ip:port/nacos/index.html
#*************** Spring Boot Related Configurations ***************#
### Default web context path:请求名称
server.servlet.contextPath=/nacos
### Include message field
server.error.include-message=ALWAYS
### Default web server port:端口号
server.port=8848
#*************** Access Control Related Configurations ***************#
### If enable spring security, this option is deprecated in 1.2.0:
#spring.security.enabled=false
### If turn on auth system: 开启身份校验
nacos.core.auth.enabled=true
### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false.
### The two properties is the white list for auth and used by identity the request from other server.
# 登录名称
nacos.core.auth.server.identity.key=nacos
# 登录密码
nacos.core.auth.server.identity.value=nacos
### The default token (Base64 String): 校验token
nacos.core.auth.plugin.nacos.token.secret.key=自行生成Base64(长度64位)
配置子服务的nacos配置
spring:
application:
# 服务名
name: nacos
# nacos配置
cloud:
nacos:
# nacos服务端地址
server-addr: ip:端口号
discovery:
# 与config一致
username: 登录名
password: 登录密码
配置中心-Nacos Config
动态管理配置文件
项目地址:https://github.com/alibaba/nacos
引入nacos-config依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
创建bootstrap.yml配置文件
#配置服务名称
spring:
application:
name: nacos
profiles:
active: dev
# nacos配置
cloud:
nacos:
# nacos配置中心
server-addr: ip/域名:nacos端口号
config:
username: 账号
password: 密码
namespace: 命名空间id
file-extension: 配置文件格式
#配置多个配置文件
shared-configs:
– data-id: database–dev.yaml # 配置文件id
refresh: true #动态刷新
group: DEFAULT_GROUP #分组
config:
# 关键:导入 Nacos 配置
import: optional:nacos:配置文件名称
在需要的地方可以通过@Value注入
@Value("${mysql.username}")
public String username;
@Value("${redis.username}")
public String redis;
@RefreshScope:在需要动态加载nacos配置场景如:controller、数据库连接等。
//在需要动态加载的文件类上加上@RefreshScope
@SpringBootApplication
@RefreshScope
public class NacosApplication {
@Value("${mysql.username}")
public String username;
@Value("${redis.username}")
public String redis;
@PostConstruct
public void init(){
System.out.println("mysql:"+username);
System.out.println("redis:"+redis);
}
public static void main(String[] args) {
SpringApplication.run(NacosApplication.class, args);
}
}
服务调用-OpenFeign
调用其他服务
项目地址:https://github.com/spring-cloud/spring-cloud-openfeign
快速入门
- 添加依赖: 在需要调用其他的服务的模块的pom.xml文件中添加对应的openfeign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 在需要调用的模块创建对应controller的feign文件
//提供方:模块1有StockController的add方法
@RestController
@RequestMapping("/auth")
public class AuthController {
@RequestMapping("/add")
public String add(){
return "add1";
}
}
//
/**
* 调用方:模块2 有UserController的test方法需要调用模块1StockController的add方法
* 在模块2中创建feign文件夹,新建UserFeign接口
* name:需要调用的服务名
* path:需要调用的服务路径,即对应的controller的RequestMapping
*/
@FeignClient(name = "xxx-auth",path = "/auth")
public interface UserFeign {
//声明调用的方法与对应的方法完全一致,不需要实现类
@RequestMapping("/add")
String add();
}
//调用方:在UserController的test方法中注入UserFeignService即可调用
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserFeignService userFeignService;
@RequestMapping("/test")
public String test(){
String add = userFeignService.add();
return "hello world "+add;
}
}
//最后在调用方模块2的启动中添加@EnableFeignClients注解
@SpringBootApplication
@EnableFeignClients
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
日志配置
-
全局配置:创建FeignConfig配置类并添加@Configuration注解,接着修改配置文件,将默认的日志级别修改对应feignService类的包为debug。
/**
* feign 配置类
* @Configuration注解会将配置作用与全局
*/
@Configuration
public class FeignConfig {
/**
* feign 日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
#====================配置文件======================
logging:
level:
top.xxxxx.xxxxxx.feign: debug -
局部配置:创建FeignConfig配置类不添加@Configuration注解,并在需要配置feignService的@FeignClient配置中添加configuration = 配置类class。
/**
* feign 配置类
*/
public class FeignConfig {
/**
* feign 日志级别
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
/**
局部配置
*/
@FeignClient(name = "xxxx-auth",path = "/auth",configuration = FeignConfig.class)
public interface UserFeignService {
//声明调用的方法与对应的方法完全一致,不需要实现类
@RequestMapping("/add")
String add();
}
#====================配置文件-feign局部配置======================
# feign日志局部配置
feign:
client:
config:
#调用的服务名
xxxx-auth:
loggerLevel: FULL -
日志级别
- NONE:适用与生产环境,不记录任何日志-默认。
- BASIC:适用于生产环境追踪问题,仅记录请求方法、URL、响应状态码以及执行时间。
- HEADERS:在BASIC基础上增加记录请求头和响应头。
- FULL:适用与开发环境和测试环境,记录请求头、请求体、请求元数据和响应头、响应体、响应元数据。
契约配置(了解即可)
将SpringMVC的注解替换为Feign的注解。在未使用OpenFeign的微服务项目中需要升级但有需要保留Feign的原生注解的情况。
注:一旦配置的Feign默认的原生注解,则必须适用Feign的注解不然会报错。
-
在FeignConfig中添加bean来支持原生注解。
/**
* 修改契约配置,来支持Feign原生注解
* @return
*/
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
} -
在配置文件中配置,对应的服务下添加contract: feign.Contract.Default实现。
feign:
client:
config:
#调用的服务名
xxxx-auth:
#配置支持Feign原生注解
contract: feign.Contract.Default
超时时间配置
-
全局配置:在配置类中注入超时时间配置bean
/**
* 超时时间配置
* @return
*/
@Bean
public Request.Options options(){
return new Request.Options(5000,5000);
} -
局部配置
feign:
client:
config:
#调用的服务名
xxxx-auth:
#连接超时
connectTimeout: 5000
#请求处理超时
readTimeout: 5000
自定义拦截器
创建自定义拦截器实现RequestInterceptor接口,并在FeignConfig中注入。
//====================自定义Feign注解类======================
/**
* feign请求拦截器
*/
public class FeignAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
//具体业务逻辑
template.header("Authorization", "Bearer " + "123");
}
}
//====================FeignConfig配置类======================
/**
* 自定义拦截器
* @return
*/
@Bean
public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
return new FeignAuthRequestInterceptor();
}
#====================配置文件实现自定义拦截器======================
feign:
client:
config:
#调用的服务名
xxxx-auth:
requestInterceptors[0]: top.xxxx.xxxxx.interceptor.FeignAuthRequestInterceptor
熔断限流-Sentinel
高可用防护组件,流量防护,从限流、流量整形、熔断降级、负载保护、热点防护等维度。
快速入门
github地址:https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
maven仓库查询:https://mvnrepository.com/
官方文档:https://sentinelguard.io/zh-cn/index.html
依赖导入:当前仅引入Sentinel的核心库,暂不整合Alibaba
<!–Sentinel的核心库–>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.10</version>
</dependency>
<!–使用@SentinelResource注解–>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.10</version>
</dependency>
实现代码
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
//定义资源
private static final String RESOURCE_A = "resourceA";
private static final String RESOURCE_B = "resourceB";
private static final String RESOURCE_C = "resourceC";
//进行流控
@RequestMapping("/resourceA")//注意接口名称必须与资源名称一致
public String resourceA() {
Entry entry = null;
try {
//获取资源
entry = SphU.entry(RESOURCE_A);
//业务逻辑
log.info("hello world"+"resourceA");
return "hello world"+"resourceA";
} catch (BlockException e) {
//流控逻辑处理
log.info("限流了"+"resourceA");
return "限流了"+"resourceB";
}catch (Exception e){
log.info("异常了"+"resourceA");
//异常逻辑处理
Tracer.trace(e);
}finally {
if (entry != null) {
entry.exit();
}
}
return "error"+"resourceA";
}
/**
* 初始化流控规则
*/
@PostConstruct
public void init(){
List<FlowRule> flowRules = new ArrayList<>();
//创建流控规则
FlowRule flowRule = new FlowRule();
//设置资源的流控
flowRule.setResource(RESOURCE_A);
//设置限流阈值模式
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置限流阈值
flowRule.setCount(1);
flowRules.add(flowRule);
//加载流控规则
FlowRuleManager.loadRules(flowRules);
}
}
使用@SentinelResource注解改善资源定义和流控处理方法
@RestController
@RequestMapping("/test")
@Slf4j
public class TestController {
//定义资源
private static final String RESOURCE_A = "resourceA";
private static final String RESOURCE_B = "resourceB";
private static final String RESOURCE_C = "resourceC";
/**
* 初始化流控规则
*/
@PostConstruct
public void init(){
List<FlowRule> flowRules = new ArrayList<>();
//创建流控规则
FlowRule flowRule = new FlowRule();
//设置资源的流控
flowRule.setResource(RESOURCE_B);
//设置限流阈值模式
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置限流阈值
flowRule.setCount(1);
flowRules.add(flowRule);
//加载流控规则
FlowRuleManager.loadRules(flowRules);
}
/**
* 1. 使用SentinelResource注解
* 2. 配置bean
* @return
*/
@RequestMapping("/resourceB")
//value:定义的资源 blockHandler:配置流控逻辑处理方法
@SentinelResource(value = RESOURCE_B,blockHandler = "handleExceptionUser")
public User resourceB() {
return new User("111", 222, "333");
}
/**
* 流控限制逻辑处理
* 1.必须是public
* 2.参数顺序必须一致
* 3.返回值类型必须一致
* @param e
* @return
*/
public User handleExceptionUser(BlockException e){
return new User("限流了", 222, "resourceB");
}
}
流控规则
@PostConstruct
public void init(){
List<FlowRule> flowRules = new ArrayList<>();
//创建流控规则
FlowRule flowRule = new FlowRule();
//设置资源的流控
flowRule.setResource(RESOURCE_B);
//设置限流阈值模式
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//设置限流阈值
flowRule.setCount(1);
flowRules.add(flowRule);
FlowRuleManager.loadRules(flowRules);
}
降级规则
/**
* 初始化熔断降级规则
*/
@PostConstruct
public void init2(){
//创建熔断规则
List<DegradeRule> degradeRules = new ArrayList<>();
//设置熔断资源
DegradeRule degradeRule = new DegradeRule();
//设置熔断资源
degradeRule.setResource(RESOURCE_C);
//设置熔断阈值模式
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
//设置熔断阈值
degradeRule.setCount(2);
//设置熔断时间窗口
degradeRule.setTimeWindow(5);
//设置熔断最小请求数
degradeRule.setMinRequestAmount(2);
//设置熔断统计时间窗口
degradeRule.setStatIntervalMs(60*1000);
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);
}
控制台部署
下载控台jar包:控制台jar下载
注意:因为后续需要整合为alibaba所以需要与alibaba的版本对应。
启动控制台服务。
在需要接入的服务中添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>对应下载的控制台版本号</version>
</dependency>
修改服务的启动的vm参数(如果修改了控制台默认的服务端口则需要配置)
-Dcsp.sentinel.dashboard.server=ip:端口号
整合SpringCloudAlibaba
在需要使用sentinel的服务中添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置sentinel的地址
spring:
application:
name: xxxx–sentinel #服务名称
cloud:
sentinel:
transport:
dashboard: ip:端口号
流控规则
注:以下配置操作均在控制台中进行配置。
QSP:每秒的请求数(1秒处理多少个请求)。资源名:对应的接口;单机阈值即每秒处理的请求数量。

如需设定该接口的自定义流控限制逻辑参考本章内容的《快速入门》->第3点。
注:每次重启服务都需要重新设置对应的规则,当前阶段并没有实现持久化。
并发线程数:保护业务的线程池数不被慢调用消耗干净。超过设定的单机阈值则按照处理逻辑进行处理。

异常处理
本部分是对服务进行统一异常处理配置,如果某些接口的处理逻辑不一致,则可以在对应的接口使用其对应的自定义异常处理逻辑。
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
/**
* 统一的处理方法
* @param request Servlet request 请求对象
* @param response Servlet response 响应对象
* @param e the block exception 异常
* @throws Exception
*/
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
// 设置响应状态码
response.setStatus(500);
// 设置响应内容类型
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
//判断异常对象类型
if (e instanceof FlowException){
response.getWriter().write("限流了");
}else if (e instanceof DegradeException){
response.getWriter().write("降级了");
}
//new ObjectMapper().writeValue(response.getWriter(),e.getMessage());
}
}
流控模式
在新增规则表单的高级选项中。
直接流控模式:前面的处理模式都是属于直接流控模式即影响的是接口本身。

关联流控模式:即当关联资源的请求数/并发数达到阈值了,受影响的就是当前资源(接口),而不是关联资源(接口)。

链路流控模式:即按照链路的方式来执行的,当入口资源请求当前资源时,达到规则设定时触发异常处理,但是受影响的资源是入口资源而不是当前的资源。

若使用链路模式没生效,需要在对应的配置文件中去添加:
spring.cloud.sentinel.web-context-unify: false
若配置了@SentinelResource的接口必须要指定blockHandler参数
流控降级(提供服务端)
快速失败:默认的流控效果,即当超过规则要求的阈值的请求将直接拒绝,返回流控异常处理结果。

Warm UP(预热):在设定的预热时长内,让请求缓慢进入。(适用流量突然激增的场景)

排队等待:将拒绝的请求,进行超时时间的等待,在超时时间内若处理的阈值可以接受则处理拒绝的请求。

熔断降级(调用服务端)
慢调用(默认):
最大RT:超过设定的RT即视为慢调用;最小请求数:在最小请求数内如果出现慢调用,可忽略不进行降级处理;比列阈值:当慢调用数达到该比列则对该资源(接口)进行降级处理;熔断时长:执行熔断降级的时长,当结束后进入半开状态。

异常比列:访问数中异常数达到设定的比列阈值则触发熔断降级。

异常数:当访问中的请求出现异常的数量达到设定的异常数时触发熔断降级。

整合OpenFeign降级
必备依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改配置文件,开启oenfeign对sentinel的支持
feign:
sentinel:
enabled: true
创建openfeign的fallback实现类进行兜底:即openfeign创建的是接口,限制提供实现这个接口在出现异常后适用fallback实现类去处理。
@Component
public class UserFeignFallbackService implements UserFeignService{
@Override
public String add(String name) {
return "降级处理";
}
}
在对应的接口的FeignClient注解里面添加fallback参数。
@FeignClient(name = "xxxx-auth",path = "/auth",fallback = UserFeignFallbackService.class)
热点参数流控
需要进行热点参数流控则必须使用@SentinelResource注解,并且需要填写blockHandler参数
参数说明
| 资源名 | 当前需要进行参数热点流控的接口 | |
| 参数索引 | 需要进行参数热点流控的索引位置 | 如:test接口参数-t1、t2、t3,需要对t2进行热点流控,则填写1 |
| 单机阈值 | 对该参数的流控阈值 | 填写思路:1.大部分都是热点参数,则针对热点参数来进行流控;2.大部分都是普通参数,则针对普通参数来设置 |
| 统计窗口 | x秒访问的QPS数 |
针对热点参数进行设置:在高级选项中填写对应的热点参数的流控数据。参数类型:当前热点对应的数据类型;参数值:即热点参数(如:热点参数是1,则填写1);限流阈值:在统计窗口时长内可访问的次数。

系统保护规则
LOAD自适应(仅对Linux/Unix系统生效):当系统的load值超过设定的阈值就会触发熔断降级。

RT(平均RT):当系统的入口流量的平均RT超过设定阈值则触发熔断降级。

线程数:当单台服务器的所有入口流量的并发线程数超过设定的阈值则触发熔断降级。

入口QPS:当单台服务器的所有入口流量的QPS达到阈值则触发熔断降级。

CPU使用率:当CPU使用率超过设定的阈值则触发熔断降级。

规则持久化
解决服务重启需要重新配置规则
| 原始模式 | API规则推送至客户端,并更新在内存中 | 缺:无法保证一致性,且重启服务后规则丢失。 |
| Pull模式 | 客户端主动向管理中心定期轮询拉起规则 | 优:规则持久化;缺:无法保证一致性,拉取规则频繁 |
| Push模式(生产环境推荐) | 由规则中心统一推送,客户端注册监听,可使用nacos | 优:规则持久化、一致性、速度快;缺:需要第三方依赖 |
Push模式(生产环境推荐)
引入第三方依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
nacos中编写配置文件
| resource | 与控制台的资源名一致(即接口) | api接口 |
| controlBehavior | 流量控制效果 | 可选:直接拒绝-0、Warm Up-1、匀速排队-2 |
| count | 限流阈值 | 参考单机阈值 |
| grade | 限流阈值类型 | 可选:QPS -1、并发线程数-0 |
| limitApp | 流控针对的调用来源 | 默认不区分调用来源 |
| strategy | 调用关系限流策略 | 可选:直接-0、关联-1、链路-2 |
[
{
"resource":"/xxx",
"controlBehavior":0,
"count":10.0,
"grade":1,
"limitApp":"default",
"strategy":0
}
]
在服务的配置文件中配置关联的nacos中编写的配置文件,并重启服务。
spring:
application:
name: xxxx–sentinel
cloud:
sentinel:
datasource:
rule-flow: #该名称可自定义
# nacos服务端地址
server-addr: ip:端口号
username: nacos账号
password: nacos密码
data-id: sentinel–flow–rules #对应nacos的配置文件名称
namespace: xxxxxxxxxxxxxx # 命名空间id
rule-type: flow #规则类型
分布式事务-Seata
事务:可参考数据库事务理解。
分布式事务:同一个接口中修改不同数据库的表;不同服务修改同一个数据库的表,在这两种情况下需要使用分布式事务。
项目地址:https://github.com/apache/incubator-seata
注:若项目的数据是单库,不涉及多库的情况则无需集成。
分布式事务解决方案
AT模式(无侵入、seata主推、二阶段提交)
只需要关注业务SQL(一阶段),seata框架会自动生成事务提交和回滚操作(二阶段)。
一阶段:seata拦截业务SQL,解析当条SQL的元数据(作为回滚使用),执行业务SQL后将当前更新的数据查出来(作为提交使用),最后进行行锁操作。
二阶段:
– 提交操作:清除回滚、提交的元数据以及行锁。
– 回滚操作:先将更新的数据与数据库数据进行对比(预防脏写),然后将回滚的元数据逆向生成update语句实现数据还原(还原数据),最后清除回滚、提交的元数据以及行锁。
TCC模式(了解、二阶段提交)
需要在业务中实现try、confirm、cancel操作,一阶段执行try,二阶段提交执行confirm、回滚执行cancel。
seata角色
– TC(事务协调者):维护全局和分支事务的状态以及驱动全局事务的提交或回滚(需要单独部署server服务端)。
– TM(事务管理器):定义全局事务的范围:开始、提交、回滚全局事务。
– RM(资源管理器):管理分支事务处理的资源,与TC交互注册分支事务和报告分支事务的状态,并驱动分支事务的提交、回滚。
快速入门
TC服务搭建官方文档:https://seata.apache.org/zh-cn/docs/ops/deploy-guide-beginner
TC服务下载:https://github.com/apache/incubator-seata/releases
本节涉及的资源:https://github.com/apache/incubator-seata/tree/master/script
TC服务搭建
服务端存储模式说明
– file(**默认**): 单机模式,全局事务会话信息内存中读写并异步(默认)持久化本地文件root.data,性能较高。
– db(**本文使用**): 高可用模式,全局事务会话信息通过db共享,相应性能差些。
– redis(Seata-Server 1.3及以上版本支持):**存在事务信息丢失风险**,需配置合适当前场景的redis持久化配置, 性能较高。
配置DB数据源
下载TC启动包(版本要按照SpringCloudAlibaba适配版本进行下载)并进行解压(tar -zxvf 压缩包名称)。
修改seata的conf文件夹下的application.yml文件。
seata:
config:
# support: nacos, consul, apollo, zk, etcd3
type: file
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: file
store:
# 修改模式位db
mode: db
# 新增-配置db源(数据库版本需要大于5.7)
db:
datasource: druid
db-type: mysql
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/seata?rewriteBatchedStatements=true
user: mysql
password: mysql
min-conn: 10
max-conn: 100
global-table: global_table
branch-table: branch_table
lock-table: lock_table
distributed-lock-table: distributed_lock
query-limit: 1000
max-wait: 5000
创建数据库seata以及初始化(访问资源地址,选择server->db->mysql.sql,根据配置的db类型选择)。
注:下载的是linux版本的则在seata/script下有相关资源文件。
— mysql.sql
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_status_gmt_modified` (`status` , `gmt_modified`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(128),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`status` TINYINT NOT NULL DEFAULT '0' COMMENT '0:locked ,1:rollbacking',
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_status` (`status`),
KEY `idx_branch_id` (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
CREATE TABLE IF NOT EXISTS `distributed_lock`
(
`lock_key` CHAR(20) NOT NULL,
`lock_value` VARCHAR(20) NOT NULL,
`expire` BIGINT,
primary key (`lock_key`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8mb4;
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('AsyncCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryCommitting', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('RetryRollbacking', ' ', 0);
INSERT INTO `distributed_lock` (lock_key, lock_value, expire) VALUES ('TxTimeoutCheck', ' ', 0);
配置nacos
配置application.yml的注册中心和配置中心。
seata:
# 修改配置中心
config:
type: nacos
nacos:
application: seata–server
server-addr: ip:端口号
group: SEATA_GROUP
namespace:
cluster: default
username: 账号
password: 密码
# 修改nacos注册中心
registry:
# 修改为nacos
type: nacos
nacos:
application: seata–server
server-addr: ip:端口号
group: SEATA_GROUP
namespace:
cluster: default
username: 账号
password: 密码
修改seata/script/config-center/config.txt文件,其余不变。
#可以自定义,但是客户端需要一致
service.vgroupMapping.xxxxxx=default
#修改
store.mode=db
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
#按照实际修改
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true
store.db.user=username
store.db.password=password
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.distributedLockTable=distributed_lock
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
进入seata/script/config-center/nacos文件夹下执行命令以注册。
sh /home/yblog/seata/script/config-center/nacos/nacos-config.sh -h ip -p 端口号 -g SEATA_GROUP -t 命名空间id -u 账号 -w 密码
若脚本执行报错,则将sh修改为bash
参数说明:
- -h:nacos的ip地址
- -p:端口号
- -g:分组
- -t:命名空间 建议新开一个空间专门保存该配置
- -u:账号
- -w:密码
注册完配置以后即可启动服务
客户端搭建
引入seata依赖(需要操作数据则需要引入orm框架、数据库驱动等)。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
在涉及分布式事务的数据库中添加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` DATATIME NOT NULL ,
`log_modified` DATATIME NOT NULL ,
PRIMARY KEY (`id`),
UNIQUE KEY `UX_UNDO_LOG` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 ;
修改对应服务的配置文件。
seata:
#配置事务分组
tx-service-group: xxxxxx #需要和seata服务端的service.vgroupMapping.xxxxxx一致
#配置seata注册中心
registry:
type: nacos
nacos:
server-addr: ip:端口号
application: seata–server #默认值可不配置,如果修改了需要配置
username: xxxx
password: xxxx
group: SEATA_GROUP #默认值可不配置,如果修改了需要配置
#配置seata配置中心
config:
type: nacos
nacos:
server-addr: ip:端口号
username: xxxx
password: xxxx
group: SEATA_GROUP #默认值可不配置,如果修改了需要配置
使用seata实现分布式事务,在对应的接口上添加@GlobalTransactional注解。
@RequestMapping("/test")
@GlobalTransactional //分布式事务注解
public String test(){
//创建a表数据
sorockService.createData("1");
//调用其他服务
userFeignService.add("tset");
return "hello world ";
}
若启动服务没有生效则查看服务的启动类是否添加@EnableTransactionManagement 注解,没有添加则需要添加上。
网关-Gateway
通过Gateway网关统一接收前端的请求,将请求分发到对应的微服务中进行处理,但是Gateway不能打包为war且不在传统的servlet容器中运行,直接打包为jar即可。
基础概念
快速入门
环境准备
在Gateway中不需要springboot的web依赖,引入会报错。
添加Gateway依赖,使用Spring Cloud的组件需要在父pom中引入对应的版本管理器
<!–spring-cloud 父pom中引入对应的版本管理器–>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!–spring-cloud-Gateway 在对应的Gateway服务中引入–>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
编写Gateway的配置文件
server:
port: 8083
spring:
application:
name: xxxx–gateway
# nacos配置
cloud:
#配置网关
gateway:
#配置转发路由数组
routes:
– id: yblog–user #唯一标识:一般是对应项目name
uri: http://localhost:8082/xxxx–user #转发地址:转发到那个服务
#路由断言规则:路由匹配规则
predicates:
#将http://localhost:8083/xxxx-user/user/test转为
#http://localhost:8082/xxxx-user/user/test
– Path=/xxxx–user/** #路径匹配规则
#过滤要求
filters:
– StripPrefix=1 #去除请求的第一层划分
#http://localhost:8082/xxxx-user/user/test->http://localhost:8082/user/test
访问http://localhost:8083/xxxx-user/user/test即可请求到xxxx-user/user/test的服务
整合nacos
在Gateway中引入nacos依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
修改Gateway的配置文件。
标准化(推荐):
server:
port: 8083
spring:
application:
name: xxxx–gateway
# nacos配置
cloud:
nacos:
# nacos服务端地址
server-addr: ip:port
discovery:
username: 账号
password: 密码
namespace: 命名空间
#配置网关
gateway:
#配置转发路由
routes:
– id: yblog–user #唯一标识:一般是对应项目name
uri: lb://xxxx–user #lb://服务名称 lb:使用nacos的负载均衡策略
#路由断言规则:路由匹配规则
predicates:
– Path=/xxxx–user/** #路径匹配规则
#过滤要求
filters:
– StripPrefix=1 #去除请求的第一层划分
简写:
server:
port: 8083
spring:
application:
name: xxxx–gateway
# nacos配置
cloud:
nacos:
# nacos服务端地址
server-addr: ip:port
discovery:
username: 账号
password: 密码
namespace: 命名空间
#配置网关
gateway:
discovery:
locator:
enabled: true #开启服务发现自动生成路由,则不在需要编写routes数组
断言工厂
当请求gateway时,使用断言进行匹配,匹配成功则进行路由转发,匹配失败就返回404。
在配置文件的predicates下添加断言规则。
内置断言工厂
官方断言工厂文档
Datetime类型(配置):根据时间进行判断
| AfterRoutePredicateFactort | 1个日期参数 | 请求日期是否晚于指定日期 | – After=2017-01-20T17:42:47.789-07:00[America/Denver] |
| BeforeRoutePredicateFactort | 1个日期参数 | 请求日期是否早于指定日期 | – Before=2017-01-20T17:42:47.789-07:00[America/Denver] |
| BetweenRoutePredicateFactort | 2个日期参数 | 请求日期是否在指定时间范围内 | – Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] |
远程地址:根据IP地址进行判断(RemoteAddrRoutePredicateFactort:(一个IP地址参数)请求主机地址是否在地址段中)
– RemoteAddr=192.168.1.1/24
Cookie(配置):根据请求的Cookie进行判断(CookieRoutePredicateFactort:(一个cookie名称参数+一个正则表达式参数)请求的cookie是否有给定的名称,并且值与正则表达式匹配)
– Cookie=chocolate,ch.p
Header(配置):根据请求的Header进行判断(HeaderRoutePredicateFactort:(一个标题名称参数+一个正则表达式参数)请求的Header是否有给定的名称,并且值与正则表达式匹配)
– Header=X–Request–Id,\\d+
Host:根据请求的Host进行判断(HostRoutePredicateFactort:(一个主机名参数)请求的Host是否满足匹配规则)
– Host=**.somehost.org,**.anotherhost.org
Method请求方法(配置):根据请求的类型进行判断(MethodRoutePredicateFactort:(一个请求类型参数)请求的类型是否满足指定的类型)
– Method=GET,POST
path请求路径(配置):根据请求的URL进行判断(PathRoutePredicateFactort:(一个请求URL参数)请求的URL是否满足指定的路径规则)
– Path=/red/{segment},/blue/{segment}
其他内置断言规则请查看官方文档:内置断言官方文档
自定义路由断言工厂
创建自定义断言工厂类。
/**
* 1.创建自定义路由断言工厂配置类,且文件名必须以RoutePredicateFactory结尾
* 2.继承AbstractRoutePredicateFactory
* 3.实现静态内部类-Config-并声明接收参数的属性即配置文件的断言参数
* 4.重写shortcutFieldOrder方法来进行绑定
* 5.重写apply方法,实现自定义断言规则判断,返回true|false
*/
@Component
public class SatokenRoutePredicateFactort extends AbstractRoutePredicateFactory<SatokenRoutePredicateFactort.Config> {
public SatokenRoutePredicateFactort() {
super(SatokenRoutePredicateFactort.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
//对应Config类中的属性名称
return Arrays.asList("token");
}
@Override
public Predicate<ServerWebExchange> apply(SatokenRoutePredicateFactort.Config config) {
return new GatewayPredicate() {
//断言逻辑判断
@Override
public boolean test(ServerWebExchange exchange) {
//判断参数值是否相等
return exchange.getRequest().getQueryParams().getFirst("token").equals(config.getToken());
}
};
}
@Validated
public static class Config {
private String token;
public String getToken() {
return token;
}
}
}
修改配置文件。
spring:
application:
name: xxxx–gateway
# nacos配置
cloud:
#配置转发路由
routes:
– id: xxxx–user
uri: lb://xxxx–user
predicates:
#自定义断言规则
– Satoken=abcdefghijklmnopqrstuvwxyz
过滤器
局部过滤器(针对某个路由)
内置过滤器
内置过滤器官方文档
自定义过滤器
创建自定义过滤器。
/**
* 自定义过滤器工厂
* 1.创建过滤器工厂类,且文件名必须以GatewayFilterFactory结尾
* 2.继承AbstractGatewayFilterFactory
* 3.重写apply方法,实现自定义过滤器逻辑
*/
@Component
public class TestGatewayFilterFactory extends AbstractGatewayFilterFactory<TestGatewayFilterFactory.Config> {
public TestGatewayFilterFactory() {
super(TestGatewayFilterFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("test");
}
@Override
public GatewayFilter apply(TestGatewayFilterFactory.Config config) {
return (exchange, chain) -> {
if (config.getTest().equals("1")){
exchange.getResponse().getHeaders().add("test","1");
}else{
exchange.getResponse().getHeaders().add("test","2");
}
return chain.filter(exchange);
};
}
public static class Config {
private String test;
public String getTest() {
return test;
}
}
}
修改配置文件。
spring:
application:
name: xxxx–gateway
cloud:
gateway:
routes:
– id: xxxx–user
uri: lb://xxxx–user
filters:
– test=1
全局过滤器(针对全局路由)
全局过滤器官方文档
只需要创建全局过滤器并且注入bean即可,无需在配置文件进行配置
适用:记录日志、鉴权等公共操作
创建全局过滤器
/**
* 全局过滤器
* 1.创建过滤器类
* 2.实现GlobalFilter接口
* 3.实现filter方法,实现过滤器逻辑
*/
@Component
@Slf4j
public class LogFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("请求路径:{}",exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
请求日志记录
请求日志配置官方文档
注:该部分是通过Java环境变量注入的,即在运行jar包是需要添加指定参数。
方案一:打包运行方式
编写当前项目的日志配置文件(需要记录日志在文件中需要配置)。
<appender name="accessLog" class="ch.qos.logback.core.FileAppender">
<file>access_log.log</file>
<encoder>
<pattern>%msg%n</pattern>
</encoder>
</appender>
<appender name="async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
</appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref="async"/>
</logger>
将当前的gateway项目打包为jar包。
使用java命令运行jar包并添加-Dreactor.netty.http.server.accessLogEnabled=true参数来启动日志记录。
方案二:IDEA配置方式
跨域处理
CORS跨域处理官方文档
跨域即前后端分离的项目不在同一个端口运行,则会出现这种情况。
- 使用配置文件方式配置跨域处理。
spring:
cloud:
#配置网关
gateway:
#配置转发路由
routes:
……
# 配置CORS跨域处理
globalcors:
cors-configurations:
'[/**]': #允许跨域访问的资源、服务…
allowedOrigins: "https://docs.spring.io" #允许的跨域来源
allowedMethods: # 允许的跨域请求类型
– GET
-
使用代码方式配置跨域处理。
注意依赖
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* 重写corsWebFilter方法即可
*/
@Configuration
public class MyCorsConfig {
@Bean
public CorsWebFilter corsWebFilter(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
//允许访问的跨域请求头
corsConfiguration.addAllowedHeader("*");
//允许访问的跨域请求类型
corsConfiguration.addAllowedMethod("*");
//允许访问的跨域来源
corsConfiguration.addAllowedOrigin("*");
//允许访问的资源
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource =
new UrlBasedCorsConfigurationSource(new PathPatternParser());
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(urlBasedCorsConfigurationSource);
}
}
整合Sentinel流控降级
添加依赖。
<!–集成sentinel实现网关熔断限流–>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
添加sentinel配置。
spring:
cloud:
sentinel:
transport:
dashboard: ip:端口号 #控制台的ip+port
实现
- 代码实现(参考sentinel部分)
- 控制台实现(只需要对geatway进行限流熔断配置即可)
详细配置说明
| API类型 | 根据资源或者api分组 | 默认是ID,选分组需要在api管理新建分组 |
| API名称 | 对应的资源名称 | 选分组则只能选择api分组中存在的分组 |
| 针对请求属性 | 类似于是否启用断言 | 可选参数属性:客户端IP、请求地址、请求头、URL参数、Cookie(选中后可设定对应参数) |
| 间隔 | 可设定x秒内QPS访问的次数 | 间隔=2,QPS=2,则2秒内可访问2次 |
| Burst size | 可超出QPS数 | 间隔=2,QPS=2,Burst size=3,则2秒内可访问5次 |
api分组参数说明
- api名称:分组名称,可自定义。
- 匹配模式:根据接口格式来选择。
- 匹配串:对应的api接口。


