🌸個人主頁:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵?熱門專欄:
🧊 Java基本語法(97平均質量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm=1001.2014.3001.5482
🍕 Collection與數據結構 (93平均質量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀線程與網絡(97平均質量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql數據庫(95平均質量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均質量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
🍃 Spring(97平均質量分)https://blog.csdn.net/2301_80050796/category_12724152.html?spm=1001.2014.3001.5482
🎃Redis(97平均質量分)https://blog.csdn.net/2301_80050796/category_12777129.html?spm=1001.2014.3001.5482
🐰RabbitMQ(97平均質量分) https://blog.csdn.net/2301_80050796/category_12792900.html?spm=1001.2014.3001.5482
感謝點贊與關注~~~
目錄
- 1. 服務雪崩
- 2. 微服務保護
- 2.1 服務保護方案
- 2.1.1 請求限流
- 1.1.2 線程隔離
- 1.1.3 服務熔斷
- 1.2 Sentinel
- 1.2.1 介紹和安裝
- 1.2.2 微服務整合
- 1.3 請求限流
- 1.4 線程隔離
- 1.4.1 OpenFeign整合Sentinel
- 1.4.2 配置線程隔離
- 1.5 服務熔斷
- 1.5.1 編寫降級邏輯
- 1.5.2 服務熔斷l
1. 服務雪崩
在微服務遠程調用的過程中,購物車服務需要查詢最新的商品信息,與購物車數據做對比,提醒用戶.如果此時商品服務查詢發生故障,查詢購物車列表在調用商品服務時,也會發生異常,從而導致購物車查詢失敗.
還有級聯失敗問題:
還是查詢購物車的業務,加入商品服務業務并發較高,占用過多的Tomcat連接,可能會導致商品服務的所有接口響應時間增加,延遲變高,甚至是長時間的阻塞至查詢失敗.
此時查詢購物車業務需要查詢并等待商品查詢結果,從而導致查詢購物車列表業務的響應時間也變長,甚至也阻塞直至無法訪.。而此時如果查詢購物車的請求較多,可能導致購物車服務的Tomcat連接占用較多,所有接口的響應時間都會增加,整個服務性能很差, 甚至不可用.
以此類推,整個微服務中與購物車服務,商品服務等有調用關系的服務可能都會出現問題,最終導致整個集群不可用.
這就是級聯失敗問題,或者叫雪崩問題.那么如何解決上述問題呢?我們接下來就給出解決的方案.
2. 微服務保護
保證服務運行的健壯性,避免級聯失敗導致的雪崩問題,就屬于微服務保護,這章我們就一起來學習一下微服務保護的常見方案已經應對技術.
2.1 服務保護方案
微服務保護的方案有很多,比如:
- 請求限流
- 線程隔離
- 服務熔斷
這些方案或多或少都會導致服務的體驗略有下降,比如請求限流,降低了并發上限,線程隔離,降低了可用資源的數量,服務熔斷,降低了服務的完整度,部分服務變得不可用或者弱可用.因此這些方案都屬于降級的方案,但通過這些方案,服務的健壯性得到了提升.
接下來,我們就逐一了解這些方案的原理.
2.1.1 請求限流
服務故障的最重要原因就是并發太高,為了解決這個問題,就能避免大部分的故障,當然,接口的并發不是一直很高,而是突發的.因此請求限流,就是限制或控制接口的并發訪問量,避免服務因為流量激增而出現故障.
請求限流往往會有一個限流器,數量高低起伏的并發請求曲線,經過限流器就變得非常平穩.
1.1.2 線程隔離
當一個業務接口響應時間過長,而且并發高時,就可能耗盡服務的線程資源,導致服務內的其他接口受到影響,所以我們必須把這種影響降低,或者縮減影響的范圍,線程隔離正是解決這個問題的好辦法.
線程隔離的思想來自輪船的艙壁模式:
輪船的船艙會被隔板分割為N個相互隔離的密閉艙,加入輪船觸礁進水,只有損壞的部分密閉倉會進水,而其他倉由于相互隔離,并不會進水,這樣就把進水控制在了部分船體,避免了整個船倉進水而整體沉沒.
為了避免某個接口故障或者壓力過大導致導致了整個服務不可用,我們可以限定每個接口可以使用的資源范圍,也就是將其"隔離"起來.
如圖所示,我們給查詢購物車業務限定可用線程數量上限為20,這樣即便查詢購物車的請求因為查詢商品服務而出現故障,也不會導致服務器的資源被耗盡,不會影響到其他的接口.
1.1.3 服務熔斷
線程隔離雖然避免了雪崩問題,但是故障服務(商品服務)依然會拖慢購物車服務(服務調用方)的接口響應速度,而且商品查詢依然會導致查詢購物車功能出現故障,購物車業務也變得不可用了.
所以我們需要做兩件事情:
- 編寫服務降級邏輯: 就是服務方調用失敗后的處理邏輯,根據業務場景,可以拋出異常,也可以返回友好提示或者默認數據.
- 異常統計和熔斷: 統計服務提供方的異常比例,當比例過高表明該接口會影響到其他服務,應該拒絕調用該接口,而是直接走降級邏輯.
1.2 Sentinel
微服務保護的技術有很多,但是在國內使用較多的還是Sentinel,所以接下來我們學習Sentinel的使用.
1.2.1 介紹和安裝
Sentinel是阿里巴巴開源的一款服務保護框架,目前已經加入SpringCloudAlibaba中。官方網站:
https://sentinelguard.io/zh-cn/,為了方便監控微服務,我們需要先把Sentinel的控制臺搭建出來.
-
首先下載jar包:
https://github.com/alibaba/Sentinel/releases
-
啟動: 將jar包放在任意中文,不包含特殊字符的目錄之下,重命名為sentinel-dashboard.jar
然后運行如下的命令啟動控制臺:
java -Dserver.port=8090 -Dcsp.sentinel.dashboard.server=localhost:8090 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
- 訪問
訪問http://localhost:8090/
需要輸入賬號和密碼,默認都是:sentinel
登錄之后,即可看到控制臺,默認會監控sentinel-dashboard服務本身:
1.2.2 微服務整合
- 在想要引入限流的微服務某塊引入Sentinel依賴
<!--sentinel-->
<dependency><groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 配置控制臺
修改application.yml文件,添加下面的內容:
spring:cloud: sentinel:transport:dashboard: localhost:8090
- 訪問引入該微服務的任意端點
Sentinel的客戶端會將服務訪問的信息提交到Sentinel-dashboard控制臺,并展示出統計信息:
點擊簇點鏈路菜單,就是單機調用鏈路,是一次請求進入服務后經過的每一個被Sentinel監控的的資源,默認情況下,Sentinel會監控SpringMVC的每一個接口.
因此,我們看到/carts
這個接口路徑就是其中一個簇點,我們可以對其進行限流,熔斷,隔離等保護措施.
不過需要注意的是,我們的SpringMVC接口是按照Restful風格設計,因此購物車的查詢,刪除,修改等接口全部都是/carts
路徑:
默認情況下Sentinel會把路徑作物簇點資源的名稱,無法區分區分路徑相同但是請求方式不同的接口,查詢,刪除,修改等都被識別為一個簇點資源,這顯然是不合適的.
首先,在cart-service
的application.yml``中添加下面的配置:
spring:cloud:sentinel:transport:dashboard: localhost:8090http-method-specify: true # 開啟請求方式前綴
然后重啟服務,通過頁面訪問購物車的相關接口,可以看到Sentinel控制臺的簇點鏈路發生了變化:
1.3 請求限流
在簇點鏈路后面點擊留空按鈕,可以對其做限流配置:
在彈出的菜單中可以這樣填寫:
這樣就把查詢購物車列表這個簇點資源的流量限制在了每秒6個,也就是QPS最大為6.
我們利用了Jemeter做限流測試,我們每秒發出10個請求:
最終監控結果如下:
可以看得出get:/carts
這個接口的通過QPS穩定在6附近,而拒絕的QPS穩定在4附近,符合我們的預期.
1.4 線程隔離
限流可以降低服務器的壓力,減少引并發流量引起的服務故障的概率,但是不能完全避免服務故障,但某個服務出現故障,我們必須隔離對這個服務的調用,避免發生雪崩.
比如,查詢購物車的時候需要查詢商品,為了避免因商品服務出現故障導致導致購物車服務級聯失敗,我們可以把購物車業務中查詢商品的部分隔離起來,限制可用的線程資源:
這樣即便商品服務出現了故障,最多導致查詢購物車的業務故障,并且可用的線程資源也被限定在一定的范圍之內,不會導致整個購物車服務崩潰.
所以我們要對查詢商品的FeignClient接口做線程隔離.
1.4.1 OpenFeign整合Sentinel
修改cart-service模塊的application.yml文件,開啟Feign的Sentinel功能:
feign:sentinel:enabled: true # 開啟feign對sentinel的支持
需要注意的是,默認情況之下SpringBoot項目的Tomcat最大線程數是200,允許的最大連接是8492,單機測試很難打滿.
所以我們需要配置一下cart-service模塊的application.yml文件,修改Tomcat連接:
server:port: 8082tomcat:threads:max: 50 # 允許的最大線程數accept-count: 50 # 最大排隊等待數量max-connections: 100 # 允許的最大連接
然后重啟car-service服務,可以看到查詢商品的FeignClient自動變成了一個簇點資源:
1.4.2 配置線程隔離
接下來,點擊查詢商品的FeignClient對應的簇點資源后面的流控按鈕:
在彈出的表單中填寫下面內容:
注意,這里勾選的時并發線程數限制,也就是說這個查詢功能最多使用5個線程,而不是5QPS.如果查詢商品的接口每秒處理2個請求,則5個線程的實際QPS在10左右,而超出的請求自然會被拒絕.
我們使用Jemeter測試,每秒發送100個請求:
最終測試接口如下:
進入查詢購物車的請求每秒在100,而在查詢商品時卻只剩下每秒100左右,符合我們的預期.
此時如果我們通過頁面訪問購物車的其他接口,例如添加購物車,修改購物車商品數量,發現不受影響:
響應時間非常短,這就證明線程隔離起到了作用,盡管查詢購物車這個接口并發很高,但是它能使用的線程資源被限制了,因此會不會影響到其他的接口.
1.5 服務熔斷
在上一個章節,我們利用了線程隔離對查詢購物車業務進行了隔離,保護了購物車服務的其他接口,由于查詢商品的功能耗時較高,在加上線程隔離限定了線程數為5,導致接口吞吐能力有限,最終QPS只有10 左右,這就導致了幾個問題.
第一,超出的QPS上限的請求只能拋出異常,從而導致購物車的查詢失敗,但是從業務角度來說,即便沒有查詢到最新的商品信息,購物車也應該展示給用戶,用戶體驗更好,也就是給查詢失敗設置一個降級處理邏輯.
第二,由于查詢商品的延遲較高,從而導致 查詢購物車的響應時間也變長,這樣不僅拖慢了購物車服務,消耗了購物車服務的更多資源,而且用戶體驗也很差,對于商品服務這種不太健康的接口,我們應該直接停止調用,直接走降級邏輯,避免影響到當前服務,也就是將商品接口熔斷.
1.5.1 編寫降級邏輯
觸發限流或熔斷后的請求不一定要直接報錯,也可以返回一些默認數據或者友好提示,用戶體驗會更好.
給FeignClient編寫失敗后的降級有兩種方式:
- 方式一; FallbackClass,無法對遠程調用的異常做處理
- 方式二: FallbackFactory,可以對遠程調用做異常處理,我們一般選擇這種方式
這里我們演示方式二的降級處理:
步驟一: 在hm-api模塊中給ItemClient定義降級處理類,實現FallbackFactory:
代碼如下:
package com.hmall.api.client.fallback;import com.hmall.api.client.ItemClient;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import com.hmall.common.exception.BizIllegalException;
import com.hmall.common.utils.CollUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;import java.util.Collection;
import java.util.List;@Slf4j
public class ItemClientFallback implements FallbackFactory<ItemClient> {@Overridepublic ItemClient create(Throwable cause) {return new ItemClient() {@Overridepublic List<ItemDTO> queryItemByIds(Collection<Long> ids) {log.error("遠程調用ItemClient#queryItemByIds方法出現異常,參數:{}", ids, cause);// 查詢購物車允許失敗,查詢失敗,返回空集合return CollUtils.emptyList();}@Overridepublic void deductStock(List<OrderDetailDTO> items) {// 庫存扣減業務需要觸發事務回滾,查詢失敗,拋出異常throw new BizIllegalException(cause);}};}
}
步驟二: 在hm-api模塊中的com.hmall.api.config.DefaultFeignConfig
類中將ItemClientFallback
注冊為一個Bean.
步驟三: 在hm-api模塊中的ItemClient
接口中使用ItemClientFallbackFactory
:
啟動后,再次測試,發現限流的請求不再報錯,走了降級邏輯:
但是未被限流的請求延時依然很高:
1.5.2 服務熔斷l
查詢商品的RT較高,從而導致查詢購物車的RT事件也變得很長,這樣不僅拖慢了購物車服務,消耗了購物車很多的資源,而且用戶體驗也很差,對于商品服務這種不太健康的接口,我們應該停止調用,直接走降級邏輯,避免影響到當前服務,這也就是將商品查詢接口熔斷,當商品服務接口恢復正常之后,再允許調用,這其實就是熔斷器的工作模式了.
Sentinel中的斷路器不僅僅可以統計某個接口的慢請求比例,還可使統計異常請求比例,當這些比例超出閾值時,就會熔斷該接口,即攔截訪問接口的一切請求,降級處理,當該接口恢復正常時,再放行對于該接口的請求,斷路器的工作狀態切換有一個狀態機來控制:
狀態機包括是三個狀態:
- closed: 關閉狀態,斷路器放行所有請求,并開始統計異常比例,慢請求比例,超過閾值則切換到open狀態
- open: 打開狀態,服務調用被熔斷,訪問被熔斷的服務會拒絕訪問,并快速失敗,直接走降級邏輯,Open狀態持續一段時間后進入half-open狀態.
- half-open: 半開狀態,放行一次請求,根據執行結果來判斷接下來的操作.
- 請求成功: 則切換到closed狀態
- 請求失敗: 則切換到open狀態
我們可以在控制臺通過點擊簇點后的"熔斷"按鈕來配置熔斷策略:
在彈出的表格中這樣填寫:
這種是按照慢調用比例來做熔斷,上述配置的含義是:
- RT超過200毫秒的請求就是慢調用
- 統計最近的1000ms內的最少5次請求,如果慢調用比例不超過0.5,則觸發熔斷
- 熔斷持續時長20s
配置完成之后,再次利用jemeter進行測試,可以發現:
在一開始一段時間是允許訪問的,后來觸發熔斷之后,查詢商品服務的接口通過QPS直接為0,所有請求都被熔斷了,而查詢購物車的本身沒有受到影響.
此時整個購物車查詢服務的平均RT影響不大: