Spring Cloud Alibaba系列

第一节 项目简介

  Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。

  依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。

 

 

 

 

 

第二章 依赖管理

Spring Cloud Alibaba BOM 包含了它所使用的所有依赖的版本。
 

一、版本管理规范

 
项目的版本号格式为 x.x.x 的形式,其中 x 的数值类型为数字,从 0 开始取值,且不
限于 0~9 这个范围。项目处于孵化器阶段时,第一位版本号固定使用 0,即版本号为 0.x.x
的格式。
由于 Spring Boot 1 和 Spring Boot 2 在 Actuator 模块的接口和注解有很大的变更,且
spring-cloud-commons 从 1.x.x 版本升级到 2.0.0 版本也有较大的变更,因此 Spring Cloud
Alibaba 采取跟 SpringBoot 版本号一致的版本:
 1.5.x 版本适用于 Spring Boot 1.5.x
 2.0.x 版本适用于 Spring Boot 2.0.x
 2.1.x 版本适用于 Spring Boot 2.1.x
 2.2.x 版本适用于 Spring Boot 2.2.x
下表展示了 Spring Cloud Alibaba & Spring Cloud & Spring Boot 兼容关系:
The Spring Cloud Alibaba & Spring Cloud & Spring Boot compatibility table

 

以后我们的项目选择的版本为:
 Spring Boot 2.2.3.RELEASE
 Spring Cloud Hoxton.SR3
 Spring Cloud Alibaba 2.2.0.RELEASE
 

二、依赖管理

  Spring Cloud Alibaba BOM 包含了它所使用的所有依赖的版本。如果您是 Maven Central
用户,请将我们的 BOM 添加到您的 pom.xml 中的 <dependencyManagement> 部分。 这
将允许您省略任何 Maven 依赖项的版本,而是将版本控制委派给 BOM。
 
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement> 
 
在下面的章节中,假设您使用的是 Spring Cloud Alibaba bom,相关 starter 依赖将不包
含版本号。
 
 

三、父项目的创建

 
使用父项目能控制所有子项目依赖的全局版本,能有效的去除子项目里面重复的依赖,
减少出错的几率。在此,我们将创建一个父 pom,让所有的子模块都继承该父 pom。
 

3.1 spring-cloud-alibaba-examples 项目的创建

 
spring-cloud-alibaba-examples 将作为我们子项目的最大父 pom 项目!
注意:在使用 IDEA 之前我们假设您已经设置了相应的 JDK 配置和 Maven 配置
 

3.1.1 使用 IDEA 创建一个 Maven 项目

 
创建一个新的 Maven 项目 
选择 Next: 
填写下面的相关信息:
Name:spring-cloud-alibaba-examples
Location:D:workspacespring-cloud-alibaba-examples(不要随便放)
GroupId:com.bjsxt
Version:1.0
最后,点击 finish 完成创建的过程!
 
 
 

3.1.2 Spring Boot 版本的控制

 
  我们采用<parent> 的方式来导入 Spriing Boot 的版本号。
打开项目的 pom.xml 文件,添加依赖内容: 
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
 
这样,我们的项目就已经规定了 Spring Boot 的版本为 2.2.3.RELEASE 了。
 
 

3.1.3 Spring Cloud 版本的控制

 
我们使用依赖管理的方式来添加 Spring Cloud 的版本信息,在<properties> 里面定义版
本信息,这里面我们选择 Hoxton.SR3 这个版本!
 
<properties>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
</properties>
 
  之后在 <dependencyManagement> 里面添加 spring-cloud 的 bom 信息,这将允许您省略任何 Maven 依赖项的版本,而是将版本控制委派给 BOM。
 
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement> 
 
 

3.1.4 Spring Cloud Alibaba 版本的控制

 
  同样,我们使用依赖管理的方式来添加 Spring Cloud Alibaba 的版本信息。在<properties>里面定义版本信息,这里面我们选择 2.2.0.RELEASE 这个版本!
 
<properties>
...
<com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version>
...
</properties>
 
  之后在 <dependencyManagement> 里面添加 spring-cloud 的 bom 信息,这将允许您省略任何 Maven 依赖项的版本,而是将版本控制委派给 BOM。 
 
 
<dependencyManagement>
<dependencies>
...
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com-alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
....
</dependencies>
</dependencyManagement>
 

3.1.4 设置为 pom 的版本方式

添加项目的打包方式: 
<packaging>pom</packaging>
这将保证我们的项目是以 pom 打包的。 
 

3.1.5 完整的 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent> 
 
<groupId>com.bjsxt</groupId>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<version>1.0</version>
<properties>
<spring-cloud.version>Hoxton.SR3</spring-cloud.version>
<com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com-alibaba-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project> 
 

3.2 项目的打包

3.2.1 删除项目里面多余的文件夹

  我们的项目仅仅是一个父 pom 文件,因此,项目只需要保留 pom.xml 即可,我们在此
可以删除 src 目录。
 

3.2.2 执行打包

 
使用 Maven 打包我们的项目。
 

 

3.2.3 观察打包后的效果
 
我们打开我们 Maven 设置的本地仓库地址,如图所示:
打开 Maven 里面的 settings.xml 文件,找到该标签
发现我们本地仓库的地址位于 D 盘的 libjar 文件夹里面:
打开该文件夹: 

第三章 Nacos

 
一、什么是 Nacos?
  Nacos 致力于发现、配置和管理微服务。它提供了一组简单易用的特性集,帮助快速实
现动态服务发现、服务配置、服务元数据及流量管理。使用 Nacos 可以更敏捷和容易地构
建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务
范式、云原生范式) 的服务基础设施。
服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、
配置和管理: 
 Kubernetes Service.
 gRPC & Dubbo RPC Service.
 Spring Cloud RESTful Service.

二、Nacos 的关键特性

 
1.服务发现和服务健康监测 ,支持基于 DNS 和 RPC 的服务发现,支持基于传输层和
应用层的监控检查;
2.动态配置服务 ,可以以中心化、外部化和动态化的方式管理所有环境的应用配置和
服务配置,同时支持版本追踪和一键回滚等功能;
3.动态 DNS 服务 ,动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均
衡、更灵活的路由策略、流量控制以及数据中心内网的简单 DNS 解析服务;
4.服务及其元数据管理 ,管理数据中心的所有服务及元数据,包括管理服务的描述、
生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服
务的 SLA 以及最首要的 metrics 统计数据。
 

三、Nacos 的核心概念

 服务 (Service)
服务是指一个或一组软件功能(例如特定信息的检索或一组操作的执行),其目的是不同的
客户端可以为不同的目的重用(例如通过跨进程的网络调用)。Nacos 支持主流的服务生态,
如 Kubernetes Service、gRPC|Dubbo RPC Service 或者 Spring Cloud RESTful Service.
 服务注册中心 (Service Registry)
服务注册中心,它是服务实例及元数据的数据库。服务实例在启动时注册到服务注册表,并
在关闭时注销。服务和路由器的客户端查询服务注册表以查找服务的可用实例。服务注册中
心可能会调用服务实例的健康检查 API 来验证它是否能够处理请求。
 服务元数据 (Service Metadata)
服务元数据是指包括服务端点(endpoints)、服务标签、服务版本号、服务实例权重、路由规
则、安全策略等描述服务的数据
 服务提供方 (Service Provider)
是指提供可复用和可调用服务的应用方
3 服务消费方 (Service Consumer)
是指会发起对某个服务调用的应用方
 配置 (Configuration)
在系统开发过程中通常会将一些需要变更的参数、变量等从代码中分离出来独立管理,以独
立的配置文件的形式存在。目的是让静态的系统工件或者交付物(如 WAR,JAR 包等)更
好地和实际的物理运行环境进行适配。配置管理一般包含在系统部署的过程中,由系统管理
员或者运维人员完成这个步骤。配置变更是调整系统运行时的行为的有效手段之一。
 配置管理 (Configuration Management)
在数据中心中,系统中所有配置的编辑、存储、分发、变更管理、历史版本管理、变更审计
等所有与配置相关的活动统称为配置管理。
 名字服务 (Naming Service)
提供分布式系统中所有对象(Object)、实体(Entity)的“名字”到关联的元数据之间的映射管理
服务,例如 ServiceName -> Endpoints Info, Distributed Lock Name -> Lock Owner/Status Info,
DNS Domain Name -> IP List, 服务发现和 DNS 就是名字服务的 2 大场景。
 配置服务 (Configuration Service)
在服务或者应用运行过程中,提供动态配置或者元数据以及配置管理的服务提供者。
 
 
 

第三章 Nacos 注册中心

 

一、Nacos Discovery 简介

 
  使用 Spring Cloud Alibaba Nacos Discovery,可基于 Spring Cloud 的编程模型快速接入 Nacos
服务注册功能。
  服务发现是微服务架构体系中最关键的组件之一。如果尝试着用手动的方式来给每一个客户
端来配置所有服务提供者的服务列表是一件非常困难的事,而且也不利于服务的动态扩缩
容。Nacos Discovery 可以帮助您将服务自动注册到 Nacos 服务端并且能够动态感知和刷新
某个服务实例的服务列表。除此之外,Nacos Discovery 也将服务实例自身的一些元数据信
息-例如 host,port, 健康检查 URL,主页等内容注册到 Nacos。
 
 

二、Nacos Server 安装

 
在使用 Nacos 之前我们首先要获取和安装 Nacos。
 

2.1 Nacos Server 的下载

 
因为 Spring Cloud Alibaba 2.2.0.RELEASE 内置的 Nacos client 版本为 1.1.4,所以我们使用这个
版本的 Nacos。Nacos 下载地址:https://github.com/alibaba/nacos/releases:
 
我们找到 1.1.4 版本后点击:
 
找到相关系统的压缩包,这里面我使用的是 window 系统,所以我们选择
nacos-server-1.1.4.zip 该文件,来点击下载他。
提示:若大家的下载速度较慢,可以在今天的软件目录里面去下载。
 
 
2.2 Nacos Server 目录的说明
 
将下载的 nacos-server-1.1.4 复制到我们的开发工具的文件夹里面。(没有的同学可以新建
一个,养成一个良好的习惯)
 

 

 

 

2.3 配置 Nacos Server

 
进入${Nacos}/conf 目录里面,使用文件编辑器打开 application.properties 文件,这里面我使
用的是 sublime text:

 

  Nacos 默认使用嵌入式数据库实现数据的存储,并不方便观察数据存储的基本情况,这
里面我们修改为使用 Mysql 数据库做数据的存储,方便我们观察数据的结构。
在配置文件末尾添加如下配置: 
spring.datasource.platform=mysql
db.num=1
5db.url.0=jdbc:mysql://localhost:3306/nacos?characterEncoding=utf8&connectTi
meout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456 
 
 
注意:
 上面的 url 地址是我的服务器地址,如果你的 mysql 安装在虚拟机或云服务器上面,就
填写真实的地址。
 db.user 用户名
 db.password 密码
2.4 Mysql 表的导入
提示:Nacos 建议使用 5.7 以上的 Mysql 数据库,版本较低可能存储兼容性问题
我使用的 Mysql 数据库版本为 5.7
打开${Nacos}/conf/文件夹: 
 
新建数据库: 
新建 Nacos 的的数据库:

 

 

 

具体表的细节和设计,我们将在源码分析的阶段讲解。
2.5 Nacos Server 的启动
上面工作都完成后,现在我们来启动一个单机版的 Nacos 服务器。
进入到${Nacos}/bin 目录里面:
 

 

 

 

三、框架的搭建
我们将使用搭建所示的测试案例。
3.1 创建 nacos-examples 的父项目
我们将为 Nacos 创建一个单独项目,方便我们来学习和管理 nacos 的相关功能。
使用 IDEA 创建一个 Maven 模块:

 

12点击 next:
Name:nacos-examples
其他项将会自动的填充进去。
点击 Finish 完成创建。
修改 nacos-examples 里面的 pom.xml 文件,在此我们引入:
 spring-boot-starter-web (spring-boot-web 开启的最基础的依赖)
 
13 spring-cloud-alibaba-nacos-discovery(nacos 做服务发现的最基础的依赖)
 
在<dependencies>添加这 2 个依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
 
 
修改 nacos-examples 项目的打包方式:
<packaging>pom</packaging>
 
最后,完整的 pom.xml 文件如下所示:
 
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-examples</artifactId>
<packaging>pom</packaging>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
 
 

3.2 创建服务的提供者 provider

 
服务提供者将为以后的服务消费者提供一些接口的供远程调用。
使用 IDEA 为 nacos-examples 创建一个子模块:
将 Next 进入下一步操作:
注意:我们的 parent 必须选择为 nacos-examples。
 
Name:provider(服务的提供者)
其他的项将自动的填充,不需要我们修改。
Provider 的依赖分析:
我们的 provider 已经自动的从 nacos-exmaples 里面把这 2 个依赖继承过来了。
3.3 创建服务的消费者
服务消费者将远程调用服务提供者提供的接口。
使用 IDEA 为 nacos-examples 创建一个子模块:
将 Next 进入下一步操作:
注意:我们的 parent 必须选择为 nacos-examples。
Name:consumer(服务的提供者)
其他的项将自动的填充,不需要我们修改。
17Consumer 的依赖分析:
我们的 consumer 已经自动的从 nacos-exmaples 里面把这 2 个依赖继承过来了。
至此,我们项目的骨架已经搭建完成。如下图所示:

四、使用 Nacos 做注册中心

 

4.1 provider 项目的完善

Provider 现在还是一个空的项目,里面没有任何的数据接口。
 

4.1.1 添加一个 application.yml 配置文件

 
我们给 provider 添加一个配置文件:
文件的名称为:
完成创建后,IDEA 能自动的识别该 yml 文件。
编辑该配置文件,添加如下的配置:
 
server:
port: 8080
spring:
application:
name: provider-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
 
 
配置说明:
server.port provider 服务端口为 8001 ;
spring.application.name 服务名称为 provider-service;
spring.cloud.nacos.server-addr ,指定 Nacos 注册中心的地址;
 
4.1.2 添加一个启动类
 
Provider 还没有启动类,我们需要给他添加一个。
名称为:com.bjsxt.ProviderApplication
com.bjsxt 为包名,ProviderApplicaton 为类的名称,创建完成后,自动的出现:
在该类里面添加如下的代码:
 
 
@SpringBootApplication // 标记为 SpringBoot 的应用
@EnableDiscoveryClient // 启用服务发现的功能
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args) ;
}
}
 

4.1.3 添加一个 API 接口

 
提供者通过提供 API 接口供服务提供者调用。
在 provider 里面创建一个类:
名称为 controller.EchoController
成功后,如下图所示:
添加一个接口:
 
@RestController
public class EchoController {
/**
* echo message
* @param message
* @return reply content
*/
@GetMapping("/echo/{message}")
public ResponseEntity<String> echo(@PathVariable("message")
String message){
return
ResponseEntity.ok(String.format("hello,%s",message)) ;
}
}
 
该接口将会对访问的人输入一个 hello,xxx。
 
4.1.4 启动 provider 测试
 
现在我们的 provider 已经准备就绪,启动看能否注册到 Nacos 上面 。
发现 provider 已经启动成功了。
打开 Nacos 页面观察 provider 是否上线:
服务的提供者已经成功的上线了。
 
4.2 consumer 项目的完成
 
Consumer 现在还是一个空的项目,里面没有任何的数据接口,也没有对服务的提供者进行
调用。
 
4.2.1 添加一个 application.yml 配置文件
我们给 consumer 添加一个配置文件
名称为:application.yml
 
IDEA 能自动的识别该配置文件的:
 
编辑该文件,给该文件添加如下的配置信息:
 
server:
port: 8090
spring:
application:
name: consumer-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
 
4.2.2 添加一个启动类
 
Consumer 现在还没有任何的启动类,我们需要给他添加一个。
名称为:com.bjsxt.ConsumerApplication
创建成功后,如下图所示:
 
在该类里面添加如下代码:
@SpringBootApplication // 标记为 SpringBoot 的应用
@EnableDiscoveryClient // 开启服务的发现功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args) ;
}
}
 
4.2.3 服务发现的测试
继续改造启动类,在里面添加如下代码:
 
@Autowired
private DiscoveryClient discoveryClient ;// 快速服务的发现
@GetMapping("/discovery/{serviceName}")
public ResponseEntity<List<String>> discovery(@PathVariable("serviceName")
String serviceName){
/**
* 通过服务的 ID / 名称得到服务的实例列表
*/
List<ServiceInstance> instances =
discoveryClient.getInstances(serviceName);
if(instances==null || instances.isEmpty()){
return ResponseEntity.notFound().build() ;
}
List<String> services = new ArrayList<>(instances.size());
instances.forEach(instance->{
services.add(instance.getHost()+":"+instance.getPort());
});
return ResponseEntity.ok(services) ;
}
 
因为我们在启动类里面添加了接口,所以要必须添加@RestController 该注解来标记该类:
最后,我们启动 consumer-service 项目:
观察 consumer-service 是否成功的注册到了 Nacos 上面:
Consumer-service 已经成功了上线了。
在浏览器输入:
http://localhost:8090/discovery/provider-service
其中:provider-service 代表服务的名称
服务器给我们响应:
 
代表服务已经发现成功。
输入一个不存在的服务名称:
http://localhost:8090/discovery/provider
服务器给我们响应:
代表,该服务还没有服务的提供者。
 
4.2.4 远程调用的测试
在 consumer,我们调用 provider 里面的 echo 接口
继续改造我们的启动类,注册一个 RestTemplate 对象:
 
/**
* 在容器里面注入一个 RestTempalte 对象
* @return
*/
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}
 
调用测试:
 
@Autowired
27private RestTemplate restTemplate ;
@GetMapping("/rpcv1/{message}")
public ResponseEntity<String> rpcV1(@PathVariable("message") String message){
ResponseEntity<String> responseEntity = restTemplate.getForEntity(
"http://localhost:8081/echo/{message}",
String.class,
message
);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok(String.format("远程调用成功,结果
为%s",responseEntity.getBody())) ;
}
return ResponseEntity.badRequest().body("远程调用失败") ;
}
@GetMapping("/rpcv2/{message}")
public ResponseEntity<String> rpcV2(@PathVariable("message") String message){
List<ServiceInstance> instances =
discoveryClient.getInstances("nacos-provider");
if(instances==null || instances.isEmpty()){
return ResponseEntity.badRequest().body("当前服务没有服务的提供者") ;
}
ServiceInstance serviceInstance = instances.get(0);
String instance =
serviceInstance.getHost()+":"+serviceInstance.getPort() ;
ResponseEntity<String> responseEntity = restTemplate.getForEntity(
String.format("http://%s/echo/{message}",instance),
String.class,
message
);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok(String.format("远程调用成功,结果
为%s",responseEntity.getBody())) ;
}
return ResponseEntity.badRequest().body("远程调用失败") ;
}
@GetMapping("/rpcv3/{message}")
public ResponseEntity<String> rpcV3(@PathVariable("message") String message){
List<ServiceInstance> instances =
28discoveryClient.getInstances("nacos-provider");
if(instances==null || instances.isEmpty()){
return ResponseEntity.badRequest().body("当前服务没有服务的提供者") ;
}
ServiceInstance serviceInstance = loadbalance(instances);
System.out.println(serviceInstance);
String instance =
serviceInstance.getHost()+":"+serviceInstance.getPort() ;
ResponseEntity<String> responseEntity = restTemplate.getForEntity(
String.format("http://%s/echo/{message}",instance),
String.class,
message
);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok(String.format("远程调用成功,结果
为%s",responseEntity.getBody())) ;
}
return ResponseEntity.badRequest().body("远程调用失败") ;
}
 
 
其中,loadBalance 的实现逻辑为:
 
/**
* 从一个服务的实例列表里面选择一个服务的实例
* @param instances
* 实例列表
* @return
* 具体的实例
*/
private ServiceInstance loadbalance(List<ServiceInstance> instances) {
Random random = new Random(System.currentTimeMillis());
return instances.get(random.nextInt(instances.size())) ;
}
 
 
重启 Consummer-service:
 
在浏览器里面输入:
http://localhost:8090/rpc/world
 
服务器给我们相应的数据为:
远程调用已经成功!
 
 
五、负载均衡测试
当服务压力突然变大时,我们需要通过对服务进行弹性伸缩和动态的扩容,让服务展示更强
的并发能力和容错能力。
 
5.1 修改服务提供者的数据接口
修改服务提供者的接口,可以让我们很清晰的看到,我们当前调用的是那一台服务提供者。
让我们来修改 provider 里面 EchoController 这个类
 
代码如下:
@RestController
public class EchoController { 
 
@Value("${server.port}")
private Integer port ;
/**
* echo message
* @param message
* @return reply content
*/
@GetMapping("/echo/{message}")
public ResponseEntity<String> echo(@PathVariable("message") String
message){
return ResponseEntity.ok(String.format("hello,%s,我是服务提供
者:%s",message,port)) ;
}
}
因为我们在同一台机器上启动多个服务提供者,那服务提供者的端口肯定不相同,我们
在此使用 port 作为服务提供者的唯一标示。
@Value : 从 env 里面注入这个值
 
5.2 启动多个服务的提供者
先停止所有的服务:
在启动一个服务的提供者:
启动成功后:
 
启动其他 1 个:
进入编辑页面:
2 个操作:
Name:不能重复,这里我修改为 ProviderApplication-2
Program arguments:--server.port=8081 这句话的意思是,在启动该服务时,添加一个
变量: server.port=8081 ,类似我们使用这样的命令启动:
 
java -jar xxx.jar --server.port=8081
 
--server.port 该 值 将 会 被 添 加 到 spring 的 env 里 面 , 并 且 该 方 式 的 优 先 级 高 于
application.yml 里面的配置值。所以,系统会使用该 port 来启动服务。
修改完毕后,点击 ok。
现在启动我们的复制的 ProviderApplicaton-2:
观察 Nacos 里面的数据:
服务:provider-service 已经有 2 个实例了,点击详情:
 
可以手动的下线该服务。
 

5.3 测试负载均衡

 
修改 consumer 里面的 ConsumerApplication ,在里面的末尾添加如下的代码:
 
@Autowired
private LoadBalancerClient loadBalancerClient ;
@GetMapping("/choose/{serviceName}")
public ResponseEntity<String> choose(@PathVariable("serviceName") String serviceName){
ServiceInstance instance = loadBalancerClient.choose(serviceName);
System.out.println(String.format("本次选择的实例
为:%s:%s",instance.getHost(),instance.getPort()));
return ResponseEntity.ok(instance.getHost()+":"+instance.getPort()) ;
}
 
其中:
 LoadBalancerClient :Ribbon 提供给我们最外层的接口,使用它我们可以很轻易的实现
负载均衡。
 LoadBalancerClient.choose(serviceName):通过给定的服务名称,Ribbon 底层通过负载
均衡的算法得到一个可以进行远程的实例。
现在,启动 Consumer 进行测试:
在浏览器里面输入:
 
http://localhost:8090/choose/provider-service
得到:

 刷新页面:

并且后台输出:
发现负载均衡已经完成。
 

5.4 更简单的远程调用测试

 
在之前,我们在 consumer 使用 RestTemplate 调用 provider 时,手动实现了负载均衡,url
的拼接:
代码相当的冗余。现在,我们使用 Ribbon 的方式来实现:
在注入 RestTemplate 的方法上添加注解:
远程调用的代码变为:
@GetMapping("/rpc/ribbon/{message}")
public ResponseEntity<String> rpcByRibbon(@PathVariable("message") String
message){
ResponseEntity<String> responseEntity =
restTemplate.getForEntity("http://provider-service/echo/" + message,
String.class);
if(responseEntity.getStatusCode()==HttpStatus.OK){
return ResponseEntity.ok("调用成功,provider-service 相应给我们的数
据为:"+responseEntity.getBody()) ;
}
return ResponseEntity.badRequest().body("调用失败,provider-service 的
相应码为:"+responseEntity.getStatusCode()) ;
}

重启 consumer 测试:
 
在浏览器里面输入:
http://localhost:8090/rpc/ribbon/{message}
刷新测试:
负载均衡已经完成了。
 
 

六、Nacos Discovery 对外暴露的 Endpoint

 
Nacos Discovery 内部提供了一个 Endpoint, 对应的 endpoint id 为 nacos-discovery。我们通
过该 Endpoint,能获取到:
 当前服务有哪些服务订阅者 ;
 当前应用 Nacos 的基础配置信息 ;
 

6.1 给项目添加依赖

 
假设我们想看服务提供者(provider)有那些订阅者,以及 Nacos 的基础配置信息。
我们就需要给 provider 项目的 pom.xml 文件里面添加:
 
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
 

6.2 修改配置文件

 
Endpoint 本身对外界隐藏显示,我们需要在配置里面开启对 Endponit 的显示支持。
修改 application.yml 配置文件,在里面添加如下的配置:
 
management:
38endpoints:
web:
exposure:
include: "*"
 
说明:
 exposure.include:对外界保留那些 Endpoint,若是所有则使用* ;
 

6.3 查询效果

 
重启项目,浏览器访问:
http://localhost:8081/actuator/nacos-discovery
效果为:
 
 

七、Nacos Discovery Starter 更多的配置项

 

 

第三章 Nacos 配置中心

一、Nacos Config 简介
 
使用 Spring Cloud Alibaba Nacos Config,可基于 Spring Cloud 的编程模型快速接入 Nacos 配
置管理功能。
 
上一节 Spring Cloud Alibaba Nacos 注册中心记录了 Nacos 作为注册中心的使用方式,这节继
续记录下Nacos作为配置中心的使用方式。本节使用的Spring Cloud版本为Hoxton.SR3,
Spring
Cloud Alibaba 版本为 2.2.0.RELEASE,Spring Boot 版本为 2.2.3.RELEASE。
 
二、项目的搭建
  我们将在我们 nacos-examples 的基础上,搭建一个 config-client,用来今天 nacos 配置中
心的案例测试 
 
 

2.1 创建 config-client 项目

 
使用 IDEA 创建一个 Maven 模块:
选择 Maven:
点击下一步:
Parent:选择 nacos-examples
Name:config-client
其他的项,保持默认。
点击 FINISH 完成创建的过程:
 

2.2 添加依赖

 
我们的项目继承自 nacos-examples,它里面已经包含 2 个基本的依赖:
 服务注册和发现:spring-cloud-alibaba-nacos-discovery 这个是微服务里面必不可缺的组
 Web 开发相关:spring-boot-starter-web 开发 web 项目最基础的依赖
现在,我们在里面添加今天我们要学习的配置获取的组件:
 
spring-cloud-alibaba-nacos-config
编辑 config-client 里面的 pom.xml 文件,添加以下的内容:
 
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
</dependency>
</dependencies> 
 
在添加一个 mavne 的打包插件:
 
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
 
查看项目所有的依赖:
现在,项目里面已经包含上面的 3 个依赖了。
 

2.3 完整的 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nacos-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>config-client</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 
 
至此,项目的搭建完成了。
 

三、在 nacos-server 里面添加配置

 
相关的工作图,如图: 
 
Nacos-client 会从 Nacos-Server 里面获取配置文件,首先,Nacos-Server 里面需要有配
置文件才能获取。
 
新建如下所示的配置信息:
 
Data ID:
nacos-config.properties
Group :
DEFAULT_GROUP
配置格式:
Properties
配置内容:
user.name=nacos-config-properties
user.age=90 
 
打开 Nacos 的管理页面:
现在我们看见,里面还没有任何的配置信息。
点击:

 

 

点击发布,完成配置文件的发表任务: 

 

返回后,配置信息已经发布成功: 

四、获取配置信息 

我们将演示在 config-client 里面获取上面我们写的 2 个配置信息

4.1 添加一个配置文件

文件名称为: 
注意:不是 application.yml ,bootstrap.yml 比 application 有更高的优先级。
Idea 能自动的识别该文件的格式:
 
编辑该文件,添加以下内容:
 
server:
port: 8070
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
config: # 指定配置中心的地址和配置中心使用的数据格式
server-addr: localhost:8848
file-extension: properties
application:
name: config-client 
 
配置说明:
 server.port: 服务的运行端口
spring.cloud.nacos.discovery.server-addr: 注册中心地址
spring.cloud.nacos.config.server-addr: 配置中心地址
spring.cloud.nacos.config.file-extension: 配置中心的配置属性的数据格式
spring.application.name: 应用的名称 
 

4.2 新建一个启动类 

名称为:com.bjsxt.ConfigClientApplication 
 
编辑该类:
@SpringBootApplication
@EnableDiscoveryClient
@RefreshScope
@RestController
public class ConfigClientApplication {
@Value("${user.name}")
private String userName ;
@Value("${user.age}")
private Integer userAge ;
public static void main(String[] args) {
SpringApplication.run(ConfigClientApplication.class,args) ;
}
/**
* 获取配置文件里面用户的信息
* @return
*/
@GetMapping("/user/info")
public ResponseEntity<String> getUser(){
return ResponseEntity.ok(
String.format("从配置中心获取的信息为:user:%s,
age:%s",userName,userAge)) ;
}
}
说明:
@RefreshScope:代表配置文件是可以被刷新的
@Value:从 env 里面获取配置信息
 

4.3 启动测试 

在浏览器里面输入:
 
http://localhost:8070/user/info
效果已经完成了。
 
 

五、获取配置规则

nacos 配置中心通过 namespace、dataId 和 group 来唯一确定一条配置。
 
 Namespace:即命名空间。默认的命名空间为 public,我们可以在 Nacos 控制台中新建
命名空间;
 dataId:即配置文件名称
 Group:即配置分组,默认为 DEFAULT_GROUP,可以通过 spring.cloud.nacos.config.group
配置。
其中:dataId 是最关键的配置字段:格式如下:

说明:
 prefix 默 认 为 spring.application.name 的 值 , 也 可 以 通 过 配 置 项
spring.cloud.nacos.config.prefix 来配置;
 spring.profiles.active 即为当前环境对应的 profile。注意,当 spring.profiles.active 为空时,
对应的连接符-也将不存在,dataId 的拼接格式变成${prefix}.${file-extension};
 file-extension 为 配 置 内 容 的 数 据 格 式 , 可 以 通 过 配 置 项 spring.cloud.nacos.config.file-extension 来配置。
这就是上面我们为什么能获得到配置的原因了。
 
 

六、配置划分实战 

Nacos 配置中心的 namespace、dataId 和 group 可以方便灵活地划分配置。比如,我们现在
有一个项目需要开发,项目名称为 bjsxt,项目开发人员分为两个组:GROUP_A 和 GROUP_B,
项目分为三个环境:开发环境 dev、测试环境 test 和生产环境 prod。
Bjsxt->GRUOR_A->dev
 
6.1 在 Nacos 控制台中新建一个名称为 bjsxt 的命名空间

点击新建命令空间:
填写以下的内容: 
 
点击确定,完成创建。
完成后,如图所示:
新建完成后:我们看见它自动帮我们生产了一个 ID:
 
8defab18-df88-49e5-b13e-526f89da87ad
记录该 ID 值。
 

6.2 在 Nacos 新建配置 

切换完成后,点击 +:
填写以上的信息。点击发布:
完成后:
已经完成创建。
 
 

6.3 获取配置文件

 
修改 config-client 里的 bootstrap.yml 文件:
server:
port: 8070
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
config: # 指定配置中心的地址和配置中心使用的数据格式
server-addr: localhost:8848
file-extension: yml
#properties
group: GROUP_A # 获取 GROUP_A 里面的配置
namespace: 8defab18-df88-49e5-b13e-526f89da87ad # 命名空间,写 id
的值
#
prefix: ${spring.application.name} # 前缀,默认为应用的名称,不需要
修改
application:
name: config-client
profiles:
active: dev # 使用的 dev 环境的配置
 
 

6.4 重启 config-client 测试 

浏览器访问: 
 
 
http://localhost:8070/user/info 
 
得到: 
配置信息已经获取成功。
 

七、配置回滚

 
Nacos 中,修改配置点击发布后会创建一个对应的历史版本快照,我们可以在 Nacos 控制台
的历史版本列表中找到这些快照。
 

7.1 动态刷新

该注解,可以刷新配置。故无需启动就能看见最新的配置信息。
 

7.2 修改配置文件 

点击发布。
出现对比页面:
 
点击确认发布:
 
然后点击返回。
 
 

7.3 历史版本的查询

可以看见修改的日期。
 

7.4 回滚

将配置信息回滚为之前的版本:
 
根据修改时间,我们将 12:38:53 的数据回滚为 12:25:12 的数据:
 
已经自动的展示回滚的内容了,点击回滚配置: 
点击确认,完成回滚:
再次测试:
浏览器输入:
 
http://localhost:8070/user/info
 
已经回滚成功了: 

八、获取多个配置

 
除了通过上面的方式指定一个唯一配置外,我们还可以同时获取多个配置文件的内容。
 
 

8.1 修改 config-client 里面的配置文件

server:
port: 8070
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
config:
extension-configs: # 多个配置
- dataId: test-a.yml
group: DEFAULT_GROUP
refresh: true
- dataId: test-b.yml
group: DEFAULT_GROUP
refresh: false
# 指定配置中心的地址和配置中心使用的数据格式
#
server-addr: localhost:8848
#
file-extension: yml
#properties
#
group: GROUP_A # 获取 GROUP_A 里面的配置
#
namespace: 8defab18-df88-49e5-b13e-526f89da87ad # 命名空间,写 id 的值
##
prefix: ${spring.application.name} # 前缀,默认为应用的名称,不需要修
改
application:
name: config-client
profiles:
active: dev # 使用的 dev 环境的配置
说明:
 spring.cloud.nacos.config.extension-configs[n].dataId,指定多个配置的 dataId,必须包含
文件格式,支持 properties、yaml 或 yml;
 spring.cloud.nacos.config.extension-configs[n].group,指定分组;
 spring.cloud.nacos.config.extension-configs[n].refresh,是否支持刷新。
上 面 的 配 置 中 , 我 们 分 别 从 DEFAULT_GROUP 中 获 取 了 config-client-a.yml 和
config-client-b.yml 配置内容,并且 config-client-a.yml 支持刷新,config-client-b.yml 不支持刷新。
注意:
没有 namespace 的配置,言外之意就是 Nacos 目前还不支持多个配置指定不同的命名空间。
 

8.2 在 Nacos 里面完成这 2 个配置文件的创建

config-client-a.yml:
切换 namespace:
点击 + 完成创建: 
点击发布。
config-client-b.yml
 

8.3 获取配置信息

 
重启 config-client,测试:
浏览器访问: 
 
 

九、Spring Cloud Alibaba Nacos Config 常用的配置.

 

第四章 Spring Cloud Alibaba Sentinel

一、Sentinel 简介

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,
从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
 

Sentinel 具有以下特征:

 
 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,
例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下
游不可用应用等。
 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应
用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与
Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可
快速地接入 Sentinel。
 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩
展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
 

二、Sentinel 控制台安装

 
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和
集群),规则管理和推送的功能。本节将详细记录何如通过 Sentinel 控制台控制 Sentinel 客
户端的各种行为。Sentinel 控制台的功能主要包括:流量控制、降级控制、热点配置、系统
规则和授权规则等。
 

2.1 下载 Sentinel

找到:1.7.1 版本: 
 

点击 sentinel-dashboard-1.7.1.jar 完成下载:
 
提示:
 
直接在 github 下载,速度非常的慢,大家可以从今天的软件目录里面去下载: 

2.2 启动 sentinel-dashboard

 
将下载好的 sentinel-dashboard-1.7.1.jar 复制到安装软件的目录里面。
使用:
    java -jar sentinel-dashboard-1.7.1.jar
 
来启动一个 sentinel-dashboard 的实例。
 
启动成功后:
我们可以通过浏览器访问:
  http://localhost:8080/ 
其中,用户名:sentinel
密码: sentinel
更多可用的启动参数配置:
 
java -D 参数名=参数值 -jar xx.jar
java -jar xx.jar --参数名=参数值
 
 
-Dsentinel.dashboard.auth.username=sentinel 用于指定控制台的登录用户名为 sentinel;
 -Dsentinel.dashboard.auth.password=123456 用于指定控制台的登录密码为 123456,如
果省略这两个参数,默认用户和密码均为 sentinel;
 -Dserver.servlet.session.timeout=7200 用于指定 Spring Boot 服务端 session 的过期时
间,如 7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;
 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口
 
 
三、搭建客户端
刚才我们搭建了 sentinel-dashboard,我们还需要搭建一个客户端,用于测试 sentinel 的各种
功能。
我们将搭建如图所示的 Maven 项目结构:

3.1 使用 IDEA 创建子模块

选择 Maven 项目:
 
点击 Next:
 
Parent:选择 spring-cloud-alibaba-examples
Name:命名为 sentinel-example-client
其他的项保持默认值即可。
点击 Finish 完成创建。 
 

3.2 添加依赖

 
修改 sentinel-example-client 里面的 pom.xml 文件:
添加以下的内容:
 
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies> 
 
 
有 2 个依赖:
 spring-cloud-starter-alibaba-sentinel 这是 spring cloud 和 sentinel 集成的项目
 spring-boot-starter-web 开启 web 最基础的依赖
添加 spring boot 的打包插件:
 
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> 
 
这样,我们的项目打包好了后,可以使用 java -jar 来直接运行了。
 

3.3 完整的 pom.xml 

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-example-client</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 

3.4 添加一个配置文件

命名为: 
修改该配置文件,添加以下的配置:
 
server:
port: 8085
spring:
application:
name: sentinel-client
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8719 
 
 
其中:
 spring.cloud.sentinel.transport.dashboard 指定了 sentinel 控制台的 ip 和端口地址;
 spring.cloud.sentinel.transport.port 代表 sentinel 客户端和控制台通信的端口,默认为
8719,如果这个端口已经被占用,那么 sentinel 会自动从 8719 开始依次+1 扫描,直到
找到未被占用的端口。
 

3.5 添加一个启动类 

名称为:com.bjsxt.SentinelClientApplication
添加如下的代码: 
 
@SpringBootApplication
public class SentinelClientApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelClientApplication.class ,args) ;
}
}
 
3.6 添加一个 Controller
 
名称为:controller.TestController
在 TestContrller 里面添加如下接口: 
 
@RestController
public class TestController {
@GetMapping("/hello")
public ResponseEntity<String> hello(){
return ResponseEntity.ok("hello,sentinel") ;
}
}
 
 

3.7 启动项目

在浏览器访问:
 
http://localhost:8080/#/dashboard/home
 
出现: 
发现并没有任何的功能。
此时,我们访问一下我们写的 hello 接口: 
 
多访问几次。
再次访问:
控制台已经显示正常了。
并且,在簇点链路中可以看到刚刚那笔请求,我们可以对它进行流控、降级、授权、热点等
配置(控制台是懒加载的,如果没有任何请求,那么控制台也不会有任何内容)。 
 

四、流控规则

 
流量的控制规则。
在簇点链路列表中,点击/hello 后面的流控按钮:
出现: 
 资 源 名 : 标 识 资 源 的 唯 一 名 称 , 默 认 为 请 求 路 径 , 也 可 以 在 客 户 端 中 使 用
@SentinelResource 配置;
 针 对 来 源 : Sentinel 可 以 针 对 服 务 调 用 者 进 行 限 流 , 填 写 微 服 务 名 称 即
spring.application.name,默认为 default,不区分来源;
 阈值类型、单机阈值:
 QPS(Queries-per-second,每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,
进行限流;
 线程数:当调用该 api 的线程数达到阈值的时候,进行限流。
 是否集群:默认不集群;
 流控模式:
 直接:当 api 调用达到限流条件的时,直接限流;
 关联:当关联的资源请求达到阈值的时候,限流自己;
 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,则
进行限流)。
 流控效果:
 快速失败:直接失败;
 Warm Up:根据 codeFactor(冷加载因子,默认值为 3)的值,从阈值/codeFactor,经
过预热时长,才达到设置的 QPS 阈值;
 排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为 QPS,否则无效。
 
 
4.1 QPS 直接失败
 
演示下 QPS 直接失败设置及效果。点击簇点链路列表中/hello 请求后面的流控按钮:
 
  上面设置的效果是,1 秒钟内请求/hello 资源的次数达到 2 次以上的时候,进行限流。
点击新增完成该规则的设置。
现在,在浏览器访问:
当手速快点的时候(1 秒超过 2 次),页面返回 Blocked by Sentinel (flow limiting)。并且响应
码为 429。
 
 
4.2 线程数直接失败
 
4.2.1 添加接口
 
在 TestController 里面新建一个接口。 
/**
* 线程直接失败
* @return
* @throws InterruptedException
*/
@GetMapping("/thread")
public ResponseEntity<String> threadMode() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return ResponseEntity.ok("hello,sentinel!") ;
}
 
其中,我们添加了:
 
TimeUnit.SECONDS.sleep(1);
 
 
让线程睡 1s ,这样,更容易触发规则。
重启项目。
访问我们添加的 thread 的接口: 
 
http://localhost:8085/thread
发现,页面需要等待 1s 左右才会响应,这是线程 sleep 的效果。

4.2.2 新增流控规则

 

点击新增,完成创建。

4.2.3 测试该规则 

 
浏览器快速的访问:
http://localhost:8085/thread 

4.3 关联

 
访问某个接口达到一定的流控规则后,开始限制本接口。
 

4.3.1 在 TestController 里面添加 api 接口 

 
@GetMapping("/test1")
public ResponseEntity<String> test1(){
return ResponseEntity.ok("hello,test1") ;
}
@GetMapping("/test2")
public ResponseEntity<String> test2(){
return ResponseEntity.ok("hello,test2") ;
}
 
重启项目,正常的访问 test1,test2 测试:

4.3.2 添加规则

我们想让 test1 关联 test2,也就是说,访问 test2 接口达到某种规则后,开始限流 test1 。

上述流控规则表示:当 1 秒内访问/test2 的次数大于 2 的时候,限流/test1。
 

4.3.3 测试规则

我们使用打开 2 个网页,密集访问/test2,然后我们手动浏览器请求/test1,看看效果。
 

 

发现已经开始限流了。
 

4.4 链路

 
程序的调用可以看成为一条链路。当触发链路的某条规则后,该链路被限流。
上图从 API 出发,可以发现有 2 条链路,分别为:
Link1-->hello-->other
Link2-->hello-->other
2 条链路在调用上,限流规则互补影响。
 

 

4.4.2 添加接口 

@Autowired
private TestService testService ;
@GetMapping("/link1")
public ResponseEntity<String> link1(){
return ResponseEntity.ok(
String.format("link1,调用 test,结果
为%s",testService.hello())) ;
}
@GetMapping("/link2")
public ResponseEntity<String> lin2(){
return ResponseEntity.ok(
String.format("link2,调用 test,结果为%s",testService.hello())) ;
}
 

4.4.3 声明资源

 
我们现在把 TestService 里面的 hello 方法变成一个资源:
注意:
@SentinelResource("hello")将该方法标识为一个 sentinel 资源,名称为 hello。
然后重启测试:

4.4.4 添加链路规则

点击簇点链路: 

在入口资源,我们使用的是 link1,点击新增,完成对规则的添加。
上述配置的意思是,当通过/link1 访问 hello 的时候,QPS 大于 2 则进行限流;言外之意就是
/link 访问 hello 请求并不受影响。
 

4.4.5 测试该规则

 
打开 link1 ,link2 接口。

 

访问测试 n 次,发现还是不能成功。难受!
具体的错误: 

4.5 预热 Warm Up

 
流控效果除了直接失败外,我们也可以选择预热 Warm Up。
sentinel 客户端的默认冷加载因子 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预
热时长逐渐升至设定的 QPS 阈值。
比如:
我们给 hello 资源设置该规则。

 

上面的配置意思是:对于/hello 资源,一开始的 QPS 阈值为 3,经过 10 秒后,QPS 阈值
达到 10。
新增完成后: 
快速访问/hello
http://localhost:8085/hello 

 

 

 

最快的手速点刷新,一开始会常看到 Blocked by Sentinel (flow limiting)的提示,10 秒后
几乎不再出现(因为你的手速很难达到 1 秒 10 下)。

4.6 排队等待

 
排队等待方式不会拒绝请求,而是严格控制请求通过的间隔时间,也即是让请求以均匀的速
度通过。
在 TestController 里面添加接口:
private static Logger logger = LoggerFactory.getLogger(TestController.class) ;
@GetMapping("/queue")
public ResponseEntity<String> queue(){
logger.info("开始处理请求");
return ResponseEntity.ok("ok") ;
 
重启项目并且访问 queue 接口: 
给 queue 添加一个限流规则:
 

 

五、降级规则

 
Sentinel 除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重
要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求
发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或
异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而
导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔
断(默认行为是抛出 DegradeException)。
当访问系统失败超过一定的次数后,对该接口进行熔断的操作。
 
我们可以发现,降级策略分为 3 种:
 RT,平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的
平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口
(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地
熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超
出 此 阈 值 的 都 会 算 作 4900 ms , 若 需 要 变 更 此 上 限 可 以 通 过 启 动 配 置 项
-Dcsp.sentinel.statistic.max.rt=xxx 来配置。
 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒
异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状
态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这
个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值
之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,
则结束熔断状态后仍可能再进入熔断状态。
 

5.1 RT

 
当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms
为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,
对这个方法的调用都会自动地熔断(抛出 DegradeException) 
 

5.1.1 添加测试接口

 
@GetMapping("/rt")
public ResponseEntity<String> rt() throws InterruptedException {
TimeUnit.SECONDS.sleep(1);
return ResponseEntity.ok("ok") ;
}
 

5.1.2 添加降级的规则 

5.1.3 测试

 
打开 Apache jmeter 。
添加一个线程组: 
1 s 10 个线程同时发请求。
 

 

 

 

 

5.2 异常比例

 
当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值之后,资源进入降
级状态,在接下来的时间窗口内,程序都会快速失败。
 

5.2.1 添加接口 

@GetMapping("/exception")
public ResponseEntity<String> exception() throws InterruptedException {
throw new RuntimeException("就是不想成功!") ;
}
 

5.2.2 添加降级规则

上面的配置含义是:如果/exception 的 QPS 大于 5,并且每秒钟的请求异常比例大于 0.5
的话,那么在未来的 3 秒钟(时间窗口)内,sentinel 断路器打开,该 api 接口不可用。
也就是说,如果一秒内有 10 个请求进来,超过 5 个以上都出错,那么会触发熔断,1
秒钟内这个接口不可用。
 
 

5.2.3 测试

 
打开 Jmeter,修改请求的地址:

开始测试

 
在浏览器打开:
 
http://localhost:8086/exception 
 
直接被熔断,停止 jemter,等待 3s,在此访问: 

5.3 异常数

 
当策略为异常数时表示:当指定时间窗口内,请求异常数大于等于某个值时,触发降级。继
续使用上面的接口测试。
 

5.3.1 添加规则 

上面的规则表示:在 60 秒内,访问/exception 请求异常的次数大于等于 5,则触发降级。
 

5.3.2 测试该规则

 
可以看到,当第 5 次访问的时候成功触发了降级。
 

六、热点规则

 
热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对
其访问进行限制。
 

6.1 添加一个接口 

@GetMapping("/buy")
@SentinelResource("buy")
public ResponseEntity<String> buy(String prodName,Integer prodCount){
return ResponseEntity.ok("买" + prodCount + "份" + prodName );
 

6.2 添加热点的规则

 
对这个资源添加热点规则: 
上面的配置含义是:对 buy 资源添加热点规则,当第 0 个参数的值为华为的时候 QPS
阈值为 3,否则为 1。此外,如果第 0 个参数不传,那么这笔请求不受该热点规则限制。
 
 

6.3 测试效果

 
不是华为: 
买 1 次后,里面限流。
 

七、系统规则

 
系统规则则是针对整个系统设置限流规则,并不针对某个资源,设置页面如下: 

阈值类型包含以下五种:

 
 Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自
适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算
的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps minRt 估
算得出。设定参考值一般是 CPU cores 2.5。
 CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围
0.0-1.0),比较灵敏。
 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫
秒。
 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
 

八、授权规则

 
授权规则用于配置资源的黑白名单: 
 
上述配置表示:只有 appA 和 appB 才能访问 test1 资源。
 
第四章 Spring Cloud Alibaba Sentinel
 
一、@SentinelResource 简介
Sentinel 提供了@SentinelResource 注解用于定义资源,并提供可选的异常回退和 Block 回退。
异常回退指的是@SentinelResource 注解标注的方法发生 Java 异常时的回退处理;Block 回退
指的是当@SentinelResource 资源访问不符合 Sentinel 控制台定义的规则时的回退(默认返回
Blocked by Sentinel (flow limiting))。这节简单记录下该注解的用法。
 

2.1 搭建 sentinel-example

 
我们将在 sentinel-example 里面演示所有@SentinelResource 的的功能。
 
2.1.4 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>sentinel-examples</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
 
2.1.1 使用 IDEA 创建一个 Maven 项目

 

2.1.2 添加依赖 
 
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies> 
 
 Spring-boot-stater-web 是开发 web 最基础的依赖;
 spring-cloud-alibaba-nacos-discovery 是服务的发现组件
 

2.1.3 修改项目的打包方式

 
<packaging>pom</packaging>
 

2.2 搭建 sentinel-provider

 
Provide 是一个普通的服务的提供者。
 

2.2.1 使用 IDEA 创建一个 Maven 项目

选择 Maven 项目:
 

 

Parent:选择 sentinel-example
Name:sentinel-provider
点击 Finish,完成创建。
2.2.2 修改项目的打包方式
我们修改项目的打包方式,以后我们可以使用 jar 来发布项目。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> 
 

2.2.3 完整的 pom.xml 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-provider</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 
 
2.3 搭建 sentinel-consumer
 
Provide 是一个普通的服务的消费者。
 
2.3.1 使用 IDEA 创建一个 Maven 项目
点击 Next,填写以下的内容:
Parent:选择 sentinel-example
Name:sentinel-consumer
点击 Finish,完成创建。
 

2.3.2 修改 pom.xml 文件

 
我们将在该项目里面演示@SentinelResource 的功能。
 
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
 
我们修改项目的打包方式,以后我们可以使用 jar 来发布项目。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> 
 

2.3.3 完整的 pom.xml 文件

 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-consumer</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 
 

三、完善 sentinel-provider

我们在 provider 里面添加一个模拟数据接口。
 
3.1 添加一个数据接口
 
代码如下:
@RestController
public class GoodsController {
@GetMapping("/goods/buy/{name}/{count}")
public ResponseEntity<String> buy(
@PathVariable("name") String name,
@PathVariable("count") Integer count) {
return ResponseEntity.ok(String.format("购买%d 份%s", count, name));
}
}
 

3.2 添加配置文件

server:
port: 8081
spring:
application:
name: sentinel-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
仅仅是为了让服务能注册到注册中心而已。
 
 

3.3 添加一个启动类 

代码如下:
 
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelProviderApplication.class ,args) ;
}
}
 

3.4 启动测试

 
在启动之前,我们必须保证 Nacos 已经启动成功。
测试接口:
 
http://localhost:8081/goods/buy/huawei/1

四、完善 sentinel-consumer

 
4.1 调用服务提供者 
代码如下:
 
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("buy/{name}/{count}")
@SentinelResource(value = "buy", fallback = "buyFallback", blockHandler =
"buyBlock")
public ResponseEntity<String> buy(@PathVariable String name, @PathVariable
Integer count) {
if (count >= 20) {
throw new IllegalArgumentException("购买数量过多");
}
if ("miband".equalsIgnoreCase(name)) {
throw new NullPointerException("已售罄");
}
Map<String, Object> params = new HashMap<>(2);
params.put("name", name);
params.put("count", count);
return ResponseEntity.ok(
this.restTemplate.getForEntity("http://sentinel-provider/goods/buy/{name}/{
count}", String.class, params).getBody());
}
// 异常回退
public ResponseEntity<String> buyFallback(@PathVariable String name,
@PathVariable Integer count, Throwable throwable) {
return ResponseEntity.ok(
String.format("【进入 fallback 方法】购买%d 份%s 失败,%s", count,
name, throwable.getMessage()));
}
// sentinel 回退
public ResponseEntity<String> buyBlock(@PathVariable String name,
@PathVariable Integer count, BlockException e) {
return ResponseEntity.ok(String.format("【进入 blockHandler 方法】购买%d
份%s 失败,当前购买人数过多,请稍后再试", count, name));
}
}
 
4.2 添加配置文件
内容如下:
server:
  port: 8083
spring:
  application:
    name: sentinel-consumer
cloud:
  nacos:
  discovery:
  server-addr: localhost:8848
sentinel:
  transport:
  dashboard: localhost:8080
  port: 8719
 
 
4.3 添加启动类
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelConsumerApplication.class ,args) ;
}
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}
}
4.4 启动测试
4.4.1 启动软件
在启动之前,必须保证这些软件已经启动:
Nacos-Server
Sentinel-Dashboard
Sentinel-provider
准备就绪后,启动 sentine-consumer
 

 4.4.2 添加流控的规则

 

 当访问该资源,QPS 超过 2 时,抛出异常。

测试:
http://192.168.1.11:8083/buy/huawei/1
五、其他用法
5.1 说明
在当前类中编写回退方法会使得代码变得冗余耦合度高,我们可以将回退方法抽取出来到一
个指定类中.
 
5.2 新增 BuyBlockHandler 
 

 代码如下:

public class BuyBlockHandler {
// sentinel 回退
public static String buyBlock(@PathVariable String name, @PathVariable
Integer count, BlockException e) {
return String.format("【进入 blockHandler 方法】购买%d %s 失败,当前购买
人数过多,请稍后再试", count, name);
}
}
 
5.3 新增 BuyFallBack
 

 代码如下:

public class BuyBlockHandler {
// sentinel 回退
public static ResponseEntity<String> buyBlock(@PathVariable String name,
@PathVariable Integer count, BlockException e) {
return ResponseEntity.ok(String.format("【进入 blockHandler 方法】购买%d
%s 失败,当前购买人数过多,请稍后再试", count, name));
}
}
 
5.4 改造 BuyController
 
@RestController
public class BuyController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("buy/{name}/{count}")
@SentinelResource(
value = "buy",
fallback = "buyFallback",
fallbackClass = BuyFallBack.class ,
blockHandler = "buyBlock",
blockHandlerClass = BuyBlockHandler.class ,
)
public ResponseEntity<String> buy(@PathVariable String name, @PathVariable
Integer count) {
if (count >= 20) {
throw new IllegalArgumentException("购买数量过多");
}
if ("miband".equalsIgnoreCase(name)) {
throw new NullPointerException("已售罄");
}
Map<String, Object> params = new HashMap<>(2);
params.put("name", name);
params.put("count", count);
return ResponseEntity.ok(
this.restTemplate.getForEntity("http://sentinel-provider/goods/buy/{name}/{
count}", String.class, params).getBody())
}
server:
port: 8083
spring:
application:
13name: sentinel-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719 
 
 
 
 
第五章 Spring Cloud Alibaba Seata
一、Seata 简介
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易
用的分布式事务服务。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮
演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双 11,对各 BU 业务进行了
有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为
了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社
区共建的形式帮助其技术更加可靠与完备。
二、Seata-Server 的安装
在使用 Seata 之前,我们首先要安装 Seata-Server 服务器。
2.1 下载 Seata
由于我们使用的 spring cloud alibaba 版本为 2.2.0.RELEASE,他里面控制了 seata 的版本为
1.0.0,故我们在此下载 1.0.0 版本的 seata。
访问:
https://github.com/seata/seata/releases/tag/v1.0.0
由于我使用的是 windows 的电脑,故选择 seata-server-1.0.0.zip 该版本。
点击该文件下载。
当下载速度较慢时,大家可以从今天的软件目录里面去下载。
2.2 Seata-Server 目录分析
 
Bin:可执行文件目录
Conf:配置文件目录lib:依赖的 jar
LICENSE:授权文件
2.3 Seata 启动
进入{seata}/bin 目录里面,双击:
代表 seata-server 已经启动成功。
三、框架的搭建
在本示例中,我们模拟了一个用户购买货物的场景:
 StorageService 负责扣减库存数量;
 OrderService 负责保存订单;
 AccountService 负责扣减用户账户余额;
 Business 负责用户下单的整个流程处理;
3.1 搭建 seata-examples 项目
seata-examples 用来控制所有项目的依赖版本号,以及去除公共的依赖 seata。
3.1.1 使用 IDEA 创建 Maven 项目
选择 Maven 项目:
点击 Next,添加以下的信息:
Parent:选择 spring-cloud-alibaba-examples
Name:seata-examples
其他的信息保持默认即可。然后点击 Finish 即可完成创建。
3.1.2 添加依赖
打开项目的 pom.xml 文件,添加以下的依赖。
<dependencies>
<!--
服务注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--
seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--
web 项目的基础依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>
3.1.3 完整的 pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>seata-examples</artifactId>
<dependencies>
<!--
服务注册-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
</dependency>
<!--
seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
<!--
web 项目的基础依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
最后,我们的项目的依赖关系如下:可以看见,我们的 seata 版本为 1.0.0 。

3.2 搭建 account-service 项目

 
Account-service 项目将负责扣减用户账户余额
3.2.1 使用 IDEA 创建一个 Maven 项目
选择 Maven 项目:
点击 Next 后,填写以下的信息:
 
Parent:seata-examples
Name:account-service
其他的值保持默认即可。
3.2.2 添加依赖
我们需要使用 ORM 框架来完成对数据库的操作。在次我们需要使用 Mybatis-Plus 来操作数据库。
打开 pom.xml ,在里面添加如下内容:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
为了以后我们打包发布我们的项目,在此我们添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
为了以后我们打包发布我们的项目,在此我们添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.2.3 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目的依赖关系如下:3.3 搭建 business-service 项目
在 business 将主要完成下单逻辑,包含库存的扣减,订单的创建。
3.3.1 使用 IDEA 创建一个 Maven 项目
选择 Maven 项目:
点击 Next 后,填写以下的信息:Parent:seata-examples
Name:business-service
其他的值保持默认即可。
3.3.2 添加依赖
为了以后我们打包发布我们的项目,在此我们添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.2.3 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-examples</artifactId><groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目的依赖关系如下:
3.3 搭建 business-service 项目
在 business 将主要完成下单逻辑,包含库存的扣减,订单的创建。
3.3.1 使用 IDEA 创建一个 Maven 项目
选择 Maven 项目:
点击 Next 后,填写以下的信息:
Parent:seata-examples
Name:business-service
其他的值保持默认即可。
3.3.2 添加依赖
为了以后我们打包发布我们的项目,在此我们添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.2.3 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-examples</artifactId><groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>account-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目的依赖关系如下:
3.4 搭建 order-service 项目
order-service 项目将负责保存用户订单。
3.4.1 使用 IDEA 创建一个 Maven 项目
选择 Maven 项目:
点击 Next 后,填写以下的信息:Parent:seata-examples
Name:order-service
其他的值保持默认即可。
3.4.2 添加依赖
我们需要使用 ORM 框架来完成对数据库的操作。在次我们需要使用 Mybatis-Plus 来操作数
据库。
打开 pom.xml ,在里面添加如下内容:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
为了以后我们打包发布我们的项目,在此我们添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>3.4.3 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目的依赖关系如下:
3.5 搭建 storage-service 项目
storage-service 将负责扣除商品的库存。
3.5.1 使用 IDEA 创建一个 Maven 项目
 
点击 Next 后,填写以下的信息:
Parent:seata-examples
Name:storage-service
其他的值保持默认即可。
3.5.2 添加依赖
我们需要使用 ORM 框架来完成对数据库的操作。在次我们需要使用 Mybatis-Plus 来操作数
据库。
打开 pom.xml ,在里面添加如下内容:
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency></dependencies>
为了以后我们打包发布我们的项目,在此我们添加 boot-maven 的打包插件:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
3.5.3 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>seata-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>order-service</artifactId>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3</version>
</dependency>
<!--MySQL 依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies><build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
项目的依赖关系如下:
3.6 完整的项目的
案例为四、代码的完善
4.1 数据库表导入
在测试分布式事务之前,我们需要先设计数据库,以及准备测试数据。
新建数据库,命名为:seata
导入 Sql:
Sql 文件在今天的软件文件夹里面:
导入该 sql:点击开始,进行导入。
成功后,发现成功的条数为:
表有如下:account :用户的账号表
Order:订单表;
Stoage:商品的库存表;
undo_log:回滚事务表,SEATA AT 模式需要 UNDO_LOG 表。
4.2 模型对象和 Mapper 对象生成
使用 IDEA 连接数据库:
成功后,如图所示:
提示:
若大家没有安装 mybatis 的代码生成插件,可以在今天的软件文件夹里面下载安装。
 Account_tbl:
 Order_tbl:
 Storage_tbl:
代码生成完毕后:
4.3 storage-service 代码的完善
4.3.1 接口设计
在 storage-service 里面,主要完成对库存的扣减。
新建一个接口:命名为:StorageService,代码如下:
代码如下:
public interface StorageService {
/**
* 扣减商品的库存
* @param commodityCode
* 商品的编码
* @param count
* 扣减商品的数量
*/
void deduct(String commodityCode, int count);
}
 
4.3.2 实现该接口
名称为:impl.StorageService,代码的实现如下:
@Service
public class StorageServiceImpl implements StorageService {
private static Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class);
@Autowired
private StorageTblMapper storageTblMapper;
@Override
public void deduct(String commodityCode, int count) {
logger.info("开始扣减库存,商品编码:{},数量:{}", commodityCode, count);
StorageTbl storageTbl = storageTblMapper.selectOne(
new LambdaQueryWrapper<StorageTbl>()
.eq(StorageTbl::getCommodityCode, commodityCode));
int idleCount = storageTbl.getCount() - count;
if (idleCount < 0) {
throw new RuntimeException("库存不足");
}
storageTbl.setCount(idleCount);
storageTblMapper.updateById(storageTbl);
logger.info("库存扣减成功,商品编码:{},剩余数量:{}", commodityCode, idleCount);
}
}4.3.3 使用 Restful 暴露此接口
添加一个 Controller
代码如下:
@RestController
public class StorageController {
private static Logger logger = LoggerFactory.getLogger(StorageController.class) ;
@Autowired
private StorageService storageService ;
/**
* 扣减商品的库存
* @param commodityCode 商品的编码
* @param count 商品的数量
* @return
*/
@GetMapping("/deduct/{commodityCode}/{count}")
public ResponseEntity<Void> deduct(
@PathVariable("commodityCode") String commodityCode,
@PathVariable("count") Integer count){
logger.info("Account Service ... xid: " + RootContext.getXID());
// 开始扣减库存
storageService.deduct(commodityCode , count);
return ResponseEntity.ok().build() ;
}
}4.3.4 添加配置文件
在 resource 目录里面新建配置文件:
内容如下:
server:
port: 18084
spring:
application:
name: storage-service
cloud:
alibaba:
seata:
tx-service-group: storage-service
nacos:
discovery:
server-addr: localhost:8848
datasource:
name: storageDataSource
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url:
jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
druid:
max-active: 20
min-idle: 2
initial-size: 2
seata:
service:
vgroup-mapping:
account-service: default
grouplist:
default: 127.0.0.1:8091
disable-global-transaction: falseenabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
4.3.5 添加启动类
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class StorageServiceApplication {
public static void main(String[] args) {
SpringApplication.run(StorageServiceApplication.class ,args) ;
}
}
4.3.6 启动项目测试
启动项目后,打印该日志,说明连接 seata-server 成功。4.4 account-service 代码的完善
4.4.1 接口设计
在 account-service 里面,主要完成对用户余扣减。
新建一个接口:
命名为:AccountService,代码如下:
代码如下:
public interface AccountService {
/**
* 从用户的账号扣减金额
* @param userId
* 用户的 Id
* @param money
* 金额
*/
void debit(String userId, int money);
}
4.4.2 实现该接口
名称为:impl.StorageService,代码的实现如下:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountTblMapper accountTblMapper;
private static Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
@Override
public void debit(String userId, int money) {
logger.info("准备扣减用户:{} 余额,扣减的数目为:{}", userId, money);
AccountTbl accountTbl = accountTblMapper.selectOne(
new LambdaQueryWrapper<AccountTbl>()
.eq(AccountTbl::getUserId, userId));
int idleMoney = accountTbl.getMoney() - money;
if (idleMoney < 0) {
throw new RuntimeException("用户余额不足");
}
accountTbl.setMoney(idleMoney);
accountTblMapper.updateById(accountTbl);
logger.info("扣减用户{}金额成功,剩余金额为{}", userId, money);
}
}
 
4.4.3 使用 Restful 暴露此接口
添加一个 Controller
名称为:
代码如下:
@RestController
public class AccountController {
@Autowired
private AccountService accountService ;
private static Logger logger =
LoggerFactory.getLogger(AccountController.class) ;
@GetMapping("/debit/{userId}/{money}")
public ResponseEntity<Void> debit(
@PathVariable("userId") String userId,
@PathVariable("money") Integer money){
logger.info("Account Service ... xid: " + RootContext.getXID());
// 开始扣减余额
accountService.debit(userId , money);
return ResponseEntity.ok().build() ;
}
}
4.4.4 添加配置文件
在 resource 目录里面新建配置文件:
内容如下:
server:
port: 18085
spring:
application:
name: account-service
cloud:
alibaba:
seata:
tx-service-group: account-service
nacos:
discovery:
server-addr: localhost:8848
datasource:
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
druid:
max-active: 20
min-idle: 2
initial-size: 2
seata:
service:
vgroup-mapping:
account-service: default
grouplist:
default: 127.0.0.1:8091disable-global-transaction: false
enabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
 
 
4.4.5 添加启动类
命名为 AccountServiceApplication ,代码如下:
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class AccoutServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AccoutServiceApplication.class ,args) ;
}
}
 
4.4.6 启动项目测试
启动项目后,打印该日志,说明连接 seata-server 成功。4.5 order-service 代码的完善
4.5.1 接口设计
在 order-service 里面,主要完成保存用户订单的操作。
新建一个接口:
命名为:OrderService,代码如下:
代码如下:
public interface OrderService {
/**
* 创建一个订单
* @param userId 用户 id
* @param commodityCode 商品的编号
* @param orderCount 商品的数量
* @return OrderTbl
*/OrderTbl create(String userId, String commodityCode, int orderCount) ;
}
 
4.5.4 Ribbon 集成
创建一个配置类:
代码如下:
@Configuration
public class HttpUtilConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}
}
 
4.5.2 实现该接口
名称为:impl.OrderService,代码的实现如下:
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderTblMapper orderTblMapper;
@Autowired
private AccountService accountService;
private static Logger logger =
LoggerFactory.getLogger(OrderServiceImpl.class);
@Override
public OrderTbl create(String userId, String commodityCode, int orderCount)
{
logger.info("准备为{}创建一个订单,商品编号为{},数量为{}", userId,
commodityCode, orderCount);
// 1 计算总金额
int orderMoney = calculate(commodityCode, orderCount);
accountService.debit(userId, orderMoney);
OrderTbl order = new OrderTbl();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(orderCount);order.setMoney(orderMoney);
orderTblMapper.insert(order);
// INSERT INTO orders ...
return order;
}
private int calculate(String commodityCode, int orderCount) {
// 我们现在没有商品的表,在此我们把商品的价格定死
int prodPrice = 0 ;
if("HUAWEI_0001".equals(commodityCode)){ // 华为时 100
prodPrice = 100;
}else if ("XIAOMI_002".equals(commodityCode)){ // 小米时 200
prodPrice = 200 ;
}else {
prodPrice = 1000 ; // 其他为 1000
}
return orderCount * prodPrice ;
}
}
4.5.3 远程调用 account-service 的实现
创建一个 AccountService 的类,该类里面主要完成对 accout-servic 的远程调用。
名称为:
/**
* 实现对账号服务的远程调用
*/
@Service
public class AccountService {
private static Logger logger = LoggerFactory.getLogger(AccountService.class) ;
/**
* 1 ribbon 的方式
*/
@Autowired
private RestTemplate restTemplate ;
/**
* 2 feign 的方式
*/
public void debit(String userId, int orderMoney) {
ResponseEntity<Void> entity = restTemplate.
getForEntity(
"http://accout-service/debit/{userId}/{orderMoney}",
Void.class,
userId,
orderMoney
);
if(entity.getStatusCode()== HttpStatus.OK){
logger.info("扣减用户{}金额成功,本次扣减的数目为{}",userId,orderMoney);
return ;
}
logger.info("扣减用户{}金额失败",userId);
throw new RuntimeException("扣减金额失败") ;
}
}我们在此使用的时 Ribbon 做远程调用,下面的章节我们也会测试 Feign 。
4.5.5 使用 Restful 暴露此接口
代码如下:
@RestController
public class OrderController {
private static Logger logger = LoggerFactory.getLogger(OrderController.class) ;
@Autowired
private OrderService orderService ;
/**
* 创建订单
* @param userId
* 用户 Id
* @param commodityCode
* 商品的编号* @param orderCount
* 商品的数量
* @return
*/
@GetMapping("/create/{userId}/{commodityCode}/{orderCount}")
public ResponseEntity<Void> create(
@PathVariable("userId") String userId,
@PathVariable("commodityCode") String commodityCode,
@PathVariable("orderCount") int orderCount){
logger.info("Order Service ... xid: " + RootContext.getXID());
orderService.create(userId, commodityCode, orderCount) ;
return ResponseEntity.ok().build() ;
}
}
4.5.6 添加配置文件
在 resource 目录里面新建配置文件:
命名为:application.yml
内容如下:
server:
port: 18086
spring:
application:
name: order-service
cloud:
alibaba:
seata:
tx-service-group: order-service
nacos:discovery:
server-addr: localhost:8848
datasource:
name: orderDataSource
type: com.alibaba.druid.pool.DruidDataSource
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
url:
jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
druid:
max-active: 20
min-idle: 2
initial-size: 2
seata:
service:
vgroup-mapping:
order-service: default
grouplist:
default: 127.0.0.1:8091
disable-global-transaction: false
enabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
 
4.5.7 添加启动类
命名为:OrderServiceApplication
代码如下:
@SpringBootApplication@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class ,args) ;
}
}
4.58 启动项目测试
启动项目后,打印该日志,说明连接 seata-server 成功。
4.6 business-service 代码的完善
4.6.1 接口设计
在 business-service 里面,主要完成下单的逻辑,包含 2 个主要的步骤,就是对库存服务和
订单服务的远程调用。
新建一个接口:
命名为:com.bjsxt.service.BusinessService
代码如下:
public interface BusinessService {
/**
* 采购/下单的过程
* @param userId
* 用户的 Id
* @param commodityCode
* 商品的编码
* @param orderCount
* 商品的数量
*/
void purchase(String userId, String commodityCode, int orderCount) ;
}
4.6.2 实现该接口
名称为:impl.BusinessServiceImpl,代码的实现如下:
@Service
public class BusinessServiceImpl implements BusinessService {
private static Logger logger = LoggerFactory.getLogger(BusinessServiceImpl.class) ;
@Autowired
private StorageService storageService;
@Autowired
private OrderService orderService;
@Override
public void purchase(String userId, String commodityCode, int orderCount) {
logger.info("准备下单,用户:{},商品:{},数量:
{}",userId,commodityCode,orderCount);
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount) ;
logger.info("下单完成");
}
}
4.6.3 远程调用 storage-service 的实现
创建一个 StorageService 的类,该类里面主要完成对 storage-servic 的远程调用。名称为:
@Service
public class StorageService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
/**
* 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate ;
/**
* 2 采用 Feign 的形式
*/
public void deduct(String commodityCode, int orderCount) {
ResponseEntity<Void> entity = restTemplate.
getForEntity(
"http://storage-service/debut/{commodityCode}/{orderCount}",
Void.class,
commodityCode,
orderCount
);
if (entity.getStatusCode()== HttpStatus.OK){
logger.info("扣减库存成功,商品编号为{},本次扣减的数量为{}",commodityCode,orderCount);
return;
}
throw new RuntimeException("扣减库存失败") ;}
}
我们在此使用的时 Ribbon 做远程调用,下面的章节我们也会测试 Feign 。
 
4.6.4 远程调用 order-service 的实现
新建一个类:
 
代码如下:
@Service
public class OrderService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
/**
* 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate ;
/**
* 2 采用 Feign 的形式
*/
public void create(String userId, String commodityCode, int orderCount) {
ResponseEntity<Void> entity = restTemplate.
getForEntity(
"http://order-service/create/{userId}/{commodityCode}/{orderCount}",
Void.class,
userId ,
commodityCode,
orderCount
);
if (entity.getStatusCode()== HttpStatus.OK){
logger.info("订单创建成功,用户为{} ,商品编号为{},本次扣减的数量为{}",userId ,
commodityCode,orderCount);
return;}
throw new RuntimeException("订单创建失败") ;
}
}
4.6.5 集成 Ribbon
 
添加一个 HttpUtilConfig 的配置类:
代码如下:
@Configuration
public class HttpUtilConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate(){
return new RestTemplate() ;
}
}
4.6.6 添加配置文件
在 resource 目录里面新建配置文件:
命名为:application.yml
内容如下:
server:
port: 18087
spring:
application:
name: business-service
cloud:
alibaba:
seata:
tx-service-group: business-service
nacos:
discovery:
server-addr: localhost:8848
seata:
service:
vgroup-mapping:
business-service: default
grouplist:
default: 127.0.0.1:8091
disable-global-transaction: false
enabled: true
 
4.6.7 添加启动类
命名为:BusinessServiceApplication
代码如下:
@SpringBootApplication
@EnableDiscoveryClient
public class BusinessServiceApplication {
public static void main(String[] args) {
SpringApplication.run(BusinessServiceApplication.class ,args) ;
}
}
 
4.6.8 暴露下单接口
继续改造启动类:
@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class BusinessServiceApplication {
@Autowired
private BusinessService businessService ;
public static void main(String[] args) {
SpringApplication.run(BusinessServiceApplication.class ,args) ;
}
/*** 开始下单
* @param userId
* 用户的 Id
* @param commodityCode
* 商品的编号
* @param orderCount
* 商品的数量
* @return
*/
@GetMapping("/purchase/{userId}/{commodityCode}/{orderCount}")
public ResponseEntity<Void> purchase(
@PathVariable("userId") String userId,
@PathVariable("commodityCode")String commodityCode,
@PathVariable("orderCount")Integer orderCount){
businessService.purchase(userId,commodityCode,orderCount);
return ResponseEntity.ok().build() ;
}
}
4.6.9 启动项目测试
启动项目后,打印该日志,说明连接 seata-server 成功。
 
4.7 总体的调用流程如下
 
都启动完成后:
Nacos-Server:

 

4.8 正常下单测试
在浏览器里面访问:
http://localhost:18087/purchase/SXT_USER_1/HUAWEI_0001/1
代表 SXT_USER_1 购买 HUAWEI_0001 产品 1 件。
数据库里面:
 Accout_tbl 里面,SXT_USER_1 用户的金额减少 100; Storage_tbl 里面,HUAWEI_0001 的库存减少了 1;
 Order_Tbl 里面,创建了一条订单记录;
说明,此时远程调用时正常的。
4.9 分布式事务的演示
我们演示如图的异常:
我们可以发现,远程调用共有 3 处。
4.9.1 在 accout-service 服务扣减余额触发异常
4.9.2 重启 accout-service
4.9.3 还原数据库里面的数据
Account_Tbl:Storage_Tbl:
4.9.4 重新下单测试
 
http://localhost:18087/purchase/SXT_USER_1/HUAWEI_0001/1
数据库的数据:
Account_Tbl:Storage_Tbl:
我们发现,分布式事务产生了,accout-service 内部的异常,导致 accout_tbl 表数据回滚了。
但是,在 storage_tbl :位于 stoage-service 的事务却没有回滚。
4.10 使用 Seata 解决分布式问题
4.10.1 改造 accout-service 里面的 AccountServiceImpl
当用户的 ID 为:SXT_USER_2 时,我们抛出异常,当为其他用户时,我们正常的下单。
4.10.2 改造 BusinessServiceImpl
添加一个注解,看他是否能解决分布式事务的问题
4.10.3 重启测试
重启 accout-service,business-service 测试
使用 SXT_USER_1 正常的下单测试:
Stoage_tbl:库存正常
Accout_Tbl:余额正常
使用 SXT_USER_2 下单测试:

发现发生异常后,
stoage_tbl 里面的没有发生改变,数据正常
Accout_tbl 里面的数据也没有发生改变,数据正常
分布式事务测试成功了
五、集成 Feign 测试 Seata
在上面的章节中,我们使用的时 Ribbon + RestTemplate 的形式做的远程调用。下面我们来演
示 Feign 的调用方式。
5.1 改造 business-service
5.1.1 添加依赖
修改 business-service 项目里面的 pom.xml 文件,在里面添加依赖。
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
</dependencies>
5.1.2 添加 OrderServiceFeign
里面的代码如下:
@FeignClient("order-service")
public interface OrderServiceFeign {
@GetMapping("/create/{userId}/{commodityCode}/{orderCount}")
ResponseEntity<Void> create(
@PathVariable("userId") String userId,
@PathVariable("commodityCode") String commodityCode,
@PathVariable("orderCount") Integer orderCount);
}
5.1.3 添加 StorageServiceFeign
@FeignClient("storage-service")public interface StorageServiceFeign {
@GetMapping("/deduct/{commodityCode}/{orderCount}")
ResponseEntity<Void> deduct(
@PathVariable("commodityCode") String commodityCode,
@PathVariable("orderCount") Integer orderCount
) ;
}
5.1.5 改造 OrderService
@Service
public class OrderService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
/**
* 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate ;
@Autowired
private OrderServiceFeign orderServiceFeign ;
/**
* 2 采用 Feign 的形式
*/
public void create(String userId, String commodityCode, int orderCount){// Ribbon
//
ResponseEntity<Void> entity = restTemplate.
//
getForEntity(
//
"http://order-service/create/{userId}/{commodityCode}/{orderCount}",
//
Void.class,
//
userId ,
//
commodityCode,
//
orderCount
//
);
//Feign
ResponseEntity<Void> entity = orderServiceFeign.create(userId, commodityCode,
orderCount);
if (entity.getStatusCode()== HttpStatus.OK){
logger.info("订单创建成功,用户为{} ,商品编号为{},本次扣减的数量为{}",userId ,
commodityCode,orderCount);
return;
}
throw new RuntimeException("订单创建失败") ;
}
}
5.1.7 改造 StorageService
代码如下:
@Service
public class StorageService {
private static Logger logger = LoggerFactory.getLogger(StorageService.class);
/*** 1 采用 Ribbon 的形式
*/
@Autowired
private RestTemplate restTemplate;
@Autowired
private StorageServiceFeign storageServiceFeign;
/**
* 2 采用 Feign 的形式
*/
public void deduct(String commodityCode, int orderCount) {
// Ribbon
//
ResponseEntity<Void> entity = restTemplate.
//
getForEntity(
//
"http://storage-service/deduct/{commodityCode}/{orderCount}",
//
Void.class,
//
commodityCode,
//
orderCount
//
);
//Feign
ResponseEntity<Void> entity = storageServiceFeign.deduct(commodityCode,
orderCount);
if (entity.getStatusCode() == HttpStatus.OK) {
logger.info("扣减库存成功,商品编号为{},本次扣减的数量为{}", commodityCode,
orderCount);
return;
}
throw new RuntimeException("扣减库存失败");
}
}
 
5.1.6 在启动类里面开启对 Feign 的支持
5.2 改造 order-service
 
5.2.1 添加依赖
在 dependencies 添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
 
5.2.2 添加接口
里面的代码如下:
@FeignClient("account-service")
public interface AccountServiceFeign {
@GetMapping("/debit/{userId}/{orderMoney}")
ResponseEntity<Void> debit(
@PathVariable("userId") String userId,
@PathVariable("orderMoney") Integer orderMoney
) ;
}
5.2.3 修改 AccoutService
/**
* 实现对账号服务的远程调用
*/
@Service
public class AccountService {
private static Logger logger = LoggerFactory.getLogger(AccountService.class) ;
/**
* 1 ribbon 的方式
*/
@Autowired
private RestTemplate restTemplate ;
@Autowired
private AccountServiceFeign accountServiceFeign ;
/**
* 2 feign 的方式*/
public void debit(String userId, int orderMoney) {
//Ribbon
//
ResponseEntity<Void> entity = restTemplate.
//
getForEntity(
//
"http://accout-service/debit/{userId}/{orderMoney}",
//
Void.class,
//
userId,
//
orderMoney
//
);
ResponseEntity<Void> entity = accountServiceFeign.debit(userId, orderMoney);
if(entity.getStatusCode()== HttpStatus.OK){
logger.info("扣减用户{}金额成功,本次扣减的数目为{}",userId,orderMoney);
return ;
}
logger.info("扣减用户{}金额失败",userId);
throw new RuntimeException("扣减金额失败") ;
}
}
5.2.4 在启动类里面添加对 Feign 的支持
5.3 重启测试
重启 order-service ,business-service
还原数据库数据,开始测试。
正常下单测试:
使用 SXT_USER_2 下单:
出错了,但是数据库的各个表都正常。
Seata 测试成功了。
 

第六章 Spring Cloud Alibaba Dubbo

 

一、项目简介

 
Dubbo Spring Cloud 基于 Dubbo Spring Boot 2.7.1 和 Spring Cloud 2.x 开发,无论开发人
员是 Dubbo 用户还是 Spring Cloud 用户,都能轻松地驾驭,并以接近“零”成本的代价使应
用向上迁移。Dubbo Spring Cloud 致力于简化 Cloud Native 开发成本,提高研发效能以及提
升应用性能等目的。
Dubbo Spring Cloud 首个 Preview Release,随同 Spring Cloud Alibaba `0.2.2.RELEASE` 和
0.9.0.RELEASE 一同发布,分别对应 Spring Cloud Finchley 与 Greenwich(下文分别简称为 “F”
版 和 “G” 版)
 

二、功能的完成度

 
由于 Dubbo Spring Cloud 构建在原生的 Spring Cloud 之上,其服务治理方面的能力可
认为是 Spring Cloud Plus,不仅完全覆盖 Spring Cloud 原生特性,而且提供更为稳定和成熟
的实现,特性比对如下表所示:

 

三、框架的搭建

 
我们将搭建如图所示的项目框架
3.1 搭建 spring-cloud-dubbo-examples
 
spring-cloud-dubbo-exmaples 是一个父项目,用来给子项目控制版本和去除公共的依赖。
 
3.1.1 创建项目
 
使用 IDEA 创建一个模块:
选择 Maven:
点击 Next,进行下一步操作:
Parent:必须选择之前我们创建的 spring-cloud-alibaba-examples。
Name:spring-cloud-dubbo-examples 项目的名称
点击 Finish,完成项目的创建。
至此,spring-cloud-dubbo-examples 项目已经完成创建了。
 

3.1.2 添加依赖

 
打开该项目的 pom.xml,添加以下内容: 
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies> 
 
 
3.1.3 修改项目的打包方式
 
<packaging>pom</packaging>
3.1.4 完整的 pom.xml 文件如下
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-alibaba-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
 
3.2 搭建 dubbo-api
dubbo-api 里面将存放用于发表服务的接口。
 
3.2.1 创建 dubbo-api 项目
使用 IDEA 创建一个子模块。 
选择 Maven 项目: 
点击 Next 进行下一步操作:
Parent:选择 spring-cloud-dubbo-examples
Name:名称为 dubbo-api
点击 Finish 完成项目的创建:

3.2.2 完整的 pom.xml 文件如下

 
dubbo-api 的 pom.xml 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-api</artifactId>
</project> 
 
3.3 搭建 dubbo-provider
3.3.1 创建 dubbo-provider 项目
搭建 dubbo-provider 用来做一个服务的提供者。
使用 IDEA 创建一个模块:
点击 Next,进行下一步操作:
Parent:选择 spring-cloud-alibaba-examples
Name:dubbo-provider
点击 Finish,完成项目的创建。
 
3.3.2 修改 Maven 的打包方式
Maven 项目默认会被 target 目录下的 class 文件打包在一个 jar 里面,该 jar 并不能直接运行,
我们需要修改它的打包方式为 spring-boot 的打包,这样,打包后的项目将能直接被运行。
修改 pom.xml ,添加如下的内容: 
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
 
这样,该项目将最终被打包成为一个 jar,能直接通过 java -jar 来运行
3.3.3 完整的 pom.xml 文件如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-provider</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> 
 

3.4 搭建 dubbo-consumer

 

3.4.1 创建 dubbo-provider-consumer 项目

 
搭建 dubbo-provider 用来做一个服务的提供者。
使用 IDEA 创建一个模块: 
 
选择 Maven 项目:
点击 Next,进行下一步操作:
Parent:选择 spring-cloud-alibaba-examples
Name:dubbo-consumer
点击 Finish,完成项目的创建。
 
3.4.2 修改 Maven 的打包方式
 
Maven 项目默认会被 target 目录下的 class 文件打包在一个 jar 里面,该 jar 并不能直接运行,
我们需要修改它的打包方式为 spring-boot 的打包,这样,打包后的项目将能直接被运行。
修改 pom.xml ,添加如下的内容:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
这样,该项目将最终被打包成为一个 jar,能直接通过 java -jar 来运行
 
3.4.3 完整的 pom.xml 文件如下
 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud-dubbo-examples</artifactId>
<groupId>com.bjsxt</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dubbo-consumer</artifactId>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
 
3.4.4 完整的项目结构

四、代码的完善

 
4.1 dubbo-api 代码的完善
4.1.1 定义 Dubbo 服务接口
Dubbo 服务接口是服务提供方与消费方的远程通讯契约,通常由普通的 Java 接口
(interface)来声明。 
代码如下:
 
public interface EchoService {
String echo(String message);
}
 
4.1.2 项目的打包
 
Api 项目主要是为了把 rpc 中定义的接口发布出去。
我们可以使用 Maven 的普通打包方式把编译后的 class 文件打包为 jar。 
打包成功后,项目的 jar 位于:
4.2 dubbo-provider 代码的完善
4.2.1 添加依赖
在 dubbo-provider 的 pom.xml 的 dependencies 添加以下的依赖
<dependencies>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
 
4.2.2 实现 dubbo-api 里面定义的接口
代码的内容如下:
@Service
public class EchoServiceImpl implements EchoService {
@Override
public String echo(String message) {
return "[echo] Hello, " + message;
}
 
4.2.3 添加配置文件 
内容如下:
dubbo:
scan:
# dubbo 服务扫描基准包
base-packages: com.bjsxt.service.impl
cloud:
subscribed-services: dubbo-provider
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1
registry:
# 挂载到 Spring Cloud 注册中心
address: spring-cloud://localhost
spring:
application:
# Dubbo 应用名称
name: dubbo-provider
main:
# Spring Boot 2.1 需要设定
allow-bean-definition-overriding: true
cloud:
nacos:
# Nacos 服务发现与注册配置
discovery:
server-addr: localhost:8848 
 
4.2.4 启动类
代码如下:
@SpringBootApplication
@EnableDiscoveryClient
public class ProviderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderServiceApplication.class, args) ;
}
}
 
4.3 dubbo-consumer 代码的完善
4.3.1 添加依赖
在 dubbo-consumer 的 pom.xml 的 dependencies 添加以下的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.bjsxt</groupId>
<artifactId>dubbo-api</artifactId>
<version>1.0</version>
</dependency>
<!-- Dubbo Spring Cloud Starter -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
 
4.3.2 添加配置文件 
内容如下:
dubbo:
registry:
# 挂载到 Spring Cloud 注册中心
address: nacos://127.0.0.1:8848
cloud:
subscribed-services: dubbo-provider
server:
port: 8080
spring:
application:
# Dubbo 应用名称
name: dubbo-consumer
main:
# Spring Boot 2.1 需要设定
allow-bean-definition-overriding: true
cloud:
nacos:
# Nacos 服务发现与注册配置
discovery:
server-addr: 127.0.0.1:8848 
 

4.3.3 启动类 

代码如下: 
@EnableDiscoveryClient
@SpringBootApplication
@RestController
public class ConsumerServiceApplication {
@Reference
private EchoService echoService ;
public static void main(String[] args) {
SpringApplication.run(ConsumerServiceApplication.class,args) ;
}
@GetMapping("/rpc")
public ResponseEntity<String> rpc(){
return ResponseEntity.ok(String.format("调用结果
为%s",echoService.echo("info")));
}
}
 
4.4 远程调用测试
 启动 Nacos-Server
 启动 dubbo-provider
 启动 dubbo-consumer
查看 Nacos 控制台:
http://localhost:8848/nacos/ 
浏览器访问: 
调用已经成功;
 

五、负载均衡调用测试

 
5.1 启动多个服务的提供者
修改服务提供者里面实现类的代码:

 

 

 

 

5.2 使用消费者负载均衡调用测试
访问:
http://localhost:8080/rpc
 
个人学习笔记,记录日常学习,便于查阅及加深,仅为方便个人使用。
原文地址:https://www.cnblogs.com/wq-9/p/14354687.html