[TOC]
# 自定義sentinel-spring-boot-starter
sentinel-spring-boot-starter 封裝完善了sentinel的功能,通過了平臺通用的熔斷限流能力。
## 什么是 Sentinel
> 隨著微服務的流行,服務和服務之間的穩定性變得越來越重要。 Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
## Sentinel 的特征
* **豐富的應用場景**:**Sentinel承接了阿里巴巴近 10 年的**雙十一大促流量\*\*的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的范圍)、消息削峰填谷、實時熔斷下游不可用應用等。
* **完備的實時監控**:\*\*Sentinel 同時提供實時的監控功能。您可以在控制臺中看到接入應用的單臺機器秒級數據,甚至 500 臺以下規模的集群的匯總運行情況。
* **廣泛的開源生態**:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴并進行簡單的配置即可快速地接入 Sentinel。
* **完善的 SPI 擴展點**:Sentinel 提供簡單易用、完善的 SPI 擴展點。您可以通過實現擴展點,快速的定制邏輯。例如定制規則管理、適配數據源等。
## 應用訪問次數控制
* 核心類

* 核心redis數據結構

* 核心表結構
oauth_client_details 增加 if_limit 是否需要控制訪問次數 ,limit_count 訪問閥值

## 滑動窗口流量助手
### 代碼一覽

### 滑動窗口
如果我們希望能夠知道某個接口的每秒處理成功請求數(成功 QPS)、每秒處理失敗請求數(失敗 QPS),以及處理每個成功請求的平均耗時(avg RT),我們只需要控制 Bucket 統計一秒鐘的指標數據即可。我們只需要控制 Bucket 統計一秒鐘內的指標數據即可。但如何才能確保 Bucket 存儲的就是精確到 1 秒內的數據呢?
最 low 的做法就是啟一個定時任務每秒創建一個 Bucket,但統計出來的數據誤差絕對很大。Sentinel 是這樣實現的,它定義一個 Bucket 數組,根據時間戳來定位到數組的下標。假設我們需要統計每 1 秒處理的請求數等數據,且只需要保存最近一分鐘的數據。那么 Bucket 數組的大小就可以設置為 60,每個 Bucket 的 windowLengthInMs(窗口時間)大小就是 1000 毫秒(1 秒),如下圖所示。

由于每個 Bucket 存儲的是 1 秒的數據,假設 Bucket 數組的大小是無限大的,那么我們只需要將當前時間戳去掉毫秒部分就能得到當前的秒數,將得到的秒數作為索引就能從 Bucket 數組中獲取當前時間窗口的 Bucket。
一切資源均有限,所以我們不可能無限的存儲 Bucket,我們也不需要存儲那么多歷史數據在內存中。當我們只需要保留一分鐘的數據時,Bucket 數組的大小就可以設置為 60,我們希望這個數組可以循環使用,并且永遠只保存最近 1 分鐘的數據,這樣不僅可以避免頻繁的創建 Bucket,也減少內存資源的占用。
這種情況下如何定位 Bucket 呢?我們只需要將當前時間戳去掉毫秒部分得到當前的秒數,再將得到的秒數與數組長度取余數,就能得到當前時間窗口的 Bucket 在數組中的位置(索引),如下圖所示:

根據當前時間戳計算出當前時間窗口的 Bucket 在數組中的索引,算法實現如下:

calculateTimeIdx 方法中,取余數就是實現循環利用數組。如果想要獲取連續的一分鐘的 Bucket 數據,就不能簡單的從頭開始遍歷數組,而是指定一個開始時間和結束時間,從開始時間戳開始計算 Bucket 存放在數組中的下標,然后循環每次將開始時間戳加上 1 秒,直到開始時間等于結束時間。
由于循環使用的問題,當前時間戳與一分鐘之前的時間戳和一分鐘之后的時間戳都會映射到數組中的同一個 Bucket,因此,必須要能夠判斷取得的 Bucket 是否是統計當前時間窗口內的指標數據,這便要數組每個元素都存儲 Bucket 時間窗口的開始時間戳。
比如當前時間戳是 1577017699235,Bucket 統計一秒的數據,將時間戳的毫秒部分全部替換為 0,就能得到 Bucket 時間窗口的開始時間戳為 1577017699000。
計算 Bucket 時間窗口的開始時間戳代碼實現如下:
```
/**
* 獲取bucket開始時間戳
*
* @param timeMillis
* @return
*/
protected long calculateWindowStart(long timeMillis) {
/**
* 假設窗口大小為1000毫秒,即數組每個元素存儲1秒鐘的統計數據
* timeMillis % windowLengthInMs 就是取得毫秒部分
* timeMillis - 毫秒數 = 秒部分
* 這就得到每秒的開始時間戳
*/
return timeMillis - timeMillis % windowLengthInMs;
}
```
### 使用案例
統計每秒鐘
```
public class Main{ private FlowHelper flowHelper = new FlowHelper(FlowType.Second); }
```
輸出最近一秒鐘統計
```
public class Main {
public static void print() {
Flower flower = flowHelper.getFlow(FlowType.Second);
System.out.println("總請求數:" + flower.total());
System.out.println("成功請求數:" + flower.totalSuccess());
System.out.println("異常請求數:" + flower.totalException());
System.out.println("平均請求耗時:" + flower.avgRt());
System.out.println("最大請求耗時:" + flower.maxRt());
System.out.println("最小請求耗時:" + flower.minRt());
System.out.println("平均請求成功數(每毫秒):" + flower.successAvg());
System.out.println("平均請求異常數(每毫秒):" + flower.exceptionAvg());
System.out.println();
}
}
```
統計每分鐘
```
public class Main{ private FlowHelper flowHelper = new FlowHelper(FlowType.Minute); }
```
輸出最近一分鐘統計
```
public class Main {
public static void print() {
Flower flower = flowHelper.getFlow(FlowType.Minute);
System.out.println("總請求數:" + flower.total());
System.out.println("成功請求數:" + flower.totalSuccess());
System.out.println("異常請求數:" + flower.totalException());
System.out.println("平均請求耗時:" + flower.avgRt());
System.out.println("最大請求耗時:" + flower.maxRt());
System.out.println("最小請求耗時:" + flower.minRt());
System.out.println("平均請求成功數(每秒鐘):" + flower.successAvg());
System.out.println("平均請求異常數(每秒鐘):" + flower.exceptionAvg());
System.out.println();
}
}
```
統計每小時
```
public class Main{ private FlowHelper flowHelper = new FlowHelper(FlowType.HOUR); }
```
輸出最近一小時統計
```
public class Main {
public static void print() {
Flower flower = flowHelper.getFlow(FlowType.HOUR);
System.out.println("總請求數:" + flower.total());
System.out.println("成功請求數:" + flower.totalSuccess());
System.out.println("異常請求數:" + flower.totalException());
System.out.println("平均請求耗時:" + flower.avgRt());
System.out.println("最大請求耗時:" + flower.maxRt());
System.out.println("最小請求耗時:" + flower.minRt());
System.out.println("平均請求成功數(每分鐘):" + flower.successAvg());
System.out.println("平均請求異常數(每分鐘):" + flower.exceptionAvg());
System.out.println();
}
}
```
- 01.前言
- 02.快速開始
- 01.maven構建項目
- 02.安裝mysql數據庫
- 03.安裝redis緩存中間件
- 04.快速啟動框架
- 03.總體流程
- 01.架構設計圖
- 02.oauth接口
- 03.功能介紹
- 04.部署細節
- 04.模塊詳解
- 01.基礎介紹
- 02.自定義db-spring-boot-starter
- 03.自定義log-spring-boot-starter
- 04.自定義redis-spring-boot-starter
- 05.自定義base-spring-boot-starter
- 06.自定義common-spring-boot-starter
- 07.自定義loadbalancer-spring-boot-starter
- 08.自定義swagger-spring-boot-starter
- 09.自定義uaa-client-spring-boot-starter
- 10.自定義uaa-server-spring-boot-starter
- 11.自定義oss-spring-boot-starter
- 12.自定義sentinel-spring-boot-starter
- 05.服務詳解
- 01.nacos-server
- 02.auth-server
- 03.user-center
- 04.new-api-gateway
- 05.file-center
- 06.log-center
- 07.back-center
- 08.auth-sso模塊
- 09.admin-server
- 10.job-center
- 06.系統安全
- 01.非法字符漏洞攻擊
- 02.防重放攻擊
- 03.代碼審計
- 04.Xray掃洞
- 05.混沌工程質量保證
- 07.生產部署K8S
- 01.基本環境安裝
- 02.基本組件安裝
- 03.集群驗證
- 04.安裝Metrics Server
- 05.安裝容器平臺
- 06.Ingress網關
- 07.metalb負載均衡器
- 08.容器平臺集群
- 08.K8S資源練習
- 01.Deployment
- 02.StatefulSet
- 03.DaemonSet
- 04.redis集群服務
- 05.elasticsearch集群
- 06.rocketmq部署
- 09.生產容器化部署
- 01.nacos集群部署
- 02.user-center服務
- 03.auth-server服務
- 04.new-api-gateway服務
- 技術交流