文章目錄
- 微服務介紹
-
- 版本選擇
- 元件介紹
- SpringCloud H版
-
- 服務注冊中心
-
- Eureka
-
- 服務注冊
- 服務入駐
- Eureka叢集
-
- 注冊中心部署叢集
- 服務部署叢集
- 負載均衡
- 服務資訊完善
- 服務發現
- 自我保護
- Zookeeper
-
- 服務注冊
- 消費注冊
- Consul
-
- 服務注冊
- 消費注冊
- CAP特點
- 服務調用
-
- Ribbon
-
- 負載均衡規則
- 規則替換
- OpenFeign
-
- 服務調用
- 逾時
- 兜底方法
- 日志
- 服務降級
-
- Hystrix
-
- 服務降級
-
- 全局配置降級
- 服務熔斷
- Hystrix圖形化
- 服務網關
-
- GetWay
-
- 項目搭建
- 配置網關
- 動态路由
- 斷言
- Filter
- 服務配置
-
- Config
-
- 配置中心
- 用戶端
- 動态重新整理(用戶端)
- BUS
- SpringCloud Alibaba
-
- Nacos
-
- 服務中心
-
- 生産者
- 消費者
- 配置中心
-
- 添加、讀取配置
- 命名空間、分組
- 持久化(mysql)
- 叢集
- Sentinel
-
- 服務監控
- 服務限流
-
- 門檻值類型/單機門檻值
- 流控模式
- 流控效果
- 服務降級
-
- RT
- 異常比例
- 異常數
- 熱點key
- 系統規則
- @SentinelResource注解配置
-
- 配置熔斷方法
- 自定義限流方法(blockHandler)
- 自定義處理異常(fallback)
- 持久化
- Seata
-
- 持久化
- Seata
微服務介紹
- 微服務是一種架構風格
- 一個應用拆分為一組小型服務
- 每個服務運作在自己的程序内,也就是可獨立部署和更新
- 服務圍繞業務功能拆分
- 可以由全自動部署機制獨立部署
- 去中心化,服務自治。服務可以使用不同的語言、不同的存儲技術
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiIXZ05WZj91YpB3IwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSPZpWT41kaOh3ZU1kZWdEZoVjMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4YzYjNTZhVDNkhTZlR2Y4YGO1QTOhNWYwIjNzQTN2Q2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
版本選擇
- SpringBoot 和 SpringCloud需要一一對應
- 截至2021/6/13
學習環境
SpringCloud | Hoxton.SR1 |
---|---|
SpringBoot | 2.2.2.RELEASE |
SpringCloud alibaba | 2.1.0.RELEASE |
JDK | 8 |
元件介紹
SpringCloud H版
父pom
<?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>
<groupId>com.lun</groupId>
<artifactId>LearnCloud</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging><!-- 這裡添加,注意不是jar或war -->
<!-- 統一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<!-- 子子產品繼承之後,提供作用:
鎖定版本+子modlue不用寫groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
服務注冊中心
Eureka
什麼是服務治理
- 在傳統的RPC遠端調用架構中,管理每個服務與服務之間依賴關系比較複雜,管理比較複雜
- 是以需要使用服務治理,管理服務于服務之間依賴關系,可以實作服務調用、負載均衡、容錯等,實作服務發現與注冊。
服務注冊
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
Eureka Server提供服務注冊
各個微服務節點通過配置啟動後,會在EurekaServer中進行注冊,這樣EurekaServer中的服務系統資料庫中将會存儲所有可用服務節點的資訊,服務節點的資訊可以在界面中直覺看到。
yml檔案
server:
port: 7001
eureka:
instance:
hostname: locathost #eureka服務端的執行個體名稱
client:
#false表示不向注冊中心注冊自己。
register-with-eureka: false
#false表示自己端就是注冊中心,我的職責就是維護服務執行個體,并不需要去檢索服務
fetch-registry: false
service-url:
#設定與Eureka server互動的位址查詢服務和注冊服務都需要依賴這個位址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
主啟動
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
輸入localhost:7001進入服務首頁
服務入駐
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置
eureka:
client:
#表示是否将自己注冊進Eurekaserver預設為true。
register-with-eureka: true
#是否從EurekaServer抓取已有的注冊資訊,預設為true。單節點無所謂,叢集必須設定為true才能配合ribbon使用負載均衡
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
主啟動
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8081 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8081.class,args);
}
}
Eureka叢集
注冊中心部署叢集
将Eureka注冊中心互相注冊,互相守望
- 修改cloud-eureka-server7001配置檔案
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服務端的執行個體名稱
client:
register-with-eureka: false #false表示不向注冊中心注冊自己。
fetch-registry: false #false表示自己端就是注冊中心,我的職責就是維護服務執行個體,并不需要去檢索服務
service-url:
#叢集指向其它eureka
defaultZone: http://eureka7002.com:7002/eureka/
#單機就是7001自己
#defaultZone: http://eureka7001.com:7001/eureka/
- 修改cloud-eureka-server7002配置檔案
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服務端的執行個體名稱
client:
register-with-eureka: false #false表示不向注冊中心注冊自己。
fetch-registry: false #false表示自己端就是注冊中心,我的職責就是維護服務執行個體,并不需要去檢索服務
service-url:
#叢集指向其它eureka
defaultZone: http://eureka7001.com:7001/eureka/
#單機就是7002自己
#defaultZone: http://eureka7002.com:7002/eureka/
服務部署叢集
- 将支付服務8001微服務,訂單服務80微服務釋出到上面2台Eureka叢集配置中
eureka:
client:
#表示是否将自己注冊進Eurekaserver預設為true。
register-with-eureka: true
#是否從EurekaServer抓取已有的注冊資訊,預設為true。單節點無所謂,叢集必須設定為true才能配合ribbon使用負載均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
負載均衡
部署多個服務中心後可實作負載均衡,減少伺服器壓力
@Value("${server.port}")
private String serverPort;//可以根據配置檔案 輸出該服務的端口号
- 将請求端口改為 服務名 (spring.application.name)
@Slf4j
@RestController
public class OrderController {
public static final String PAYMENT_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
RestTemplate restTemplate;
@GetMapping("/consumer/get")
public CommonResult<Payment> get(int id){
log.info(String.valueOf(id));
CommonResult commonResult = restTemplate.getForObject(PAYMENT_URL + "/get?id="+id, CommonResult.class);
return commonResult;
}
}
- 将restTemplate加上負載均衡注解**@LoadBalanced**
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced//使用@LoadBalanced注解賦予RestTemplate負載均衡的能力
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 就可以通過請求80端口,動态調用不同端口的服務,實作負載均衡
服務資訊完善
eureka:
...
instance:
instance-id: payment8001 #在eureka注冊中展示可讀性高的名稱
prefer-ip-address: true #位址欄顯示ip
服務發現
對于注冊進eureka裡面的微服務,可以通過服務發現來獲得該服務的資訊
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/discovery")
public CommonResult discovery(){
List<String> services = discoveryClient.getServices();
//列印注冊中心下的所有服務
log.info(services.toString());
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-payment-service");
for (ServiceInstance instance:instances){
//列印該服務的資訊
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return new CommonResult(200,"請求成功",services);
}
自我保護
在自我保護模式中,Eureka Server會保護服務系統資料庫中的資訊,不再登出任何服務執行個體
什麼是自我保護模式?
- 預設情況下,如果EurekaServer在一定時間内沒有接收到某個微服務執行個體的心跳,EurekaServer将會登出該執行個體(預設90秒)
- 但是當網絡分區故障發生(延時、卡頓、擁擠)時,微服務與EurekaServer之間無法正常通信,以上行為可能變得非常危險了
- 因為微服務本身其實是健康的,此時本不應該登出這個微服務
在自我保護模式中,Eureka Server會保護服務系統資料庫中的資訊,不再登出任何服務執行個體。
禁止自我保護
使用eureka.server.enable-self-preservation = false可以禁用自我保護模式
eureka:
---
server:
enable-self-preservation: false
當該服務超過等待上限未發送心跳時, eureka注冊中心将會删除該服務
Zookeeper
- zookeeper是一個分布式協調工具,可以實作注冊中心功能
- 服務注冊使用的是臨時節點
服務注冊
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8004</artifactId>
<dependencies>
<!-- SpringBoot整合Web元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合zookeeper用戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自帶的zookeeper3.5.3 防止與3.4.9起沖突-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 8004
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 121.43.55.*:2181
使用**@EnableDiscoveryClient**即可完成zookeeper注冊
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8004 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class,args);
}
}
消費注冊
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8004</artifactId>
<dependencies>
<!-- SpringBoot整合Web元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- SpringBoot整合zookeeper用戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自帶的zookeeper3.5.3 防止與3.4.9起沖突-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 80
#服務别名----注冊zookeeper到注冊中心名稱
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
connect-string: 121.43.55.*:2181
@SpringBootApplication
@EnableDiscoveryClient
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
測試調用
@RestController
public class OrderController {
public static final String INVOKE_URL="http://cloud-provider-payment/payment/zk";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/payment")
public String payInfo(){
String result=restTemplate.getForObject(INVOKE_URL,String.class);
return result;
}
}
Consul
- Consul是一套開源的分布式服務發現和配置管理系統
- 提供了微服務系統中的服務治理、配置中心、控制總線等功能。這些功能中的每一個都可以根據需要單獨使用,也可以一起使用以建構全方位的服務網格,總之Consul提供了一種完整的服務網格解決方案。
#啟動開發模式(快速啟動一個單節點Consul,但是不能資料持久化,不能用于生産環境)
consul agent -dev
服務注冊
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-providerconsul-payment8006</artifactId>
<dependencies>
<!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
spring:
application:
name: consul-provider-payment
####consul注冊中心位址
cloud:
consul:
host: localhost
port: 8500
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain8006 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8006.class,args);
}
}
消費注冊
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumerconsul-order80</artifactId>
<dependencies>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 注冊同上
- 調用服務
@RestController
public class OrderController {
public static final String INVOKE_URL="http://consul-provider-payment/payment/zk";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/payment")
public String payInfo(){
String result=restTemplate.getForObject(INVOKE_URL,String.class);
return result;
}
}
通過**http://localhost:8500/**可進入可視化頁面
CAP特點
CAP:
- C:Consistency (一緻性)
- A:Availability (可用性)
- P:Partition tolerance (容錯性)
CAP理論的核心是:一個分布式系統不可能同時很好的滿足一緻性,可用性和分區容錯性這三個需求。
是以,根據CAP原理将NoSQL資料庫分成了滿足CA原則、滿足CP原則和滿足AP原則三大類:
- CA - 單點叢集,滿足—緻性,可用性的系統,通常在可擴充性上不太強大。
- CP - 滿足一緻性,分區容忍必的系統,通常性能不是特别高。 (Zookeeper,Consul)
- AP - 滿足可用性,分區容忍性的系統,通常可能對一緻性要求低一些。 (Eureka)
服務調用
Ribbon
Spring Cloud Ribbon是基于Netflix Ribbon實作的一套用戶端負載均衡的工具。
先前工程項目沒有引入spring-cloud-starter-ribbon也可以使用ribbon。
<dependency>
<groupld>org.springframework.cloud</groupld>
<artifactld>spring-cloud-starter-netflix-ribbon</artifactid>
</dependency>
這是因為spring-cloud-starter-netflix-eureka-client自帶了spring-cloud-starter-ribbon引用。
@Configuration
public class ApplicationConfig {
@Bean
@LoadBalanced //加上此注解即可實作負載均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
RestTemplate
getForObject() / getForEntity() - GET請求方法
- getForObject():傳回對象為響應體中資料Json資料。
- getForEntity():傳回對象為ResponseEntity對象,包含了響應中的一些重要資訊,比如響應頭、響應狀态碼、響應體等。
@GetMapping("/consumer/entityGet")
public CommonResult<Payment> EntityGet(int id){
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/get?id="+id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()){
System.out.println(entity.getStatusCode()); //擷取狀态碼
System.out.println(entity.getHeaders()); //擷取頭資訊
return entity.getBody();
}else{
return new CommonResult<>(entity.getStatusCodeValue(),"操作失敗");
}
}
負載均衡規則
lRule:根據特定算法中從服務清單中選取一個要通路的服務
RoundRobinRule | 輪詢 (預設) |
---|---|
RandomRule | 随機 |
RetryRule | 先按照RoundRobinRule的政策擷取服務,如果擷取服務失敗則在指定時間内會進行重試 |
WeightedResponseTimeRule | 對RoundRobinRule的擴充,響應速度越快的執行個體選擇權重越大,越容易被選擇 |
BestAvailableRule | 會先過濾掉由于多次通路故障而處于斷路器跳閘狀态的服務,然後選擇一個并發量最小的服務 |
AvailabilityFilteringRule | 先過濾掉故障執行個體,再選擇并發較小的執行個體 |
ZoneAvoidanceRule | 預設規則,複合判斷server所在區域的性能和server的可用性選擇伺服器 |
規則替換
這個自定義配置類不能放在**@ComponentScan**所掃描的目前包下以及子包下,
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule(); //定義為随機
}
}
主啟動類
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class) //加上此注解實作規則替換
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
OpenFeign
Feign能幹什麼
- Feign旨在使編寫Java Http用戶端變得更容易。
- 通過feign隻需要定義服務綁定接口且以聲明式的方法,優雅而簡單的實作了服務調用。
OpenFeign
- OpenFeign是Spring Cloud在Feign的基礎上支援了SpringMVC的注解,如@RequesMapping等等
- OpenFeign的@Feignclient可以解析SpringMVC的@RequestMapping注解下的接口,并通過動态代理的方式産生實作類,實作類中做負載均衡并調用其他服務。
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-feign-order80</artifactId>
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基礎通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
服務調用
#yml配置檔案
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
//主啟動
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
業務類
- 業務邏輯接口[email protected]配置調用provider服務
- 建立PaymentFeignService接口并新增注解@FeignClient
Service層
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") //此處是服務注冊中心-消費端的名字
public interface PaymentFeignService
{
@GetMapping(value = "/payment/get/{id}") //此處的Mapping需要和 服務端的Mapping相同
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
controller層
@RestController
@Slf4j
public class OrderFeignController
{
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
return paymentFeignService.getPaymentById(id);
}
}
- Feign自帶負載均衡配置項
- 接口的Mapping需要和 服務端的Mapping相同
逾時
- 當Feign通路服務端時,預設時間為1秒
- 當服務端的相應時間超過時,便會報逾時異常
YML檔案裡需要開啟OpenFeign用戶端逾時控制
#設定feign用戶端逾時時間(OpenFeign預設支援ribbon)(機關:毫秒)
ribbon:
#指的是建立連接配接後從伺服器讀取到可用資源所用的時間
ReadTimeout: 5000
#指的是建立連接配接所用的時間,适用于網絡狀況正常的情況下,兩端連接配接所用的時間
ConnectTimeout: 5000
兜底方法
- 當請求不到服務提供者的方法時,此時我們需要配置兜底方法
service接口
@FeignClient(value = "nacos-payment-provider",fallback = PaymentServiceImpl.class) //使用fallback配置實作兜底方法
public interface PaymentService {
@GetMapping("/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id);
}
實作類
@Component
public class PaymentServiceImpl implements PaymentService {
@Override
public String getPayment(Integer id) {
return "方法出錯啦 老鐵";
}
}
日志
Feign提供了日志列印功能,我們可以通過配置來調整日恙級别,進而了解Feign 中 Http請求的細節。
日志級别
- NONE:預設的,不顯示任何日志;
- BASIC:僅記錄請求方法、URL、響應狀态碼及執行時間;
- HEADERS:除了BASIC中定義的資訊之外,還有請求和響應的頭資訊;
- FULL:除了HEADERS中定義的資訊之外,還有請求和響應的正文及中繼資料。
配置日志bean
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
YML檔案裡需要開啟日志的Feign用戶端
logging:
level:
# feign日志以什麼級别監控哪個接口
com.lun.springcloud.service.PaymentFeignService: debug
服務降級
Hystrix
Hystrix是一個用于處理分布式系統的延遲和容錯的開源庫,在分布式系統裡,許多依賴不可避免的會調用失敗,比如逾時、異常等,Hystrix能夠保證在一個依賴出問題的情況下,不會導緻整體服務失敗,避免級聯故障,以提高分布式系統的彈性。
服務降級
伺服器忙,請稍後再試,不讓用戶端等待并立刻傳回一個友好提示,fallback
哪些情況會出發降級
- 程式運作導常
- 逾時
- 服務熔斷觸發服務降級
- 線程池/信号量打滿也會導緻服務降級
<?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>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-hystrix-payment8001</artifactId>
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
逾時導緻伺服器變慢(轉圈) - 逾時不再等待
出錯(當機或程式運作出錯) - 出錯要有兜底
解決:
- 對方服務(8001)逾時了,調用者(80)不能一直卡死等待,必須有服務降級。
- 對方服務(8001)down機了,調用者(80)不能一直卡死等待,必須有服務降級。
- 對方服務(8001)OK,調用者(80)自己出故障或有自我要求(自己的等待時間小于服務提供者),自己處理降級。
需要在主啟動類添加**@EnableHystrix** 注解
- 然後對可能出現逾時或錯誤的方法進行服務降級
- 無論該方法逾時還是**發生異常 **都會調用指定的fallback方法
@Override
@HystrixCommand(fallbackMethod = "payment_Timeout",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_error(int id) {
// if(id==10){
// throw new MyException(444,"asd");
// }
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "線程:"+Thread.currentThread().getName()+" \t error"+id;
}
public String payment_Timeout(int id){
return "線程:"+Thread.currentThread().getName()+" \t error"+id+" time out";
}
消費端降級
當使用feign進行服務調用時
#開啟
feign:
hystrix:
enabled: true
@SpringBootApplication
@EnableFeignClients
@EnableHystrix//添加到此處
public class OrderHystrixMain80{
public static void main(String[] args){
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
@GetMapping("/feign/payment/hystrix/error/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="15000")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
return paymentFeignService.paymentInfo_TimeOut(id);
}
//降級方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "系統繁忙";
}
全局配置降級
- 除了個别重要核心業務有專屬,其它普通的可以通過@DefaultProperties(defaultFallback = “”)統一跳轉到統一處理結果頁面
- 通用的和獨享的各自分開,避免了代碼膨脹,合理減少了代碼量
- 全局配置需要 方法與 指定Fallback 方法 傳回類型一緻
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //使用此注解指定Fallback方法
public class OrderFeignController {
---
@GetMapping("/consumer/get")
@HystrixCommand //需要調用全局配置時 使用此注解
public CommonResult getPayment(){
int a=1/0;
return paymentFeignService.discovery();
}
// 下面是全局fallback方法
public CommonResult payment_Global_FallbackMethod()
{
return new CommonResult(444,"系統錯誤");
}
@GetMapping("/feign/payment/hystrix/error/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = { //指定降級方法時調用此注解
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="15000")
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
return paymentFeignService.paymentInfo_TimeOut(id);
}
//降級方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "系統繁忙";
}
}
另外 當服務端當機時,我們可以通過Feign的接口去實作對接口的兜底方法
@Service
public class PaymentFeignServiceImpl implements PaymentFeignService { //實作接口
@Override
public CommonResult discovery() {
return new CommonResult(444,"無法調用該服務");
}
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall paymentInfo_OK";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall paymentInfo_TimeOut";
}
}
實作類
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE",fallback = PaymentFeignServiceImpl.class) //首選會調取注冊中心的服務方法,若當機,便選擇實作類方法
public interface PaymentFeignService {
@GetMapping("/discovery")
public CommonResult discovery();
@GetMapping("/payment/hystrix/ok/{id}")
String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/error/{id}")
String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
服務熔斷
熔斷機制是應對雪崩效應的一種微服務鍊路保護機制。當扇對外連結路的某個微服務出錯不可用或者響應時間太長時,會進行服務的降級,進而熔斷該節點微服務的調用,快速傳回錯誤的響應資訊。當檢測到該節點微服務調用響應正常後,恢複調用鍊路。
發生熔斷
- 達到請求次數 (預設10s内 20次請請求)
- 超過允許的錯誤的百分比 (預設超過50%失敗)
- 達到以上門檻值,熔斷器便會打開
- 當打開時,所有的請求都不會進行轉發
- 一段時間之後(預設是5秒),這個時候斷路器是半開狀态,會讓其中一個請求進行轉發。如果成功,斷路器會關閉,若失敗,繼續開啟。
//=====服務熔斷
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否開啟斷路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 請求次數
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 時間視窗期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失敗率達到多少後跳閘
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
if(id < 0) {
throw new RuntimeException("******id 不能負數");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"調用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
return "id 不能負數 id: " +id;
}
全部配置
@HystrixCommand(fallbackMethod = "fallbackMethod",
groupKey = "strGroupCommand",
commandKey = "strCommand",
threadPoolKey = "strThreadPool",
commandProperties = {
// 設定隔離政策,THREAD 表示線程池 SEMAPHORE:信号池隔離
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 當隔離政策選擇信号池隔離的時候,用來設定信号池的大小(最大并發數)
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 配置指令執行的逾時時間
@HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
// 是否啟用逾時時間
@HystrixProperty(name = "execution.timeout.enabled", value = "true"),
// 執行逾時的時候是否中斷
@HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),
// 執行被取消的時候是否中斷
@HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
// 允許回調方法執行的最大并發數
@HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
// 服務降級是否啟用,是否執行回調函數
@HystrixProperty(name = "fallback.enabled", value = "true"),
// 是否啟用斷路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
// 該屬性用來設定在滾動時間窗中,斷路器熔斷的最小請求數。例如,預設該值為 20 的時候,如果滾動時間窗(預設10秒)内僅收到了19個請求, 即使這19個請求都失敗了,斷路器也不會打開。
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),
// 該屬性用來設定在滾動時間窗中,表示在滾動時間窗中,在請求數量超過 circuitBreaker.requestVolumeThreshold 的情況下,如果錯誤請求數的百分比超過50, 就把斷路器設定為 "打開" 狀态,否則就設定為 "關閉" 狀态。
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
// 該屬性用來設定當斷路器打開之後的休眠時間窗。 休眠時間窗結束之後,會将斷路器置為 "半開" 狀态,嘗試熔斷的請求指令,如果依然失敗就将斷路器繼續設定為 "打開" 狀态,如果成功就設定為 "關閉" 狀态。
@HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
// 斷路器強制打開
@HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
// 斷路器強制關閉
@HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
// 滾動時間窗設定,該時間用于斷路器判斷健康度時需要收集資訊的持續時間
@HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),
// 該屬性用來設定滾動時間窗統計名額資訊時劃分"桶"的數量,斷路器在收集名額資訊的時候會根據設定的時間窗長度拆分成多個 "桶" 來累計各路徑成本,每個"桶"記錄了一段時間内的采集名額。
// 比如 10 秒内拆分成 10 個"桶"收集這樣,是以 timeinMilliseconds 必須能被 numBuckets 整除。否則會抛異常
@HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
// 該屬性用來設定對指令執行的延遲是否使用百分位數來跟蹤和計算。如果設定為 false, 那麼所有的概要統計都将傳回 -1。
@HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
// 該屬性用來設定百分位統計的滾動視窗的持續時間,機關為毫秒。
@HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
// 該屬性用來設定百分位統計滾動視窗中使用 “ 桶 ”的數量。
@HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
// 該屬性用來設定在執行過程中每個 “桶” 中保留的最大執行次數。如果在滾動時間窗内發生超過該設定值的執行次數,
// 就從最初的位置開始重寫。例如,将該值設定為100, 滾動視窗為10秒,若在10秒内一個 “桶 ”中發生了500次執行,
// 那麼該 “桶” 中隻保留 最後的100次執行的統計。另外,增加該值的大小将會增加記憶體量的消耗,并增加排序百分位數所需的計算時間。
@HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),
// 該屬性用來設定采集影響斷路器狀态的健康快照(請求的成功、 錯誤百分比)的間隔等待時間。
@HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
// 是否開啟請求緩存
@HystrixProperty(name = "requestCache.enabled", value = "true"),
// HystrixCommand的執行和事件是否列印日志到 HystrixRequestLog 中
@HystrixProperty(name = "requestLog.enabled", value = "true"),
},
threadPoolProperties = {
// 該參數用來設定執行指令線程池的核心線程數,該值也就是指令執行的最大并發量
@HystrixProperty(name = "coreSize", value = "10"),
// 該參數用來設定線程池的最大隊列大小。當設定為 -1 時,線程池将使用 SynchronousQueue 實作的隊列,否則将使用 LinkedBlockingQueue 實作的隊列。
@HystrixProperty(name = "maxQueueSize", value = "-1"),
// 該參數用來為隊列設定拒絕門檻值。 通過該參數, 即使隊列沒有達到最大值也能拒絕請求。
// 該參數主要是對 LinkedBlockingQueue 隊列的補充,因為 LinkedBlockingQueue 隊列不能動态修改它的對象大小,而通過該屬性就可以調整拒絕請求的隊列大小了。
@HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
}
)
public String doSomething() {
...
}
Hystrix圖形化
注冊端加入依賴
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 9001
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001
{
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class, args);
}
}
啟動後通路http://localhost:9001/hystrix
服務端
加入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixMain8001
{
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class, args);
}
/**
*此配置是為了服務監控而配置,與服務容錯本身無關,springcloud更新後的坑
*ServletRegistrationBean因為springboot的預設路徑不是"/hystrix.stream",
*隻要在自己的項目裡配置上下面的servlet就可以了
*否則,Unable to connect to Command Metric Stream 404
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
點選按鈕, 即可檢視降級資訊
服務網關
GetWay
- Gateway是在Spring生态系統之上建構的API網關服務,基于Spring 5,Spring Boot 2和Project Reactor等技術。
- Gateway旨在提供一種簡單而有效的方式來對API進行路由,以及提供一些強大的過濾器功能,例如:熔斷、限流、重試等
作用
- 反向代理
- 鑒權
- 流量控制
- 熔斷
- 日志監控
- …
微服務架構中網關的位置
項目搭建
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-gateway-gateway9527</artifactId>
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基礎配置類-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務清單内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
主啟動
@SpringBootApplication
@EnableEurekaClient
public class GetMain9527 {
public static void main(String[] args) {
SpringApplication.run(GetMain9527.class,args);
}
}
配置網關
方法1.配置yml
server:
port: 9527
spring:
application:
name: cloud-gateway
###########################################3
cloud:
gateway:
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://localhost:8001 #比對後提供服務的路由位址
#uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/payment/circuit/** # 斷言,路徑相比對的進行路由
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: http://localhost:8001 #比對後提供服務的路由位址
#uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/discovery # 斷言,路徑相比對的進行路由
############################################
eureka:
instance:
hostname: cloud-gateway-service
client: #服務提供者provider注冊進eureka服務清單内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
分别調用
- http://localhost:8001/payment/circuit/1
- http://localhost:9527/payment/circuit/1
可實作9527網關通路8001的 路由比對
方法2.配置config
@Configuration
public class GetWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_csdn",
r -> r.path("/csdn")
.uri("https://blog.csdn.net/nate_18163126")).build();
return routes.build();
}
@Bean
public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
routes.route("path_route_bli",
r -> r.path("/get")
.uri("http://localhost:8001/get")).build();
return routes.build();
}
}
動态路由
預設情況下Gateway會根據注冊中心注冊的服務清單,以注冊中心上微服務名為路徑建立動态路由進行轉發,進而實作動态路由的功能
服務端需要加入
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: payment_routh #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
#uri: http://localhost:8001
uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/payment/circuit/** # 斷言,路徑相比對的進行路由
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://localhost:8001
uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/discovery # 斷言,路徑相比對的進行路由
- 将url改為注冊中心的服務名,即可實作負載均衡
斷言
- 斷言:predicates
- Predicate就是為了實作一組比對規則,讓請求過來找到對應的Route進行處理。
常用的Route種類
- The After Route Predicate Factory
- The Before Route Predicate Factory
- The Between Route Predicate Factory
- The Cookie Route Predicate Factory
- The Header Route Predicate Factory
- The Host Route Predicate Factory
- The Method Route Predicate Factory
- The Path Route Predicate Factory
- The Query Route Predicate Factory
- The RemoteAddr Route Predicate Factory
- The weight Route Predicate Factory
After路由斷言
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
routes:
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/discovery # 斷言,路徑相比對的進行路由
- After=2021-07-11T16:04:57.939+08:00[Asia/Shanghai] # 這個時間後才能起效
時間戳獲得方式
public static void main(String[] args) {
ZonedDateTime time=ZonedDateTime.now();
System.out.println(time);
}
Before同理
Between配置
spring:
cloud:
gateway:
routes:
- id: between_route
uri: https://example.org
# 兩個時間點之間
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
Cookie配置
- 必須要帶上 username為zcj的cookie才能通路
spring:
cloud:
gateway:
routes:
- id: payment_routh2 #payment_route #路由的ID,沒有固定規則但要求唯一,建議配合服務名
# uri: http://localhost:8001 #比對後提供服務的路由位址
uri: lb://cloud-payment-service #比對後提供服務的路由位址
predicates:
- Path=/discovery # 斷言,路徑相比對的進行路由
- Cookie=username, zcj #必須要帶上 username為zcj的cookie才能通路
通路
curl http://localhost:9527/discovery --cookie "username=zcj" #cmd
{"code":200,"message":"請求成功8001","data":["cloud-gateway","cloud-payment-service"]}
Header配置
predicates:
- Path=/discovery # 斷言,路徑相比對的進行路由
- Header=count, \d+ # 需要 header攜帶的count為數字
Method配置
predicates:
- Path=/discovery # 斷言,路徑相比對的進行路由
# - Cookie=username, zcj
# - Header=count, \d+
- Method=GET
Filter
- 路由過濾器可用于修改進入的HTTP請求和傳回的HTTP響應,路由過濾器隻能指定路由進行使用。Spring Cloud Gateway内置了多種路由過濾器,他們都由GatewayFilter的工廠類來産生。
@Component
@Slf4j
public class GateWayFilter implements GlobalFilter, Ordered {
//過濾器配置
//必須攜帶name的參數的請求才能通過過濾器
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("---------"+new Date());
String name = exchange.getRequest().getQueryParams().getFirst("name");
if (name==null){
log.info("----------非法使用者");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
服務配置
Config
配置中心
SpringCloud Config為微服務架構中的微服務提供集中化的外部配置支援,配置伺服器為各個不同微服務應用的所有環境提供了一個中心化的外部配置。
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-center-3344</artifactId>
<dependencies>
<!--添加消息總線RabbitMQ支援-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
主啟動
@SpringBootApplication
@EnableConfigServer
public class ConfigMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigMain3344.class,args);
}
}
Github上設定配置檔案
- application-dev.yml
config:
info: "master branch,springcloud-config/config-dev.yml version=7"
- application-prod.yml
config:
info: "master branch,springcloud-config/config-prod.yml version=1"
- config-test.yml
config:
info: "master branch,springcloud-config/config-test.yml version=1"
通路網址:http://localhost:3344/master/application-dev.yml 即可獲得配置檔案
- 格式:/ {label} / {application}-{profile}.yml
用戶端
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-3355</artifactId>
<dependencies>
<!--添加消息總線RabbitMQ支援-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 負責讀取3344配置中心的配置檔案
配置檔案
server:
port: 3355
spring:
application:
name: config-client
cloud:
#Config用戶端配置
config:
label: master #分支名稱
name: config #配置檔案名稱
profile: dev #讀取字尾名稱 上述3個綜合:master分支上config-dev.yml的配置檔案被讀取http://config-3344.com:3344/master/config-dev.yml
uri: http://localhost:3344 #配置中心位址k
#服務注冊到eureka位址
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka
主啟動
@EnableEurekaClient
@SpringBootApplication
public class ConfigClientMain3355
{
public static void main(String[] args) {
SpringApplication.run(ConfigClientMain3355.class, args);
}
}
業務類
@RestController
@RefreshScope
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;
@GetMapping("/configInfo")
public String getConfigInfo()
{
return configInfo;
}
}
測試
啟動Config配置中心3344微服務并自測
- http://config-3344.com:3344/master/config-prod.yml
- http://config-3344.com:3344/master/config-dev.yml
啟動3355作為Client準備通路
- http://localhost:3355/configlnfo
動态重新整理(用戶端)
當我們修改遠端Github配置倉庫後
- 重新整理3344,發現ConfigServer配置中心響應 (需要等待)
- 重新整理3355,發現ConfigClient用戶端沒有任何響應
- 3355沒有變化除非自己重新開機或者重新加載
為了避免每次更新配置都要重新開機用戶端微服務3355
- 加入依賴配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 加入配置檔案
# 暴露監控端點
management:
endpoints:
web:
exposure:
include: "*"
- 加入@RefreshScope
@RestController
@RefreshScope//<-----
public class ConfigClientController
{
...
}
- 通過cmd 發送
curl -X POST "http://localhost:3355/actuator/refresh"
即可完成 用戶端3355的配置重新整理
BUS
Spring Cloud Bus 配合Spring Cloud Config 使用可以實作配置的動态重新整理。
<?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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-config-client-3366</artifactId>
<dependencies>
<!--添加消息總線RabbitMQ支援-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 溜了溜了 (●ˇ∀ˇ●)
SpringCloud Alibaba
Spring Cloud Alibaba 緻力于提供微服務開發的一站式解決方案。此項目包含開發分布式應用微服務的必需元件,友善開發者通過 Spring Cloud 程式設計模型輕松使用這些元件來開發分布式應用服務。
能幹嘛
- Sentinel:把流量作為切入點,從流量控制、熔斷降級、系統負載保護等多個次元保護服務的穩定性。
- Nacos:一個更易于建構雲原生應用的動态服務發現、配置管理和服務管理平台。
- RocketMQ:一款開源的分布式消息系統,基于高可用分布式叢集技術,提供低延時的、高可靠的消息釋出與訂閱服務。
- Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 架構。
- Seata:阿裡巴巴開源産品,一個易于使用的高性能微服務分布式事務解決方案。
- Alibaba Cloud OSS: 阿裡雲對象存儲服務(Object Storage Service,簡稱 OSS),是阿裡雲提供的海量、安全、低成本、高可靠的雲存儲服務。您可以在任何應用、任何時間、任何地點存儲和通路任意類型的資料。
- Alibaba Cloud SchedulerX: 阿裡中間件團隊開發的一款分布式任務排程産品,提供秒級、精準、高可靠、高可用的定時(基于 Cron 表達式)任務排程服務。
- Alibaba Cloud SMS: 覆寫全球的短信服務,友好、高效、智能的互聯化通訊能力,幫助企業迅速搭建客戶觸達通道。
Nacos
介紹
- 一個更易于建構雲原生應用的動态服務發現、配置管理和服務管理平台。
- Nacos: Dynamic Naming and Configuration Service
- Nacos就是注冊中心+配置中心的組合 -> Nacos = Eureka+Config+Bus
安裝
- 從nacos官網 https://nacos.io/zh-cn/index.html 下載下傳安裝包
-
tar -xvf nacos-server-1.1.4.tar.gz 解壓
啟動:
cd /nacos/bin #nacos bin目錄
sh startup.sh -m standalone
通路:localhost:8848/nacos,輸入預設賬号密碼:nacos,nacos
服務中心
生産者
父pom
<dependencyManagement>
<dependencies>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
依賴
<?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>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-provider-payment9001</artifactId>
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
yml
server:
port: 9001
spring:
application:
name: nacos-payment-provider #配置生産者名稱
cloud:
nacos:
discovery:
server-addr: 121.43.55.*:8848 #配置Nacos位址
management:
endpoints:
web:
exposure:
include: '*'
主啟動
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class,args);
}
}
業務
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id){
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}
- 再配置相同的生産者9002
消費者
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>LearnCloud</artifactId>
<groupId>com.lun.springcloud</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-consumer-nacos-order83</artifactId>
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.lun.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
<!-- SpringBoot整合Web元件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
yml配置檔案
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 121.43.55.*:8848
#消費者通路服務名稱
service-url:
nacos-user-service: http://nacos-payment-provider
主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain83.class,args);
}
}
Config
@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced //負載均衡
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
調取服務
@RestController
@Slf4j
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}") //讀取配置檔案服務名
private String serverUrl;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id)
{
return restTemplate.getForObject(serverUrl+"/payment/nacos/"+id,String.class); //調取服務
}
}
- 通路http://localhost:83/consumer/payment/nacos/13
- 即可實作調取服務提供者,并實作負載均衡
配置中心
Nacos同springcloud-config一樣,在項目初始化時,要保證先從配置中心進行配置拉取,拉取配置之後,才能保證項目的正常啟動。
springboot中配置檔案的加載是存在優先級順序的,bootstrap優先級高于application
配置中心讀取格式:
${spring.application.name)}-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
{項目名}-{運作環境}.{配置檔案格式}
nacos-config-client-dev.yaml
添加、讀取配置
配置新增
Nacos界面配置對應 - 設定DataId
依賴
<?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>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-config-nacos-client3377</artifactId>
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基礎配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
*配置檔案(bootstrap.yml ~~~~~)*
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
config:
server-addr: 121.43.55.*:8848 #Nacos作為配置中心位址
file-extension: yaml #指定yaml格式的配置
profiles:
active: dev # 表示開發環境
# group: DEV_GROUP
# namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4
配置檔案(application.yml)
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: 121.43.55.*:8848 #Nacos服務注冊中心位址
主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377
{
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
讀取配置資訊
@RestController
@RefreshScope //支援Nacos的動态重新整理功能。
public class ConfigClientController
{
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
- 通路控制層http://localhost:3377/config/info,即可讀取配置檔案
- 配置@RefreshScope注解,修改配置中心配置檔案,即可實作實時重新整理,
命名空間、分組
分組
通過Group實作環境區分 - 建立Group
在nacos圖形界面控制台上面建立配置檔案DatalD
bootstrap+application
在config下增加一條group的配置即可。可配置為DEV_GROUP或TEST GROUP
命名空間
建立dev/test的Namespace
回到服務管理-服務清單檢視
按照域名配置填寫
配置檔案
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服務注冊中心位址
config:
server-addr: localhost:8848 #Nacos作為配置中心位址
file-extension: yaml #指定yaml格式的配置
group: DEV_GROUP
namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4 #<------------指定namespace
持久化(mysql)
Nacos預設自帶的是嵌入式資料庫derby,nacos的pom.xml中可以看出。
derby到mysql切換配置步驟:
- nacos-server-1.1.4\nacos\conf錄下找到nacos-mysql.sql檔案,執行腳本。
- nacos-server-1.1.4\nacos\conf目錄下找到application.properties,添加以下配置(按需修改對應值)。
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=1234
即可實作服務注冊mysql持久化
叢集
- 隻有一台伺服器~
- 溜了、溜了
Sentinel
Sentinel 是什麼?
随着微服務的流行,服務和服務之間的穩定性變得越來越重要。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個次元保護服務的穩定性。
Sentinel 的主要特性:
Hystrix與Sentinel比較:
- Hystrix
- 需要我們程式員自己手工搭建監控平台
- 沒有一套web界面可以給我們進行更加細粒度化得配置流控、速率控制、服務熔斷、服務降級
- Sentinel
- 單獨一個元件,可以獨立出來。
- 直接界面化的細粒度統一配置。
下載下傳
- https://github.com/alibaba/Sentinel/releases
- 下載下傳到本地sentinel-dashboard-1.7.0.jar
運作
- Java 8 環境
- 8080端口不能被占用
java -jar sentinel-dashboard-1.7.0.jar
登陸
- http://localhost:8080/
- 登入賬号密碼均為sentinel
服務監控
建立工程 - cloudalibaba-sentinel-service8401
依賴
<?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>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloudalibaba-sentinel-service8401</artifactId>
<dependencies>
<dependency><!-- 引入自己定義的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 後續做持久化用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringBoot整合Web元件+actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
配置檔案
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: 121.43.55.*:8848 #Nacos服務注冊中心位址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard位址 嘗試使用linux啟動 結果方法監控為空 隻好在本地注冊了
port: 8720
management:
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true # 激活Sentinel對Feign的支援
主啟動
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
控制層
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
log.info(Thread.currentThread().getName()+"\t"+"...testB");
return "------testB";
}
}
啟動8401微服務後檢視sentienl控制台
- 剛啟動,空空如也,啥都沒有
- Sentinel采用的懶加載
- 執行一次通路即可
- http://localhost:8401/testA
- http://localhost:8401/testB
服務限流
資源名:唯一名稱,預設請求路徑。
針對來源:Sentinel可以針對調用者進行限流,填寫微服務名,預設default(不區分來源)。
門檻值類型/單機門檻值
- QPS(每秒鐘的請求數量)︰當調用該API的QPS達到門檻值的時候,進行限流。
- 線程數:當調用該API的線程數達到門檻值的時候,進行限流。
流控模式
- 直接:API達到限流條件時,直接限流。
- 關聯:當關聯的資源達到門檻值時,就限流自己。
- 鍊路:隻記錄指定鍊路上的流量(指定資源從入口資源進來的流量,如果達到門檻值,就進行限流)【API級别的針對來源】。
關聯
- 當自己關聯的資源達到門檻值時,就限流自己
- 當與A關聯的資源B達到閥值後,就限流A自己(B惹事,A挂了)
設定testA
當關聯資源/testB的QPS閥值超過1時,就限流/testA的Rest通路位址,當關聯資源到門檻值後限制配置好的資源名。
流控效果
- 快速失敗:直接失敗,抛異常。
- Warm up:根據Code Factor(冷加載因子,預設3)的值,從門檻值/codeFactor,經過預熱時長,才達到設定的QPS門檻值。
- 排隊等待:勻速排隊,讓請求以勻速的速度通過,門檻值類型必須設定為QPS,否則無效。
WarmUp
可以将某個方法設的初始QPS設定為門檻值的1/3 (預設為1/3),當到達高通路階段時,再開放到最大QPS階段
- 案例,閥值為10+預熱時長設定5秒。
- 系統初始化的閥值為10/ 3約等于3,即閥值剛開始為3;然後過了5秒後閥值才慢慢升高恢複到10
應用場景
如:秒殺系統在開啟的瞬間,會有很多流量上來,很有可能把系統打死,預熱方式就是把為了保護系統,可慢慢的把流量放進來,慢慢的把閥值增長到設定的閥值。
排隊等待
勻速排隊,讓請求以均勻的速度通過,閥值類型必須設成QPS,否則無效。
設定:/testA每秒1次請求,超過的話就排隊等待,等待的逾時時間為20000毫秒。
服務降級
RT(平均響應時間,秒級)
- 平均響應時間 超出門檻值 且 在時間視窗内通過的請求>=5,兩個條件同時滿足後觸發降級。
- 視窗期過後關閉斷路器。
- RT最大4900(更大的需要通過-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)。
異常比列(秒級)
- QPS >= 5且異常比例(秒級統計)超過門檻值時,觸發降級;時間視窗結束後,關閉降級 。
異常數(分鐘級)
- 異常數(分鐘統計)超過門檻值時,觸發降級;時間視窗結束後,關閉降級
RT
@GetMapping("/testD")
public String testD() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 測試RT");
}
- 預設當某個方法請求大于5,且平均處理時間大于200ms時,便發生服務降級
異常比例
@GetMapping("/testD")
public String testD() {
log.info("testD 異常比例");
int age = 10/0;
return "------testD";
}
- 預設當某個方法請求大于5,且發送異常的比例超過0.2,便發生服務降級
異常數
- 異常數是按照分鐘統計的,時間視窗一定要大于等于60秒。
- 達到指定的異常數,便發生熔斷降級
熱點key
何為熱點?熱點即經常通路的資料。很多時候我們希望統計某個熱點資料中通路頻次最高的 Top K 資料,并對其通路進行限制。比如:
- 商品 ID 為參數,統計一段時間内最常購買的商品 ID 并進行限制
- 使用者 ID 為參數,針對一段時間内頻繁通路的使用者 ID 進行限制
@RestController
@Slf4j
public class FlowLimitController
{
...
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler/*兜底方法*/ = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
return "------testHotKey";
}
/*兜底方法*/
public String deal_testHotKey (String p1, String p2, BlockException exception) {
return "------deal_testHotKey,o(╥﹏╥)o"; //sentinel系統預設的提示:Blocked by Sentinel (flow limiting)
}
}
- 我們可以指定某個參數進行QPS限制
- 方法testHotKey裡面第一個參數隻要QPS超過每秒1次,馬上降級處理
- 資源名必須相同
參數例外項
- 普通 - 超過1秒鐘一個後,達到門檻值1後馬上被限流
- 我們期望p1參數當它是某個特殊值時,它的限流值和平時不一樣 (可以設定例外)
- 特例 - 假如當p1的值等于5時,它的門檻值可以達到200
系統規則
系統保護規則是從應用級别的入口流量進行控制,從單台機器的 load、CPU 使用率、平均 RT、入口 QPS 和并發線程數等幾個次元監控應用名額,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。
- 在系統規則可以設定整個項目的全局限制
- 包括cpu、線程數、QPS等
@SentinelResource注解配置
配置熔斷方法
- @SentinelResource的value可以為請求路徑,也可以為指定key
- 需要與sentinel配置中心key保持一緻,并且唯一
@GetMapping("/testA")
@SentinelResource(value = "testA",blockHandler= "deal_A")
public String testA()
{
return "------testA";
}
public String deal_A(BlockException exception) //此處需要攜帶BlockException
{
return "挂了";
}
自定義限流方法(blockHandler)
- 可以抽取指定的類裡的方法作為熔斷方法
- 需要攜帶BlockException
- 方法為靜态方法
public class HandlerException {
public static String handlerException(BlockException exception) {
return "項目已經挂了";
}
public static String handlerException2(BlockException exception) {
return "項目已經挂了2";
}
}
方法配置
@GetMapping("/testB")
@SentinelResource(value = "testB", //指明key
blockHandlerClass = HandlerException.class, //指定自定義類
blockHandler = "handlerException") //指定類裡的方法、此處也可以直接指定方法名
public String testB()
{
return "------testB";
}
自定義處理異常(fallback)
- Fallback指當該方法發生異常時,跳轉自定義處理的方法
@GetMapping(value = "/consumer/payment/nacos/{id}")
@SentinelResource(value = "paymentInfo",fallback = "handlerFallback")
public String paymentInfo(@PathVariable("id") Long id)
{
if (id<0){
throw new NullPointerException ("NullPointerException,該ID沒有對應記錄,空指針異常");
}else{
return restTemplate.getForObject(serverUrl+"/payment/nacos/"+id,String.class);
}
}
public String handlerFallback(@PathVariable Long id,Throwable e) {
return "發生兜底異常handlerFallback,exception内容 "+id+" "+e;
}
- 若fallback和blockHandler同時配置時,并且同時可以觸發
- 會優先觸發blockHandler,它會使請求進不了方法
忽略異常(exceptionsToIgnore)
- exceptionsToIgnore可以使fallbak忽略某個異常
@GetMapping(value = "/consumer/payment/nacos/{id}")
@SentinelResource(value = "paymentInfo",
fallback = "handlerFallback",
exceptionsToIgnore = {IllegalArgumentException.class})
public String paymentInfo(@PathVariable("id") Long id)
{
if (id<0){
throw new NullPointerException ("NullPointerException,該ID沒有對應記錄,空指針異常");
}else{
return restTemplate.getForObject(serverUrl+"/payment/nacos/"+id,String.class);
}
}
public String handlerFallback(@PathVariable Long id,Throwable e) {
return "發生兜底異常handlerFallback,exception内容 "+id+" "+e;
}
持久化
- 當某個項目重新開機時,會導緻sentinel限制丢失
- 為了防止熔斷限制不丢失,需要設定持久化,将方法限制注入進nacos
添加依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 121.43.55.*:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
datasource: #<---------------------------關注點,添加Nacos資料源配置
ds1:
nacos:
server-addr: 121.43.55.*:8848 #配置持久化
dataId: cloud-alibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
service-url:
nacos-user-service: http://nacos-payment-provider
添加nacos配置檔案
[{
"resource": "paymentInfo", #限制方法的 key
"IimitApp": "default", #來源應用;
"grade": 1, #門檻值類型,0表示線程數, 1表示QPS;
"count": 1, #單機門檻值;
"strategy": 0, #流控模式,0表示直接,1表示關聯,2表示鍊路;
"controlBehavior": 0, #流控效果,0表示快速失敗,1表示Warm Up,2表示排隊等待;
"clusterMode": false #是否叢集。
}]
成功将該限制持久化
Seata
問題
- 一次業務操作需要跨多個資料源或需要跨多個系統進行遠端調用,就會産生分布式事務問題。
Seata是一款開源的分布式事務解決方案,緻力于在微服務架構下提供高性能和簡單易用的分布式事務服務。
逾時異常,加了**@GlobalTransactional**
用@GlobalTransactional标注OrderServiceImpl的create()方法。
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
...
/**
* 建立訂單->調用庫存服務扣減庫存->調用賬戶服務扣減賬戶餘額->修改訂單狀态
* 簡單說:下訂單->扣庫存->減餘額->改狀态
*/
@Override
//rollbackFor = Exception.class表示對任意異常都進行復原
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order)
{
...
}
}
還是模拟AccountServiceImpl添加逾時,下單後資料庫資料并沒有任何改變,記錄都添加不進來,達到出異常,資料庫復原的效果。
一個典型的分布式事務過程
分布式事務處理過程的一ID+三元件模型:
- Transaction ID XID 全局唯一的事務ID
- 三元件概念
- TC (Transaction Coordinator) - 事務協調者:維護全局和分支事務的狀态,驅動全局事務送出或復原。
- TM (Transaction Manager) - 事務管理器:定義全局事務的範圍:開始全局事務、送出或復原全局事務。
- RM (Resource Manager) - 資料總管:管理分支事務處理的資源,與TC交談以注冊分支事務和報告分支事務的狀态,并驅動分支事務送出或復原。
處理過程:
- TM向TC申請開啟一個全局事務,全局事務建立成功并生成一個全局唯一的XID;
- XID在微服務調用鍊路的上下文中傳播;
- RM向TC注冊分支事務,将其納入XID對應全局事務的管轄;
- TM向TC發起針對XID的全局送出或復原決議;
- TC排程XID下管轄的全部分支事務完成送出或復原請求。
對應記錄,空指針異常");
}else{
return restTemplate.getForObject(serverUrl+"/payment/nacos/"+id,String.class);
}
}
public String handlerFallback(@PathVariable Long id,Throwable e) {
return "發生兜底異常handlerFallback,exception内容 “+id+” "+e;
}
- 若fallback和blockHandler同時配置時,并且同時可以觸發
- 會優先觸發**blockHandler**,它會使請求進不了方法
**忽略異常**(exceptionsToIgnore)
- **exceptionsToIgnore**可以使fallbak忽略某個異常
```java
@GetMapping(value = "/consumer/payment/nacos/{id}")
@SentinelResource(value = "paymentInfo",
fallback = "handlerFallback",
exceptionsToIgnore = {IllegalArgumentException.class})
public String paymentInfo(@PathVariable("id") Long id)
{
if (id<0){
throw new NullPointerException ("NullPointerException,該ID沒有對應記錄,空指針異常");
}else{
return restTemplate.getForObject(serverUrl+"/payment/nacos/"+id,String.class);
}
}
public String handlerFallback(@PathVariable Long id,Throwable e) {
return "發生兜底異常handlerFallback,exception内容 "+id+" "+e;
}
持久化
- 當某個項目重新開機時,會導緻sentinel限制丢失
- 為了防止熔斷限制不丢失,需要設定持久化,将方法限制注入進nacos
添加依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 121.43.55.*:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8720
datasource: #<---------------------------關注點,添加Nacos資料源配置
ds1:
nacos:
server-addr: 121.43.55.*:8848 #配置持久化
dataId: cloud-alibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
service-url:
nacos-user-service: http://nacos-payment-provider
添加nacos配置檔案
[外鍊圖檔轉存中…(img-3tC3oPRV-1627957252903)]
[{
"resource": "paymentInfo", #限制方法的 key
"IimitApp": "default", #來源應用;
"grade": 1, #門檻值類型,0表示線程數, 1表示QPS;
"count": 1, #單機門檻值;
"strategy": 0, #流控模式,0表示直接,1表示關聯,2表示鍊路;
"controlBehavior": 0, #流控效果,0表示快速失敗,1表示Warm Up,2表示排隊等待;
"clusterMode": false #是否叢集。
}]
成功将該限制持久化
[外鍊圖檔轉存中…(img-gZNe6u2U-1627957252903)]
Seata
問題
- 一次業務操作需要跨多個資料源或需要跨多個系統進行遠端調用,就會産生分布式事務問題。
Seata是一款開源的分布式事務解決方案,緻力于在微服務架構下提供高性能和簡單易用的分布式事務服務。
逾時異常,加了**@GlobalTransactional**
用@GlobalTransactional标注OrderServiceImpl的create()方法。
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
...
/**
* 建立訂單->調用庫存服務扣減庫存->調用賬戶服務扣減賬戶餘額->修改訂單狀态
* 簡單說:下訂單->扣庫存->減餘額->改狀态
*/
@Override
//rollbackFor = Exception.class表示對任意異常都進行復原
@GlobalTransactional(name = "fsp-create-order",rollbackFor = Exception.class)
public void create(Order order)
{
...
}
}
還是模拟AccountServiceImpl添加逾時,下單後資料庫資料并沒有任何改變,記錄都添加不進來,達到出異常,資料庫復原的效果。
一個典型的分布式事務過程
分布式事務處理過程的一ID+三元件模型:
- Transaction ID XID 全局唯一的事務ID
- 三元件概念
- TC (Transaction Coordinator) - 事務協調者:維護全局和分支事務的狀态,驅動全局事務送出或復原。
- TM (Transaction Manager) - 事務管理器:定義全局事務的範圍:開始全局事務、送出或復原全局事務。
- RM (Resource Manager) - 資料總管:管理分支事務處理的資源,與TC交談以注冊分支事務和報告分支事務的狀态,并驅動分支事務送出或復原。
處理過程:
- TM向TC申請開啟一個全局事務,全局事務建立成功并生成一個全局唯一的XID;
- XID在微服務調用鍊路的上下文中傳播;
- RM向TC注冊分支事務,将其納入XID對應全局事務的管轄;
- TM向TC發起針對XID的全局送出或復原決議;
- TC排程XID下管轄的全部分支事務完成送出或復原請求。