微服務(wù)保護(hù)
a.sentinel
雪崩問(wèn)題
微服務(wù)調(diào)用鏈路中的某個(gè)服務(wù)故障,引起整個(gè)鏈路中的所有微服務(wù)都不可用,這就是雪崩
解決雪崩問(wèn)題的常見(jiàn)方式有四種:
- 超時(shí)處理:設(shè)定超時(shí)時(shí)間,請(qǐng)求超過(guò)一定時(shí)間沒(méi)有響應(yīng)就返回錯(cuò)誤信息,不會(huì)無(wú)休止等待
- 艙壁模式:限定每個(gè)業(yè)務(wù)能使用的線程數(shù),避免耗盡整個(gè)tomcat的資源,因此也叫線程隔離
- 熔斷降級(jí):由斷路器統(tǒng)計(jì)業(yè)務(wù)執(zhí)行的異常比例,如果超出閾值則會(huì)熔斷該業(yè)務(wù),攔截訪問(wèn)該業(yè)務(wù)的一切請(qǐng)求
- 限制業(yè)務(wù)訪問(wèn)的QPS(每秒處理請(qǐng)求的數(shù)量),避免服務(wù)因流量的突增而故障。
Sentinel是阿里巴巴開(kāi)源的一款微服務(wù)流量控制組件。
微服務(wù)整合Sentinel
1.引入sentinel依賴(lài):
<!--引入sentinel依賴(lài)-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置控制臺(tái)地址:
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # sentinel控制臺(tái)地址
3.訪問(wèn)微服務(wù)的任意端點(diǎn),觸發(fā)sentinel監(jiān)控
b.sentinel限流規(guī)則
簇點(diǎn)鏈路
簇點(diǎn)鏈路:就是項(xiàng)目?jī)?nèi)的調(diào)用鏈路,鏈路中被監(jiān)控的每個(gè)接口就是一個(gè)資源。默認(rèn)情況下sentinel會(huì)監(jiān)控SpringMVC的每一個(gè)端點(diǎn)(Endpoint: controller中的每一個(gè)方法),因此SpringMVC的每一個(gè)端點(diǎn)(Endpoint)就是調(diào)用鏈路中的一個(gè)資源。
需求:給 /order/{orderId}這個(gè)資源設(shè)置流控規(guī)則,QPS不能超過(guò) 5。然后利用jemeter測(cè)試。
1.設(shè)置流控規(guī)則:
2.jemeter測(cè)試:
1) 流控模式
在添加限流規(guī)則時(shí),點(diǎn)擊高級(jí)選項(xiàng),可以選擇三種流控模式:
- 直接:統(tǒng)計(jì)當(dāng)前資源的請(qǐng)求,觸發(fā)閾值時(shí)對(duì)當(dāng)前資源直接限流,也是默認(rèn)的模式
- 關(guān)聯(lián):統(tǒng)計(jì)與當(dāng)前資源相關(guān)的另一個(gè)資源,觸發(fā)閾值時(shí),對(duì)當(dāng)前資源限流
- 鏈路:統(tǒng)計(jì)從指定鏈路訪問(wèn)到本資源的請(qǐng)求,觸發(fā)閾值時(shí),對(duì)指定鏈路限流
1.a) 關(guān)聯(lián)模式
- 關(guān)聯(lián)模式:統(tǒng)計(jì)與當(dāng)前資源相關(guān)的另一個(gè)資源,觸發(fā)閾值時(shí),對(duì)當(dāng)前資源限流
- 使用場(chǎng)景:比如用戶(hù)支付時(shí)需要修改訂單狀態(tài),同時(shí)用戶(hù)要查詢(xún)訂單。查詢(xún)和修改操作會(huì)爭(zhēng)搶數(shù)據(jù)庫(kù)鎖,產(chǎn)生競(jìng)爭(zhēng)。業(yè)務(wù)需求是優(yōu)先對(duì)支付和更新訂單的業(yè)務(wù),因此當(dāng)修改訂單業(yè)務(wù)觸發(fā)閾值時(shí),需要對(duì)查詢(xún)訂單業(yè)務(wù)限流。
當(dāng)/write資源訪問(wèn)量觸發(fā)閾值時(shí),就會(huì)對(duì)/read資源限流,避免影響/write資源。
需求:
- 在OrderController新建兩個(gè)端點(diǎn):/order/query和/order/update,無(wú)需實(shí)現(xiàn)業(yè)務(wù)
- 配置流控規(guī)則,當(dāng)/order/ update資源被訪問(wèn)的QPS超過(guò)5時(shí),對(duì)/order/query請(qǐng)求限流
滿(mǎn)足下面條件可以使用關(guān)聯(lián)模式:
- 兩個(gè)有競(jìng)爭(zhēng)關(guān)系的資源
- 一個(gè)優(yōu)先級(jí)較高,一個(gè)優(yōu)先級(jí)較低
1.b) 鏈路模式
鏈路模式:只針對(duì)從指定鏈路訪問(wèn)到本資源的請(qǐng)求做統(tǒng)計(jì),判斷是否超過(guò)閾值。
例如有兩條請(qǐng)求鏈路:
- /test1 -> /common
- /test2 -> /common
如果只希望統(tǒng)計(jì)從/test2進(jìn)入到/common的請(qǐng)求,則可以這樣配置:
需求:
有查詢(xún)訂單和創(chuàng)建訂單業(yè)務(wù),兩者都需要查詢(xún)商品。針對(duì)從查詢(xún)訂單進(jìn)入到查詢(xún)商品的請(qǐng)求統(tǒng)計(jì),并設(shè)置限流。
步驟:
- 1.在OrderService中添加一個(gè)queryGoods方法,不用實(shí)現(xiàn)業(yè)務(wù)
- 2.在OrderController中,改造/order/query端點(diǎn),調(diào)用OrderService中的queryGoods方法
- 3.在OrderController中添加一個(gè)/order/save的端點(diǎn),調(diào)用OrderService的queryGoods方法
- 4.給queryGoods設(shè)置限流規(guī)則,從/order/query進(jìn)入queryGoods的方法限制QPS必須小于2
Sentinel默認(rèn)只標(biāo)記Controller中的方法為資源,如果要標(biāo)記其它方法,需要利用@SentinelResource注解
@SentinelResource("goods")
public void queryGoods(){
System.err.println("查詢(xún)商品");
}
Sentinel默認(rèn)會(huì)將Controller方法做context整合,導(dǎo)致鏈路模式的流控失效,需要修改application.yml,添加配置:
spring:
cloud:
sentinel:
web-context-unify: false # 關(guān)閉context整合
2) 流控效果
流控效果是指請(qǐng)求達(dá)到流控閾值時(shí)應(yīng)該采取的措施,包括三種:
- 快速失?。哼_(dá)到閾值后,新的請(qǐng)求會(huì)被立即拒絕并拋出FlowException異常。是默認(rèn)的處理方式。
- warm up:預(yù)熱模式,對(duì)超出閾值的請(qǐng)求同樣是拒絕并拋出異常。但這種模式閾值會(huì)動(dòng)態(tài)變化,從一個(gè)較小值逐漸增加到最大閾值。
- 排隊(duì)等待:讓所有的請(qǐng)求按照先后次序排隊(duì)執(zhí)行,兩個(gè)請(qǐng)求的間隔不能小于指定時(shí)長(zhǎng)
2.a) 預(yù)熱模式
warm up也叫預(yù)熱模式,是應(yīng)對(duì)服務(wù)冷啟動(dòng)的一種方案。請(qǐng)求閾值初始值是 threshold / coldFactor,持續(xù)指定時(shí)長(zhǎng)后,逐漸提高到threshold值。而coldFactor的默認(rèn)值是3.
例如,我設(shè)置QPS的threshold為10,預(yù)熱時(shí)間為5秒,那么初始閾值就是 10 / 3 ,也就是3,然后在5秒后逐漸增長(zhǎng)到10.
案例:需求:給/order/{orderId}這個(gè)資源設(shè)置限流,最大QPS為10,利用warm up效果,預(yù)熱時(shí)長(zhǎng)為5秒
2.b) 排隊(duì)等待
排隊(duì)等待是讓所有請(qǐng)求進(jìn)入一個(gè)隊(duì)列中,然后按照閾值允許的時(shí)間間隔依次執(zhí)行。后來(lái)的請(qǐng)求必須等待前面執(zhí)行完成,如果請(qǐng)求預(yù)期的等待時(shí)間超出最大時(shí)長(zhǎng),則會(huì)被拒絕。
例如:QPS = 5,意味著每200ms處理一個(gè)隊(duì)列中的請(qǐng)求;timeout = 2000,意味著預(yù)期等待超過(guò)2000ms的請(qǐng)求會(huì)被拒絕并拋出異常
需求:給/order/{orderId}這個(gè)資源設(shè)置限流,最大QPS為10,利用排隊(duì)的流控效果,超時(shí)時(shí)長(zhǎng)設(shè)置為5s
3) 熱點(diǎn)參數(shù)限流
之前的限流是統(tǒng)計(jì)訪問(wèn)某個(gè)資源的所有請(qǐng)求,判斷是否超過(guò)QPS閾值。而熱點(diǎn)參數(shù)限流是分別統(tǒng)計(jì)參數(shù)值相同的請(qǐng)求,判斷是否超過(guò)QPS閾值。
在熱點(diǎn)參數(shù)限流的高級(jí)選項(xiàng)中,可以對(duì)部分參數(shù)設(shè)置例外配置:
結(jié)合上一個(gè)配置,這里的含義是對(duì)0號(hào)的long類(lèi)型參數(shù)限流,每1秒相同參數(shù)的QPS不能超過(guò)5,有兩個(gè)例外:
- 如果參數(shù)值是100,則每1秒允許的QPS為10
- 如果參數(shù)值是101,則每1秒允許的QPS為15
案例:給/order/{orderId}這個(gè)資源添加熱點(diǎn)參數(shù)限流,規(guī)則如下:
- 默認(rèn)的熱點(diǎn)參數(shù)規(guī)則是每1秒請(qǐng)求量不超過(guò)2
- 給102這個(gè)參數(shù)設(shè)置例外:每1秒請(qǐng)求量不超過(guò)4
- 給103這個(gè)參數(shù)設(shè)置例外:每1秒請(qǐng)求量不超過(guò)10
熱點(diǎn)參數(shù)限流對(duì)默認(rèn)的SpringMVC資源無(wú)效,只對(duì)添加@SentinelResource注解的方法產(chǎn)生效果
@SentinelResource("hot")
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根據(jù)id查詢(xún)訂單并返回
return orderService.queryOrderById(orderId);
}
c.隔離和降級(jí)
雖然限流可以盡量避免因高并發(fā)而引起的服務(wù)故障,但服務(wù)還會(huì)因?yàn)槠渌蚨收?。而要將這些故障控制在一定范圍,避免雪崩,就要靠線程隔離(艙壁模式)和熔斷降級(jí)手段了。
不管是線程隔離還是熔斷降級(jí),都是對(duì)客戶(hù)端(調(diào)用方)的保護(hù)。
1) Feign整合Sentinel
SpringCloud中,微服務(wù)調(diào)用都是通過(guò)Feign來(lái)實(shí)現(xiàn)的,因此做客戶(hù)端保護(hù)必須整合Feign和Sentinel。
1.修改OrderService的application.yml文件,開(kāi)啟Feign的Sentinel功能
feign:
sentinel:
enabled: true # 開(kāi)啟Feign的Sentinel功能
2.給FeignClient編寫(xiě)失敗后的降級(jí)邏輯
- 方式一:FallbackClass,無(wú)法對(duì)遠(yuǎn)程調(diào)用的異常做處理
- 方式二:FallbackFactory,可以對(duì)遠(yuǎn)程調(diào)用的異常做處理 (推薦)
步驟一:在feing-api項(xiàng)目的clients.fallback中定義類(lèi),實(shí)現(xiàn)FallbackFactory:
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查詢(xún)用戶(hù)異常", throwable);
return new User();
}
};
}
}
步驟二:在feing-api項(xiàng)目中的DefaultFeignConfiguration類(lèi)中將UserClientFallbackFactory注冊(cè)為一個(gè)Bean:
public class DefaultFeignConfiguration {
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
}
步驟三:在feing-api項(xiàng)目中的UserClient接口中使用UserClientFallbackFactory:
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
2) 線程隔離
線程隔離有兩種方式實(shí)現(xiàn):
- 線程池隔離
- 信號(hào)量隔離QPS(Sentinel默認(rèn)采用)
線程池隔離
- 優(yōu)點(diǎn):
- 支持主動(dòng)超時(shí)
- 支持異步調(diào)用
- 缺點(diǎn)
- 線程的額外開(kāi)銷(xiāo)比較大
- 場(chǎng)景
- 低扇出
信號(hào)量隔離
- 優(yōu)點(diǎn):
- 輕量級(jí),無(wú)額外開(kāi)銷(xiāo)
- 缺點(diǎn):
- 不支持主動(dòng)超時(shí)
- 不支持異步調(diào)用
- 場(chǎng)景
- 高頻調(diào)用
- 高扇出
2.a) 線程隔離(艙壁模式)
在添加限流規(guī)則時(shí),可以選擇兩種閾值類(lèi)型:
- QPS:就是每秒的請(qǐng)求數(shù),在快速入門(mén)中已經(jīng)演示過(guò)
- 線程數(shù):是該資源能使用用的tomcat線程數(shù)的最大值。也就是通過(guò)限制線程數(shù)量,實(shí)現(xiàn)艙壁模式。
案例:給 UserClient的查詢(xún)用戶(hù)接口設(shè)置流控規(guī)則,線程數(shù)不能超過(guò) 2
3) 熔斷降級(jí)
熔斷降級(jí)是解決雪崩問(wèn)題的重要手段。其思路是由斷路器統(tǒng)計(jì)服務(wù)調(diào)用的異常比例、慢請(qǐng)求比例,如果超出閾值則會(huì)熔斷該服務(wù)。即攔截訪問(wèn)該服務(wù)的一切請(qǐng)求;而當(dāng)服務(wù)恢復(fù)時(shí),斷路器會(huì)放行訪問(wèn)該服務(wù)的請(qǐng)求。
斷路器熔斷策略有三種:慢調(diào)用、異常比例、異常數(shù)
3.a) 熔斷策略-慢調(diào)用
- 慢調(diào)用:業(yè)務(wù)的響應(yīng)時(shí)長(zhǎng)(RT)大于指定時(shí)長(zhǎng)的請(qǐng)求認(rèn)定為慢調(diào)用請(qǐng)求。在指定時(shí)間內(nèi),如果請(qǐng)求數(shù)量超過(guò)設(shè)定的最小數(shù)量,慢調(diào)用比例大于設(shè)定的閾值,則觸發(fā)熔斷。例如:
解讀:RT超過(guò)500ms的調(diào)用是慢調(diào)用,統(tǒng)計(jì)最近10000ms內(nèi)的請(qǐng)求,如果請(qǐng)求量超過(guò)10次,并且慢調(diào)用比例不低于0.5,則觸發(fā)熔斷,熔斷時(shí)長(zhǎng)為5秒。然后進(jìn)入half-open狀態(tài),放行一次請(qǐng)求做測(cè)試。
案例:給 UserClient的查詢(xún)用戶(hù)接口設(shè)置降級(jí)規(guī)則,慢調(diào)用的RT閾值為50ms,統(tǒng)計(jì)時(shí)間為1秒,最小請(qǐng)求數(shù)量為5,失敗閾值比例為0.4,熔斷時(shí)長(zhǎng)為5s
提示:為了觸發(fā)慢調(diào)用規(guī)則,我們需要修改UserService中的業(yè)務(wù),增加業(yè)務(wù)耗時(shí):
3.b) 熔斷策略-異常比例、異常數(shù)
異常比例或異常數(shù):統(tǒng)計(jì)指定時(shí)間內(nèi)的調(diào)用,如果調(diào)用次數(shù)超過(guò)指定請(qǐng)求數(shù),并且出現(xiàn)異常的比例達(dá)到設(shè)定的比例閾值(或超過(guò)指定異常數(shù)),則觸發(fā)熔斷。例如:
案例:給 UserClient的查詢(xún)用戶(hù)接口設(shè)置降級(jí)規(guī)則,統(tǒng)計(jì)時(shí)間為1秒,最小請(qǐng)求數(shù)量為5,失敗閾值比例為0.4,熔斷時(shí)長(zhǎng)為5s
提示:為了觸發(fā)異常統(tǒng)計(jì),我們需要修改UserService中的業(yè)務(wù),拋出異常:
d.授權(quán)規(guī)則及規(guī)則持久化
1) 授權(quán)規(guī)則
授權(quán)規(guī)則可以對(duì)調(diào)用方的來(lái)源做控制,有白名單和黑名單兩種方式
- 白名單:來(lái)源(origin)在白名單內(nèi)的調(diào)用者允許訪問(wèn)
- 黑名單:來(lái)源(origin)在黑名單內(nèi)的調(diào)用者不允許訪問(wèn)
Sentinel是通過(guò)RequestOriginParser這個(gè)接口的parseOrigin來(lái)獲取請(qǐng)求的來(lái)源的。
從request中獲取一個(gè)名為origin的請(qǐng)求頭,作為origin的值:
在order-service中創(chuàng)建sentinel包
package cn.itcast.order.sentinel;
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
// 1.獲取請(qǐng)求頭
String origin = request.getHeader("origin");
// 2.非空判斷
if (StringUtils.isEmpty(origin)) {
origin = "blank";
}
return origin;
}
}
在gateway服務(wù)中,利用網(wǎng)關(guān)的過(guò)濾器添加名為gateway的origin頭:
spring:
gateway:
default-filters:
- AddRequestHeader=Truth, ABCDEFGHIJKLMN
- AddRequestHeader=origin, gateway # 添加名為origin的請(qǐng)求頭,值為gateway
2) 自定義異常結(jié)果
默認(rèn)情況下,發(fā)生限流、降級(jí)、授權(quán)攔截時(shí),都會(huì)拋出異常到調(diào)用方。如果要自定義異常時(shí)的返回結(jié)果,需要實(shí)現(xiàn)BlockExceptionHandler接口:
BlockException包含很多個(gè)子類(lèi),分別對(duì)應(yīng)不同的場(chǎng)景:
異常 | 說(shuō)明 |
---|---|
FlowException | 限流異常 |
ParamFlowException | 熱點(diǎn)參數(shù)限流的異常 |
DegradeException | 降級(jí)異常 |
AuthorityException | 授權(quán)規(guī)則異常 |
SystemBlockException | 系統(tǒng)規(guī)則異常 |
在order-service的sentinel包中定義類(lèi),實(shí)現(xiàn)BlockExceptionHandler接口:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-661866.html
package cn.itcast.order.sentinel;
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = "未知異常";
int status = 429;
if (e instanceof FlowException){
msg = "請(qǐng)求被限流";
} else if (e instanceof ParamFlowException) {
msg = "請(qǐng)求被熱點(diǎn)參數(shù)限流";
} else if (e instanceof DegradeException) {
msg = "請(qǐng)求被降級(jí)";
} else if (e instanceof AuthorityException) {
msg = "沒(méi)有權(quán)限訪問(wèn)";
status = 401;
}
response.setContentType("application/json;charset=utf-8");
response.setStatus(status);
response.getWriter().println("{\"msg\": " + msg + ", \"status\": " + status + "}");
}
}
3) 規(guī)則持久化
3.a) 規(guī)則管理模式
Sentinel的控制臺(tái)規(guī)則管理有三種模式:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-661866.html
- 原始模式:Sentinel的默認(rèn)模式,將規(guī)則保存在內(nèi)存,重啟服務(wù)會(huì)丟失
- pull模式:控制臺(tái)將配置的規(guī)則推送到Sentinel客戶(hù)端,而客戶(hù)端會(huì)將配置規(guī)則保存在本地文件或數(shù)據(jù)庫(kù)中。以后會(huì)定時(shí)去本地文件或數(shù)據(jù)庫(kù)中查詢(xún),更新本地規(guī)則。
- push模式:控制臺(tái)將配置規(guī)則推送到遠(yuǎn)程配置中心,例如Nacos。Sentinel客戶(hù)端監(jiān)聽(tīng)Nacos,獲取配置變更的推送消息,完成本地配置更新。
到了這里,關(guān)于微服務(wù)中間件--微服務(wù)保護(hù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!