什么是雪崩
由于網(wǎng)絡(luò)原因或者自身的原因孕索,服務(wù)并不能保證 100% 可用,如果單個(gè)服務(wù)出現(xiàn)問題躏碳,調(diào)用這個(gè)服務(wù)就會(huì)出現(xiàn)線程阻塞搞旭,此時(shí)若有大量的請(qǐng)求涌入,Servlet 容器的線程資源會(huì)被消耗完畢菇绵,導(dǎo)致服務(wù)癱瘓肄渗。服務(wù)與服務(wù)之間的依賴性,故障會(huì)傳播咬最,會(huì)對(duì)整個(gè)微服務(wù)系統(tǒng)造成災(zāi)難性的嚴(yán)重后果翎嫡,這就是服務(wù)故障的 “雪崩” 效應(yīng)。
解決方案
熔斷器模型
服務(wù)熔斷是指當(dāng)某個(gè)服務(wù)提供者無法正常為服務(wù)調(diào)用者提供服務(wù)時(shí)永乌,比如請(qǐng)求超時(shí)钝的、服務(wù)異常等翁垂,為了防止整個(gè)系統(tǒng)出現(xiàn)雪崩效應(yīng),暫時(shí)將出現(xiàn)故障的接口隔離出來,斷絕與外部接口的聯(lián)系尤莺,當(dāng)觸發(fā)熔斷之后够委,后續(xù)一段時(shí)間內(nèi)該服務(wù)調(diào)用者的請(qǐng)求都會(huì)直接失敗,直到目標(biāo)服務(wù)恢復(fù)正常窝爪。
熔斷降級(jí)參考指標(biāo)
服務(wù)降級(jí)需要有一個(gè)參考指標(biāo),一般來說有以下幾種常見方案:
? 平均響應(yīng)時(shí)間:比如1s內(nèi)持續(xù)進(jìn)入5個(gè)請(qǐng)求,對(duì)應(yīng)時(shí)刻的平均響應(yīng)時(shí)間均超過閾值衙伶,那么接下來在一個(gè)固定的時(shí)間窗口內(nèi),對(duì)這個(gè)方法的訪問都會(huì)自動(dòng)熔斷害碾。
? 異常比例:當(dāng)某個(gè)方法每秒調(diào)用所獲得的異呈妇ⅲ總數(shù)的比例超過設(shè)定的閾值時(shí),該資源會(huì)自動(dòng)進(jìn)入降級(jí)狀態(tài)慌随,也就是在接下來的一個(gè)固定時(shí)間窗口中芬沉,對(duì)這個(gè)方法的調(diào)用都會(huì)自動(dòng)返回。
? 異常數(shù)量:和異常比例類似阁猜,當(dāng)某個(gè)方法在指定時(shí)間窗口內(nèi)獲得的異常數(shù)量超過閾值時(shí)會(huì)觸發(fā)熔斷丸逸。
Sentinel
Sentinel是面向分布式服務(wù)架構(gòu)的輕量級(jí)流量控制組件,主要以流量為切入點(diǎn)剃袍,從限流黄刚、流量整形、服務(wù)降級(jí)民效、系統(tǒng)負(fù)載保護(hù)等多個(gè)維度來幫助我們保障微服務(wù)的穩(wěn)定性憔维。
文檔
https://github.com/alibaba/Sentinel/wiki
特性
豐富的應(yīng)用場景:幾乎涵蓋所有的應(yīng)用場景,例如秒殺(即突發(fā)流量控制在系統(tǒng)容量可以承受的范圍)畏邢、消息削峰填谷业扒、集群流量控制等。
? 實(shí)時(shí)監(jiān)控:Sentinel提供了實(shí)時(shí)監(jiān)控功能棵红。開發(fā)者可以在控制臺(tái)中看到接入應(yīng)用的單臺(tái)機(jī)器秒級(jí)數(shù)據(jù)凶赁,甚至500臺(tái)以下規(guī)模的集群匯總運(yùn)行情況。
? 開源生態(tài)支持:Sentinel提供開箱即用的與其他開源框架/庫的整合逆甜,例如與Spring Cloud虱肄、Dubbo、gRPC的整合交煞。開發(fā)者只需要引入相應(yīng)的依賴并進(jìn)行簡單的配置即可快速接入Sentinel咏窿。
? SPI擴(kuò)展點(diǎn)支持:Sentinel提供了SPI擴(kuò)展點(diǎn)支持,開發(fā)者可以通過擴(kuò)展點(diǎn)來定制化限流規(guī)則素征,動(dòng)態(tài)數(shù)據(jù)源適配等需求集嵌。
組成
Sentinel分為兩個(gè)部分:
? 核心庫(Java客戶端):不依賴任何框架/庫萝挤,能夠運(yùn)行于所有Java運(yùn)行時(shí)環(huán)境,同時(shí)對(duì)Dubbo根欧、Spring Cloud等框架也有較好的支持怜珍。
? 控制臺(tái)(Dashboard):基于Spring Boot開發(fā),打包后可以直接運(yùn)行凤粗,不需要額外的Tomcat等應(yīng)用容器酥泛。
啟動(dòng)控制臺(tái)
官方 GitHub Release 頁面 頁面下載最新版本的控制臺(tái) JAR 包。
啟動(dòng)
java -Dserver.port=10006 -Dcsp.sentinel.dashboard.server=localhost:10006 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
#ubuntu
nohup java -Dserver.port=10006 -Dcsp.sentinel.dashboard.server=localhost:10006 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.2.jar >/dev/null 2> /dev/null &
notice:
n1.jdk環(huán)境必須是JDK1.8及以上嫌拣。
n2.從Sentinel 1.6.0 起柔袁,Sentinel 控制臺(tái)引入基本的 登錄 功能,默認(rèn)用戶名和密碼都是 sentinel异逐。
訪問
客戶端接入
pom
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改配置文件
在 Nacos 控制臺(tái)修改配置文件捶索,增加對(duì) Sentinel 的支持
spring:
application:
name: gulimail-order
cloud:
nacos:
discovery:
server-addr: xxx.xxx.xx.xx:8848
sentinel:
transport:
dashboard: xxx.xxx.xx.xx:10006
server:
port: 9090
修改 api接口
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.fallback.OrderFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinelFallback")
public ResponseResult testSentinel(String args){
log.info("輸入?yún)?shù)》》》"+args);
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
測試熔斷
當(dāng)QPS>1時(shí)
notice:
n1. 必須至少請(qǐng)求過一次才能在 Sentinel 控制臺(tái)看到對(duì)應(yīng)的服務(wù)
n2.這個(gè)sentinel部署的服務(wù)器必須要和服務(wù)應(yīng)用服務(wù)器之間網(wǎng)絡(luò)是能夠互相連接的,否則sentinel會(huì)連接不上應(yīng)用的服務(wù)器灰瞻,無法監(jiān)控應(yīng)用腥例。
n3.每次重啟項(xiàng)目后,流控規(guī)則清空箩祥。
在實(shí)際應(yīng)用中院崇,大都采用JSON格式的數(shù)據(jù),所以如果希望修改觸發(fā)限流之后的返回結(jié)果形式袍祖,則可以通過自定義限流異常來處理底瓣,比如限流異常和熔斷異常。
限流異常
限流異常專門負(fù)責(zé)處理sentinel限流拋出的異常(BlockException)
定義限流異常處理類
package com.pl.gulimailcart.Infrastructure.sentinel.block;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName OrderBlockHandler
* @Author pl
* @Date 2021/3/12
* @Version V1.0.0
*/
@Slf4j
public class OrderBlockHandler {
public static ResponseResult<String> testSentinelFallback(String args,BlockException blockException) {
return new ResponseResult(ErrorCodeEnume.HTTP_SENTINEL.getCode(),ErrorCodeEnume.HTTP_SENTINEL.getMsg());
}
}
OrderApi
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.sentinel.block.OrderBlockHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinel",blockHandler = "testSentinelFallback",blockHandlerClass = OrderBlockHandler.class)
public ResponseResult testSentinel(String args){
log.info("輸入?yún)?shù)》》》"+args);
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
notice:
n1.blockHandlerClass
blockHandler默認(rèn)需要和原方法寫在一個(gè)類中蕉陋,但是若希望使用其他類的函數(shù)捐凭,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法凳鬓。
n2.blockHandlerClass中的方法必須是static修飾
JAVA異常
fallback用于處理java異常
fallback類
package com.pl.gulimailcart.Infrastructure.sentinel.fallback;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import lombok.extern.slf4j.Slf4j;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName OrderFallback
* @Author pl
* @Date 2021/3/12
* @Version V1.0.0
*/
@Slf4j
public class OrderFallback {
public static ResponseResult testSentinelFallback(String args,Throwable ex){
log.warn("Invoke testSentinelFallback: " + ex.getClass().getTypeName());
ex.printStackTrace();
log.info("進(jìn)入testSentinelFallback茁肠,輸入?yún)?shù)》》》"+args);
return new ResponseResult<String>(ErrorCodeEnume.HTTP_FALLBACK.getCode(), ErrorCodeEnume.HTTP_FALLBACK.getMsg(),ex.getMessage());
}
}
OrderApi
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.sentinel.fallback.OrderFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinel",fallback = "testSentinelFallback",fallbackClass = OrderFallback.class)
public ResponseResult testSentinel(String args){
log.info("輸入?yún)?shù)》》》"+args);
int i = 10 / 0;
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
notice:
n1.fallbackClass中的方法必須是static修飾
n2.如果項(xiàng)目中有配置@ControllerAdvice注解的統(tǒng)一異常處理,則不太需要運(yùn)用fallback屬性
fallback和blockHandler一起使用
package com.pl.gulimailcart.Interfaces.facade;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.pl.configuration.BusinessException.ErrorCodeEnume;
import com.pl.dddgulimail.common.dto.ResponseResult;
import com.pl.gulimailcart.Infrastructure.sentinel.block.OrderBlockHandler;
import com.pl.gulimailcart.Infrastructure.sentinel.fallback.OrderFallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <p>
*
* @Description: TODO
* </p>
* @ClassName CartApi
* @Author pl
* @Date 2021/1/20
* @Version V1.0.0
*/
@Slf4j
@RestController()
@RequestMapping("order")
public class OrderApi {
@GetMapping("/testFeign")
public String testFeign() {
return "cartList";
}
@PostMapping("/testSentinel")
@SentinelResource(value = "testSentinel",fallback = "testSentinelFallback",fallbackClass = OrderFallback.class,blockHandler = "testSentinelFallback",blockHandlerClass = OrderBlockHandler.class)
public ResponseResult testSentinel(String args){
log.info("輸入?yún)?shù)》》》"+args);
int i = 10 / 0;
return new ResponseResult<>(ErrorCodeEnume.SUCCESS.getCode(), ErrorCodeEnume.SUCCESS.getMsg());
}
}
FQS<1
FQS>1