SpringCloud Alibaba系列
第一节 项目简介
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案,通过阿里中间件来迅速搭建分布式应用系统。
第二章 依赖管理
一、版本管理规范
二、依赖管理
<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>
三、父项目的创建
3.1 spring-cloud-alibaba-examples 项目的创建
3.1.1 使用 IDEA 创建一个 Maven 项目
3.1.2 Spring Boot 版本的控制
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
3.1.3 Spring Cloud 版本的控制
<properties> <spring-cloud.version>Hoxton.SR3</spring-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> </dependencies> </dependencyManagement>
3.1.4 Spring Cloud Alibaba 版本的控制
<properties> ... <com-alibaba-cloud.version>2.2.0.RELEASE</com-alibaba-cloud.version> ... </properties>
<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>
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 删除项目里面多余的文件夹
3.2.2 执行打包
3.2.3 观察打包后的效果
第三章 Nacos
二、Nacos 的关键特性
三、Nacos 的核心概念
第三章 Nacos 注册中心
一、Nacos Discovery 简介
二、Nacos Server 安装
2.1 Nacos Server 的下载
2.2 Nacos Server 目录的说明
2.3 配置 Nacos Server
- 上面的 url 地址是我的服务器地址,如果你的 mysql 安装在虚拟机或云服务器上面,就
- db.user 用户名
- db.password 密码
2.4 Mysql 表的导入
2.5 Nacos Server 的启动
三、框架的搭建
3.1 创建 nacos-examples 的父项目
- spring-boot-starter-web (spring-boot-web 开启的最基础的依赖)
- spring-cloud-alibaba-nacos-discovery(nacos 做服务发现的最基础的依赖)
<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>
<packaging>pom</packaging>
<?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-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <packaging>pom</packaging> <modules> <module>provider</module> <module>consumer</module> <module>config-client</module> </modules> <artifactId>nacos-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>
3.2 创建服务的提供者 provider
3.3 创建服务的消费者
四、使用 Nacos 做注册中心
4.1 provider 项目的完善
4.1.1 添加一个 application.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 添加一个启动类
@SpringBootApplication // 标记为 SpringBoot 的应用
@EnableDiscoveryClient // 启用服务发现的功能
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class,args) ;
}
}
4.1.3 添加一个 API 接口
@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)) ; } }
4.1.4 启动 provider 测试
4.2 consumer 项目的完成
4.2.1 添加一个 application.yml 配置文件
我们给 consumer 添加一个配置文件
server:
port: 8090
spring:
application:
name: consumer-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
@SpringBootApplication // 标记为 SpringBoot 的应用
@EnableDiscoveryClient // 开启服务的发现功能
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args) ;
}
}
@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) ; }
http://localhost:8090/discovery/provider-service
/** * 在容器里面注入一个 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("远程调用失败") ; }
/** * 从一个服务的实例列表里面选择一个服务的实例 * @param instances * 实例列表 * @return * 具体的实例 */ private ServiceInstance loadbalance(List<ServiceInstance> instances) { Random random = new Random(System.currentTimeMillis()); return instances.get(random.nextInt(instances.size())) ; }
http://localhost:8090/rpc/world
@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)) ; } }
java -jar xxx.jar --server.port=8081
5.3 测试负载均衡
@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()) ; }
http://localhost:8090/choose/provider-service
刷新页面:
5.4 更简单的远程调用测试
@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()) ; }
六、Nacos Discovery 对外暴露的 Endpoint
6.1 给项目添加依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
6.2 修改配置文件
management: 38endpoints: web: exposure: include: "*"
6.3 查询效果
七、Nacos Discovery Starter 更多的配置项
第三章 Nacos 配置中心
2.1 创建 config-client 项目
2.2 添加依赖
spring-cloud-alibaba-nacos-config
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 里面添加配置
四、获取配置信息
config-client项目的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-SNAPSHOT</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>
4.1 添加一个配置文件
server: port: 8070 spring: cloud: nacos: discovery: server-addr: localhost:8848 config: server-addr: localhost:8848 file-extension: properties application: name: config-client
4.2 新建一个启动类
@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)) ; } }
4.3 启动测试
五、获取配置规则
六、配置划分实战
8defab18-df88-49e5-b13e-526f89da87ad
6.2 在 Nacos 新建配置
6.3 获取配置文件
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 测试
七、配置回滚
7.1 动态刷新
7.2 修改配置文件
7.3 历史版本的查询
7.4 回滚
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,必须包含
- spring.cloud.nacos.config.extension-configs[n].group,指定分组;
- spring.cloud.nacos.config.extension-configs[n].refresh,是否支持刷新。
8.2 在 Nacos 里面完成这 2 个配置文件的创建
8.3 获取配置信息
九、Spring Cloud Alibaba Nacos Config 常用的配置.
第四章 Spring Cloud Alibaba Sentinel
Sentinel 具有以下特征:
- 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,
- 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应
- 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与
- 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩
二、Sentinel 控制台安装
2.1 下载 Sentinel
2.2 启动 sentinel-dashboard
- -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 指定控制台地址和端口
三、搭建客户端
3.1 使用 IDEA 创建子模块
3.2 添加依赖
<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>
- spring-cloud-starter-alibaba-sentinel 这是 spring cloud 和 sentinel 集成的项目
- spring-boot-starter-web 开启 web 最基础的依赖
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
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 客户端和控制台通信的端口,默认为
3.5 添加一个启动类
@SpringBootApplication public class SentinelClientApplication { public static void main(String[] args) { SpringApplication.run(SentinelClientApplication.class ,args) ; } }
3.6 添加一个 Controller
@RestController public class TestController { @GetMapping("/hello") public ResponseEntity<String> hello(){ return ResponseEntity.ok("hello,sentinel") ; } }
3.7 启动项目
四、流控规则
- 资 源 名 : 标 识 资 源 的 唯 一 名 称 , 默 认 为 请 求 路 径 , 也 可 以 在 客 户 端 中 使 用
- 针 对 来 源 : Sentinel 可 以 针 对 服 务 调 用 者 进 行 限 流 , 填 写 微 服 务 名 称 即
- 阈值类型、单机阈值:
-
- QPS(Queries-per-second,每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,进行限流;
-
- 线程数:当调用该 api 的线程数达到阈值的时候,进行限流。
-
- 是否集群:默认不集群;
- 流控模式:
-
- 直接:当 api 调用达到限流条件的时,直接限流;
-
- 关联:当关联的资源请求达到阈值的时候,限流自己;
-
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,则进行限流)。
- 流控效果:
-
- 快速失败:直接失败;
-
- Warm Up:根据 codeFactor(冷加载因子,默认值为 3)的值,从阈值/codeFactor,经过预热时长,才达到设置的 QPS 阈值;
-
- 排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为 QPS,否则无效。
4.1 QPS 直接失败
4.2 线程数直接失败
4.2.1 添加接口
/** * 线程直接失败 * @return * @throws InterruptedException */ @GetMapping("/thread") public ResponseEntity<String> threadMode() throws InterruptedException { TimeUnit.SECONDS.sleep(1); return ResponseEntity.ok("hello,sentinel!") ; }
TimeUnit.SECONDS.sleep(1);
http://localhost:8085/thread
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") ; }
4.3.2 添加规则
4.3.3 测试规则
4.4 链路
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 声明资源
4.4.4 添加链路规则
点击簇点链路:
4.4.5 测试该规则
4.5 预热 Warm Up
根据codeFactor
(冷加载因子,默认为3)的值,即请求 QPS 从 threshold / 3
开始,经预热时长逐渐升至设定的 QPS 阈值
4.6 排队等待
private static Logger logger = LoggerFactory.getLogger(TestController.class) ; @GetMapping("/queue") public ResponseEntity<String> queue(){ logger.info("开始处理请求"); return ResponseEntity.ok("ok") ; }
五、降级规则
- RT,平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 5 个请求,对应时刻的
- 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒
- 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值
5.1 RT
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 测试
5.2 异常比例
5.2.1 添加接口
@GetMapping("/exception") public ResponseEntity<String> exception() throws InterruptedException { throw new RuntimeException("就是不想成功!") ; }
5.2.2 添加降级规则
5.2.3 测试
开始测试
5.3 异常数
5.3.1 添加规则
5.3.2 测试该规则
六、热点规则
6.1 添加一个接口
@GetMapping("/buy") @SentinelResource("buy") public ResponseEntity<String> buy(String prodName,Integer prodCount){ return ResponseEntity.ok("买" + prodCount + "份" + prodName ); }
6.2 添加热点的规则
6.3 测试效果
七、系统规则
阈值类型包含以下五种:
八、授权规则
第四章 Spring Cloud Alibaba Sentinel
一、@SentinelResource 简介
2.1 搭建 sentinel-example
<?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>
<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>
2.1.3 修改项目的打包方式
<packaging>pom</packaging>
2.2 搭建 sentinel-provider
2.2.1 使用 IDEA 创建一个 Maven 项目
<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.2 修改 pom.xml 文件
<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>
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
@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 启动测试
四、完善 sentinel-consumer
@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.4.2 添加流控的规则
当访问该资源,QPS 超过 2 时,抛出异常。
代码如下:
代码如下:
第五章 Spring Cloud Alibaba Seata
一、Seata 简介
二、Seata-Server 的安装
2.1 下载 Seata
2.2 Seata-Server 目录分析
2.3 Seata 启动
三、框架的搭建
3.1 搭建 seata-examples 项目
3.1.1 使用 IDEA 创建 Maven 项目
3.1.2 添加依赖
<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>
3.2 搭建 account-service 项目
3.2.2 添加依赖
<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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<?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 项目
3.3.1 使用 IDEA 创建一个 Maven 项目
3.3.2 添加依赖
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<?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 项目
3.3.1 使用 IDEA 创建一个 Maven 项目
3.3.2 添加依赖
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<?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 项目
3.4.1 使用 IDEA 创建一个 Maven 项目
3.4.2 添加依赖
<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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<?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 项目
3.5.1 使用 IDEA 创建一个 Maven 项目
3.5.2 添加依赖
<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 数据库表导入
成功后,如图所示:
4.3.2 实现该接口
@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); } }
@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 添加配置文件
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: false enabled: 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.4.2 实现该接口
@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 暴露此接口
@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 添加配置文件
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:8091 disable-global-transaction: false enabled: true mybatis-plus: mapper-locations: classpath:/mapper/*.xml
4.4.5 添加启动类
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class AccoutServiceApplication {
public static void main(String[] args) {
SpringApplication.run(AccoutServiceApplication.class ,args) ;
}
}
4.4.6 启动项目测试
4.5.1 接口设计
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 实现该接口
@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 的实现
/** * 实现对账号服务的远程调用 */ @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("扣减金额失败") ; } }
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 添加配置文件
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
@SpringBootApplication@EnableDiscoveryClient @MapperScan("com.bjsxt.mapper") public class OrderServiceApplication { public static void main(String[] args) { SpringApplication.run(OrderServiceApplication.class ,args) ; } }
4.6 business-service 代码的完善
4.6.1 接口设计
public interface BusinessService { /** * 采购/下单的过程 * @param userId * 用户的 Id * @param commodityCode * 商品的编码 * @param orderCount * 商品的数量 */ void purchase(String userId, String commodityCode, int orderCount) ; }
4.6.2 实现该接口
@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 的实现
@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("扣减库存失败") ;} }
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
@Configuration public class HttpUtilConfig { @LoadBalanced @Bean public RestTemplate restTemplate(){ return new RestTemplate() ; } }
4.6.6 添加配置文件
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 添加启动类
@SpringBootApplication @EnableDiscoveryClient public class BusinessServiceApplication { public static void main(String[] args) { SpringApplication.run(BusinessServiceApplication.class ,args) ; } }
@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 启动项目测试
4.7 总体的调用流程如下
4.8 正常下单测试
- Accout_tbl 里面,SXT_USER_1 用户的金额减少 100; Storage_tbl 里面,HUAWEI_0001 的库存减少了 1;
- Order_Tbl 里面,创建了一条订单记录;
4.9 分布式事务的演示
4.9.1 在 accout-service 服务扣减余额触发异常
4.9.2 重启 accout-service
4.9.3 还原数据库里面的数据
4.10 使用 Seata 解决分布式问题
4.10.1 改造 accout-service 里面的 AccountServiceImpl
4.10.2 改造 BusinessServiceImpl
发现发生异常后,
五、集成 Feign 测试 Seata
5.1 改造 business-service
5.1.1 添加依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId></dependency> </dependencies>
@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); }
@FeignClient("storage-service") public interface StorageServiceFeign { @GetMapping("/deduct/{commodityCode}/{orderCount}") ResponseEntity<Void> deduct( @PathVariable("commodityCode") String commodityCode, @PathVariable("orderCount") Integer orderCount ) ; }
@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 添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
@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 重启测试
第六章 Spring Cloud Alibaba Dubbo
一、项目简介
二、功能的完成度
三、框架的搭建
3.1 搭建 spring-cloud-dubbo-examples
3.1.1 创建项目
3.1.2 添加依赖
<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 修改项目的打包方式
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
3.2.2 完整的 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>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
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 项目
3.4.2 修改 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>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>
四、代码的完善
4.1 dubbo-api 代码的完善.
4.1.1 定义 Dubbo 服务接口
4.1.2 项目的打包
4.2.2 实现 dubbo-api 里面定义的接口
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 添加依赖
<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 远程调用测试
五、负载均衡调用测试
5.1 启动多个服务的提供者
5.2 使用消费者负载均衡调用测试