魏县网站建设,网站建设西班牙语,Wordpress主题上传PHP值,网站模块建设文章目录Spring Cloud Alibaba是什么Spring Cloud AlibabaSpring Cloud Alibaba 组件Spring Cloud Alibaba 的应用场景Spring Cloud 两代实现组件对比Spring Cloud Alibaba 版本依赖Spring Cloud Alibaba 组件版本关系Spring Cloud Alibaba NacosNacos 的特性服务发现服务健康监…
文章目录Spring Cloud Alibaba是什么Spring Cloud AlibabaSpring Cloud Alibaba 组件Spring Cloud Alibaba 的应用场景Spring Cloud 两代实现组件对比Spring Cloud Alibaba 版本依赖Spring Cloud Alibaba 组件版本关系Spring Cloud Alibaba NacosNacos 的特性服务发现服务健康监测动态配置服务动态 DNS 服务服务及其元数据管理Nacos 两大组件Nacos 服务注册中心安装和运行 Nacos Server搭建服务提供者搭建服务消费者Nacos 配置中心Nacos Server 集群化部署Spring Cloud Alibaba SentinelSentinel 的组成Sentinel 的基本概念SentinelResource 注解Sentinel 控制台安装 Sentinel 控制台Sentinel 的开发流程引入 Sentinel 依赖定义资源适配主流框架自动定义资源熔断降级规则Sentinel 熔断状态Sentinel 熔断规则属性Sentinel 实现熔断降级过程通过 Sentinel 控制台定义熔断降级规则Spring Cloud Alibaba SeataSeata 的发展历程分布式事务相关概念Seata 整体工作流程XID核心组件Seata AT 模式AT 模式的前提AT 模式的工作机制一阶段二阶段提交二阶段回滚下载 Seata 服务器Seata 配置中心Seata 整合 Nacos 配置中心添加 Maven 依赖Seata Server 配置Seata Client 配置上传配置到 Nacos 配置中心验证 Nacos 配置中心Seata 注册中心Seata 整合 Nacos 注册中心添加 Maven 依赖Seata Server 配置注册中心Seata Client 配置注册中心验证 Nacos 注册中心Seata 事务分组Seata Server 配置Seata Client 配置上传配置到 Nacos获取事务分组获取 TC 集群名查找 TC 服务小结启动 Seata Server业务系统集成 Seata创建订单Order服务搭建库存Storage服务搭建账户Account服务GlobalTransactional 注解Spring Cloud Alibaba是什么
Spring Cloud 本身并不是一个拿来即可用的框架它是一套微服务规范这套规范共有两代实现。
第一代实现 Spring Cloud Netflix第二代实现 Spring Cloud Alibaba。
2018 年 12 月12 日Netflix 公司宣布 Spring Cloud Netflix 系列大部分组件都进入维护模式不再添加新特性。这严重地限制了 Spring Cloud 的高速发展于是各大互联网公司和组织开始把目光转向 Spring Cloud 的第二代实现Spring Cloud Alibaba。
Spring Cloud Alibaba
Spring Cloud Alibaba 是阿里巴巴结合自身丰富的微服务实践而推出的微服务开发的一站式解决方案是 Spring Cloud 第二代实现的主要组成部分。
Spring Cloud Alibaba 吸收了 Spring Cloud Netflix 的核心架构思想并进行了高性能改进。自 Spring Cloud Netflix 进入停更维护后Spring Cloud Alibaba 逐渐代替它成为主流的微服务框架。
Spring Cloud Alibaba 是国内首个进入 Spring 社区的开源项目。2018 年 7 月Spring Cloud Alibaba 正式开源并进入 Spring Cloud 孵化器中孵化2019 年 7 月Spring Cloud 官方宣布 Spring Cloud Alibaba 毕业并将仓库迁移到 Alibaba Github OSS 下。
虽然 Spring Cloud Alibaba 诞生时间不久但俗话说的好“大树底下好乘凉”依赖于阿里巴巴强大的技术影响力Spring Cloud Alibaba 在业界得到了广泛的使用成功案例也越来越多。
Spring Cloud Alibaba 组件
Spring Cloud Alibaba 包含了多种开发分布式微服务系统的必需组件
Nacos阿里巴巴开源产品一个更易于构建云原生应用的动态服务发现,配置管理和服务管理平台。Sentinel阿里巴巴开源产品把流量作为切入点,从流量控制,熔断降级,系统负载保护等多个维度保护服务的稳定性。RocketMQApache RocketMQ 是一款基于Java 的高性能、高吞吐量的分布式消息和流计算平台。DubboApache Dubbo 是一款高性能的 Java RPC 框架。Seata阿里巴巴开源产品一个易于使用的高性能微服务分布式事务解决方案。Alibaba Cloud OSS阿里云对象存储服务器Object Storage Service简称OSS是阿里云提供的海量、安全、低成本、高可靠的云存储服务。Alibaba Cloud Schedulerx阿里中间件团队开发的一款分布式调度产品,支持周期性的任务与固定时间点触发任务。
通过 Spring Cloud Alibaba 的这些组件我们只需要添加一些注解和少量配置就可以将 Spring Cloud 应用接入阿里微服务解决方案通过阿里中间件来迅速搭建分布式应用系统。
Spring Cloud Alibaba 的应用场景
Spring Cloud Alibaba 的应用场景如下
大型复杂的系统例如大型电商系统。高并发系统例如大型门户网站、商品秒杀系统。需求不明确且变更很快的系统例如创业公司业务系统。
Spring Cloud 两代实现组件对比
下表展示了 Spring Cloud 两代实现的组件对比情况。
Spring Cloud 第一代实现Netflix状态Spring Cloud 第二代实现Alibaba状态Ereka2.0 孵化失败Nacos Discovery 性能更好感知力更强Ribbon停更进维Spring Cloud Loadbalancer Spring Cloud 原生组件用于代替 RibbonHystrix停更进维Sentinel 可视化配置上手简单Zuul停更进维Spring Cloud Gateway 性能为 Zuul 的 1.6 倍Spring Cloud Config搭建过程复杂约定过多无可视化界面上手难点大Nacos Config搭建过程简单有可视化界面配置管理更简单容易上手
Spring Cloud Alibaba 版本依赖
Spring Cloud、Spring Cloud Alibaba 以及 Spring Boot 之间版本依赖关系如下。
Spring Cloud 版本Spring Cloud Alibaba 版本Spring Boot 版本Spring Cloud 2020.0.12021.12.4.2Spring Cloud Hoxton.SR122.2.7.RELEASE2.3.12.RELEASESpring Cloud Hoxton.SR92.2.6.RELEASE2.3.2.RELEASESpring Cloud Greenwich.SR62.1.4.RELEASE2.1.13.RELEASESpring Cloud Hoxton.SR32.2.1.RELEASE2.2.5.RELEASESpring Cloud Hoxton.RELEASE2.2.0.RELEASE2.2.X.RELEASESpring Cloud Greenwich2.1.2.RELEASE2.1.X.RELEASESpring Cloud Finchley2.0.4.RELEASE停止维护建议升级2.0.X.RELEASESpring Cloud Edgware1.5.1.RELEASE停止维护建议升级1.5.X.RELEASE
Spring Cloud Alibaba 组件版本关系
Spring Cloud Alibaba 下各组件版本关系如下表。
Spring Cloud Alibaba 版本Sentinel 版本Nacos 版本RocketMQ 版本Dubbo 版本Seata 版本2.2.7.RELEASE1.8.12.0.34.6.12.7.131.3.02.2.6.RELEASE1.8.11.4.24.4.02.7.81.3.02021.1 or 2.2.5.RELEASE or 2.1.4.RELEASE or 2.0.4.RELEASE1.8.01.4.14.4.02.7.81.3.02.2.3.RELEASE or 2.1.3.RELEASE or 2.0.3.RELEASE1.8.01.3.34.4.02.7.81.3.02.2.1.RELEASE or 2.1.2.RELEASE or 2.0.2.RELEASE1.7.11.2.14.4.02.7.61.2.02.2.0.RELEASE1.7.11.1.44.4.02.7.4.11.0.02.1.1.RELEASE or 2.0.1.RELEASE or 1.5.1.RELEASE1.7.01.1.44.4.02.7.30.9.02.1.0.RELEASE or 2.0.0.RELEASE or 1.5.0.RELEASE1.6.31.1.14.4.02.7.30.7.1
Spring Cloud Alibaba Nacos
Nacos 英文全称为 Dynamic Naming and Configuration Service是一个由阿里巴巴团队使用 Java 语言开发的开源项目。
Nacos 是一个更易于帮助构建云原生应用的动态服务发现、配置和服务管理平台参考自 Nacos 官网。
Nacos 的命名是由 3 部分组成
组成部分全称描述Nanaming/nameServer即服务注册中心与 Spring Cloud Eureka 的功能类似。coconfiguration即配置中心与 Spring Cloud ConfigSpring Cloud Bus 的功能类似。sservice即服务表示 Nacos 实现的服务注册中心和配置中心都是以服务为核心的。
我们可以将 Nacos 理解成服务注册中心和配置中心的组合体它可以替换 Eureka 作为服务注册中心实现服务的注册与发现还可以替换 Spring Cloud Config 作为配置中心实现配置的动态刷新。
Nacos 作为服务注册中心经历了十年“双十一”的洪峰考验具有简单易用、稳定可靠、性能卓越等优点可以帮助用户更敏捷、容易地构建和管理微服务应用。
Nacos 支持几乎所有主流类型“服务”的发现、配置和管理
Kubernetes ServicegRPC Dubbo RPC ServiceSpring Cloud RESTful Service
Nacos 的特性
Nacos 提供了一系列简单易用的特性能够帮助我们快速地实现动态服务发现、服务配置等功能。
服务发现
Nacos 支持基于 DNS 和 RPC 的服务发现。当服务提供者使用原生 SDK、OpenAPI 或一个独立的 Agent TODO 向 Nacos 注册服务后服务消费者可以在 Nacos 上通过 DNS TODO 或 HTTPAPI 查找、发现服务。
服务健康监测
Nacos 提供对服务的实时健康检查能够阻止请求发送到不健康主机或服务实例上。Nacos 还提供了一个健康检查仪表盘能够帮助我们根据健康状态管理服务的可用性及流量。
动态配置服务
动态配置服务可以让我们以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要让配置管理变得更加高效、敏捷。
配置中心化管理让实现无状态服务变得更简单让服务按需弹性扩展变得更容易。
Nacos 提供了一个简洁易用的 UI 帮助我们管理所有服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性帮助我们更安全地在生产环境中管理配置变更和降低配置变更带来的风险。
动态 DNS 服务
Nacos 提供了动态 DNS 服务能够让我们更容易地实现负载均衡、流量控制以及数据中心内网的简单 DNS 解析服务。
Nacos 提供了一些简单的 DNS APIs TODO可以帮助我们管理服务的关联域名和可用的 IP:PORT 列表。
服务及其元数据管理
Nacos 能让我们从微服务平台建设的视角管理数据中心的所有服务及元数据包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及 metrics 统计数据。
Nacos 两大组件
与 Eureka 类似Nacos 也采用 CSClient/Server客户端/服务器架构它包含两大组件如下表。
组件描述功能Nacos ServerNacos 服务端与 Eureka Server 不同Nacos Server 由阿里巴巴团队使用 Java 语言编写并将 Nacos Server 的下载地址给用户用户只需要直接下载并运行即可。Nacos Server 可以作为服务注册中心帮助 Nacos Client 实现服务的注册与发现。Nacos Server 可以作为配置中心帮助 Nacos Client 在不重启的情况下实现配置的动态刷新。Nacos ClientNacos 客户端通常指的是微服务架构中的各个服务由用户自己搭建可以使用多种语言编写。Nacos Client 通过添加依赖 spring-cloud-starter-alibaba-nacos-discovery在服务注册中心Nacos Server中实现服务的注册与发现。Nacos Client 通过添加依赖 spring-cloud-starter-alibaba-nacos-config在配置中心Nacos Server中实现配置的动态刷新。
Nacos 服务注册中心
Nacos 作为服务注册中心可以实现服务的注册与发现流程如下图。 图1Nacos 服务注册与发现
在图 1 中共涉及到以下 3 个角色
服务注册中心Register Service它是一个 Nacos Server可以为服务提供者和服务消费者提供服务注册和发现功能。服务提供者Provider Service它是一个 Nacos Client用于对外服务。它将自己提供的服务注册到服务注册中心以供服务消费者发现和调用。服务消费者Consumer Service它是一个 Nacos Client用于消费服务。它可以从服务注册中心获取服务列表调用所需的服务。
Nacos 实现服务注册与发现的流程如下
从 Nacos 官方提供的下载页面中下载 Nacos Server 并运行。服务提供者 Nacos Client 启动时会把服务以服务名spring.application.name的方式注册到服务注册中心Nacos Server服务消费者 Nacos Client 启动时也会将自己的服务注册到服务注册中心服务消费者在注册服务的同时它还会从服务注册中心获取一份服务注册列表信息该列表中包含了所有注册到服务注册中心上的服务的信息包括服务提供者和自身的信息在获取了服务提供者的信息后服务消费者通过 HTTP 或消息中间件远程调用服务提供者提供的服务。
安装和运行 Nacos Server
下面我们以 Nacos 2.0.3 为例演示下如何安装和运行 Nacos Server步骤如下。
使用浏览器访问 Nacos Server 下载页面并在页面最下方点击链接 nacos-server-2.0.3.zip如下图。 图2Nacos Server 下载
下载完成后解压 nacos-server-2.0.3.zip目录结构如下。 图3Nacos Server 目录结构
Nacos Server 下各目录说明如下
bin用于存放 Nacos 的可执行命令。conf用于存放 Nacos 配置文件。target用于存放 Nacos 应用的 jar 包。
打开命令行窗口跳转到 Nacos Server 安装目录的 bin 下执行以下命令以单机模式启动 Nacos Server。
startup.cmd -m standaloneNacos Server 启动日志如下。
nacos is starting with standalone,--.,--.|,--,: : | Nacos 2.0.3
,--.| : ,---. Running in stand alone mode, All function modules
| : : | | ,\ .--.--. Port: 8848
: | \ | : ,--.--. ,---. / / | / / Pid: 27512
| : ; | / \ / \. ; ,. :| : /./ Console: http://192.168.3.138:8848/nacos/index.html;. ;.--. .-. | / / | |: :| : ;_
| | | \ | \__\/: . .. / | .; : \ \ . https://nacos.io: | ; . , .--.; | ; :__| : | ----. \
| | -- / / ,. | | .|\ \ / / /-- /: | ; : . \ : : ---- --. /
; |. | , .-./\ \ / -----
--- ----- ----2021-11-08 16:16:38,877 INFO Bean org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler5ab9b447 of type [org.springframework.security.access.expression.method
.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2021-11-08 16:16:38,884 INFO Bean methodSecurityMetadataSource of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by al
l BeanPostProcessors (for example: not eligible for auto-proxying)
2021-11-08 16:16:40,001 INFO Tomcat initialized with port(s): 8848 (http)
2021-11-08 16:16:40,713 INFO Root WebApplicationContext: initialization completed in 14868 ms
2021-11-08 16:16:52,351 INFO Initializing ExecutorService applicationTaskExecutor
2021-11-08 16:16:52,560 INFO Adding welcome page: class path resource [static/index.html]
2021-11-08 16:16:54,239 INFO Creating filter chain: Ant [pattern/**], []
2021-11-08 16:16:54,344 INFO Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter7dd611c8, org.springframework.security.web.con
text.SecurityContextPersistenceFilter5c7668ba, org.springframework.security.web.header.HeaderWriterFilterfb713e7, org.springframework.security.web.csrf.CsrfFilter6ec7bce0, org.springframework.secur
ity.web.authentication.logout.LogoutFilter7d9ba6c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter158f4cfe, org.springframework.security.web.servletapi.SecurityContextHolderAwa
reRequestFilter6c6333cd, org.springframework.security.web.authentication.AnonymousAuthenticationFilter5d425813, org.springframework.security.web.session.SessionManagementFilter13741d5a, org.springf
ramework.security.web.access.ExceptionTranslationFilter3727f0ee]
2021-11-08 16:16:54,948 INFO Initializing ExecutorService taskScheduler
2021-11-08 16:16:54,977 INFO Exposing 16 endpoint(s) beneath base path /actuator
2021-11-08 16:16:55,309 INFO Tomcat started on port(s): 8848 (http) with context path /nacos
2021-11-08 16:16:55,319 INFO Nacos started successfully in stand alone mode. use embedded storage使用浏览器访问“http://localhost:8848/nacos”跳转到 Nacos Server 登陆页面如下图。 图4Nacos Server 登陆页面
在登陆页输入登录名和密码默认都是 nacos点击提交按钮跳转到 Nacos Server 控制台主页如下图。 图5Nacos Server 控制台
自此我们就完成了 Nacos Server 的下载、安装和运行工作。
搭建服务提供者
接下来我们来搭建一个服务提供者步骤如下。
创建一个名为 spring-cloud-alibaba-demo 的 Maven 工程 该工程的 pom.xml 内容如下。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionpackagingpom/packagingparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.5.6/versionrelativePath/ !-- lookup parent from repository --/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-demo/artifactIdversion1.0-SNAPSHOT/versionpropertiesmaven.compiler.source8/maven.compiler.sourcemaven.compiler.target8/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncodingmaven.compiler.source1.8/maven.compiler.sourcemaven.compiler.target1.8/maven.compiler.targetjunit.version4.12/junit.versionlog4j.version1.2.17/log4j.versionlombok.version1.16.18/lombok.versionspring-cloud.version2020.0.4/spring-cloud.version/propertiesdependencyManagementdependencies!--Spring Cloud Alibaba 的版本信息--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-alibaba-dependencies/artifactIdversion2021.1/versiontypepom/typescopeimport/scope/dependency!--Spring Cloud 的版本信息--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-dependencies/artifactIdversion${spring-cloud.version}/versiontypepom/typescopeimport/scope/dependency/dependencies/dependencyManagement
/project在该工程的 pom.xml 中我们通过 dependencyManagement 对 Spring Cloud Alibaba 的版本信息进行管理该工程下的各个子模块在引入 Spring Cloud Alibaba 的各个组件时就不要单独指定版本号了。 在 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-provider-8001 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖内容如下。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersion!--父工程--parentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-provider-8001/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-provider-8001/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!--Spring Cloud Alibaba Nacos discovery --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build
/project在 spring-cloud-alibaba-provider-8001 的配置文件 application.properties 中添加以下配置内容如下。
#端口号
server.port8001
#服务名
spring.application.namespring-cloud-alibaba-provider
#Nacos Server 的地址
spring.cloud.nacos.discovery.server-addr127.0.0.1:8848
management.endpoints.web.exposure.include*在 net.biacheng.c.controller 包下创建一个名为 DeptController 的 Controller 类代码如下。
package net.demo.c.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
RestController
Slf4j
public class DeptController {Value(${server.port})private String serverPort;GetMapping(value /dept/nacos/{id})public String getPayment(PathVariable(id) Integer id) {return h2提醒您服务访问成功/h2服务名spring-cloud-alibaba-providerbr / 端口号 serverPort br / 传入的参数 id;}
}在 spring-cloud-alibaba-provider-8001 的主启动类上使用 EnableDiscoveryClient 注解开启 Nacos 服务发现功能代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
SpringBootApplication
EnableDiscoveryClient //开启服务发现功能
public class SpringCloudAlibabaProvider8001Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaProvider8001Application.class, args);}
}启动 spring-cloud-alibaba-provider-8001使用浏览器访问“http://localhost:8001/dept/nacos/1”。 使用浏览器访问“http://localhost:8848/nacos”查看“服务管理”下的“服务列表”如下图。 图7服务注册列表
从图 7 可以看到我们搭建的服务提供者 spring-cloud-alibaba-provider-8001 所提供的服务已经被注册到了 Nacos Server 上了。
搭建服务消费者
下面我们就来搭建一个服务消费者来消费 spring-cloud-alibaba-provider-8001 提供的服务步骤如下。
在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-consumer-nacos-8801 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-consumer-nacos-8081/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-consumer-nacos-8081/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependencies!--SpringCloud ailibaba nacos discovery--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependency!--由于 Netflix Ribbon 进入停更维护阶段因此新版本的 Nacos discovery 都已经移除了 Ribbon 此时我们需要引入 loadbalancer 代替 --dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-loadbalancer/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build
/project注意由于 Netflix Ribbon 已经进入停更维护状态Nacos Discovery 已经停了对 Ribbon 的支持因此我们需要在服务消费者的 pom.xml 中引入 spring-cloud-loadbalancer 才能调用服务提供者提供的服务。 在 spring-cloud-alibaba-consumer-nacos-8801 的配置文件 application.yml 中添加以下配置。
server:port: 8801 #端口号
spring:application:name: spring-cloud-alibaba-consumer #服务名cloud:nacos:discovery:server-addr: localhost:8848 #Nacos server 的地址
#以下配置信息并不是默认配置而是我们自定义的配置目的是不在 Controller 内硬编码服务提供者的服务名
service-url:nacos-user-service: http://spring-cloud-alibaba-provider #服务提供者的服务名在 spring-cloud-alibaba-consumer-nacos-8801 的主启动类上使用 EnableDiscoveryClient 注解开启服务发现功能代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
SpringBootApplication
EnableDiscoveryClient // 开启服务注册与发现功能
public class SpringCloudAlibabaConsumerNacos8801Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaConsumerNacos8081Application.class, args);}
}在 net.demo.c.config 包下创建一个名为 ApplicationContextBean 的配置类并使用 LoadBalanced 注解与 Ribbon 进行集成开启负载均衡功能代码如下。
package net.demo.c.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
Configuration
public class ApplicationContextBean {BeanLoadBalanced //与 Ribbon 集成并开启负载均衡功能public RestTemplate getRestTemplate() {return new RestTemplate();}
}在 net.demo.c.controller 包下创建一个名为 DeptController_Consumer 的 Controller 类代码如下。
package net.demo.c.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
RestController
Slf4j
public class DeptController_Consumer {Resourceprivate RestTemplate restTemplate;Value(${service-url.nacos-user-service})private String serverURL; //服务提供者的服务名GetMapping(/consumer/dept/nacos/{id})public String paymentInfo(PathVariable(id) Long id) {return restTemplate.getForObject(serverURL /dept/nacos/ id, String.class);}
}启动 spring-cloud-alibaba-consumer-nacos-8801查看 Nacos Server 的服务列表如下图。 图8服务消费者
使用浏览器访问“http://localhost:8801/consumer/dept/nacos/1”。
Nacos 配置中心
Nacos Server 还可以作为配置中心对 Spring Cloud 应用的外部配置进行统一地集中化管理。而我们只需要在应用的 POM 文件中引入 spring-cloud-starter-alibaba-nacos-config 即可实现配置的获取与动态刷新。
从配置管理的角度看Nacos 可以说是 Spring Cloud Config 的替代方案但相比后者 Nacos 的使用更简单操作步骤也更少。
接下来我们通过一个实例来演示下 Nacos 是如何实现配置的统一管理和动态刷新的。
在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-config-client-3377 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-config-client-3377/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-nacos-config-client-3377/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--SpringCloud2020及以后的版本默认不启用 bootstrap 配置我们需要在pom里面显式地引入--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-bootstrap/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!--Spring Cloud Alibaba Config 依赖--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-config/artifactId/dependency!--SpringCloud ailibaba nacos 服务注册与发现模块 --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependency!--Spring Boot 监控模块--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build
/project注意我们使用的 Spring Cloud 2020 版本默认不启用 bootstrap若想要在应用启动时加载 bootstrap 配置例如 bootstrap.yml 或 bootstrap.properties就需要我们在 pom.xml 中显式的引入 spring-cloud-starter-bootstrap 依赖。
在 spring-cloud-alibaba-config-client-3377 的类路径例如 /resources 目录下添加一个 bootstrap.yml配置内容如下。
server:port: 3377 #端口号
spring:application:name: config-client #服务名cloud:nacos:discovery:server-addr: 127.0.0.1:8848 #Nacos服务注册中心地址config:server-addr: 127.0.0.1:8848 #Nacos作为配置中心地址file-extension: yaml #指定yaml格式的配置在 spring-cloud-alibaba-config-client-3377 的类路径例如 /resources 目录下添加一个 application.yml配置内容如下。
spring:profiles:active: dev #激活 dev 的配置在 net.demo.c.controller 包下创建一个名为 ConfigClientController 的 Controller 类并在该类上使用 RefreshScope 注解实现配置的自动更新代码如下。
package net.demo.c.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
RestController
RefreshScope
public class ConfigClientController {Value(${config.info})private String ConfigInfo;GetMapping(/config/info)public String getConfigInfo(){return ConfigInfo;}
}在 spring-cloud-alibaba-config-client-3377 的主启动类上使用 EnableDiscoveryClient 注解开启服务发现功能代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
SpringBootApplication
EnableDiscoveryClient
public class SpringCloudAlibabaNacosConfigClient3377Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaNacosConfigClient3377Application.class, args);}
}启动 Nacos Server并在 Nacos Server 控制台的“配置管理”下的“配置列表”中点击“”按钮新建如下配置。
Data ID: config-client-dev.yamlGroup : DEFAULT_GROUP配置格式: YAML配置内容: config:info: c.demo.net在 Nacos Server 中配置的 dataId即 Data ID的完整格式如下
${prefix}-${spring.profiles.active}.${file-extension}dataId 格式中各参数说明如下
${prefix}默认取值为微服务的服务名即配置文件中 spring.application.name 的值我们可以在配置文件中通过配置 spring.cloud.nacos.config.prefix 来指定。${spring.profiles.active}表示当前环境对应的 Profile例如 dev、test、prod 等。当没有指定环境的 Profile 时其对应的连接符也将不存在 dataId 的格式变成 prefix.{prefix}.prefix.{file-extension}。${file-extension}表示配置内容的数据格式我们可以在配置文件中通过配置项 spring.cloud.nacos.config.file-extension 来配置例如 properties 和 yaml。
启动 spring-cloud-alibaba-config-client-3377并使用浏览器访问“http://localhost:3377/config/info”结果如下图。 图10Nacos Config
在 Nacos Server 中将 config-client-dev.yaml 中的配置修改成如下内容。
config:info: this is c.demo.net在不重启 spring-cloud-alibaba-config-client-3377 的情况下使用浏览器再次访问“http://localhost:3377/config/info”结果如下图。
图11Nacos Cofig
Nacos Server 集群化部署
在实际的项目开发中一个微服务系统往往由十几几十个甚至几百个微服务组成。 这些服务若全部注册到同一台 Nacos Server就极有可能导致 Nacos Server 因为不堪重负而崩溃最终导致整个微服务系统瘫痪。解决这个问题最直接的办法就是使用 Nacos Server 集群。
Nacos Server 的集群化部署有一个十分明显的优点那就是可以保障系统的高可用性。在集群化部署中只要不是所有的 Nacos Server 都停止工作Nacos Client 就还可以从集群中正常的 Nacos Server 上获取服务信息及配置而不会导致系统的整体瘫痪这就是 Nacos Server 集群化部署的高可用性。
下图展示了 Nacos Server 集群化部署的基本架构。 图12Nacos Server 集群架构
下面我们以 Windows 系统为例演示如何部署 Nacos Server 集群。
在 MySQL 中新建一个名为 nacos_config 的数据库实例并在该数据库下执行以下 SQL 语句。
/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 config_info */
/******************************************/
CREATE TABLE config_info (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT id,data_id varchar(255) NOT NULL COMMENT data_id,group_id varchar(255) DEFAULT NULL,content longtext NOT NULL COMMENT content,md5 varchar(32) DEFAULT NULL COMMENT md5,gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 修改时间,src_user text COMMENT source user,src_ip varchar(50) DEFAULT NULL COMMENT source ip,app_name varchar(128) DEFAULT NULL,tenant_id varchar(128) DEFAULT COMMENT 租户字段,c_desc varchar(256) DEFAULT NULL,c_use varchar(64) DEFAULT NULL,effect varchar(64) DEFAULT NULL,type varchar(64) DEFAULT NULL,c_schema text,PRIMARY KEY (id),UNIQUE KEY uk_configinfo_datagrouptenant (data_id,group_id,tenant_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENTconfig_info;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 config_info_aggr */
/******************************************/
CREATE TABLE config_info_aggr (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT id,data_id varchar(255) NOT NULL COMMENT data_id,group_id varchar(255) NOT NULL COMMENT group_id,datum_id varchar(255) NOT NULL COMMENT datum_id,content longtext NOT NULL COMMENT 内容,gmt_modified datetime NOT NULL COMMENT 修改时间,app_name varchar(128) DEFAULT NULL,tenant_id varchar(128) DEFAULT COMMENT 租户字段,PRIMARY KEY (id),UNIQUE KEY uk_configinfoaggr_datagrouptenantdatum (data_id,group_id,tenant_id,datum_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENT增加租户字段;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 config_info_beta */
/******************************************/
CREATE TABLE config_info_beta (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT id,data_id varchar(255) NOT NULL COMMENT data_id,group_id varchar(128) NOT NULL COMMENT group_id,app_name varchar(128) DEFAULT NULL COMMENT app_name,content longtext NOT NULL COMMENT content,beta_ips varchar(1024) DEFAULT NULL COMMENT betaIps,md5 varchar(32) DEFAULT NULL COMMENT md5,gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 修改时间,src_user text COMMENT source user,src_ip varchar(50) DEFAULT NULL COMMENT source ip,tenant_id varchar(128) DEFAULT COMMENT 租户字段,PRIMARY KEY (id),UNIQUE KEY uk_configinfobeta_datagrouptenant (data_id,group_id,tenant_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENTconfig_info_beta;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 config_info_tag */
/******************************************/
CREATE TABLE config_info_tag (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT id,data_id varchar(255) NOT NULL COMMENT data_id,group_id varchar(128) NOT NULL COMMENT group_id,tenant_id varchar(128) DEFAULT COMMENT tenant_id,tag_id varchar(128) NOT NULL COMMENT tag_id,app_name varchar(128) DEFAULT NULL COMMENT app_name,content longtext NOT NULL COMMENT content,md5 varchar(32) DEFAULT NULL COMMENT md5,gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 修改时间,src_user text COMMENT source user,src_ip varchar(50) DEFAULT NULL COMMENT source ip,PRIMARY KEY (id),UNIQUE KEY uk_configinfotag_datagrouptenanttag (data_id,group_id,tenant_id,tag_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENTconfig_info_tag;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 config_tags_relation */
/******************************************/
CREATE TABLE config_tags_relation (id bigint(20) NOT NULL COMMENT id,tag_name varchar(128) NOT NULL COMMENT tag_name,tag_type varchar(64) DEFAULT NULL COMMENT tag_type,data_id varchar(255) NOT NULL COMMENT data_id,group_id varchar(128) NOT NULL COMMENT group_id,tenant_id varchar(128) DEFAULT COMMENT tenant_id,nid bigint(20) NOT NULL AUTO_INCREMENT,PRIMARY KEY (nid),UNIQUE KEY uk_configtagrelation_configidtag (id,tag_name,tag_type),KEY idx_tenant_id (tenant_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENTconfig_tag_relation;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 group_capacity */
/******************************************/
CREATE TABLE group_capacity (id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 主键ID,group_id varchar(128) NOT NULL DEFAULT COMMENT Group ID空字符表示整个集群,quota int(10) unsigned NOT NULL DEFAULT 0 COMMENT 配额0表示使用默认值,usage int(10) unsigned NOT NULL DEFAULT 0 COMMENT 使用量,max_size int(10) unsigned NOT NULL DEFAULT 0 COMMENT 单个配置大小上限单位为字节0表示使用默认值,max_aggr_count int(10) unsigned NOT NULL DEFAULT 0 COMMENT 聚合子配置最大个数0表示使用默认值,max_aggr_size int(10) unsigned NOT NULL DEFAULT 0 COMMENT 单个聚合数据的子配置大小上限单位为字节0表示使用默认值,max_history_count int(10) unsigned NOT NULL DEFAULT 0 COMMENT 最大变更历史数量,gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 修改时间,PRIMARY KEY (id),UNIQUE KEY uk_group_id (group_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENT集群、各Group容量信息表;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 his_config_info */
/******************************************/
CREATE TABLE his_config_info (id bigint(64) unsigned NOT NULL,nid bigint(20) unsigned NOT NULL AUTO_INCREMENT,data_id varchar(255) NOT NULL,group_id varchar(128) NOT NULL,app_name varchar(128) DEFAULT NULL COMMENT app_name,content longtext NOT NULL,md5 varchar(32) DEFAULT NULL,gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,src_user text,src_ip varchar(50) DEFAULT NULL,op_type char(10) DEFAULT NULL,tenant_id varchar(128) DEFAULT COMMENT 租户字段,PRIMARY KEY (nid),KEY idx_gmt_create (gmt_create),KEY idx_gmt_modified (gmt_modified),KEY idx_did (data_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENT多租户改造;/******************************************/
/* 数据库全名 nacos_config */
/* 表名称 tenant_capacity */
/******************************************/
CREATE TABLE tenant_capacity (id bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 主键ID,tenant_id varchar(128) NOT NULL DEFAULT COMMENT Tenant ID,quota int(10) unsigned NOT NULL DEFAULT 0 COMMENT 配额0表示使用默认值,usage int(10) unsigned NOT NULL DEFAULT 0 COMMENT 使用量,max_size int(10) unsigned NOT NULL DEFAULT 0 COMMENT 单个配置大小上限单位为字节0表示使用默认值,max_aggr_count int(10) unsigned NOT NULL DEFAULT 0 COMMENT 聚合子配置最大个数,max_aggr_size int(10) unsigned NOT NULL DEFAULT 0 COMMENT 单个聚合数据的子配置大小上限单位为字节0表示使用默认值,max_history_count int(10) unsigned NOT NULL DEFAULT 0 COMMENT 最大变更历史数量,gmt_create datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间,gmt_modified datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 修改时间,PRIMARY KEY (id),UNIQUE KEY uk_tenant_id (tenant_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENT租户容量信息表;CREATE TABLE tenant_info (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT id,kp varchar(128) NOT NULL COMMENT kp,tenant_id varchar(128) default COMMENT tenant_id,tenant_name varchar(128) default COMMENT tenant_name,tenant_desc varchar(256) DEFAULT NULL COMMENT tenant_desc,create_source varchar(32) DEFAULT NULL COMMENT create_source,gmt_create bigint(20) NOT NULL COMMENT 创建时间,gmt_modified bigint(20) NOT NULL COMMENT 修改时间,PRIMARY KEY (id),UNIQUE KEY uk_tenant_info_kptenantid (kp,tenant_id),KEY idx_tenant_id (tenant_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COLLATEutf8_bin COMMENTtenant_info;CREATE TABLE users (
username varchar(50) NOT NULL PRIMARY KEY,
password varchar(500) NOT NULL,
enabled boolean NOT NULL
);CREATE TABLE roles (
username varchar(50) NOT NULL,
role varchar(50) NOT NULL,
UNIQUE INDEX idx_user_role (username ASC, role ASC) USING BTREE
);CREATE TABLE permissions (role varchar(50) NOT NULL,resource varchar(255) NOT NULL,action varchar(8) NOT NULL,UNIQUE INDEX uk_role_permission (role,resource,action) USING BTREE
);INSERT INTO users (username, password, enabled) VALUES (nacos, $2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu, TRUE);INSERT INTO roles (username, role) VALUES (nacos, ROLE_ADMIN);在 Nacos Server 安装目录下的 conf 文件夹中将 cluster.conf.example 重命名为 cluster.conf然后在该文件中添加以下内容。
192.168.3.138:3333
192.168.3.138:4444
192.168.3.138:5555配置说明如下 192.168.138 为本地电脑主机的 IP 地址这里最好不要写成 localhost 或 127.0.0.1否则 Nacos Server 集群可能会搭建失败 本次搭建的 Nacos Server 集群的端口分别为3333、4444、5555。
在 config 目录下的 application.properties 中将 server.port端口号修改为 3333并在该文件中添加 MySQL 数据库配置具体修改内容如下。
server.port3333
################ MySQL 数据库配置##################
spring.datasource.platformmysqldb.num1
db.url.0jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncodingutf8connectTimeout1000socketTimeout3000autoReconnecttrueuseUnicodetrueuseSSLfalseserverTimezoneAsia/Shanghai
db.userrootdb.passwordroot 4. 将该 Nacos Server 目录复制到另外两台机器上并将它们的端口号分别修改为 4444 和 5555。
下载 Nginx并修改 Nginx 中 conf 目录下的 nginx.conf 的配置内容如下。
#user nobody;
worker_processes 1;#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections 1024;
}http {include mime.types;default_type application/octet-stream;sendfile on;keepalive_timeout 65;upstream cluster{server 127.0.0.1:3333;server 127.0.0.1:4444;server 127.0.0.1:5555;}server {listen 1111;server_name localhost;#charset koi8-r;#access_log logs/host.access.log main;location / {#root html;#index index.html index.htm;proxy_pass http://cluster;}}
}启动集群中所有的 Nacos Server当出现以下内容时表示 Nacos Server 启动成功。
nacos is starting with cluster,--.,--.|,--,: : | Nacos 2.0.3
,--.| : ,---. Running in cluster mode, All function modules
| : : | | ,\ .--.--. Port: ****
: | \ | : ,--.--. ,---. / / | / / Pid: 21592
| : ; | / \ / \. ; ,. :| : /./ Console: http://192.168.3.138:3333/nacos/index.html;. ;.--. .-. | / / | |: :| : ;_
| | | \ | \__\/: . .. / | .; : \ \ . https://nacos.io: | ; . , .--.; | ; :__| : | ----. \
| | -- / / ,. | | .|\ \ / / /-- /: | ; : . \ : : ---- --. /
; |. | , .-./\ \ / -----
--- ----- ----2021-11-09 16:25:00,993 INFO The server IP list of Nacos is [192.168.3.138:3333, 192.168.3.138:4444, 192.168.3.138:5555]2021-11-09 16:27:07,318 INFO Nacos is starting...2021-11-09 16:27:08,325 INFO Nacos is starting...2021-11-09 16:27:09,340 INFO Nacos is starting...2021-11-09 16:27:10,343 INFO Nacos is starting...2021-11-09 16:27:10,742 INFO Nacos started successfully in cluster mode. use external storage当集群中的所有 Nacos Server 都启动成功后双击 Nignx 安装目录下的 nginx.exe启动 Nginx。 图13Nginx 启动脚本
使用浏览器访问“http://localhost:1111/nacos/”若成功访问 Nacos Server 的控制台则说明 Nacos 集群部署成功如下图。
图14Nacos 集群
将主工程 spring-cloud-alibaba-demo 下所有子模块配置文件中的 Nacos Server 地址统一修改为localhost:1111。我们以 spring-cloud-alibaba-consumer-nacos-8801 为例配置文件 application.yml 的配置内容如下。
server:port: 8801 #端口号
spring:application:name: spring-cloud-alibaba-consumer #服务名cloud:nacos:discovery:#server-addr: localhost:8848 #单机版 Nacos Server 的地址server-addr: localhost:1111 #集群版 Nacos Server 的地址
#以下配置信息并不是默认配置而是我们自定义的配置目的是不在 Controller 内硬编码服务提供者的服务名
service-url:nacos-user-service: http://spring-cloud-alibaba-provider #服务提供者的服务名重启 spring-cloud-alibaba-consumer-nacos-8801并使用浏览器访问“http://localhost:1111/nacos”查看“服务管理”下的“服务列表”结果如下图。 图15服务注册到 Nacos Server 集群上
Spring Cloud Alibaba Sentinel
Sentinel 是由阿里巴巴中间件团队开发的开源项目是一种面向分布式微服务架构的轻量级高可用流量控制组件。
Sentinel 主要以流量为切入点从流量控制、熔断降级、系统负载保护等多个维度帮助用户保护服务的稳定性。
Sentinel 具有以下优势:
丰富的应用场景Sentinel 承接了阿里巴巴近 10 年的“双十一”大促流量的核心场景例如秒杀将突发流量控制在系统可以承受的范围、消息削峰填谷、集群流量控制、实时熔断下游不可用服务等。完备的实时监控Sentinel 提供了实时监控功能。用户可以在控制台中看到接入应用的单台机器的秒级数据甚至是 500 台以下规模集群的汇总运行情况。广泛的开源生态Sentinel 提供了开箱即用的与其它开源框架或库例如 Spring Cloud、Apache Dubbo、gRPC、Quarkus的整合模块。我们只要在项目中引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。此外Sentinel 还提供 Java、Go 以及 C 等多语言的原生实现。完善的 SPI 扩展机制Sentinel 提供简单易、完善的 SPI 扩展接口我们可以通过实现这些扩展接口快速地定制逻辑例如定制规则管理、适配动态数据源等。 SPI 全称为 Service Provider Interface是一种服务发现机制。它可以在 ClassPath 路径下的 META-INF/services 文件夹查找文件并自动加载文件中定义的类。 从功能上来说Sentinel 与 Spring Cloud Netfilx Hystrix 类似但 Sentinel 要比 Hystrix 更加强大例如 Sentinel 提供了流量控制功能、比 Hystrix 更加完善的实时监控功能等等。
Sentinel 的组成
Sentinel 主要由以下两个部分组成
Sentinel 核心库Sentinel 的核心库不依赖任何框架或库能够运行于 Java 8 及以上的版本的运行时环境中同时对 Spring Cloud、Dubbo 等微服务框架提供了很好的支持。Sentinel 控制台DashboardSentinel 提供的一个轻量级的开源控制台它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。
Sentinel 核心库不依赖 Sentinel Dashboard但两者结合使用可以有效的提高效率让 Sentinel 发挥它最大的作用。
Sentinel 的基本概念
Sentinel 的基本概念有两个它们分别是资源和规则。
基本概念描述资源资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容例如由应用程序提供的服务或者是服务里的方法甚至可以是一段代码。我们可以通过 Sentinel 提供的 API 来定义一个资源使其能够被 Sentinel 保护起来。通常情况下我们可以使用方法名、URL 甚至是服务名来作为资源名来描述某个资源。规则围绕资源而设定的规则。Sentinel 支持流量控制、熔断降级、系统保护、来源访问控制和热点参数等多种规则所有这些规则都可以动态实时调整。
SentinelResource 注解
SentinelResource 注解是 Sentinel 提供的最重要的注解之一它还包含了多个属性如下表。
属性说明必填与否使用要求value用于指定资源的名称必填-entryTypeentry 类型可选项默认为 EntryType.OUT-blockHandler服务限流后会抛出 BlockException 异常而 blockHandler 则是用来指定一个函数来处理 BlockException 异常的。简单点说该属性用于指定服务限流后的后续处理逻辑。可选项blockHandler 函数访问范围需要是 public返回类型需要与原方法相匹配参数类型需要和原方法相匹配并且最后加一个额外的参数类型为 BlockExceptionblockHandler 函数默认需要和原方法在同一个类中若希望使用其他类的函数则可以指定 blockHandler 为对应的类的 Class 对象注意对应的函数必需为 static 函数否则无法解析。blockHandlerClass若 blockHandler 函数与原方法不在同一个类中则需要使用该属性指定 blockHandler 函数所在的类。可选项不能单独使用必须与 blockHandler 属性配合使用该属性指定的类中的 blockHandler 函数必须为 static 函数否则无法解析。fallback用于在抛出异常包括 BlockException时提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常除了 exceptionsToIgnore 里面排除掉的异常类型进行处理。可选项返回值类型必须与原函数返回值类型一致方法参数列表需要和原函数一致或者可以额外多一个 Throwable 类型的参数用于接收对应的异常fallback 函数默认需要和原方法在同一个类中若希望使用其他类的函数则可以指定 fallbackClass 为对应的类的 Class 对象注意对应的函数必需为 static 函数否则无法解析。fallbackClass若 fallback 函数与原方法不在同一个类中则需要使用该属性指定 blockHandler 函数所在的类。可选项不能单独使用必须与 fallback 或 defaultFallback 属性配合使用该属性指定的类中的 fallback 函数必须为 static 函数否则无法解析。defaultFallback默认的 fallback 函数名称通常用于通用的 fallback 逻辑即可以用于很多服务或方法。默认 fallback 函数可以针对所以类型的异常除了 exceptionsToIgnore 里面排除掉的异常类型进行处理。可选项返回值类型必须与原函数返回值类型一致方法参数列表需要为空或者可以额外多一个 Throwable 类型的参数用于接收对应的异常defaultFallbackexceptionsToIgnore用于指定哪些异常被排除掉不会计入异常统计中也不会进入 fallback 逻辑中而是会原样抛出。可选项-注在 Sentinel 1.6.0 之前fallback 函数只针对降级异常DegradeException进行处理不能处理业务异常。 Sentinel 控制台
Sentinel 提供了一个轻量级的开源控制台 Sentinel Dashboard它提供了机器发现与健康情况管理、监控单机和集群、规则管理与推送等多种功能。
Sentinel 控制台提供的功能如下:
查看机器列表以及健康情况Sentinel 控制台能够收集 Sentinel 客户端发送的心跳包判断机器是否在线。监控单机和集群聚合Sentinel 控制台通过 Sentinel 客户端暴露的监控 API可以实现秒级的实时监控。规则管理和推送通过 Sentinel 控制台我们还能够针对资源定义和推送规则。鉴权从 Sentinel 1.6.0 起Sentinel 控制台引入基本的登录功能默认用户名和密码都是 sentinel。
Sentinel Dashboard 是我们配置和管理规则例如流控规则、熔断降级规则等的重要入口之一。通过它我们不仅可以对规则进行配置和管理还能实时查看规则的效果。
安装 Sentinel 控制台
下面我们就来演示下如何下载和安装 Sentinel 控制台具体步骤如下。
使用浏览器访问 Sentinel Dashboard 下载页面下载 Sentinel 控制台的 jar 包如下图。 图1Sentinel 控制台下载
打开命令行窗口跳转到 Sentinel Dashboard jar 包所在的目录执行以下命令启动 Sentinel Dashboard。
java -jar sentinel-dashboard-1.8.2.jar启动完成后使用浏览器访问“http://localhost:8080/”跳转到 Sentinel 控制台登陆页面如下图。 图2Sentinel 控制台登录页
分别输入用户名和密码默认都是 sentinel点击下方的登录按钮结果如下图。 图3Sentinel 控制台主页
Sentinel 的开发流程
Sentinel 的开发流程如下
引入 Sentinel 依赖在项目中引入 Sentinel 的依赖将 Sentinel 整合到项目中定义资源通过对主流框架提供适配或 Sentinel 提供的显式 API 和注解可以定义需要保护的资源此外 Sentinel 还提供了资源的实时统计和调用链路分析定义规则根据实时统计信息对资源定义规则例如流控规则、熔断规则、热点规则、系统规则以及授权规则等。检验规则是否在生效运行程序检验规则是否生效查看效果。
引入 Sentinel 依赖
为了减少开发的复杂程度Sentinel 对大部分的主流框架都进行了适配例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux 和 Reactor 等。以 Spring Cloud 为例我们只需要引入 spring-cloud-starter-alibaba-sentinel 的依赖就可以方便地将 Sentinel 整合到项目中。
下面我们就通过一个简单的实例演示如何将 Sentinel 整合到 Spring Cloud 项目中步骤如下。
在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-sentinel-service-8401 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-sentinel-service-8401/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-sentinel-service-8401/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependencies!--Nacos 服务发现依赖--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependency!--Snetinel 依赖--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-sentinel/artifactId/dependency!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到--dependencygroupIdcom.alibaba.csp/groupIdartifactIdsentinel-datasource-nacos/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build
/project在 spring-cloud-alibaba-sentinel-service-8401 的类路径下新建一个配置文件 application.yml配置内容如下。
server:port: 8401 #端口
spring:application:name: sentinel-service #服务名cloud:nacos:discovery:#Nacos服务注册中心(集群)地址server-addr: localhost:1111sentinel:transport:#配置 Sentinel dashboard 地址dashboard: localhost:8080#默认8719端口假如被占用会自动从8719开始依次1扫描,直至找到未被占用的端口port: 8719
management:endpoints:web:exposure:include: *在 net.demo.c.controller 下创建一个名为 SentinelFlowLimitController 的 Controller 类代码如下。
package net.demo.c.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
RestController
Slf4j
public class SentinelFlowLimitController {Value(${server.port})private String serverPort;GetMapping(/testA)public String testA() {return 提醒您服务访问成功------testA;}GetMapping(/testB)public String testB() {return 提醒您服务访问成功------testB}
}spring-cloud-alibaba-sentinel-service-8401 主启动类代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
SpringBootApplication
EnableDiscoveryClient
public class SpringCloudAlibabaSentinelService8401Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaSentinelService8401Application.class, args);}
}依次启动 Nacos Server 集群、 spring-cloud-alibaba-sentinel-service-8401使用浏览器访问“http://localhost:8401/testA”. 使用浏览器访问 Sentinel 控制台主页我们发现在“首页”下方新增了一个“sentinel-servcie”的菜单而这正是 spring-cloud-alibaba-sentinel-service-8401 的服务名spring.application.name说明 Sentinel 已经监控到这个服务如下图。
图5Sentinel 控制台主页
点击“实时监控”查看 sentinel-service 下各请求的实时监控数据如下图所示。 图6Sentinel 实时监控
定义资源
资源是 Sentinel 中的核心概念之一。在项目开发时我们只需要考虑这个服务、方法或代码是否需要保护如果需要保护就可以将它定义为一个资源。
Sentinel 为我们提供了多种定义资源的方式
适配主流框架自动定义资源通过 SphU 手动定义资源通过 SphO 手动定义资源注解方式定义资源
适配主流框架自动定义资源
Sentinel 对大部分的主流框架都进行了适配我们只要引入相关的适配模块例如 spring-cloud-starter-alibaba-sentinelSnetinel 就会自动将项目中的服务包括调用端和服务端定义为资源资源名就是服务的请求路径。此时我们只要再定义一些规则这些资源就可以享受到 Sentinel 的保护。
我们可以在 Sentinel 控制台的“簇点链路”中直接查看被 Sentinel 监控的资源如下图。 图7Sentinel 控制台-簇点链路 通过 SphU 手动定义资源 Sentinel 提供了一个名为 SphU 的类它包含的 try-catch 风格的 API 可以帮助我们手动定义资源。
下面我们就通过一个实例来演示下如何通过 SphU 定义资源。
在 spring-cloud-alibaba-sentinel-service-8401 下的 SentinelFlowLimitController 中新增一个 testAbySphU() 方法定义一个名为 testAbySphU 的资源代码如下。
package net.demo.c.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphO;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
RestController
Slf4j
public class SentinelFlowLimitController {Value(${server.port})private String serverPort;GetMapping(/testA)public String testA() {return testAbySphU();}GetMapping(/testB)public String testB() {return 提醒您服务访问成功------testB;}/*** 通过 SphU 手动定义资源* return*/public String testAbySphU() {Entry entry null;try {entry SphU.entry(testAbySphU);//您的业务逻辑 - 开始log.info(提醒您服务访问成功------testAserverPort);return 提醒您服务访问成功------testAserverPort;//您的业务逻辑 - 结束} catch (BlockException e1) {//流控逻辑处理 - 开始log.info(提醒您testA 服务被限流);return 提醒您testA 服务被限流;//流控逻辑处理 - 结束} finally {if (entry ! null) {entry.exit();}}}
}重启 spring-cloud-alibaba-sentinel-service-8401使用浏览器访问“http://localhost:8401/testA”结果如下。 提醒您服务访问成功------testA8401 访问 Sentinel 控制台主页点击 sentinel-service 下的“簇点链路”结果如下图。 图8Sentinel 通过 SphU 定义资源 通过 SphO 手动定义资源 Sentinel 还提供了一个名为 SphO 的类它包含了 if-else 风格的 API能帮助我们手动定义资源。通过这种方式定义的资源发生了限流之后会返回 false此时我们可以根据返回值进行限流之后的逻辑处理。
下面我们就通过一个实例来演示下如何通过 SphO 定义资源。
在 spring-cloud-alibaba-sentinel-service-8401 下的 SentinelFlowLimitController 中新增一个 testBbySphO() 方法定义一个名为 testBbySphO 的资源代码如下。
package net.demo.c.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphO;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
RestController
Slf4j
public class SentinelFlowLimitController {Value(${server.port})private String serverPort;GetMapping(/testA)public String testA() {return testAbySphU();}GetMapping(/testB)public String testB() {return testBbySphO();}/*** 通过 SphU 手动定义资源** return*/public String testAbySphU() {Entry entry null;try {entry SphU.entry(testAbySphU);//您的业务逻辑 - 开始log.info(提醒您服务访问成功------testA serverPort);return 提醒您服务访问成功------testA serverPort;//您的业务逻辑 - 结束} catch (BlockException e1) {//流控逻辑处理 - 开始log.info(提醒您testA 服务被限流);return 提醒您testA 服务被限流;//流控逻辑处理 - 结束} finally {if (entry ! null) {entry.exit();}}}/*** 通过 SphO 手动定义资源** return*/public String testBbySphO() {if (SphO.entry(testBbySphO)) {// 务必保证finally会被执行try {log.info(提醒您服务访问成功------testB serverPort);return 提醒您服务访问成功------testB serverPort;} finally {SphO.exit();}} else {// 资源访问阻止被限流或被降级//流控逻辑处理 - 开始log.info(提醒您testB 服务被限流);return 提醒您testB 服务被限流;//流控逻辑处理 - 结束}}
}重启 spring-cloud-alibaba-sentinel-service-8401使用浏览器访问“http://localhost:8401/testB”结果如下。 提醒您服务访问成功------testB8401 访问 Sentinel 控制台主页点击 sentinel-service 下的“簇点链路”结果如下图。
Sentinel SphO 图9Sentinel 通过 SphO 定义资源 注解方式定义资源推荐 除了以上两种方式外我们还可以通过 Sentinel 提供的 SentinelResource 注解定义资源。
下面我们就通过一个实例来演示下如何通过 SentinelResource 注解定义资源。
将 spring-cloud-alibaba-sentinel-service-8401 中 SentinelFlowLimitController 类中增加以下代码。
GetMapping(/testC)
SentinelResource(value testCbyAnnotation) //通过注解定义资源
public String testC() {log.info(提醒您服务访问成功------testC serverPort);return 提醒您服务访问成功------testC serverPort;
}重启 spring-cloud-alibaba-sentinel-service-8401使用浏览器访问“http://localhost:8401/testC”结果如下。 服务访问成功------testC8401 访问 Sentinel 控制台主页点击 sentinel-service 下的“簇点链路”结果如下图。 图10Sentinel 注解方式定义资源 Sentinel 流量控制 任何系统处理请求的能力都是有限的但任意时间内到达系统的请求量往往是随机且不可控的如果在某一个瞬时时刻请求量急剧增那么系统就很有可能被瞬时的流量高峰冲垮。为了避免此类情况发生我们都需要根据系统的处理能力对请求流量进行控制这就是我们常说的“流量控制”简称“流控”。
Sentinel 作为一种轻量级高可用流量控制组件流量控制是它最主要的工作之一。
我们可以针对资源定义流控规则Sentinel 会根据这些规则对流量相关的各项指标进行监控。当这些指标当达到或超过流控规则规定的阈值时Sentinel 会对请求的流量进行限制即“限流”以避免系统被瞬时的流量高峰冲垮保障系统的高可用性。
一条流量规则主要由下表中的属性组成我们可以通过组合这些属性来实现不同的限流效果。
属性说明默认值资源名的作用对象。-阈值值。-阈值类型流控阈值的类型包括 QPS 或并发线程数。QPS针对来源流控针对的调用来源。default表示不区分调用来源流控模式调用关系限流策略包括直接、链路和关联。直接流控效果流控效果直接拒绝、Warm Up、匀速排队不支持按调用关系限流。直接拒绝注QPS 表示并发请求数换句话说就是每秒钟最多通过的请求数。 同一个资源可以创建多条流控规则Sentinel 会遍历这些规则直到有规则触发限流或者所有规则遍历完毕为止。
Sentinel 触发限流时资源会抛出 BlockException 异常此时我们可以捕捉 BlockException 来自定义被限流之后的处理逻辑。 注意这里我们主要讲解 Sentinel 流控规则的定义与使用至于详细的流控规则配置请参考 Sentinel 官方流控文档。 通过 Sentinel 控制台定义流控规则 我们可以通过 Sentinel 控制台直接对资源定义流控规则操作步骤如下。
在 spring-cloud-alibaba-sentinel-service-8401 的 SentinelFlowLimitController 中新增一个名为 testD 的服务方法代码如下。
/*** 通过 Sentinel 控制台定义流控规则** return*/
GetMapping(/testD)
public String testD() {log.info(提醒您服务访问成功------testD serverPort);return 提醒您服务访问成功------testD serverPort;
}重启 spring-cloud-alibaba-sentinel-service-8401使用浏览器访问“http://localhost:8401/testD”结果如下。 提醒您服务访问成功------testD8401 使用浏览器访问“http://localhost:8080”登陆 Sentinel 控制台主页点击 sentinel-sevice 下的“簇点链路”结果如下图。 图11Sentinel 控制台定义流控规则
点击“/testD”右侧的“流控”按钮在弹出的“新增流控规则”窗口中定义流控规则如下图。 图12Sentinel 控制台定义流控规则
点击下方的“新增”按钮跳转到“流控规则”列表如下图。 图13Sentinel 控制台流控规则列表
快速连续频率大于每秒钟 2 次访问“http://localhost:8401/testD”结果如下。 Blocked by Sentinel (flow limiting)
若页面中出现以上信息则说明该服务已被限流但这种提示是 Sentinel 系统自动生成的用户体验不好。
在服务代码中使用 SentinelResource 注解定义资源名称并在 blockHandler 属性指定一个限流函数自定义服务限流信息代码如下。
/*** 通过 Sentinel 控制台定义流控规则**/
GetMapping(/testD)
SentinelResource(value testD-resource, blockHandler blockHandlerTestD) //通过注解定义资源
public String testD() {log.info(提醒您服务访问成功------testD serverPort);return 提醒您服务访问成功------testD serverPort;
}
/*** 限流之后的逻辑* param exception* return*/
public String blockHandlerTestD(BlockException exception) {log.info(Thread.currentThread().getName() 提醒您TestD服务访问失败! 您已被限流请稍后重试);return 提醒您TestD服务访问失败! 您已被限流请稍后重试;
}在以上代码中我们通过 SentinelResource 注解的 blockHandler 属性指定了一个 blockHandler 函数进行限流之后的后续处理。
使用 SentinelResource 注解的 blockHandler 属性时需要注意以下事项
blockHandler 函数访问范围需要是 public 返回类型需要与原方法相匹配 参数类型需要和原方法相匹配并且最后加一个额外的参数类型为 BlockException blockHandler 函数默认需要和原方法在同一个类中若希望使用其他类的函数则可以指定 blockHandler 为对应的类的 Class 对象注意对应的函数必需为 static 函数否则无法解析。 请务必添加 blockHandler 属性来指定自定义的限流处理方法若不指定则会跳转到错误页用户体验不好。
重启 spring-cloud-alibaba-sentinel-service-8401使用浏览器访问 Sentinel 控制台主页点击 sentinel-sevice 下的“簇点链路”结果如下图。 图14Sentinel 控制台-簇点链路
点击资源“testD-resource”右侧的“流控”按钮并在弹出的“新增流控规则”窗口中为这个资源定义流控规则流控规则内容为 QPS 的阈值为 2即每秒最多通过 2 个请求如下图。 图15Sentinel 控制台新增流控规则
快速连续频率大于每秒钟 2 次访问“http://localhost:8401/testD”结果如下。
提醒您TestD服务访问失败! 您已被限流请稍后重试通过代码定义流控规则 我们还可以在服务代码中调用 FlowRuleManager 类的 loadRules() 方法来定义流控规则该方法需要一个 FlowRule 类型的 List 集合作为其参数示例代码如下。
public static void loadRules(ListFlowRule rules) {currentProperty.updateValue(rules);
}FlowRule 可以通过以下属性定义流控规则如下表。
属性说明默认值resource资源名即流控规则的作用对象-count限流的阈值。-grade流控阈值的类型包括 QPS 或并发线程数QPSlimitApp流控针对的调用来源default表示不区分调用来源strategy调用关系限流策略包括直接、链路和关联直接controlBehavior流控效果直接拒绝、Warm Up、匀速排队不支持按调用关系限流直接拒绝
下面我们就通过一个简单的实例来演示下如何通过代码定义流控规则步骤如下。
在 spring-cloud-alibaba-sentinel-service-8401 的 SentinelFlowLimitController 中添加一个 initFlowRules() 方法为名为 testD-resource 的资源定义流控规则每秒最多只能通过 2 个请求即 QPS 的阈值为 2。
/*** 通过代码定义流量控制规则*/
private static void initFlowRules() {ListFlowRule rules new ArrayList();//定义一个限流规则对象FlowRule rule new FlowRule();//资源名称rule.setResource(testD-resource);//限流阈值的类型rule.setGrade(RuleConstant.FLOW_GRADE_QPS);// 设置 QPS 的阈值为 2rule.setCount(2);rules.add(rule);//定义限流规则FlowRuleManager.loadRules(rules);
}在 testD() 方法中调用 initFlowRules() 方法初始化流控规则代码如下。
GetMapping(/testD)
SentinelResource(value testD-resource, blockHandler blockHandlerTestD) //通过注解定义资源
public String testD() {initFlowRules(); //调用初始化流控规则的方法log.info(提醒您服务访问成功------testD serverPort);return 提醒您服务访问成功------testD serverPort;
}重启 spring-cloud-alibaba-sentinel-service-8401并使用浏览器访问“http://localhost:8401/testD”结果如下。 提醒您服务访问成功------testD8401 快速连续频率大于每秒钟 2 次访问“http://localhost:8401/testD”结果如下。 提醒您TestD服务访问失败! 您已被限流请稍后重试 打开命令行窗口执行以下命令查看资源的实时统计信息。 curl http://localhost:8719/cnode?idtestD-resource 控制台输出内容如下。
idx id thread pass blocked success total aRt 1m-pass 1m-block 1m-all exceptio
2 testD-resource 0 0.0 0.0 0.0 0.0 0.0 10 16 26 0.0实时统计信息各列名说明如下
thread 代表当前处理该资源的并发数pass 代表一秒内到来到的请求blocked 代表一秒内被流量控制的请求数量success 代表一秒内成功处理完的请求total 代表到一秒内到来的请求以及被阻止的请求总和RT 代表一秒内该资源的平均响应时间1m-pass 则是一分钟内到来的请求1m-block 则是一分钟内被阻止的请求1m-all 则是一分钟内到来的请求和被阻止的请求的总和exception 则是一秒内业务本身异常的总和。
熔断降级规则
除了流量控制以外对调用链路中不稳定资源的熔断降级也是保障服务高可用的重要措施之一。
在分布式微服务架构中一个系统往往由多个服务组成不同服务之间相互调用组成复杂的调用链路。如果链路上的某一个服务出现故障那么故障就会沿着调用链路在系统中蔓延最终导致整个系统瘫痪。Sentinel 提供了熔断降级机制就可以解决这个问题。
Sentinel 的熔断将机制会在调用链路中某个资源出现不稳定状态时例如调用超时或异常比例升高暂时切断对这个资源的调用以避免局部不稳定因素导致整个系统的雪崩。
熔断降级作为服务保护自身的手段通常在客户端调用端进行配置资源被熔断降级最直接的表现就是抛出 DegradeException 异常。 Sentinel 熔断策略 Sentinel 提供了 3 种熔断策略如下表所示。
熔断策略说明慢调用比例(SLOW_REQUEST_RATIO选择以慢调用比例作为阈值需要设置允许的慢调用 RT即最大响应时间若请求的响应时间大于该值则统计为慢调用。当单位统计时长statIntervalMs内请求数目大于设置的最小请求数目且慢调用的比例大于阈值则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态HALF-OPEN 状态若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断若大于设置的慢调用 RT 则再次被熔断。异常比例 (ERROR_RATIO)当单位统计时长statIntervalMs内请求数目大于设置的最小请求数目且异常的比例大于阈值则在接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态HALF-OPEN 状态若接下来的一个请求成功完成没有错误则结束熔断否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]代表 0% - 100%。异常数 (ERROR_COUNT)当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态HALF-OPEN 状态若接下来的一个请求成功完成没有错误则结束熔断否则会再次被熔断。注意Sentinel 1.8.0 版本对熔断降级特性进行了全新的改进升级以上熔断策略针对的是 Sentinel 1.8.0 及以上版本。 Sentinel 熔断状态
Sentinel 熔断降级中共涉及 3 种状态熔断状态的之间的转换过程如下图。 图16Sentinel 熔断状态转换
Sentinel 熔断降级中共涉及 3 种状态如下表。
状态说明触发条件熔断关闭状态CLOSED处于关闭状态时请求可以正常调用资源。 满足以下任意条件Sentinel 熔断器进入熔断关闭状态全部请求访问成功。单位统计时长statIntervalMs内请求数目小于设置的最小请求数目。未达到熔断标准例如服务超时比例、异常数、异常比例未达到阈值。处于探测恢复状态时下一个请求访问成功。熔断开启状态OPEN处于熔断开启状态时熔断器会一定的时间规定的熔断时长内暂时切断所有请求对该资源的调用并调用相应的降级逻辑使请求快速失败避免系统崩溃。 满足以下任意条件Sentinel 熔断器进入熔断开启状态单位统计时长内请求数目大于设置的最小请求数目且已达到熔断标准例如请求超时比例、异常数、异常比例达到阈值。处于探测恢复状态时下一个请求访问失败。探测恢复状态HALF-OPEN处于探测恢复状态时Sentinel 熔断器会允许一个请求调用资源。则若接下来的一个请求成功完成没有错误则结束熔断熔断器进入熔断关闭CLOSED状态否则会再次被熔断熔断器进入熔断开启OPEN状态。 在熔断开启一段时间降级窗口时间或熔断时长单位为 s后Sentinel 熔断器自动会进入探测恢复状态。
Sentinel 熔断规则属性
Sentinel 熔断降级规则包含多个重要属性如下表所示。
属性说明默认值使用范围资源名规则的作用对象。-所有熔断策略熔断策略Sentinel 支持3 中熔断策略慢调用比例、异常比例、异常数策略。慢调用比例所有熔断策略最大 RT请求的最大相应时间请求的响应时间大于该值则统计为慢调用。-慢调用比例熔断时长熔断开启状态持续的时间超过该时间熔断器会切换为探测恢复状态HALF-OPEN单位为 s。-所有熔断策略最小请求数熔断触发的最小请求数请求数小于该值时即使异常比率超出阈值也不会熔断1.7.0 引入。5所有熔断策略统计时长熔断触发需要统计的时长单位为 ms如 60*1000 代表分钟级1.8.0 引入。1000 ms所有熔断策略比例阈值分为慢调用比例阈值和异常比例阈值即慢调用或异常调用占所有请求的百分比取值范围 [0.0,1.0]。-慢调用比例 、异常比例异常数请求或调用发生的异常的数量。-异常数
Sentinel 实现熔断降级过程
Sentinel 实现熔断降级的步骤如下
在项目中使用 SentinelResource 注解的 fallback 属性可以为资源指定熔断降级逻辑方法。通过 Sentinel 控制台或代码定义熔断规则包括熔断策略、最小请求数、阈值、熔断时长以及统计时长等。若单位统计时长statIntervalMs内请求数目大于设置的最小请求数目且达到熔断标准例如请求超时比例、异常数、异常比例达到阈值Sentinel 熔断器进入熔断开启状态OPEN。处于熔断开启状态时 SentinelResource 注解的 fallback 属性指定的降级逻辑会临时充当主业务逻辑而原来的主逻辑则暂时不可用。当有请求访问该资源时会直接调用降级逻辑使请求快速失败而不会调用原来的主业务逻辑。在经过一段时间在熔断规则中设置的熔断时长后熔断器会进入探测恢复状态HALF-OPEN此时 Sentinel 会允许一个请求对原来的主业务逻辑进行调用并监控其调用结果。若请求调用成功则熔断器进入熔断关闭状态CLOSED 服务原来的主业务逻辑恢复否则重新进入熔断开启状态OPEN。
通过 Sentinel 控制台定义熔断降级规则
我们可以通过 Sentinel 控制台直接对资源定义熔断降级规则。
下面我们通过一个实例来演示如何通过 Sentinel 控制台对资源定义降级规则。
在 MySQL 的 demobang_jdbc 数据库中执行以下 SQL准备测试数据。
DROP TABLE IF EXISTS dept;
CREATE TABLE dept (dept_no int NOT NULL AUTO_INCREMENT,dept_name varchar(255) DEFAULT NULL,db_source varchar(255) DEFAULT NULL,PRIMARY KEY (dept_no)
) ENGINEInnoDB AUTO_INCREMENT6 DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;INSERT INTO dept VALUES (1, 开发部, demobang_jdbc);
INSERT INTO dept VALUES (2, 人事部, demobang_jdbc);
INSERT INTO dept VALUES (3, 财务部, demobang_jdbc);
INSERT INTO dept VALUES (4, 市场部, demobang_jdbc);
INSERT INTO dept VALUES (5, 运维部, demobang_jdbc);在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-provider-mysql-8003 的 Spring Boot 模块并在其 pom.xml 中添加相关依赖代码如下。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-provider-mysql-8003/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-provider-mysql-8003/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactId/dependencydependencygroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-api/artifactIdversion${project.version}/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion5.1.49/version/dependencydependencygroupIdch.qos.logback/groupIdartifactIdlogback-core/artifactId/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.0/version/dependency!--添加 Spring Boot 的监控模块--!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build
/project在 spring-cloud-alibaba-provider-mysql-8003 的类路径下创建一个配置文件 application.yml配置如下。
server:port: 8003 #端口
spring:application:name: spring-cloud-alibaba-provider-mysqlcloud:nacos:discovery:server-addr: localhost:1111######################### 数据库连接 #################################datasource:username: root #数据库登陆用户名password: root #数据库登陆密码url: jdbc:mysql://127.0.0.1:3306/spring_cloud_db2 #数据库urldriver-class-name: com.mysql.jdbc.Driver
management:endpoints:web:exposure:include: * # * 在yaml 文件属于关键字所以需要加引号
###################################### MyBatis 配置 ######################################
mybatis:# 指定 mapper.xml 的位置mapper-locations: classpath:mybatis/mapper/*.xml#扫描实体类的位置,在此处指明扫描实体类的包在 mapper.xml 中就可以不写实体类的全路径名type-aliases-package: net.demo.c.entityconfiguration:#默认开启驼峰命名法可以不用设置该属性map-underscore-to-camel-case: true在 net.demo.c.entity 包下创建一个名为 Dept 的实体类代码如下。
package net.demo.c.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
AllArgsConstructor
NoArgsConstructor //无参构造函数
Data // 提供类的get、set、equals、hashCode、canEqual、toString 方法
Accessors(chain true)
public class Dept implements Serializable {private Integer deptNo;private String deptName;private String dbSource;
}在 net.demo.c.entity 包下创建一个名为 CommonResult 的 Java 类代码如下。
package net.demo.c.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
Data
AllArgsConstructor
NoArgsConstructor
public class CommonResultT {private Integer code;private String message;private T data;public CommonResult(Integer code, String message) {this(code, message, null);}
}在 net.demo.c.mapper 包下创建一个名为 DeptMapper 的接口代码如下。
package net.demo.c.mapper;
import net.demo.c.entity.Dept;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
Mapper
public interface DeptMapper {//根据主键获取数据Dept selectByPrimaryKey(Integer deptNo);//获取表中的全部数据ListDept GetAll();
}在 spring-cloud-alibaba-provider-mysql-8003 的 /resources/mybatis/mapper/ 目录下创建一个名为 DeptMapper.xml 的 MyBatis 映射文件配置内容如下。
?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacenet.demo.c.mapper.DeptMapperresultMap idBaseResultMap typenet.demo.c.entity.Deptid columndept_no jdbcTypeINTEGER propertydeptNo/result columndept_name jdbcTypeVARCHAR propertydeptName/result columndb_source jdbcTypeVARCHAR propertydbSource//resultMapsql idBase_Column_Listdept_no, dept_name, db_source/sqlselect idselectByPrimaryKey parameterTypejava.lang.Integer resultMapBaseResultMapselectinclude refidBase_Column_List/from deptwhere dept_no #{deptNo,jdbcTypeINTEGER}/selectselect idGetAll resultTypenet.demo.c.entity.Deptselect *from dept;/select
/mapper在 net.demo.c.service 包下创建一个名为 DeptService 的接口代码如下。
package net.demo.c.service;
import net.demo.c.entity.Dept;
import java.util.List;
public interface DeptService {Dept get(Integer deptNo);ListDept selectAll();
}在 net.demo.c.service.impl 包下创建 DeptService 接口的实现类 DeptServiceImpl代码如下。
package net.demo.c.service.impl;
import net.demo.c.entity.Dept;
import net.demo.c.mapper.DeptMapper;
import net.demo.c.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
Service(deptService)
public class DeptServiceImpl implements DeptService {Autowiredprivate DeptMapper deptMapper;Overridepublic Dept get(Integer deptNo) {return deptMapper.selectByPrimaryKey(deptNo);}Overridepublic ListDept selectAll() {return deptMapper.GetAll();}
}在 net.demo.c.controller 包下创建一个名为 DeptController 的 Contorller 类代码如下。
package net.demo.c.controller;
import lombok.extern.slf4j.Slf4j;
import net.demo.c.entity.CommonResult;
import net.demo.c.entity.Dept;
import net.demo.c.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.concurrent.TimeUnit;
RestController
Slf4j
public class DeptController {Autowiredprivate DeptService deptService;Value(${server.port})private String serverPort;RequestMapping(value /dept/get/{id}, method RequestMethod.GET)public CommonResultDept get(PathVariable(id) int id) {log.info(端口 serverPort \t dept/get/);try {TimeUnit.SECONDS.sleep(1);log.info(休眠 1秒);} catch (InterruptedException e) {e.printStackTrace();}Dept dept deptService.get(id);CommonResultDept result new CommonResult(200, from mysql,serverPort: serverPort, dept);return result;}RequestMapping(value /dept/list, method RequestMethod.GET)public CommonResultListDept list() {log.info(端口 serverPort \t dept/list/);ListDept depts deptService.selectAll();CommonResultListDept result new CommonResult(200, from mysql,serverPort: serverPort, depts);return result;}
}spring-cloud-alibaba-provider-mysql-8003 的主启动类代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
SpringBootApplication
EnableDiscoveryClient
public class SpringCloudAlibabaProviderMysql8003Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaProviderMysql8003Application.class, args);}
}在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-consumer-mysql-8803 的 Spring Boot 模块并在其 pom.xml 添加依赖内容如下。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-consumer-mysql-8803/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-consumer-mysql-8803/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.version/propertiesdependencies!--SpringCloud ailibaba nacos --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactIdexclusionsexclusiongroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-ribbon/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--引入 OpenFeign 的依赖--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-loadbalancer/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependency!--SpringCloud ailibaba sentinel --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-sentinel/artifactId/dependencydependencygroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-api/artifactIdversion${project.version}/version/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build
/project在 spring-cloud-alibaba-consumer-mysql-8803 的类路径下创建一个配置文件 application.yml配置内容如下。
server:port: 8803
spring:application:name: spring-cloud-alibaba-consumer-mysql-feigncloud:nacos:discovery:server-addr: localhost:1111sentinel:transport:dashboard: localhost:8080port: 8719
# 以下配置信息并不是默认配置而是我们自定义的配置目的是不在 Controller 内硬编码 服务提供者的服务名
service-url:nacos-user-service: http://spring-cloud-alibaba-provider-mysql #消费者要方位的微服务名称
# 激活Sentinel对Feign的支持
feign:sentinel:enabled: true在 net.demo.c.service 包下创建一个名为 DeptFeignService 的接口代码如下。
package net.demo.c.service;
import net.demo.c.entity.CommonResult;
import net.demo.c.entity.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
Component
FeignClient(value spring-cloud-alibaba-provider-mysql, fallback DeptFallbackService.class)
public interface DeptFeignService {RequestMapping(value /dept/get/{id}, method RequestMethod.GET)public CommonResultDept get(PathVariable(id) int id);RequestMapping(value /dept/list, method RequestMethod.GET)public CommonResultListDept list();
}在 net.demo.c.controller 包下创建一个名为 DeptFeignController 的 Controller代码如下。
package net.demo.c.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.csp.sentinel.util.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import net.demo.c.entity.CommonResult;
import net.demo.c.entity.Dept;
import net.demo.c.service.DeptFeignService;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
RestController
Slf4j
public class DeptFeignController {ResourceDeptFeignService deptFeignService;RequestMapping(value consumer/feign/dept/get/{id}, method RequestMethod.GET)SentinelResource(value fallback, fallback handlerFallback)public CommonResultDept get(PathVariable(id) int id) {monitor();System.out.println(---------主业务逻辑);CommonResultDept result deptFeignService.get(id);if (id 6) {System.err.println(---------主业务逻辑抛出非法参数异常);throw new IllegalArgumentException(IllegalArgumentException非法参数异常....);//如果查到的记录也是 null 也控制正异常} else if (result.getData() null) {System.err.println(---------主业务逻辑抛出空指针异常);throw new NullPointerException(NullPointerException该ID没有对应记录,空指针异常);}return result;}RequestMapping(value consumer/feign/dept/list, method RequestMethod.GET)public CommonResultListDept list() {return deptFeignService.list();}//处理异常的回退方法服务降级public CommonResult handlerFallback(PathVariable int id, Throwable e) {System.err.println(---------服务降级逻辑);Dept dept new Dept(id, null, null);return new CommonResult(444, 提醒您服务被降级异常信息为 e.getMessage(), dept);}/*** 自定义事件监听器监听熔断器状态转换*/public void monitor() {EventObserverRegistry.getInstance().addStateChangeObserver(logging,(prevState, newState, rule, snapshotValue) - {SimpleDateFormat format new SimpleDateFormat(yyyy-MM-dd HH:mm:ss);if (newState CircuitBreaker.State.OPEN) {// 变换至 OPEN state 时会携带触发时的值System.err.println(String.format(%s - OPEN at %s, 发送请求次数%.2f, prevState.name(),format.format(new Date(TimeUtil.currentTimeMillis())), snapshotValue));} else {System.err.println(String.format(%s - %s at %s, prevState.name(), newState.name(),format.format(new Date(TimeUtil.currentTimeMillis()))));}});}
}在以上代码中我们通过 SentinelResource 注解的 fallback 属性指定了一个 fallback 函数进行熔断降级的后续处理。
使用 SentinelResource 注解的 blockHandler 属性时需要注意以下事项
返回值类型必须与原函数返回值类型一致方法参数列表需要和原函数一致或者可以额外多一个 Throwable 类型的参数用于接收对应的异常fallback 函数默认需要和原方法在同一个类中若希望使用其他类的函数则可以指定 fallbackClass 为对应的类的 Class 对象注意对应的函数必需为 static 函数否则无法解析。
spring-cloud-alibaba-consumer-mysql-8803 的主启动类代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
SpringBootApplication
EnableDiscoveryClient
EnableFeignClients
public class SpringCloudAlibabaConsumerMysql8803Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaConsumerMysql8803Application.class, args);}
}依次启动 spring-cloud-alibaba-provider-mysql-8003 和 spring-cloud-alibaba-consumer-mysql-8803使用浏览器访问“http://localhost:8803/consumer/feign/dept/get/3”结果如下。
{code:200,message:from mysql,serverPort: 8003,data:{deptNo:3,deptName:财务部,dbSource:spring_cloud_db2}}使用浏览器访问“http://localhost:8803/consumer/feign/dept/get/7”结果如下。
{code:444,message:提醒您服务被降级异常信息为NullPointerException该ID没有对应记录,空指针异常,data:{deptNo:7,deptName:null,dbSource:null}}控制台输出如下。
---------主业务逻辑
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务熔断降级逻辑使用浏览器访问 Sentinel 控制台在“簇点链路”列表中点击 fallback 资源的 “熔断”按钮如下图。 图17Sentinel 熔断降级
在“新增熔断规则”的窗口中为名为 “fallback”的资源定义以下熔断规则如下图。
图18Sentinel 控制台定义熔断规则
在上图中熔断规则各属性说明如下
异常数为 1统计时长为 1000 ms即 1s最小请求数为 2熔断时长为 5 秒
我们为 fallback 资源定义的熔断规则为当 1 秒钟内请求数大于 2 个且请求异常数大于 1 时服务被熔断熔断的时长为 5 秒钟。
使用浏览器连续访问“http://localhost:8803/consumer/feign/dept/get/7”访问频率大于每秒 2 个请求结果如下。
{code:444,message:提醒您服务被降级异常信息为null,data:{deptNo:7,deptName:null,dbSource:null}}控制台输出如下。
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务降级逻辑
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务降级逻辑
CLOSED - OPEN at 2021-11-17 14:06:47, 发送请求次数2.00从控制台输出可以看出在 14点 06 分 47 秒时熔断器从熔断关闭状态CLOSED切换到熔断开启状态OPEN。 在熔断开启开启状态下使用浏览器访问“http://localhost:8803/consumer/feign/dept/get/4”结果页面输出如下。 {“code”:444,“message”:“提醒您服务被降级异常信息为null”,“data”:{“deptNo”:4,“deptName”:“null”,“dbSource”:“null”}} 控制台输出如下。
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务降级逻辑
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务降级逻辑
CLOSED - OPEN at 2021-11-17 14:09:19, 发送请求次数2.00
---------服务降级逻辑
---------服务降级逻辑
---------服务降级逻辑从控制台输出可知当熔断器处于熔断开启状态时所有的请求都直接交给降级逻辑处理。
继续使用浏览器访问“http://localhost:8803/consumer/feign/dept/get/4”结果页面输出如下。
{code:200,message:from mysql,serverPort: 8003,data:{deptNo:4,deptName:市场部,dbSource:spring_cloud_db2}}控制台输出如下。
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务降级逻辑
---------主业务逻辑
---------主业务逻辑抛出空指针异常
---------服务降级逻辑
CLOSED - OPEN at 2021-11-17 14:09:19, 发送请求次数2.00
--——---服务降级逻辑
---------服务降级逻辑
---------服务降级逻辑
OPEN - HALF_OPEN at 2021-11-17 14:09:24
--——---主业务逻辑
HALF_OPEN - CLOSED at 2021-11-17 14:09:24
---------主业务逻辑
---------主业务逻辑从以上控制台输出可知熔断器在经历了 5 秒的熔断时长后自动切换到了探测恢复状态HALF-OPEN并在下一个请求成功的情况下结束了熔断开启状态切换到了熔断关闭状态CLOSED。 通过代码定义熔断规则 Sentinel 核心库中提供了的一个名为 DegradeRuleManager 类我们可以通过调用它的 loadRules() 方法来定义熔断降级规则该方法需要一个 DegradeRule 类型的 List 参数。
public static void loadRules(ListDegradeRule rules) {try {currentProperty.updateValue(rules);} catch (Throwable var2) {RecordLog.error([DegradeRuleManager] Unexpected error when loading degrade rules, var2);}
}DegradeRule 类可以用来定义一条熔断规则它包含多个与熔断规则相关的属性如下表。
下面我们就通过一个实例演示下如何通过代码定义熔断规则步骤如下。
在 spring-cloud-alibaba-consumer-mysql-8803 的 DeptFeignController 中添加一个名为 initDegradeRule 的方法代码如下。
/*** 初始化熔断策略*/
private static void initDegradeRule() {ListDegradeRule rules new ArrayList();DegradeRule rule new DegradeRule(fallback);//熔断策略为异常比例rule.setGrade(CircuitBreakerStrategy.ERROR_RATIO.getType());//异常比例阈值rule.setCount(0.7);//最小请求数rule.setMinRequestAmount(100);//统计市场单位毫秒rule.setStatIntervalMs(30000);//熔断市场单位秒rule.setTimeWindow(10);rules.add(rule);DegradeRuleManager.loadRules(rules);
}在 DeptFeignController 的 get() 方法中调用 initDegradeRule() 方法初始化熔断规则代码如下。
RequestMapping(value consumer/feign/dept/get/{id}, method RequestMethod.GET)
SentinelResource(value fallback, fallback handlerFallback)
public CommonResultDept get(PathVariable(id) int id) {initDegradeRule();monitor();System.out.println(---------主业务逻辑);CommonResultDept result deptFeignService.get(id);if (id 6) {System.err.println(---------主业务逻辑抛出非法参数异常);throw new IllegalArgumentException(IllegalArgumentException非法参数异常....);//如果查到的记录也是 null 也控制正异常} else if (result.getData() null) {System.err.println(---------主业务逻辑抛出空指针异常);throw new NullPointerException(NullPointerException该ID没有对应记录,空指针异常);}return result;
}重启 spring-cloud-alibaba-consumer-mysql-8803使用浏览器访问“http://localhost:8803/consumer/feign/dept/get/1”结果如下。 “code”:200,“message”:“from mysql,serverPort: 8003”,“data”:{“deptNo”:1,“deptName”:“开发部”,“dbSource”:“spring_cloud_db2”}} 使用浏览器访问 Sentinel 控制主页点击“熔断规则”查看熔断规则列表结果如下图。 图19Sentinel 代码定义熔断规则
从上图我们看到通过代码也能够为资源定义熔断规则。
Spring Cloud Alibaba Seata
随着业务的不断发展单体架构已经无法满足我们的需求分布式微服务架构逐渐成为大型互联网平台的首选但所有使用分布式微服务架构的应用都必须面临一个十分棘手的问题那就是“分布式事务”问题。
在分布式微服务架构中几乎所有业务操作都需要多个服务协作才能完成。对于其中的某个服务而言它的数据一致性可以交由其自身数据库事务来保证但从整个分布式微服务架构来看其全局数据的一致性却是无法保证的。
例如用户在某电商系统下单购买了一件商品后电商系统会执行下 4 步
调用订单服务创建订单数据调用库存服务扣减库存调用账户服务扣减账户金额最后调用订单服务修改订单状态
为了保证数据的正确性和一致性我们必须保证所有这些操作要么全部成功要么全部失败否则就可能出现类似于商品库存已扣减但用户账户资金尚未扣减的情况。各服务自身的事务特性显然是无法实现这一目标的此时我们可以通过分布式事务框架来解决这个问题。
Seata读音 [si:t] 就是这样一个分布式事务处理框架它是由阿里巴巴和蚂蚁金服共同开源的分布式事务解决方案能够在微服务架构下提供高性能且简单易用的分布式事务服务。
Seata 的发展历程
阿里巴巴作为国内最早一批进行应用分布式微服务化改造的企业很早就遇到微服务架构下的分布式事务问题。
阿里巴巴对于分布式事务问题先后发布了以下解决方案
2014 年阿里中间件团队发布 TXCTaobao Transaction Constructor为集团内应用提供分布式事务服务。2016 年TXC 在经过产品化改造后以 GTSGlobal Transaction Service 的身份登陆阿里云成为当时业界唯一一款云上分布式事务产品。在阿云里的公有云、专有云解决方案中开始服务于众多外部客户。2019 年起基于 TXC 和 GTS 的技术积累阿里中间件团队发起了开源项目 FescarFast EaSy Commit And Rollback, FESCAR和社区一起建设这个分布式事务解决方案。2019 年 fescar 被重命名为了seatasimple extensiable autonomous transaction architecture。TXC、GTS、Fescar 以及 seata 一脉相承为解决微服务架构下的分布式事务问题交出了一份与众不同的答卷。
分布式事务相关概念
分布式事务主要涉及以下概念
事务由一组操作构成的可靠、独立的工作单元事务具备 ACID 的特性即原子性、一致性、隔离性和持久性。本地事务本地事务由本地资源管理器通常指数据库管理系统 DBMS例如 MySQL、Oracle 等管理严格地支持 ACID 特性高效可靠。本地事务不具备分布式事务的处理能力隔离的最小单位受限于资源管理器即本地事务只能对自己数据库的操作进行控制对于其他数据库的操作则无能为力。全局事务全局事务指的是一次性操作多个资源管理器完成的事务由一组分支事务组成。分支事务在分布式事务中就是一个个受全局事务管辖和协调的本地事务。
我们可以将分布式事务理解成一个包含了若干个分支事务的全局事务。全局事务的职责是协调其管辖的各个分支事务达成一致要么一起成功提交要么一起失败回滚。此外通常分支事务本身就是一个满足 ACID 特性的本地事务。
Seata 整体工作流程
Seata 对分布式事务的协调和控制主要是通过 XID 和 3 个核心组件实现的。
XID
XID 是全局事务的唯一标识它可以在服务的调用链路中传递绑定到服务的事务上下文中。
核心组件
Seata 定义了 3 个核心组件
TCTransaction Coordinator事务协调器它是事务的协调者这里指的是 Seata 服务器主要负责维护全局事务和分支事务的状态驱动全局事务提交或回滚。TMTransaction Manager事务管理器它是事务的发起者负责定义全局事务的范围并根据 TC 维护的全局事务和分支事务状态做出开始事务、提交事务、回滚事务的决议。RMResource Manager资源管理器它是资源的管理者这里可以将其理解为各服务使用的数据库。它负责管理分支事务上的资源向 TC 注册分支事务汇报分支事务状态驱动分支事务的提交或回滚。
以上三个组件相互协作TC 以 Seata 服务器Server形式独立部署TM 和 RM 则是以 Seata Client 的形式集成在微服务中运行其整体工作流程如下图。 图1Sentinel 的工作流程
Seata 的整体工作流程如下 1.TM 向 TC 申请开启一个全局事务全局事务创建成功后TC 会针对这个全局事务生成一个全局唯一的 XID 2.XID 通过服务的调用链传递到其他服务; 3.RM 向 TC 注册一个分支事务并将其纳入 XID 对应全局事务的管辖 4.TM 根据 TC 收集的各个分支事务的执行结果向 TC 发起全局事务提交或回滚决议 5.TC 调度 XID 下管辖的所有分支事务完成提交或回滚操作。
Seata AT 模式
Seata 提供了 AT、TCC、SAGA 和 XA 四种事务模式可以快速有效地对分布式事务进行控制。
在这四种事务模式中使用最多最方便的就是 AT 模式。与其他事务模式相比AT 模式可以应对大多数的业务场景且基本可以做到无业务入侵开发人员能够有更多的精力关注于业务逻辑开发。
AT 模式的前提
任何应用想要使用 Seata 的 AT 模式对分布式事务进行控制必须满足以下 2 个前提
必须使用支持本地 ACID 事务特性的关系型数据库例如 MySQL、Oracle 等应用程序必须是使用 JDBC 对数据库进行访问的 JAVA 应用。
此外我们还需要针对业务中涉及的各个数据库表分别创建一个 UNDO_LOG回滚日志表。不同数据库在创建 UNDO_LOG 表时会略有不同以 MySQL 为例其 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,PRIMARY KEY (id),UNIQUE KEY ux_undo_log (xid,branch_id)
) ENGINEInnoDB AUTO_INCREMENT1 DEFAULT CHARSETutf8;AT 模式的工作机制
Seata 的 AT 模式工作时大致可以分为以两个阶段下面我们就结合一个实例来对 AT 模式的工作机制进行介绍。
假设某数据库中存在一张名为 webset 的表表结构如下。
列名类型主键idbigint(20)√namevarchar(255)urlvarchar(255)
在某次分支事务中我们需要在 webset 表中执行以下操作。
update webset set url c.demo.net where name XX测试;一阶段
Seata AT 模式一阶段的工作流程如下图所示。 图2Seata AT 模式一阶段
Seata AT 模式一阶段工作流程如下。 获取 SQL 的基本信息Seata 拦截并解析业务 SQL得到 SQL 的操作类型UPDATE、表名webset、判断条件where name ‘XX测试’等相关信息。 查询前镜像根据得到的业务 SQL 信息生成“前镜像查询语句”。
select id,name,url from webset where nameXX测试;执行“前镜像查询语句”得到即将执行操作的数据并将其保存为“前镜像数据beforeImage”。
idnameurl1XX测试xxx.net执行业务 SQLupdate webset set url ‘c.demo.net’ where name ‘XX测试’;将这条记录的 url 修改为 c.demo.net。 查询后镜像根据“前镜像数据”的主键id : 1生成“后镜像查询语句”。
select id,name,url from webset where id 1;执行“后镜像查询语句”得到执行业务操作后的数据并将其保存为“后镜像数据afterImage”。
idnameurl1XX测试c.xxx.net
插入回滚日志将前后镜像数据和业务 SQL 的信息组成一条回滚日志记录插入到 UNDO_LOG 表中示例回滚日志如下。
{class: io.seata.rm.datasource.undo.BranchUndoLog,xid: 172.26.54.1:8091:5962967415319516023,branchId: 5962967415319516027,sqlUndoLogs: [java.util.ArrayList,[{class: io.seata.rm.datasource.undo.SQLUndoLog,sqlType: UPDATE,tableName: webset,beforeImage: {class: io.seata.rm.datasource.sql.struct.TableRecords,tableName: webset,rows: [java.util.ArrayList,[{class: io.seata.rm.datasource.sql.struct.Row,fields: [java.util.ArrayList,[{class: io.seata.rm.datasource.sql.struct.Field,name: id,keyType: PRIMARY_KEY,type: -5,value: [java.lang.Long,1]},{class: io.seata.rm.datasource.sql.struct.Field,name: url,keyType: NULL,type: 12,value: demo.net}]]}]]},afterImage: {class: io.seata.rm.datasource.sql.struct.TableRecords,tableName: webset,rows: [java.util.ArrayList,[{class: io.seata.rm.datasource.sql.struct.Row,fields: [java.util.ArrayList,[{class: io.seata.rm.datasource.sql.struct.Field,name: id,keyType: PRIMARY_KEY,type: -5,value: [java.lang.Long,1]},{class: io.seata.rm.datasource.sql.struct.Field,name: url,keyType: NULL,type: 12,value: c.demo.net}]]}]]}}]]
}注册分支事务生成行锁在这次业务操作的本地事务提交前RM 会向 TC 注册分支事务并针对主键 id 为 1 的记录生成行锁。 以上所有操作均在同一个数据库事务内完成可以保证一阶段的操作的原子性。 本地事务提交将业务数据的更新和前面生成的 UNDO_LOG 一并提交。 上报执行结果将本地事务提交的结果上报给 TC。
二阶段提交
当所有的 RM 都将自己分支事务的提交结果上报给 TC 后TM 根据 TC 收集的各个分支事务的执行结果来决定向 TC 发起全局事务的提交或回滚。
若所有分支事务都执行成功TM 向 TC 发起全局事务的提交并批量删除各个 RM 保存的 UNDO_LOG 记录和行锁否则全局事务回滚。
二阶段回滚
若全局事务中的任何一个分支事务失败则 TM 向 TC 发起全局事务的回滚并开启一个本地事务执行如下操作。 查找 UNDO_LOG 记录通过 XID 和分支事务 IDBranch ID 查找所有的 UNDO_LOG 记录。 数据校验将 UNDO_LOG 中的后镜像数据afterImage与当前数据进行比较如果有不同则说明数据被当前全局事务之外的动作所修改需要人工对这些数据进行处理。 生成回滚语句根据 UNDO_LOG 中的前镜像beforeImage和业务 SQL 的相关信息生成回滚语句
update webset set url demo.net where id 1;还原数据执行回滚语句并将前镜像数据、后镜像数据以及行锁删除。 提交事务提交本地事务并把本地事务的执行结果即分支事务回滚的结果上报给 TC。
下载 Seata 服务器
使用浏览器访问“https://github.com/seata/seata/releases/tag/v1.4.2”在 Seata Server 下载页面分别下载“seata-server-1.4.2.zip”如下图。 图3Seata 服务器下载页面
解压 seata-server-1.4.2.zip其目录结构如下图。 图4Seata Server 目录结构
Seata Server 目录中包含以下子目录
bin用于存放 Seata Server 可执行命令。conf用于存放 Seata Server 的配置文件。lib用于存放 Seata Server 依赖的各种 Jar 包。logs用于存放 Seata Server 的日志。
Seata 配置中心
所谓“配置中心”就像是一个“大衣柜”内部存放着各种各样的配置文件我们可以根据自己的需要从其中获取指定的配置文件加载到对应的客户端中。
Seata 支持多种配置中心
nacosconsulapolloetcdzookeeperfile 读本地文件包含 conf、properties、yml 等配置文件
Seata 整合 Nacos 配置中心
对于 Seata 来说Nacos 是一种重要的配置中心实现。
Seata 整合 Nacos 配置中心的操作步骤十分简单大致步骤如下.
添加 Maven 依赖
我们需要将 nacos-client 的 Maven 依赖添加到项目的 pom.xml 文件中
dependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactIdversion最新版/version
/dependency
dependencygroupIdcom.alibaba.nacos/groupIdartifactIdnacos-client/artifactIdversion1.2.0及以上版本/version
/dependency在 Spring Cloud 项目中通常只需要在 pom.xml 中添加 spring-cloud-starter-alibaba-seata 依赖即可代码如下。
!--引入 seata 依赖--
dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId
/dependencySeata Server 配置
在 Seata Server 安装目录下的 config/registry.conf 中将配置方式config.type修改为 Nacos并对 Nacos 配置中心的相关信息进行配置示例配置如下。
config {# Seata 支持 file、nacos 、apollo、zk、consul、etcd3 等多种配置中心#配置方式修改为 nacostype nacosnacos {#修改为使用的 nacos 服务器地址serverAddr 127.0.0.1:1111#配置中心的命名空间namespace #配置中心所在的分组group SEATA_GROUP#Nacos 配置中心的用户名username nacos#Nacos 配置中心的密码password nacos}
}Seata Client 配置
我们可以在 Seata Client即微服务架构中的服务中通过 application.yml 等配置文件对 Nacos 配置中心进行配置示例代码如下。
seata:config:type: nacosnacos:server-addr: 127.0.0.1:1111 # Nacos 配置中心的地址group : SEATA_GROUP #分组namespace: username: nacos #Nacos 配置中心的用于名password: nacos #Nacos 配置中心的密码上传配置到 Nacos 配置中心
在完成了 Seata 服务端和客户端的相关配置后接下来我们就可以将配置上传的 Nacos 配置中心了操作步骤如下。
我们需要获取一个名为 config.txt 的文本文件该文件包含了 Seata 配置的所有参数明细。
我们可以通过 Seata Server 源码/script/config-center 目录中获取 config.txt然后根据自己需要修改其中的配置如下图。 图5config.txt
在 /script/config-center/nacos 目录中有以下 2 个 Seata 脚本
nacos-config.pypython 脚本。nacos-config.sh为 Linux 脚本我们可以在 Windows 下通过 Git 命令将 config.txt 中的 Seata 配置上传到 Nacos 配置中心。
在 seata-1.4.2\script\config-center\nacos 目录下右键鼠标选择 Git Bush Here并在弹出的 Git 命令窗口中执行以下命令将 config.txt 中的配置上传到 Nacos 配置中心。 sh nacos-config.sh -h 127.0.0.1 -p 1111 -g SEATA_GROUP -u nacos -w nacos
Git 命令各参数说明如下
-hNacos 的 host默认取值为 localhost-p端口号默认取值为 8848-gNacos 配置的分组默认取值为 SEATA_GROUP-uNacos 用户名-wNacos 密码
验证 Nacos 配置中心
在以上所有步骤完成后启动 Nacos Server登陆 Nacos 控制台查看配置列表结果如下图。 图6Seata Nacos 配置中心
Seata 注册中心
所谓“注册中心”可以说是微服务架构中的“通讯录”它记录了服务与服务地址的映射关系。
在分布式微服务架构中各个微服务都可以将自己注册到注册中心当其他服务需要调用某个服务时就可以从这里找到它的服务地址进行调用常见的服务注册中心有 Nacos、Eureka、zookeeper 等。
Seata 支持多种服务注册中心
eurekaconsulnacosetcdzookeepersofaredisfile 直连
Seata 通过这些服务注册中心我们可以获取 Seata Sever 的服务地址进行调用。
Seata 整合 Nacos 注册中心
对于 Seata 来说Nacos 是一种重要的注册中心实现。
Seata 整合 Nacos 注册中心的步骤十分简单步骤如下。
添加 Maven 依赖
将 nacos-client 的 Maven 依赖添加到项目的 pom.xml 文件中
dependencygroupIdio.seata/groupIdartifactIdseata-spring-boot-starter/artifactIdversion最新版/version
/dependency
dependencygroupIdcom.alibaba.nacos/groupIdartifactIdnacos-client/artifactIdversion1.2.0及以上版本/version
/dependency在 Spring Cloud 项目中通常只需要在 pom.xml 中添加 spring-cloud-starter-alibaba-seata 依赖即可代码如下。
!--引入 seata 依赖--
dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId
/dependencySeata Server 配置注册中心
在 Seata Server 安装目录下的 config/registry.conf 中将注册方式registry.type修改为 Nacos并对 Nacos 注册中心的相关信息进行配置示例配置如下。
registry {# Seata 支持 file 、nacos 、eureka、redis、zk、consul、etcd3、sofa 作为其注册中心# 将注册方式修改为 nacostype nacosnacos {application seata-server# 修改 nacos 注册中心的地址serverAddr 127.0.0.1:1111group SEATA_GROUPnamespace cluster defaultusername password }
}Seata Client 配置注册中心
我们可以在 Seata Client 的 application.yml 中对 Nacos 注册中心进行配置示例配置如下。
seata:registry:type: nacosnacos:application: seata-serverserver-addr: 127.0.0.1:1111 # Nacos 注册中心的地址group : SEATA_GROUP #分组namespace: username: nacos #Nacos 注册中心的用户名password: nacos # Nacos 注册中心的密码验证 Nacos 注册中心
在以上所有步骤完成后先启动 Nacos Server 再启动 Seata Server登录 Nacos 控制台查看服务列表结果如下图。 从图 9 可以看出seata-server 服务已经注册到了 Nacos 注册中心。
Seata 事务分组
事务分组是 Seata 提供的一种 TCSeata Server 服务查找机制。
Seata 通过事务分组获取 TC 服务流程如下 在应用中配置事务分组。
应用通过配置中心去查找配置service.vgroupMapping.{事务分组}该配置的值就是 TC 集群的名称。获得集群名称后应用通过一定的前后缀 集群名称去构造服务名。得到服务名后去注册中心去拉取服务列表获得后端真实的 TC 服务列表。
下面我们以 Nacos 服务注册中心为例介绍 Seata 事务的使用。
Seata Server 配置
在 Seata Server 的 config/registry.conf 中进行如下配置。
registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofatype nacos #使用 Nacos作为注册中心nacos {serverAddr 127.0.0.1:1111 # Nacos 注册中心的地址namespace # Nacos 命名空间id 为 Nacos 保留 public 空间控件用户勿配置 namespace publiccluster c.demo.net # seata-server在 Nacos 的集群名}
}
config {# file、nacos 、apollo、zk、consul、etcd3type nacos # 使用nacos作为配置中心nacos {serverAddr localhostnamespace cluster default}
}Seata Client 配置
Seata Client 中 application.yml 的配置如下。
spring:alibaba:seata:tx-service-group: service-order-group #事务分组名
seata:registry:type: nacos #从 Nacos 获取 TC 服务nacos:server-addr: 127.0.0.1:1111config:type: nacos #使用 Nacos 作为配置中心nacos:server-addr: 127.0.0.1:1111name在以上配置中我们通过 spring.cloud.alibaba.seata.tx-service-group 来配置 Seata 事务分组名其默认取值为服务名-fescar-service-group。
上传配置到 Nacos
将以下配置上传到 Nacos 配置中心。 service.vgroupMapping.service-order-groupc.demo.net
在以上配置中 service-order-group为事务分组的名称 c.demo.net为 TC 集群的名称。
获取事务分组 先启动 Nacos再启动 Seata Server最后再启动 Seata Client。 Seata Client 在启动时会从 application.yml 的配置中根据 spring.cloud.alibaba.seata.tx-service-group 获取事务分组的名称service-order-group。
获取 TC 集群名
使用事务分组名“service-order-group”拼接成“service.vgroupMapping.service-order-group”并从 Nacos 配置中心获取该配置的取值这个值就是 TC 集群的名称“c.demo.net”。
查找 TC 服务
根据 TC 集群名、Nacos 注册中心的地址server-addr以及命名空间namespace在 Nacos 注册中心找到真实的 TC 服务列表。
小结
通过事务分组获取服务名共需要以下 3 步 1.服务启动时从配置文件中获取服务分组的名称 2.从配置中心通过事务分组名获取 TC 集群名 3.根据 TC 群组名以及其他信息构建服务名获取真实的 TC 服务列表。
启动 Seata Server
建表 Seata Server 共有以下 3 种存储模式store.mode:
模式说明准备工作file文件存储模式默认存储模式该模式为单机模式全局事务的会话信息在内存中读写并持久化本地文件 root.data性能较高-db数据库存储模式该模式为高可用模式全局事务会话信息通过数据库共享性能较低。建数据库表redis缓存处处模式Seata Server 1.3 及以上版本支持该模式性能较高但存在事务信息丢失风险,配置 redis 持久化配置
在 db 模式下我们需要针对全局事务的会话信息创建以下 3 张数据库表。
全局事务表对应的表为global_table分支事务表对应的表为branch_table全局锁表对应的表为lock_table
在 MySQL 中创建一个名为 seata 的数据库实例并在该数据库内执行以下 SQL。
global_table 的建表 SQL 如下。
-- -------------------------------- The script used when storeMode is db --------------------------------
-- the table to store GlobalSession data
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_gmt_modified_status (gmt_modified, status),KEY idx_transaction_id (transaction_id)
) ENGINE InnoDBDEFAULT CHARSET utf8;branch_table 的建表 SQL 如下。
-- the table to store BranchSession data
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 InnoDBDEFAULT CHARSET utf8;lock_table 的建表 SQL 如下。
-- the table to store lock data
CREATE TABLE IF NOT EXISTS lock_table
(row_key VARCHAR(128) NOT NULL,xid VARCHAR(96),transaction_id BIGINT,branch_id BIGINT NOT NULL,resource_id VARCHAR(256),table_name VARCHAR(32),pk VARCHAR(36),gmt_create DATETIME,gmt_modified DATETIME,PRIMARY KEY (row_key),KEY idx_branch_id (branch_id)
) ENGINE InnoDBDEFAULT CHARSET utf8;修改 Seata Server 配置 在 seata-server-1.4.2/conf/ 目录下的 registry.conf 中将 Seata Server 的服务注册方式registry.type和配置方式config.type都修改为 Nacos修改内容如下。
registry {# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa# 将注册方式修改为 nacostype nacosnacos {application seata-server# 修改 nacos 的地址serverAddr 127.0.0.1:1111group SEATA_GROUPnamespace cluster defaultusername password }
}
config {# file、nacos 、apollo、zk、consul、etcd3#配置方式修改为 nacostype nacosnacos {#修改为使用的 nacos 服务器地址serverAddr 127.0.0.1:1111namespace group SEATA_GROUPusername nacospassword nacos#不使用 seataServer.properties 方式配置#dataId seataServer.properties}
}将 Seata 配置上传到 Nacos
1) 下载并解压 Seata Server 的源码 seata-1.4.2.zip然后修改 seata-1.4.2/script/config-center 目录下的 config.txt修改内容如下。xml
#将 Seata Server 的存储模式修改为 db
store.modedb
# 数据库驱动
store.db.driverClassNamecom.mysql.cj.jdbc.Driver
# 数据库 url
store.db.urljdbc:mysql://127.0.0.1:3306/seata?useSSLfalsecharacterEncodingUTF-8useUnicodetrueserverTimezoneUTC
# 数据库的用户名
store.db.userroot
# 数据库的密码
store.db.passwordroot
# 自定义事务分组
service.vgroupMapping.service-order-gro在 seata-1.4.2\script\config-center\nacos 目录下右键鼠标选择 Git Bush Here在弹出的 Git 命令窗口中执行以下命令将 config.txt 中的配置上传到 Nacos 配置中心。
sh nacos-config.sh -h 127.0.0.1 -p 1111 -g SEATA_GROUP -u nacos -w nacos当 Git 命令窗口出现以下执行日志时则说明配置上传成功。 Complete initialization parameters, total-count:87 , failure-count:0Init nacos config finished, please start seata-server.使用浏览器访问 Nacos 服务器主页查看 Nacos 配置列表如下图。 图8Seata 配置上传到 Nacos 注意在使用 Git 命令将配置上传到 Nacos 前应该先确保 Nacos 服务器已启动。 启动 Seata Server 双击 Seata Server 端 bin 目录下的启动脚本 seata-server.bat 启动 Seata Server。 图9Seata Server 启动脚本
Seata Server 启动日志如下。
16:52:48,549 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml]
16:52:48,549 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy]
16:52:48,550 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [file:/C:/Users/79330/Desktop/seata-server-1.4.2/conf/logback.xml]
16:52:48,551 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath.
16:52:48,551 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/C:/Users/79330/Desktop/seata-server-1.4.2/lib/seata-server-1.4.2.jar!/logback.xml]
……
SLF4J: A number (18) of logging calls during the initialization phase have been intercepted and are
SLF4J: now being replayed. These are subject to the filtering rules of the underlying logging system.
SLF4J: See also http://www.slf4j.org/codes.html#replay
16:52:49.003 INFO --- [ main] io.seata.config.FileConfiguration : The file name of the operation is registry
16:52:49.008 INFO --- [ main] io.seata.config.FileConfiguration : The configuration file used is C:\Users\79330\Desktop\seata-server-1.4.2\conf\registry.conf
16:52:51.063 INFO --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
16:52:51.981 INFO --- [ main] i.s.core.rpc.netty.NettyServerBootstrap : Server started, listen port: 8091业务系统集成 Seata
接下来我们就以电商系统为例来演示下业务系统是如何集成 Seata 的。
在电商系统中用户下单购买一件商品需要以下 3 个服务提供支持
Order订单服务创建和修改订单。Storage库存服务对指定的商品扣除仓库库存。Account账户服务 从用户帐户中扣除商品金额。
这三个微服务分别使用三个不同的数据库架构图如下所示。 图10电商系统架构图
当用户从这个电商网站购买了一件商品后其服务调用步骤如下
调用 Order 服务创建一条订单数据订单状态为“未完成”调用 Storage 服务扣减商品库存调用 Account 服务从用户账户中扣除商品金额调用 Order 服务将订单状态修改为“已完成”。
创建订单Order服务
在 MySQL 数据库中新建一个名为 seata-order 的数据库实例并通过以下 SQL 语句创建 2 张表t_order订单表和 undo_log回滚日志表。
-- ----------------------------
-- Table structure for t_order
-- ----------------------------
DROP TABLE IF EXISTS t_order;
CREATE TABLE t_order (id bigint NOT NULL AUTO_INCREMENT,user_id bigint DEFAULT NULL COMMENT 用户id,product_id bigint DEFAULT NULL COMMENT 产品id,count int DEFAULT NULL COMMENT 数量,money decimal(11,0) DEFAULT NULL COMMENT 金额,status int DEFAULT NULL COMMENT 订单状态0未完成1已完结,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT32 DEFAULT CHARSETutf8;-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS undo_log;
CREATE TABLE undo_log (branch_id bigint NOT NULL COMMENT branch transaction id,xid varchar(128) NOT NULL COMMENT global transaction id,context varchar(128) NOT NULL COMMENT undo_log context,such as serialization,rollback_info longblob NOT NULL COMMENT rollback info,log_status int NOT NULL COMMENT 0:normal status,1:defense status,log_created datetime(6) NOT NULL COMMENT create datetime,log_modified datetime(6) NOT NULL COMMENT modify datetime,UNIQUE KEY ux_undo_log (xid,branch_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 ;在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-seata-order-8005 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖内容如下。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-seata-order-8005/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-seata-order-8005/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.versionseata.version1.4.2/seata.version/propertiesdependencies!--nacos 服务注册中心--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactIdexclusionsexclusiongroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-ribbon/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--引入 OpenFeign 的依赖--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-loadbalancer/artifactId/dependency!--引入 seata 依赖--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-api/artifactIdversion${project.version}/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.19/version/dependencydependencygroupIdch.qos.logback/groupIdartifactIdlogback-core/artifactId/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.0/version/dependency!--添加 Spring Boot 的监控模块--!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!--SpringCloud ailibaba sentinel --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-sentinel/artifactId/dependency!--配置中心 nacos--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-config/artifactId/dependency/dependenciesbuildplugins!--mybatis自动生成代码插件--plugingroupIdorg.mybatis.generator/groupIdartifactIdmybatis-generator-maven-plugin/artifactIdversion1.4.0/versionconfigurationconfigurationFilesrc/main/resources/mybatis-generator/generatorConfig.xml/configurationFileverbosetrue/verbose!-- 是否覆盖true表示会替换生成的JAVA文件false则不覆盖 --overwritetrue/overwrite/configurationdependencies!--mysql驱动包--dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.19/version/dependencydependencygroupIdorg.mybatis.generator/groupIdartifactIdmybatis-generator-core/artifactIdversion1.4.0/version/dependency/dependencies/pluginplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build
/project在 spring-cloud-alibaba-seata-order-8005 的类路径/resources 目录下创建一个配置文件 bootstrap.yml配置内容如下。
spring:cloud:## Nacos认证信息nacos:config:username: nacospassword: nacoscontext-path: /nacosserver-addr: 127.0.0.1:1111 # 设置配置中心服务端地址namespace: # Nacos 配置中心的namespace。需要注意如果使用 public 的 namcespace 请不要填写这个值直接留空即可在 spring-cloud-alibaba-seata-order-8005 的类路径/resources 目录下创建一个配置文件 application.yml配置内容如下。 spring:application:name: spring-cloud-alibaba-seata-order-8005 #服务名#数据源配置datasource:driver-class-name: com.mysql.jdbc.Driver #数据库驱动name: defaultDataSourceurl: jdbc:mysql://localhost:3306/seata_order?serverTimezoneUTC #数据库连接地址username: root #数据库的用户名password: root #数据库密码cloud:nacos:discovery:server-addr: 127.0.0.1:1111 #nacos 服务器地址namespace: public #nacos 命名空间username:password:sentinel:transport:dashboard: 127.0.0.1:8080 #Sentinel 控制台地址port: 8719alibaba:seata:#自定义服务群组该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}default 中的 {my-service-group}相同tx-service-group: service-order-group
server:port: 8005 #端口
seata:application-id: ${spring.application.name}#自定义服务群组该值必须与 Nacos 配置中的 service.vgroupMapping.{my-service-group}default 中的 {my-service-group}相同tx-service-group: service-order-groupservice:grouplist:#Seata 服务器地址seata-server: 127.0.0.1:8091# Seata 的注册方式为 nacosregistry:type: nacosnacos:server-addr: 127.0.0.1:1111# Seata 的配置中心为 nacosconfig:type: nacosnacos:server-addr: 127.0.0.1:1111
feign:sentinel:enabled: true #开启 OpenFeign 功能
management:endpoints:web:exposure:include: *
###################################### MyBatis 配置 ######################################
mybatis:# 指定 mapper.xml 的位置mapper-locations: classpath:mybatis/mapper/*.xml#扫描实体类的位置,在此处指明扫描实体类的包在 mapper.xml 中就可以不写实体类的全路径名type-aliases-package: net.demo.c.entityconfiguration:#默认开启驼峰命名法可以不用设置该属性map-underscore-to-camel-case: true在 net.demo.c.entity 包下创建一个名为 Order 的实体类代码如下。
package net.demo.c.entity;
import java.math.BigDecimal;
public class Order {private Long id;private Long userId;private Long productId;private Integer count;private BigDecimal money;private Integer status;public Long getId() {return id;}public void setId(Long id) {this.id id;}public Long getUserId() {return userId;}public void setUserId(Long userId) {this.userId userId;}public Long getProductId() {return productId;}public void setProductId(Long productId) {this.productId productId;}public Integer getCount() {return count;}public void setCount(Integer count) {this.count count;}public BigDecimal getMoney() {return money;}public void setMoney(BigDecimal money) {this.money money;}public Integer getStatus() {return status;}public void setStatus(Integer status) {this.status status;}
}在 net.demo.c.mapper 包下创建一个名为 OrderMapper 的 Mapper 接口代码如下。
package net.demo.c.mapper;
import net.demo.c.entity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
Mapper
public interface OrderMapper {int deleteByPrimaryKey(Long id);int insert(Order record);int create(Order order);int insertSelective(Order record);//2 修改订单状态从零改为1void update(Param(userId) Long userId, Param(status) Integer status);Order selectByPrimaryKey(Long id);int updateByPrimaryKeySelective(Order record);int updateByPrimaryKey(Order record);
}在 /resouces/mybatis/mapper 目录下创建一个名为 OrderMapper.xml 的 MyBatis 映射文件代码如下。
?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacenet.demo.c.mapper.OrderMapperresultMap idBaseResultMap typenet.demo.c.entity.Orderid columnid jdbcTypeBIGINT propertyid/result columnuser_id jdbcTypeBIGINT propertyuserId/result columnproduct_id jdbcTypeBIGINT propertyproductId/result columncount jdbcTypeINTEGER propertycount/result columnmoney jdbcTypeDECIMAL propertymoney/result columnstatus jdbcTypeINTEGER propertystatus//resultMapsql idBase_Column_Listid, user_id, product_id, count, money, status/sqlselect idselectByPrimaryKey parameterTypejava.lang.Long resultMapBaseResultMapselectinclude refidBase_Column_List/from t_orderwhere id #{id,jdbcTypeBIGINT}/selectdelete iddeleteByPrimaryKey parameterTypejava.lang.Longdeletefrom t_orderwhere id #{id,jdbcTypeBIGINT}/deleteinsert idinsert parameterTypenet.demo.c.entity.Orderinsert into t_order (id, user_id, product_id,count, money, status)values (#{id,jdbcTypeBIGINT}, #{userId,jdbcTypeBIGINT}, #{productId,jdbcTypeBIGINT},#{count,jdbcTypeINTEGER}, #{money,jdbcTypeDECIMAL}, #{status,jdbcTypeINTEGER})/insertinsert idinsertSelective parameterTypenet.demo.c.entity.Orderinsert into t_ordertrim prefix( suffix) suffixOverrides,if testid ! nullid,/ifif testuserId ! nulluser_id,/ifif testproductId ! nullproduct_id,/ifif testcount ! nullcount,/ifif testmoney ! nullmoney,/ifif teststatus ! nullstatus,/if/trimtrim prefixvalues ( suffix) suffixOverrides,if testid ! null#{id,jdbcTypeBIGINT},/ifif testuserId ! null#{userId,jdbcTypeBIGINT},/ifif testproductId ! null#{productId,jdbcTypeBIGINT},/ifif testcount ! null#{count,jdbcTypeINTEGER},/ifif testmoney ! null#{money,jdbcTypeDECIMAL},/ifif teststatus ! null#{status,jdbcTypeINTEGER},/if/trim/insertinsert idcreate parameterTypenet.demo.c.entity.Orderinsert into t_order (user_id, product_id,count, money, status)values (#{userId,jdbcTypeBIGINT}, #{productId,jdbcTypeBIGINT},#{count,jdbcTypeINTEGER}, #{money,jdbcTypeDECIMAL}, #{status,jdbcTypeINTEGER})/insertupdate idupdateByPrimaryKeySelective parameterTypenet.demo.c.entity.Orderupdate t_ordersetif testuserId ! nulluser_id #{userId,jdbcTypeBIGINT},/ifif testproductId ! nullproduct_id #{productId,jdbcTypeBIGINT},/ifif testcount ! nullcount #{count,jdbcTypeINTEGER},/ifif testmoney ! nullmoney #{money,jdbcTypeDECIMAL},/ifif teststatus ! nullstatus #{status,jdbcTypeINTEGER},/if/setwhere id #{id,jdbcTypeBIGINT}/updateupdate idupdateByPrimaryKey parameterTypenet.demo.c.entity.Orderupdate t_orderset user_id #{userId,jdbcTypeBIGINT},product_id #{productId,jdbcTypeBIGINT},count #{count,jdbcTypeINTEGER},money #{money,jdbcTypeDECIMAL},status #{status,jdbcTypeINTEGER}where id #{id,jdbcTypeBIGINT}/updateupdate idupdateupdate t_orderset status 1where user_id #{userId}and status #{status};/update
/mapper在 net.demo.c.service 包下创建一个名为 OrderService 的 Service 接口代码如下。
package net.demo.c.service;
import net.demo.c.entity.Order;
public interface OrderService {/*** 创建订单数据* param order*/CommonResult create(Order order);
}在 net.demo.c.service 包下创建一个名为 StorageService 的接口代码如下。
package net.demo.c.service;
import net.demo.c.entity.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
FeignClient(value spring-cloud-alibaba-seata-storage-8006)
public interface StorageService {PostMapping(value /storage/decrease)CommonResult decrease(RequestParam(productId) Long productId, RequestParam(count) Integer count);
}在 net.demo.c.service 包下创建一个名为 AccountService 的接口代码如下。
package net.demo.c.service;
import net.demo.c.entity.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
FeignClient(value spring-cloud-alibaba-seata-account-8007)
public interface AccountService {PostMapping(value /account/decrease)CommonResult decrease(RequestParam(userId) Long userId, RequestParam(money) BigDecimal money);
}在 net.demo.c.service.impl 包下创建 OrderService 接口的实现类 OrderServiceImpl代码如下。
package net.demo.c.service.impl;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import net.demo.c.entity.Order;
import net.demo.c.mapper.OrderMapper;
import net.demo.c.service.AccountService;
import net.demo.c.service.OrderService;
import net.demo.c.service.StorageService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
Service
Slf4j
public class OrderServiceImpl implements OrderService {Resourceprivate OrderMapper orderMapper;Resourceprivate StorageService storageService;Resourceprivate AccountService accountService;/*** 创建订单-调用库存服务扣减库存-调用账户服务扣减账户余额-修改订单状态* 简单说下订单-扣库存-减余额-改订单状态*/OverrideGlobalTransactional(name fsp-create-order, rollbackFor Exception.class)public CommonResult create(Order order) {log.info(-----开始新建订单);//1 新建订单order.setUserId(new Long(1));order.setStatus(0);orderMapper.create(order);//2 扣减库存log.info(-----订单服务开始调用库存服务开始扣减库存);storageService.decrease(order.getProductId(), order.getCount());log.info(-----订单微服务开始调用库存扣减库存结束);//3 扣减账户log.info(-----订单服务开始调用账户服务开始从账户扣减商品金额);accountService.decrease(order.getUserId(), order.getMoney());log.info(-----订单微服务开始调用账户账户扣减商品金额结束);//4 修改订单状态从零到1,1代表已经完成log.info(-----修改订单状态开始);orderMapper.update(order.getUserId(), 0);log.info(-----修改订单状态结束);log.info(-----下订单结束了-------);return new CommonResult(200, 订单创建成功);}
}在 net.demo.c.controller 包下创建一个名为 OrderController 的 Controller代码如下。
package net.demo.c.controller;
import net.demo.c.entity.CommonResult;
import net.demo.c.entity.Order;
import net.demo.c.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
RestController
public class OrderController {Autowiredprivate OrderService orderService;GetMapping(/order/create/{productId}/{count}/{money})public CommonResult create(PathVariable(productId) Integer productId, PathVariable(count) Integer count, PathVariable(money) BigDecimal money) {Order order new Order();order.setProductId(Integer.valueOf(productId).longValue());order.setCount(count);order.setMoney(money);return orderService.create(order);}
}主启动类代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
EnableDiscoveryClient
EnableFeignClients
SpringBootApplication(scanBasePackages net.demo)
public class SpringCloudAlibabaSeataOrder8005Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaSeataOrder8005Application.class, args);}
}搭建库存Storage服务
在 MySQL 数据库中新建一个名为 seata-storage 的数据库实例并通过以下 SQL 语句创建 2 张表t_storage库存表和 undo_log回滚日志表。
-- ----------------------------
-- Table structure for t_storage
-- ----------------------------
DROP TABLE IF EXISTS t_storage;
CREATE TABLE t_storage (id bigint NOT NULL AUTO_INCREMENT,product_id bigint DEFAULT NULL COMMENT 产品id,total int DEFAULT NULL COMMENT 总库存,used int DEFAULT NULL COMMENT 已用库存,residue int DEFAULT NULL COMMENT 剩余库存,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT2 DEFAULT CHARSETutf8;-- ----------------------------
-- Records of t_storage
-- ----------------------------
INSERT INTO t_storage VALUES (1, 1, 100, 0, 100);-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS undo_log;
CREATE TABLE undo_log (branch_id bigint NOT NULL COMMENT branch transaction id,xid varchar(128) NOT NULL COMMENT global transaction id,context varchar(128) NOT NULL COMMENT undo_log context,such as serialization,rollback_info longblob NOT NULL COMMENT rollback info,log_status int NOT NULL COMMENT 0:normal status,1:defense status,log_created datetime(6) NOT NULL COMMENT create datetime,log_modified datetime(6) NOT NULL COMMENT modify datetime,UNIQUE KEY ux_undo_log (xid,branch_id)
) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENTAT transaction mode undo table;在主工程 spring-cloud-alibaba-demo 下创建一个名为 spring-cloud-alibaba-seata-storage-8006 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖内容如下。 ?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-seata-storage-8006/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-seata-storage-8006/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.versionseata.version1.4.2/seata.version/propertiesdependencies!--nacos 服务注册中心--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactIdexclusionsexclusiongroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-ribbon/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--引入 OpenFeign 的依赖--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-loadbalancer/artifactId/dependency!--seata 依赖--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-api/artifactIdversion${project.version}/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.19/version/dependencydependencygroupIdch.qos.logback/groupIdartifactIdlogback-core/artifactId/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.0/version/dependency!--添加 Spring Boot 的监控模块--!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!--SpringCloud ailibaba sentinel --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-sentinel/artifactId/dependency!--配置中心 nacos--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-config/artifactId/dependency/dependenciesbuildplugins!--mybatis自动生成代码插件--plugingroupIdorg.mybatis.generator/groupIdartifactIdmybatis-generator-maven-plugin/artifactIdversion1.4.0/versionconfigurationconfigurationFilesrc/main/resources/mybatis-generator/generatorConfig.xml/configurationFileverbosetrue/verbose!-- 是否覆盖true表示会替换生成的JAVA文件false则不覆盖 --overwritetrue/overwrite/configurationdependencies!--mysql驱动包--dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.19/version/dependencydependencygroupIdorg.mybatis.generator/groupIdartifactIdmybatis-generator-core/artifactIdversion1.4.0/version/dependency/dependencies/pluginplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build
/project在 spring-cloud-alibaba-seata-storage-8006 的类路径/resources 目录下创建一个配置文件 bootstrap.yml配置内容如下。
spring:cloud:## Nacos认证信息nacos:config:username: nacospassword: nacoscontext-path: /nacosserver-addr: 127.0.0.1:1111 # 设置配置中心服务端地址namespace: # Nacos 配置中心的namespace。需要注意如果使用 public 的 namcespace 请不要填写这个值直接留空即可在 spring-cloud-alibaba-seata-storage-8006 的类路径/resources 目录下创建一个配置文件 application.yml配置内容如下。
spring:application:name: spring-cloud-alibaba-seata-storage-8006datasource:driver-class-name: com.mysql.jdbc.Drivername: defaultDataSourceurl: jdbc:mysql://localhost:3306/seata_storage?serverTimezoneUTCusername: rootpassword: rootcloud:nacos:discovery:server-addr: 127.0.0.1:1111namespace: publicusername:password:sentinel:transport:dashboard: 127.0.0.1:8080port: 8719alibaba:seata:tx-service-group: service-storage-group
server:port: 8006
seata:application-id: ${spring.application.name}tx-service-group: service-storage-groupservice:grouplist:seata-server: 127.0.0.1:8091registry:type: nacosnacos:server-addr: 127.0.0.1:1111config:type: nacosnacos:server-addr: 127.0.0.1:1111
feign:sentinel:enabled: true
management:endpoints:web:exposure:include: *
###################################### MyBatis 配置 ######################################
mybatis:# 指定 mapper.xml 的位置mapper-locations: classpath:mybatis/mapper/*.xml#扫描实体类的位置,在此处指明扫描实体类的包在 mapper.xml 中就可以不写实体类的全路径名type-aliases-package: net.demo.c.entityconfiguration:#默认开启驼峰命名法可以不用设置该属性map-underscore-to-camel-case: true在 net.demo.c.entity 包下创建一个名为 Storage 的实体类代码如下。
package net.demo.c.entity;
public class Storage {private Long id;private Long productId;private Integer total;private Integer used;private Integer residue;public Long getId() {return id;}public void setId(Long id) {this.id id;}public Long getProductId() {return productId;}public void setProductId(Long productId) {this.productId productId;}public Integer getTotal() {return total;}public void setTotal(Integer total) {this.total total;}public Integer getUsed() {return used;}public void setUsed(Integer used) {this.used used;}public Integer getResidue() {return residue;}public void setResidue(Integer residue) {this.residue residue;}
}在 net.demo.c.mapper 包下创建一个名为 StorageMapper 的接口代码如下。
package net.demo.c.mapper;
import net.demo.c.entity.Storage;
import org.apache.ibatis.annotations.Mapper;
Mapper
public interface StorageMapper {Storage selectByProductId(Long productId);int decrease(Storage record);
}在 /resouces/mybatis/mapper 目录下创建一个名为 StorageMapper.xml 的 MyBatis 映射文件代码如下。
?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacenet.demo.c.mapper.StorageMapperresultMap idBaseResultMap typenet.demo.c.entity.Storageid columnid jdbcTypeBIGINT propertyid/result columnproduct_id jdbcTypeBIGINT propertyproductId/result columntotal jdbcTypeINTEGER propertytotal/result columnused jdbcTypeINTEGER propertyused/result columnresidue jdbcTypeINTEGER propertyresidue//resultMapsql idBase_Column_Listid, product_id, total, used, residue/sqlupdate iddecrease parameterTypenet.demo.c.entity.Storageupdate t_storagesetif testtotal ! nulltotal #{total,jdbcTypeINTEGER},/ifif testused ! nullused #{used,jdbcTypeINTEGER},/ifif testresidue ! nullresidue #{residue,jdbcTypeINTEGER},/if/setwhere product_id #{productId,jdbcTypeBIGINT}/updateselect idselectByProductId parameterTypejava.lang.Long resultMapBaseResultMapselectinclude refidBase_Column_List/from t_storagewhere product_id #{productId,jdbcTypeBIGINT}/select
/mapper在 net.demo.c.service 包下创建一个名为 StorageService 的 Service 接口代码如下。
package net.demo.c.service;
public interface StorageService {int decrease(Long productId, Integer count);
}在 net.demo.c.service.impl 包下创建 StorageService 的实现类 StorageServiceImpl代码如下。
package net.demo.c.service.impl;
import lombok.extern.slf4j.Slf4j;
import net.demo.c.entity.Storage;
import net.demo.c.mapper.StorageMapper;
import net.demo.c.service.StorageService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
Service
Slf4j
public class StorageServiceImpl implements StorageService {ResourceStorageMapper storageMapper;Overridepublic int decrease(Long productId, Integer count) {log.info(-------storage-service中扣减库存开始);log.info(-------storage-service 开始查询商品是否存在);Storage storage storageMapper.selectByProductId(productId);if (storage ! null storage.getResidue().intValue() count.intValue()) {Storage storage2 new Storage();storage2.setProductId(productId);storage.setUsed(storage.getUsed() count);storage.setResidue(storage.getTotal().intValue() - storage.getUsed());int decrease storageMapper.decrease(storage);log.info(-------storage-service 扣减库存成功);return decrease;} else {log.info(-------storage-service 库存不足开始回滚);throw new RuntimeException(库存不足扣减库存失败);}}
}在 net.demo.c.controller 包下创建一个名为 StorageController 的 Controller 类代码如下。
package net.demo.c.controller;
import lombok.extern.slf4j.Slf4j;
import net.demo.c.entity.CommonResult;
import net.demo.c.service.StorageService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
RestController
Slf4j
public class StorageController {Resourceprivate StorageService storageService;Value(${server.port})private String serverPort;PostMapping(value /storage/decrease)CommonResult decrease(RequestParam(productId) Long productId, RequestParam(count) Integer count) {int decrease storageService.decrease(productId, count);CommonResult result;if (decrease 0) {result new CommonResult(200, from mysql,serverPort: serverPort, decrease);} else {result new CommonResult(505, from mysql,serverPort: serverPort, 库存扣减失败);}return result;}
}主启动类的代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
EnableDiscoveryClient
EnableFeignClients
SpringBootApplication(scanBasePackages net.demo)
public class SpringCloudAlibabaSeataStorage8006Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaSeataStorage8006Application.class, args);}
}搭建账户Account服务
在 MySQL 数据库中新建一个名为 seata-account 的数据库实例并通过以下 SQL 语句创建 2 张表t_account账户表和 undo_log回滚日志表。
DROP TABLE IF EXISTS t_account;
CREATE TABLE t_account (id bigint NOT NULL AUTO_INCREMENT COMMENT id,user_id bigint DEFAULT NULL COMMENT 用户id,total decimal(10,0) DEFAULT NULL COMMENT 总额度,used decimal(10,0) DEFAULT NULL COMMENT 已用余额,residue decimal(10,0) DEFAULT 0 COMMENT 剩余可用额度,PRIMARY KEY (id)
) ENGINEInnoDB AUTO_INCREMENT2 DEFAULT CHARSETutf8;-- ----------------------------
-- Records of t_account
-- ----------------------------
INSERT INTO t_account VALUES (1, 1, 1000, 0, 1000);-- ----------------------------
-- Table structure for undo_log
-- ----------------------------
DROP TABLE IF EXISTS undo_log;
CREATE TABLE undo_log (branch_id bigint NOT NULL COMMENT branch transaction id,xid varchar(128) NOT NULL COMMENT global transaction id,context varchar(128) NOT NULL COMMENT undo_log context,such as serialization,rollback_info longblob NOT NULL COMMENT rollback info,log_status int NOT NULL COMMENT 0:normal status,1:defense status,log_created datetime(6) NOT NULL COMMENT create datetime,log_modified datetime(6) NOT NULL COMMENT modify datetime,UNIQUE KEY ux_undo_log (xid,branch_id)
) ENGINEInnoDB DEFAULT CHARSETutf8;在主启动类 spring-cloud-alibaba-seata-account-8007 下创建一个名为 spring-cloud-alibaba-seata-account-8007 的 Spring Boot 模块并在其 pom.xml 中添加以下依赖代码如下。
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdnet.demo.c/groupIdversion1.0-SNAPSHOT/versionartifactIdspring-cloud-alibaba-demo/artifactId/parentgroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-seata-account-8007/artifactIdversion0.0.1-SNAPSHOT/versionnamespring-cloud-alibaba-seata-account-8007/namedescriptionDemo project for Spring Boot/descriptionpropertiesjava.version1.8/java.versionseata.version1.4.2/seata.version/propertiesdependencies!--nacos 服务注册中心--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-discovery/artifactIdexclusionsexclusiongroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-netflix-ribbon/artifactId/exclusion/exclusions/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--引入 OpenFeign 的依赖--dependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-starter-openfeign/artifactId/dependencydependencygroupIdorg.springframework.cloud/groupIdartifactIdspring-cloud-loadbalancer/artifactId/dependency!--seata--dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-seata/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-devtools/artifactIdscoperuntime/scopeoptionaltrue/optional/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope/dependencydependencygroupIdnet.demo.c/groupIdartifactIdspring-cloud-alibaba-api/artifactIdversion${project.version}/version/dependencydependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.19/version/dependencydependencygroupIdch.qos.logback/groupIdartifactIdlogback-core/artifactId/dependencydependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion2.2.0/version/dependency!--添加 Spring Boot 的监控模块--!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-actuator --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency!--SpringCloud ailibaba sentinel --dependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-sentinel/artifactId/dependencydependencygroupIdcom.alibaba.cloud/groupIdartifactIdspring-cloud-starter-alibaba-nacos-config/artifactId/dependency/dependenciesbuildplugins!--mybatis自动生成代码插件--plugingroupIdorg.mybatis.generator/groupIdartifactIdmybatis-generator-maven-plugin/artifactIdversion1.4.0/versionconfigurationconfigurationFilesrc/main/resources/mybatis-generator/generatorConfig.xml/configurationFileverbosetrue/verbose!-- 是否覆盖true表示会替换生成的JAVA文件false则不覆盖 --overwritetrue/overwrite/configurationdependencies!--mysql驱动包--dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.19/version/dependencydependencygroupIdorg.mybatis.generator/groupIdartifactIdmybatis-generator-core/artifactIdversion1.4.0/version/dependency/dependencies/pluginplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins/build
/project 在 spring-cloud-alibaba-seata-account-8007 的类路径/resources 目录下创建一个配置文件 bootstrap.yml配置内容如下。
spring:cloud:## Nacos认证信息nacos:config:username: nacospassword: nacoscontext-path: /nacosserver-addr: 127.0.0.1:1111 # 设置配置中心服务端地址namespace: # Nacos 配置中心的namespace。需要注意如果使用 public 的 namcespace 请不要填写这个值直接留空即可在 spring-cloud-alibaba-seata-account-8007 的类路径/resources 目录下创建一个配置文件 application.yml配置内容如下。
spring:application:name: spring-cloud-alibaba-seata-account-8007datasource:driver-class-name: com.mysql.cj.jdbc.Drivername: defaultDataSourceurl: jdbc:mysql://localhost:3306/seata_account?serverTimezoneUTCusername: rootpassword: rootcloud:nacos:discovery:server-addr: 127.0.0.1:1111namespace: publicusername:password:sentinel:transport:dashboard: 127.0.0.1:8080port: 8719alibaba:seata:tx-service-group: service-account-group
server:port: 8007
seata:application-id: ${spring.application.name}tx-service-group: service-account-groupservice:grouplist:seata-server: 127.0.0.1:8091registry:type: nacosnacos:server-addr: 127.0.0.1:1111config:type: nacosnacos:server-addr: 127.0.0.1:1111
feign:sentinel:enabled: true
management:endpoints:web:exposure:include: *
###################################### MyBatis 配置 ######################################
mybatis:# 指定 mapper.xml 的位置mapper-locations: classpath:mybatis/mapper/*.xml#扫描实体类的位置,在此处指明扫描实体类的包在 mapper.xml 中就可以不写实体类的全路径名type-aliases-package: net.demo.c.entityconfiguration:#默认开启驼峰命名法可以不用设置该属性map-underscore-to-camel-case: true在 net.demo.c.entity 包下创建一个名为 Account 的实体类代码如下。
package net.demo.c.entity;
import java.math.BigDecimal;
public class Account {private Long id;private Long userId;private BigDecimal total;private BigDecimal used;private BigDecimal residue;public Long getId() {return id;}public void setId(Long id) {this.id id;}public Long getUserId() {return userId;}public void setUserId(Long userId) {this.userId userId;}public BigDecimal getTotal() {return total;}public void setTotal(BigDecimal total) {this.total total;}public BigDecimal getUsed() {return used;}public void setUsed(BigDecimal used) {this.used used;}public BigDecimal getResidue() {return residue;}public void setResidue(BigDecimal residue) {this.residue residue;}
}在 net.demo.c.mapper 包下创建一个名为 AccountMapper 的接口代码如下。
package net.demo.c.mapper;
import net.demo.c.entity.Account;
import org.apache.ibatis.annotations.Mapper;
import java.math.BigDecimal;
Mapper
public interface AccountMapper {Account selectByUserId(Long userId);int decrease(Long userId, BigDecimal money);
}在 /resouces/mybatis/mapper 目录下创建一个名为 AccountMapper.xml 的 MyBatis 映射文件代码如下。
?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacenet.demo.c.mapper.AccountMapperresultMap idBaseResultMap typenet.demo.c.entity.Accountid columnid jdbcTypeBIGINT propertyid/result columnuser_id jdbcTypeBIGINT propertyuserId/result columntotal jdbcTypeDECIMAL propertytotal/result columnused jdbcTypeDECIMAL propertyused/result columnresidue jdbcTypeDECIMAL propertyresidue//resultMapsql idBase_Column_Listid, user_id, total, used, residue/sqlselect idselectByUserId resultTypenet.demo.c.entity.Accountselectinclude refidBase_Column_List/from t_accountwhere user_id #{userId,jdbcTypeBIGINT}/selectupdate iddecreaseUPDATE t_accountSET residue residue - #{money},used used #{money}WHERE user_id #{userId};/update
/mapper在 net.demo.c.service 包下创建一个名为 AccountService 的接口代码如下。
package net.demo.c.service;
import org.springframework.web.bind.annotation.RequestParam;
import java.math.BigDecimal;
public interface AccountService {/*** 扣减账户余额** param userId 用户id* param money 金额*/int decrease(RequestParam(userId) Long userId, RequestParam(money) BigDecimal money);
}在 net.demo.c.service.impl 包下创建 AccountService 的实现类 AccountServiceImpl代码如下。
package net.demo.c.service.impl;
import lombok.extern.slf4j.Slf4j;
import net.demo.c.entity.Account;
import net.demo.c.mapper.AccountMapper;
import net.demo.c.service.AccountService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
Service
Slf4j
public class AccountServiceImpl implements AccountService {ResourceAccountMapper accountMapper;Overridepublic int decrease(Long userId, BigDecimal money) {log.info(-------account-service 开始查询账户余额);Account account accountMapper.selectByUserId(userId);log.info(-------account-service 账户余额查询完成 account);if (account ! null account.getResidue().intValue() money.intValue()) {log.info(-------account-service 开始从账户余额中扣钱);int decrease accountMapper.decrease(userId, money);log.info(-------account-service 从账户余额中扣钱完成);return decrease;} else {log.info(账户余额不足开始回滚);throw new RuntimeException(账户余额不足);}}
}主启动类代码如下。
package net.demo.c;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
EnableDiscoveryClient
EnableFeignClients
SpringBootApplication(scanBasePackages net.demo)
public class SpringCloudAlibabaSeataAccount8007Application {public static void main(String[] args) {SpringApplication.run(SpringCloudAlibabaSeataAccount8007Application.class, args);}
}依次启动 Nacos Server集群和 Seata Server集群最后启动 spring-cloud-alibaba-seata-order-8005当控制台出现以下日志时说明该服务已经成功连接上 Seata ServerTC。
2021-11-25 15:16:27.389 INFO 19564 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Start completed.
2021-11-25 15:16:27.553 INFO 19564 --- [ restartedMain] i.s.c.r.netty.NettyClientChannelManager : will connect to 172.30.194.1:8091
2021-11-25 15:16:27.553 INFO 19564 --- [ restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient : RM will register :jdbc:mysql://localhost:3306/seata_order
2021-11-25 15:16:27.557 INFO 19564 --- [ restartedMain] i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:RMROLE,address:172.30.194.1:8091,msg: RegisterRMRequest{resourceIdsjdbc:mysql://localhost:3306/seata_order, applicationIdspring-cloud-alibaba-seata-order-8005, transactionServiceGroupservice-order-group}
2021-11-25 15:16:28.699 INFO 19564 --- [ restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient : register RM success. client version:1.3.0, server version:1.4.2,channel:[id: 0xc6da1cb4, L:/172.30.194.1:49945 - R:/172.30.194.1:8091]
2021-11-25 15:16:28.707 INFO 19564 --- [ restartedMain] i.s.core.rpc.netty.NettyPoolableFactory : register success, cost 78 ms, version:1.4.2,role:RMROLE,channel:[id: 0xc6da1cb4, L:/172.30.194.1:49945 - R:/172.30.194.1:8091]启动 spring-cloud-alibaba-seata-storage-8006当控制台出现以下日志时说明该服务已经成功连接上 Seata ServerTC。
2021-11-25 15:16:28.621 INFO 14772 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Start completed.
2021-11-25 15:16:28.969 INFO 14772 --- [ restartedMain] i.s.c.r.netty.NettyClientChannelManager : will connect to 172.30.194.1:8091
2021-11-25 15:16:28.970 INFO 14772 --- [ restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient : RM will register :jdbc:mysql://localhost:3306/seata_storage
2021-11-25 15:16:28.974 INFO 14772 --- [ restartedMain] i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:RMROLE,address:172.30.194.1:8091,msg: RegisterRMRequest{resourceIdsjdbc:mysql://localhost:3306/seata_storage, applicationIdspring-cloud-alibaba-seata-storage-8006, transactionServiceGroupservice-storage-group}
2021-11-25 15:16:30.171 INFO 14772 --- [ restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient : register RM success. client version:1.3.0, server version:1.4.2,channel:[id: 0x7311ae2a, L:/172.30.194.1:52026 - R:/172.30.194.1:8091]
2021-11-25 15:16:30.182 INFO 14772 --- [ restartedMain] i.s.core.rpc.netty.NettyPoolableFactory : register success, cost 174 ms, version:1.4.2,role:RMROLE,channel:[id: 0x7311ae2a, L:/172.30.194.1:52026 - R:/172.30.194.1:8091]启动 spring-cloud-alibaba-seata-account-8007当控制台出现以下日志时说明该服务已经成功连接上 Seata ServerTC。
2021-11-25 15:16:29.914 INFO 8616 --- [ restartedMain] com.zaxxer.hikari.HikariDataSource : defaultDataSource - Start completed.
2021-11-25 15:16:30.253 INFO 8616 --- [ restartedMain] i.s.c.r.netty.NettyClientChannelManager : will connect to 172.30.194.1:8091
2021-11-25 15:16:30.253 INFO 8616 --- [ restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient : RM will register :jdbc:mysql://localhost:3306/seata_account
2021-11-25 15:16:30.257 INFO 8616 --- [ restartedMain] i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:RMROLE,address:172.30.194.1:8091,msg: RegisterRMRequest{resourceIdsjdbc:mysql://localhost:3306/seata_account, applicationIdspring-cloud-alibaba-seata-account-8007, transactionServiceGroupservice-account-group}
2021-11-25 15:16:31.930 INFO 8616 --- [ restartedMain] i.s.c.rpc.netty.RmNettyRemotingClient : register RM success. client version:1.3.0, server version:1.4.2,channel:[id: 0xa57ead6d, L:/172.30.194.1:52057 - R:/172.30.194.1:8091]
2021-11-25 15:16:31.941 INFO 8616 --- [ restartedMain] i.s.core.rpc.netty.NettyPoolableFactory : register success, cost 114 ms, version:1.4.2,role:RMROLE,channel:[id: 0xa57ead6d, L:/172.30.194.1:52057 - R:/172.30.194.1:8091]使用浏览器访问“http://localhost:8005/order/createByAnnotation/1/2/20”结果如下。
{code:200,message:订单创建成功,data:null}执行以下 SQL 语句查询 seata_order 数据库中的 t_order 表。
SELECT * FROM seata_order.t_order;结果如下。
iduser_idproduct_idcountmoneystatus112201
从上表可以看出已经创建一条订单数据且订单状态status已修改为“已完成”。
执行以下 SQL 语句查询 seata_storage 数据库中的 t_storage 表。
SELECT * FROM seata_storage.t_storage;结果如下。
idproduct_idtotal usedresidue11100298
从上表可以看出商品库存已经扣减 2 件仓库中商品储量从 100 件减少到了 98 件。
执行以下 SQL 语句查询 seata_account 数据库中的 t_account 表
SELECT * FROM seata_account.t_account;结果如下。
iduser_idtotalusedresidue11100020980
从上表可以看出账户余额已经扣减金额从 1000 元减少到了 980 元。
使用浏览器访问“http://localhost:8005/order/createByAnnotation/1/10/1000”结果如下。
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.Thu Nov 25 15:27:03 CST 2021
There was an unexpected error (typeInternal Server Error, status500).
[500] during [POST] to [http://spring-cloud-alibaba-seata-account-8007/account/decrease?userId1money1000] [AccountService#decrease(Long,BigDecimal)]: [{timestamp:2021-11-25T07:27:03.51200:00,status:500,error:Internal Server Error,trace:java.lang.RuntimeException: 账户余额不足\r\n\tat net.demo.c.service.impl.AccountServiceImpl.decrease(... (5673 bytes)]
feign.FeignException$InternalServerError: [500] during [POST] to [http://spring-cloud-alibaba-seata-account-8007/account/decrease?userId1money1000] [AccountService#decrease(Long,BigDecimal)]: [{timestamp:2021-11-25T07:27:03.51200:00,status:500,error:Internal Server Error,trace:java.lang.RuntimeException: 账户余额不足\r\n\tat net.demo.c.service.impl.AccountServiceImpl.decrease(... (5673 bytes)]注在本次请求中用户购买 10 件商品商品 ID 为 1商品总价为 1000 元此时用户账户余额 为 980 元因此账户服务会抛出“账户余额不足”的运行时异常。
再次查询 t_order、t_storage 和 t_account 表结果如下。 从上图可以看出
t_order订单表订单服务生成了一条订单数据id 为 2但订单状态status为未完成0t_storage库存表商品库存已扣减t_account账户表账户金额不足账户Account服务出现异常进而导致账户金额并未扣减。
GlobalTransactional 注解
在分布式微服务架构中我们可以使用 Seata 提供的 GlobalTransactional 注解实现分布式事务的开启、管理和控制。
当调用 GlobalTransaction 注解的方法时TM 会先向 TC 注册全局事务TC 生成一个全局唯一的 XID返回给 TM。
GlobalTransactional 注解既可以在类上使用也可以在类方法上使用该注解的使用位置决定了全局事务的范围具体关系如下
在类中某个方法使用时全局事务的范围就是该方法以及它所涉及的所有服务。在类上使用时全局事务的范围就是这个类中的所有方法以及这些方法涉及的服务。
接下来我们就使用 GlobalTransactional 注解对业务系统进行改造步骤如下。
在 spring-cloud-alibaba-seata-order-8005 的 net.biacheng.c 包下的 OrderController 中添加一个名为 createByAnnotation 的方法代码如下。
/*** 使用 GlobalTransactional 注解对分布式事务进行管理* param productId* param count* param money* return*/
GetMapping(/order/createByAnnotation/{productId}/{count}/{money})
GlobalTransactional(name c-demo-net-create-order, rollbackFor Exception.class)
public CommonResult createByAnnotation(PathVariable(productId) Integer productId, PathVariable(count) Integer count, PathVariable(money) BigDecimal money) {Order order new Order();order.setProductId(Integer.valueOf(productId).longValue());order.setCount(count);order.setMoney(money);return orderService.create(order);
}从以上代码可以看出添加的 createByAnnotation() 方法与 create() 方法无论是参数还是代码逻辑都一摸一样唯一的不同就是前者标注了 GlobalTransactional 注解。 将数据恢复到浏览器访问“http://localhost:8005/order/createByAnnotation/1/2/20”之后。 重启订单Order服务、库存Storage服务和账户Account服务并使用浏览器访问“http://localhost:8005/order/createByAnnotation/1/10/1000”结果如下。
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.Thu Nov 25 15:27:03 CST 2021
There was an unexpected error (typeInternal Server Error, status500).
[500] during [POST] to [http://spring-cloud-alibaba-seata-account-8007/account/decrease?userId1money1000] [AccountService#decrease(Long,BigDecimal)]: [{timestamp:2021-11-25T07:27:03.51200:00,status:500,error:Internal Server Error,trace:java.lang.RuntimeException: 账户余额不足\r\n\tat net.demo.c.service.impl.AccountServiceImpl.decrease(... (5673 bytes)]
feign.FeignException$InternalServerError: [500] during [POST] to [http://spring-cloud-alibaba-seata-account-8007/account/decrease?userId1money1000] [AccountService#decrease(Long,BigDecimal)]: [{timestamp:2021-11-25T07:27:03.51200:00,status:500,error:Internal Server Error,trace:java.lang.RuntimeException: 账户余额不足\r\n\tat net.demo.c.service.impl.AccountServiceImpl.decrease(... (5673 bytes)]在本次请求中用户购买商品的总价为 1000 元但用户账户余额只有 980 元因此账户服务会抛出运行时异常异常信息为“账户余额不足”。
spring-cloud-alibaba-seata-order-8005 控制台输出如下标红部分为回滚日志。
2021-11-25 15:26:57.586 INFO 19564 --- [nio-8005-exec-1] n.b.c.service.impl.OrderServiceImpl : -----开始新建订单
2021-11-25 15:26:58.276 INFO 19564 --- [nio-8005-exec-1] n.b.c.service.impl.OrderServiceImpl : -----订单服务开始调用库存服务开始扣减库存
2021-11-25 15:26:58.413 WARN 19564 --- [nio-8005-exec-1] c.l.c.ServiceInstanceListSupplierBuilder : LoadBalancerCacheManager not available, returning delegate without caching.
2021-11-25 15:27:00.705 INFO 19564 --- [nio-8005-exec-1] n.b.c.service.impl.OrderServiceImpl : -----订单微服务开始调用库存扣减库存结束
2021-11-25 15:27:00.705 INFO 19564 --- [nio-8005-exec-1] n.b.c.service.impl.OrderServiceImpl : -----订单服务开始调用账户服务开始从账户扣减商品金额
2021-11-25 15:27:00.723 WARN 19564 --- [nio-8005-exec-1] c.l.c.ServiceInstanceListSupplierBuilder : LoadBalancerCacheManager not available, returning delegate without caching.
2021-11-25 15:27:03.665 INFO 19564 --- [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid172.30.194.1:8091:2702361983450404762,branchId2702361983450404764,branchTypeAT,resourceIdjdbc:mysql://localhost:3306/seata_order,applicationDatanull
2021-11-25 15:27:03.670 INFO 19564 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 172.30.194.1:8091:2702361983450404762 2702361983450404764 jdbc:mysql://localhost:3306/seata_order
2021-11-25 15:27:03.738 INFO 19564 --- [h_RMROLE_1_1_16] i.s.r.d.undo.AbstractUndoLogManager : xid 172.30.194.1:8091:2702361983450404762 branch 2702361983450404764, undo_log deleted with GlobalFinished
2021-11-25 15:27:03.742 INFO 19564 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
2021-11-25 15:27:03.817 INFO 19564 --- [nio-8005-exec-1] i.seata.tm.api.DefaultGlobalTransaction : [172.30.194.1:8091:2702361983450404762] rollback status: Rollbacked
2021-11-25 15:27:03.853 ERROR 19564 --- [nio-8005-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.FeignException$InternalServerError: [500] during [POST] to [http://spring-cloud-alibaba-seata-account-8007/account/decrease?userId1money1000] [AccountService#decrease(Long,BigDecimal)]: [{timestamp:2021-11-25T07:27:03.51200:00,status:500,error:Internal Server Error,trace:java.lang.RuntimeException: 账户余额不足\r\n\tat net.demo.c.service.impl.AccountServiceImpl.decrease(... (5673 bytes)]] with root causespring-cloud-alibaba-seata-storage-8006 控制台输出如下。
2021-11-25 15:26:59.315 INFO 14772 --- [nio-8006-exec-1] n.b.c.service.impl.StorageServiceImpl : -------storage-service中扣减库存开始
2021-11-25 15:26:59.316 INFO 14772 --- [nio-8006-exec-1] n.b.c.service.impl.StorageServiceImpl : -------storage-service 开始查询商品是否存在
2021-11-25 15:27:00.652 INFO 14772 --- [nio-8006-exec-1] n.b.c.service.impl.StorageServiceImpl : -------storage-service 扣减库存成功
2021-11-25 15:27:03.568 INFO 14772 --- [h_RMROLE_1_1_16] i.s.c.r.p.c.RmBranchRollbackProcessor : rm handle branch rollback process:xid172.30.194.1:8091:2702361983450404762,branchId2702361983450404769,branchTypeAT,resourceIdjdbc:mysql://localhost:3306/seata_storage,applicationDatanull
2021-11-25 15:27:03.572 INFO 14772 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch Rollbacking: 172.30.194.1:8091:2702361983450404762 2702361983450404769 jdbc:mysql://localhost:3306/seata_storage
2021-11-25 15:27:03.631 INFO 14772 --- [h_RMROLE_1_1_16] i.s.r.d.undo.AbstractUndoLogManager : xid 172.30.194.1:8091:2702361983450404762 branch 2702361983450404769, undo_log deleted with GlobalFinished
2021-11-25 15:27:03.635 INFO 14772 --- [h_RMROLE_1_1_16] io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbackedspring-cloud-alibaba-seata-account-8007 控制台输出如下。
2021-11-25 15:27:03.366 INFO 8616 --- [nio-8007-exec-1] n.b.c.service.impl.AccountServiceImpl : -------account-service 开始查询账户余额
2021-11-25 15:27:03.484 INFO 8616 --- [nio-8007-exec-1] n.b.c.service.impl.AccountServiceImpl : -------account-service 账户余额查询完成net.demo.c.entity.Account2a95537f
2021-11-25 15:27:03.485 INFO 8616 --- [nio-8007-exec-1] n.b.c.service.impl.AccountServiceImpl : 账户余额不足开始回滚
2021-11-25 15:27:03.499 ERROR 8616 --- [nio-8007-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: 账户余额不足] with root cause
java.lang.RuntimeException: 账户余额不足到 MySQL 数据库中再次查询 t_order、t_storage 和 t_account 表结果如下。 图12Seata 事务回滚
从图 12 可以看出这次并没有出现分布式事务造成的数据不一致问题。