SpringCloudAlibaba學習
Nacos學習
Naming Configuration 前兩個字母, Service
是什么?
- 一個更易于構建云原生應用的動態服務發現,配置管理和服務管理中心
- Dynamic Naming and Configuration Service
- Nacos就是注冊中心+配置中心的組合 Eureka+Config+Bus
能干嗎?
- 替代Eureka做服務注冊中心
- 替代Config做服務配置中心
去哪下?
- https://github.com/alibaba/Nacos
- https://nacos.io/zh-cn/docs/
各種注冊中心比較
| 服務注冊與發現框架 | CAP模型 | 控制臺管理 | 社區活躍度 |
|---|---|---|---|
| Eureka | AP | 支持 | 低 |
| Zookeeper | CP | 不支持 | 中 |
| Consul | CP | 支持 | 高 |
| Nacos | AP | yes | high |
安裝并允許Nacos
- 本地Jdk 8 + Maven環境
- 從官網去下載 https://github.com/alibaba/Nacos
- 在windows環境下,解壓安裝包,直接允許bin目錄下的 startup.cmd
- 運行成功之后直接訪問 http://localhost:8848/nacos 就可以看到
基于Nacos的服務提供者
- 新建一個專案,由于這個是一個微服務,所以我們可以創建一個父專案來進行統一的版本管理
<?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.atguigu</groupId>
<artifactId>cloud2021</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloudalibaba-provider-payment9001</module>
<module>cloudalibaba-provider-payment9002</module>
<module>cloudalibaba-consumer-nacos-order83</module>
<module>cloudalibaba-config-nacos-client3377</module>
</modules>
<!--統一管理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>8.0.18</mysql.version>
<druid.verison>1.1.16</druid.verison>
<mybatis.spring.boot.verison>1.3.0</mybatis.spring.boot.verison>
</properties>
<!--子模塊繼承之后,提供作用:鎖定版本+子module不用寫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>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.verison}</version>
</dependency>
<!-- mybatis-springboot整合 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.verison}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</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>
- 需要匯入nacos的包,宣告一下可以被發現
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</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>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
- 需要寫組態檔,宣告這是一個需要被管理的服務,需要被哪個地址進行管理,需要被管理哪些東西
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
- 主啟動類也要使用注解宣告一下,這個服務可以作為一個客戶端被Nacos發現
@EnableDiscoveryClient
@SpringBootApplication
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(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id)
{
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}
- 啟動這個服務提供者,發現nacos控制臺可以看到,安裝同樣的方式,我們再來一個9002的服務提供者,它們的服務名字都一樣,但是埠卻不一樣,也就是說這是兩個埠,這兩個埠都可以提供同一個服務,
- 接下來是服務消費者
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</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>
- 組態檔
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost: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);
}
}
- 在服務呼叫者這里,我們可以使用一個模板進行呼叫,這個模板去呼叫提供者的方法,首先它們應該知道提供者的服務名稱,然后還有它們的埠,自己需要準備引數,這些我們都可以獲取到,我們還需要得到回傳值,
- 由于提供者有很多個,我們需要確定到底使用的是哪一個,這也是一個問題,這個時候需要使用到負載均衡策略,
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
- 往容器中注入的這個Bean上面又宣告了LoadBalanced注解,這表明我們的RestTemplate使用了一種負載均衡策略
- 然后我們在消費者端就可以輪流呼叫提供者提供的服務了
@RestController
@Slf4j
public class OrderNacosController {
@Resource
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);
}
}
- Nacos 其實是可以支持CP或者AP的,這兩者之間可以相互切換
- C是所有節點在同一時間看到的資料是一致的,而A的定義是所有的請求都會收到回應
- 如果不需要存盤服務級別的資訊且服務實體是通過nacos-client注冊,并能夠保持心跳上報,那么就可以選擇AP默認當前主流的服務如SPring Cloud和Dubbo服務,都適合用于AP模式,AP模式為了服務的可能性而減弱了一致性,因此AP模式下只支持注冊臨時實體
- 如果需要在服務級別撰寫或存盤配置資訊,那么CP是必須的, k8s和DNS服務則適用于CP模式
- CP模式下支持注冊持久化實體,此時則是以Raft協議為集群運行模式該模式下注冊實體之前需先注冊服務,如果服務不存在,則會回傳錯誤
- curl -X PUT ‘$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP’
Nacos作為服務配置中心
我們可以在Nacos上搭建自己的組態檔,在自己的服務中宣告自己想要使用哪個即可
- 依賴引入
<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>
- 組態檔需要配置兩個
- 為什么需要兩個呢?
- Nacos 和SpringCloud-config一樣,在專案初始化時,需要先去配置中心進行配置拉取,然后才能保證專案的正常啟動
- 組態檔的加載存在優先順序, bootstrap優先級高于application
- 在遠程配置的組態檔的命名格式都需要遵循一定的規則,要不然時無法獲取到的
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服務注冊中心地址
config:
server-addr: localhost:8848 #配置中心地址
file-extension: yaml #指定yaml格式的配置
# application.yaml
spring:
profiles:
active: dev
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yml
- 主啟動類無變化
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377 {
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
- 然后我們暴露一個埠,就可以看到在遠程組態檔上面的資訊,并且我們也可以設定自動重繪,這樣當遠程修改的時候,我們這個埠可以立即看到
@RestController
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
- 主要有以下一些方面可以可以更換組態檔,
- spring.application.name 專案的名稱
- spring.profile.active 專案的環境,一般有dev test等
- spring.cloud.nacos.config.file-extension 專案的擴展檔案名
- 此外,還有group,namespace等
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服務注冊中心地址
config:
server-addr: localhost:8848 #配置中心地址
file-extension: yaml #指定yaml格式的配
group: TEST_GROUP
namespace: 76fce2f9-7d68-4729-a7d5-b6861295483d
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yml
- 以上的這些資訊可以唯一確定一個組態檔
Nacos持久化配置
這樣一個微服務專案的組態檔一般都比較重要,如果萬一掛掉了呢,所以我們需要非常謹慎的去處理配置資訊,Nacos默認自帶的嵌入式資料庫,derby,
- 我們也可以使用MySQL對組態檔進行持久化
- 在config組態檔下有sql腳本,專門建立組態檔的資料表資訊,
- 修改application.properties ,咋i最后面加上一些資訊
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://localhost:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
- 下次就可以去mysql資料庫里面找回應的訊息了,
- 如果要做持久化,我們僅僅需要一個mysql可能還不夠,我們還需要高可用,把Nacos做一個集群,一個掛了另外一個可以頂上,它們共同使用一個mysql或者一個mysql的集群,
- 我們模擬一下使用場景, 1個Nginx,+3個Nacos注冊中心 + 1個Mysql
- 我們先確定3臺Nacos機器的不同服務埠號
- 然后修改cluster.conf, 上面列出這個集群中所有的Nacos機器,
- 再撰寫 Nacos的啟動腳本 startup.sh ,使我們可以以不同的埠啟動
- 修改內容有些亂七八糟的,這里就不想再羅列出來了,等會兒說一個更加合適的方法
- 另外我們的nginx也需要進行配置一下,然后暴露出來一個埠,監聽集群里面的埠,然后負載均衡的分配給這些埠里面某一個確定的埠
upstream cluster{
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}
server{
listen 1111;
server_name localhost;
location /{
proxy_pass http://cluster;
}
- 啟動nginx和我們的集群里面的埠即可
- 我們在客戶端修改一下服務器的ip,修改為我們nginx暴露出來的埠即可
- 或者我們復制三個nacos,然后它們的cluster.xml 都是一樣的,然后再把各自的埠修改一下,全部啟動即可
Sentinel學習
Sentinel 主要提供 流量控制,熔斷降級,系統負載保護,
可以理解為和Hystrix 的功能一致,但是這個比較優秀的是它可以 進行配置,
我們都知道,一般都是約定大于配置,配置大于編碼,這就是它比較優秀的地方,可以再很多場景下面進行詳細的配置
- 服務使用中的各種問題:
- 服務雪崩
- 服務降級
- 服務熔斷
- 服務限流
- Sentinel分為兩個部分
- 核心庫,不依賴任何框架庫,能夠運行于所有Java運行環境,同時對Dubbo、 SpringCloudd等框架也有比較好的支持
- 控制臺,基于SpringBoot開發,打包后可以直接運行,不需要web服務器
- 我們去官網上面下載jar包,就可以使用java命令進行運行了,不過運行時需要注意,確保jdk8,8080埠不能被占用,然后就可以訪問了
- 假設現在nacos和sentinel都已經啟動了,現在我們每創建一個微服務里面的介面都需要被監控到,
- 需要做如下配置,我們現在創建一個8401埠的服務,看一下具體情況,
pom
<dependencies>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</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>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>
組態檔
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默認8719,假如被占用了會自動從8719開始依次+1掃描,直至找到未被占用的埠
management:
endpoints:
web:
exposure:
include: '*'
主啟動類
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
然后我們建立一個測驗的介面類進行測驗
@Slf4j
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
@GetMapping("/testD")
public String testD() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 測驗");
return "------testD";
}
}
- 由于Sentinel采用的懶加載的模式,我們需要被管理的介面除了再組態檔上宣告過以外,還需要自己訪問一次才可以進行管理,這樣也是為了節省資源吧

流控模式的詳細資訊
- 資源名,也就是相當于我們訪問的介面的名字
- 針對來源:sentinel可以針對呼叫者進行限流,填寫微服務名字,默認是default,不區分來源
- 閾值型別 :
- QPS 每秒鐘請求數量,當呼叫該api的QPS到達閾值的時候就會進行限流
- 執行緒數:當呼叫該api的執行緒數到達閾值的時候,進行限流
- 這兩個怎么區分呢, 一個主機可以有很多個執行緒,這些執行緒可以同時呼叫一個api,QPS相當于在外部進行攔截,執行緒數相當于在系統的內部進行攔截
- 是否集群:現在先不考慮集群
- 流控模式:
- 直接:api達到限流條件時,直接限流
- 關聯:當關聯的資源達到閾值時,會限流自己
- 鏈路:只記錄指定鏈路上的流量
- 流控效果
- 直接:直接失敗,拋出例外
- 預熱:閾值除以 cold Factor(默認為3),經過預熱時長后才會達到閾值,默認每秒鐘可以接收3個請求,逐步增加,經過預熱時長之后,逐步升至設定的QPS閾值
- 預熱比較適合的應用場景時在秒殺系統開啟的瞬間,會有很多流量上來,很有可能把系統打死,預熱方式就是為了保護系統,慢慢的把流量放進來,慢慢把閾值增長到設定的閾值
- 排隊等待:讓請求以均勻1的速度通過,對應的是漏桶演算法,該方式主要用于處理間隔性突發的流量,例如訊息佇列,,在某一秒有大量的請求到來,而接下來幾秒則處于空閑狀態,我們希望系統能夠在接下來的空閑時間逐漸處理這些請求,而不是一下子拒絕掉

降級規則
- 降級策略 RT 平均回應時間,超出閾值,且在時間視窗內通過的請求大于5同時滿足時觸發
- 例外比例: QPS大于5 且例外比例(秒級統計) 超過閾值,觸發降級,時間視窗結束后關閉降級
- 例外數,例外數超過閾值(分鐘統計) 觸發降級,時間視窗結束后,關閉降級
- 我們在學習Hystrix的時候,知道它時是有一個半開的狀態的,半開的狀態系統自動去檢車是否請求有例外,沒有例外就關閉斷路器恢復使用,有例外則繼續打開斷路器不可以用,但是這里是沒有半開這個狀態的

熱點引數限流
- 熱點,就是我們經常訪問的資料,很多時候我們希望統計某一個熱點資料中訪問頻次最高的資料,并對其訪問進行限制,
- 熱點引數限流會統計傳入引數中的熱點引數,并根據配置的限流閾值與模式,對包含熱點引數的資源呼叫進行限流,熱點引數限流可以看作是一種特殊的流量控制,僅對包含熱點引數的資源呼叫生效
- Sentinel利用LRU策略統計最佳最常訪問的熱點引數,結合令牌桶演算法來進行引數級別的流量防控,熱點引數限流支持集群模式
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}
- 我們這樣寫我們的介面,然后配置熱點規則,當第一個引數的QPS超過1的時候就會進行報錯,這個時候如果還是要繼續訪問就會回傳錯誤頁面,這個錯誤頁面看著很不美觀,我們也可以自己設定
@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) {
//int age = 10/0;
return "------testHotKey";
}
//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}
- 這個時候如果訪問的頻率違背了我們定義的熱點規則,就會按照我們自定義的兜底方法來進行處理
- 但是如果我們不違背自己在Sentinel上面定義的熱點規則,那么就什么事情也沒有
- 我們定義的熱點規則還可以支持高級配置,在原本的規則基礎上,當引數型別和引數值確定了,再使用另外一套規則,使用不同的限流閾值
- 熱點引數需要注意:引數型別必須是基本型別或者String
- @SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”) 這個處理的是Sentinel控制臺配置違規的情況,有blockHandler方法配置的兜底方法,但是如果是方法內部運行時例外是無法控制的
系統規則

Sentinel 系統自適應限流從整體維度對應用入口流量進行控制,結合應用的 Load、CPU 使用率、總體平均 RT、入口 QPS 和并發執行緒數等幾個維度的監控指標,通過自適應的流控策略,讓系統的入口流量和系統的負載達到一個平衡,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性,
- 系統規則是從應用基本的入口流量進行控制,從load ,CPU使用率, 平均RT,入口QPS,和并發執行緒數等幾個維度監控應用指標,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性
- load是一個結果,如果根據load的情況來調節流量的通過率,那么就始終有延遲,也就意味著通過率的任何調整,都會過一段時間才能看到結果,當前通過率是使load惡化的一個動作,那么也至少要過1s之后才可以觀測到,同理,如果當前通過率調整時讓load好轉的當作,也需要1s之后才能繼續調整,這樣就浪費了系統的處理能力,所以我們看到的曲線,總是會有抖動
- 總的來說,load不適合我們做及時的調整,,我們應該根據系統能夠處理的請求,和允許進來的請求,來做平衡,而不是根據一個間接的指標來 load 做限流,最終我們追求的目標是,在系統不被拖垮的情況下,提高系統的吞吐率,而不是load一定要低于某個閾值,
- CPU usage 當系統CPU使用率超過閾值即觸發系統保護 ,比較靈敏
- 平均RT:當單臺機器上所有入口流量的平均RT達到閾值即觸發系統保護,單位是毫秒
- 并發執行緒數:所有入口流量的并發執行緒數達到閾值即觸發系統保護
- 入口QPS:所有入口流量的QPS達到閾值即觸發系統保護
按資源名稱限流和按照Url地址進行限流
@GetMapping("/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按資源名稱限流測驗OK", new Payment(2020, "serial001"));
}
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
return new CommonResult(200,"按url限流測驗OK",new Payment(2020,"serial002"));
}
這兩個方式其實都一樣
自定義限流邏輯處理
原來的限流邏輯有一些缺點:
- 系統默認的,沒有體現我們的業務要求
- 依照現有的條件我們自定義處理方法和業務方法都在一個類里面,耦合度太高,不太好
- 每個業務方法都有一個兜底方法,不太合適
- 全域統一的處理方法沒有體現
@GetMapping("/byResource")
@SentinelResource(value = "byResource",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handleException")
public CommonResult byResource() {
return new CommonResult(200, "按資源名稱限流測驗OK", new Payment(2020, "serial001"));
}
public class CustomerBlockHandler {
public static CommonResult handleException(BlockException e){
return new CommonResult(2000,"客戶自定義限流處理資訊");
}
}
- 按照上面的方法,我們就可以在一個統一的類里面寫一些靜態的方法,然后所有的兜底都在里面
- 需要注意的是,如果原來的方法有引數,那么兜底的方法也一定要有引數
Sentinel整合ribbon+openFeign+fallback
- 創建兩個服務提供者專案,
- 然后消費者專案創建,
@RestController
@Slf4j
public class CircleBreakerController {
public static final String SERVICE_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback") //沒有配置
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只負責業務例外
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只負責sentinel控制臺配置違規
@SentinelResource(value = "fallback",
fallback = "handlerFallback",
blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalArgumentException("IllegalArgumentException,非法引數例外....");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException,該ID沒有對應記錄,空指標例外");
}
return result;
}
//fallback
public CommonResult handlerFallback(@PathVariable Integer id, Throwable e) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(444, "兜底例外handlerFallback,exception內容 " + e.getMessage(), payment);
}
//blockHandler
public CommonResult blockHandler(@PathVariable Integer id, BlockException blockException) {
Payment payment = new Payment(id, "null");
return new CommonResult<>(445, "blockHandler-sentinel限流,無此流水: blockException " + blockException.getMessage(), payment);
}
}
- 原理上是 fallback會管理服務內部出現例外的回執,被限流降級而拋出的例外只會進入blockHandler處理邏輯
- 具體的套路就是我們訪問規則出錯,使用blockHandler里面的方法,訪問內部出錯,使用fallback里面的回執方法,如果一直訪問內部都出錯,那么就用 blockHandler的方法,因為它首先是違反了我們定義的訪問規則,然后再在內部出錯的
- 我們之前學習過Feign,現在再來回顧一遍,順便將這個專案改造一下
在服務消費者引入feign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
在組態檔上面開啟對feign的支持
#對Feign的支持
feign:
sentinel:
enabled: true
遠程呼叫,并宣告降級方法
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Integer id);
}
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Integer id) {
return new CommonResult<>(44444, "服務降級回傳,---PaymentFallbackService", new Payment(id, "errorSerial"));
}
}
在主啟動類宣告啟用Feign遠程呼叫
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
- 熔斷框架的比較
| | Sentinel | Hystrix | resilience4j |
| — | — | — | — |
| 隔離策略 | 信號量隔離(并發執行緒數限流) | 執行緒池隔離/信號量隔離 | 信號量隔離 |
| 熔斷降級策略 | 基于回應時間,例外比率,例外數 | 基于例外比率 | 基于例外比率,回應時間 |
| 實時統計實作 | 滑動視窗 LeapArray | 滑動視窗,基于 RxJava | Ring Bit Buffer |
| 動態規劃配置 | 支持多種資料源 | 支持多種資料源 | 有限支持 |
| 擴展性 | 多個擴展點 | 插件的形式 | 的介面形式 |
| 基于注解的支持 | 支持 | 支持 | 支持 |
| 限流 | 基于QPS,支持基于呼叫關系的限流 | 有限的支持 | Rate Limiter |
| 流量整形 | 支持預熱模式,勻速器模式,預熱排隊模式 | 不支持 | 簡單的Rate Limiter模式 |
| 系統自適應保護 | 支持 | 不支持 | 不支持 |
| 控制臺 | 提高開箱即用的控制臺,可配置規則,查看秒級監控,機器發現 | 簡單的監控查看 | 不提高控制臺,可對接其他監控系統 |
規則持久化
一旦我們重啟應用,Sentinel規則就會消失,生產環境需要將配置規則進行持久化,
可以將限流配置規則持久化進 Nacos 保存,只要重繪 8401 某個rest地址,sentinel控制臺的流控規則就能看到,只要Nacos里面的配置不洗掉,針對 8401 上 Sentinel上的 流控規則持續有效
- 我們發現我們指定的規則在外面的服務停止之后再上來就沒有了,針對這種情況,外面可以將外面的配置作為一個組態檔存盤到nacos上面,而nacos外面前面已經學習過,它將資訊存盤到了資料庫里面,所以外面就相當于間接的把配置資訊存盤到了資料庫上面
- 下次登錄就會發現以前配置過的規則
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
配檔案,需要宣告外面存盤到了哪個nacos宣告,存盤的格式,名字,組名,規則
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默認8719,假如被占用了會自動從8719開始依次+1掃描,直至找到未被占用的埠
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr:localhost:8848
dataid:${spring.application.name}
groupid:DEFAULT_GROUP
data-type:json
rule-type:flow
外面配置過一個規則之后,配置的格式大致如此
[
{
"resource": "/retaLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
resource: 資源名稱,可以看作是我們的介面什么的
limitApp: 來源應用
grade: 閾值型別,0表示執行緒數,1表示QPS
count: 單機閾值
strategy: 流控模式:0表示直接,1表示關聯,2表示鏈路
controlBehavior: 流控效果,0表示快速失敗,1表示預熱,2表示排隊
clusterMode: 表示是否集群
分布式事務問題
Seata是一款開源的分布式事務解決方案,致力于提供高性能和簡單易用的分布式事務服務,Seata將為用戶提供了AT,TCC,SAGA和XA事務模式
AT模式
- 前提是基于本地ACID事務的關系型資料庫,Java應用,通過JDBC訪問資料庫
- 通過兩個階段提交協議:
- 一階段:業務資料和回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源
- 二階段,提交異步化,回滾通過一階段的回滾日志進行反向補償
- 寫隔離:
- 一階段本地事務提交之前,需要確保先拿到全域鎖,
- 拿不到全域鎖就無法提交本地事務,
- 拿全域鎖的嘗試被限制在一定范圍內,超過范圍將放棄,并回滾本地事務,釋放本地鎖,
- 例子 一階段:
- 決議SQL,得到SQL的型別,表,條件等相關資訊,
- 查詢前鏡像:根據決議得到的條件資訊,生成查詢陳述句,定位資料,得到要更新這一行的資料的鏡像
- 執行業務SQL,更新這條記錄,
- 查詢后鏡像,根據前鏡像的結果,通過主鍵定位資料,得到之后的鏡像,
- 插入回滾日志,把前后鏡像資料以及業務SQL相關的資訊組成一潭訓滾日志記錄,插入到UNDO_LOG表中,
- 提交之前,向TC注冊分支,去申請要修改記錄的全域鎖,
- 本地事務提交,業務資料的更新和前面步驟中生成的UNDO_LOG 一并提交
- 將本地事務提交的結果上報給TC
- 二階段-回滾
- 收到TC的分支回滾請求,開啟一個本地事務,執行如下操作
- 通過XID和BranchID查找相應的UNDO_LOG 記錄
- 資料校驗:拿UNDO_LOG中的后鏡像與當前資料進行比較,如果有不同,說明資料被當前全域事務之外的動作做了修改,這種情況需要根據配置策略來做處理
- 根據UNDO_LOG 中的前鏡像和業務SQL的相關資訊生成執行回滾的陳述句
- 然后提交本地事務,并把本地事務的執行結果上報給TC
- 二階段-提交
- 收到TC的分支提交請求,把請求放入一個異步任務的佇列中,馬上回傳提交成功的結果給TC,
- 異步任務階段的分支提交請求將異步和批量的洗掉相應UNDO_LOG記錄
TCC模式
上一步AT模式的全域事務整體式兩階段提交,全域事務時由若干分支事務組成,分支事務 要滿足兩階段提交的模型要求,即每個分支事務都具備自己的兩個階段
- 一階段 prepare行為 : 在本地事務中,一并提交業務資料更新和相應回滾日志記錄
- 二階段 commit 或 rollback行為 : commit行為,馬上成功結束,自動異步批量清理回滾日志
- 二階段 rollback : 通過回滾日志,自動生成補償操作,完成資料回滾
分布式事務的處理程序: ID+三組件模型
- 全域唯一的事務ID
- TC: Transaction Coordinator 事務協調器,維護全域事務的運行狀態,負責協調并驅動全域事務的提交或回滾
- TM:Transaction Manager 控制全域事務的邊界,負責開啟一個全域事務,并最終發起全域提交或全域回滾的決議
- RM:Resource Manager 控制分支事務,負責分支注冊,狀態匯報,并接收事務協調器的指令,驅動分支(本地)事務dd1提交和回滾
處理程序
- TM向TC申請開啟一個全域事務,全域事務創建成功,并生成一個全域唯一的XID
- XID在微服務呼叫鏈路的背景關系中傳播
- RM向TC注冊分支事務,將其納入XID對應全域事務的管轄,
- TM向TC發起針對XID的全域提交或回滾決議
- TC調度XID管轄的全部分支事務完成提交或回滾請求
這個太新了,我決定放棄!!!以后再來
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/260551.html
標籤:其他
