點(diǎn)贊再看,養(yǎng)成習(xí)慣凡壤,搜一搜【一角錢技術(shù)】關(guān)注更多原創(chuàng)技術(shù)文章蛋辈。本文 GitHub org_hejianhui/JavaStudy 已收錄目养,有我的系列文章挂滓。
一馒索、什么是雪崩效應(yīng)并淋?
業(yè)務(wù)場景寓搬,高并發(fā)調(diào)用
- 正常情況下,微服務(wù)A B C D 都是正常的县耽。
- 隨著時間推移句喷,在某一個時間點(diǎn) 微服務(wù)A突然掛了,此時的微服務(wù)B 還在瘋狂的調(diào)用微服務(wù)A兔毙,由于A已經(jīng)掛了唾琼,所以B調(diào)用A必須等待服務(wù)調(diào)用超時。而我們知道每次B -> A 的適合B都會去創(chuàng)建線程(而線程由計算機(jī)的資源澎剥,比如cpu锡溯、內(nèi)存等)。由于是高并發(fā)場景,B 就會阻塞大量的線程祭饭。那邊B所在的機(jī)器就會去創(chuàng)建線程芜茵,但是計算機(jī)資源是有限的,最后B的服務(wù)器就會宕機(jī)倡蝙。(說白了微服務(wù)B 活生生的被豬隊友微服務(wù)A給拖死了)
- 由于微服務(wù)A這個豬隊友活生生的把微服務(wù)B給拖死了九串,導(dǎo)致微服務(wù)B也宕機(jī)了,然后也會導(dǎo)致微服務(wù) C D 出現(xiàn)類似的情況寺鸥,最終我們的豬隊友A成功的把微服務(wù) B C D 都拖死了猪钮。這種情況也叫做服務(wù)雪崩。也有一個專業(yè)術(shù)語(cascading failures)級聯(lián)故障胆建。
二烤低、容錯三板斧
2.1 超時
簡單來說就是超時機(jī)制,配置以下超時時間笆载,假如1秒——每次請求在1秒內(nèi)必須返回扑馁,否則到點(diǎn)就把線程掐死,釋放資源凉驻!
思路:一旦超時檐蚜,就釋放資源。由于釋放資源速度較快沿侈,應(yīng)用就不會那么容易被拖死。
代碼演示:(針對調(diào)用方處理)
// 第一步:設(shè)置RestTemplate的超時時間
@Configuration
public class WebConfig {
@Bean
public RestTemplate restTemplate() {
//設(shè)置restTemplate的超時時間
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(1000);
requestFactory.setConnectTimeout(1000);
RestTemplate restTemplate = new RestTemplate(requestFactory);
return restTemplate;
}
}
// 第二步:進(jìn)行超時異常處理
try{
ResponseEntity<ProductInfo> responseEntity= restTemplate.getForEntity(uri+orderInfo.getProductNo(), ProductInfo.class);
productInfo = responseEntity.getBody();
}catch (Exception e) {
log.info("調(diào)用超時");
throw new RuntimeException("調(diào)用超時");
}
// 設(shè)置全局異常處理
@ControllerAdvice
public class NiuhExceptionHandler {
@ExceptionHandler(value = {RuntimeException.class})
@ResponseBody
public Object dealBizException() {
OrderVo orderVo = new OrderVo();
orderVo.setOrderNo("-1");
orderVo.setUserName("容錯用戶");
return orderVo;
}
}
2.2 艙壁隔離模式
有興趣可以先了解一下船艙構(gòu)造——一般來說市栗,現(xiàn)代的輪船都會分很多艙室缀拭,艙室直接用鋼板焊死,彼此隔離填帽。這樣即使有某個/某些船艙進(jìn)水蛛淋,也不會營銷其它艙室,浮力夠篡腌,船不會沉褐荷。
代碼中的艙壁隔離(線程池隔離模式)
M類使用線程池1,N類使用線程池2嘹悼,彼此的線程池不同叛甫,并且為每個類分配的線程池大小,例如 coreSIze=10杨伙。
舉例子:M類調(diào)用B服務(wù)其监,N類調(diào)用C服務(wù),如果M類和N類使用相同的線程池限匣,那么如果B服務(wù)掛了抖苦,N類調(diào)用B服務(wù)的接口并發(fā)又很高,你又沒有任何保護(hù)措施,你的服務(wù)就很可能被M類拖死锌历。而如果M類有自己的線程池贮庞,N類也有自己的線程池,如果B服務(wù)掛了究西,M類頂多是將自己的線程池占滿窗慎,不會影響N類的線程池——于是N類依然能正常工作。
思路:不把雞蛋放在一個籃子里怔揩,你有你的線程池捉邢,我有我的線程池,你的線程池滿類和我也沒關(guān)系商膊,你掛了也和我也沒關(guān)系伏伐。
2.3 斷路器模式
現(xiàn)實世界的斷路器大家肯定都很了解,每個人家里都會有斷路器晕拆。斷路器實時監(jiān)控電路的情況藐翎,如果發(fā)現(xiàn)電路電流異常,就會跳閘实幕,從而防止電路被燒毀吝镣。
軟件世界的斷路器可以這樣理解:實時監(jiān)測應(yīng)用,如果發(fā)現(xiàn)在一定時間內(nèi)失敗次數(shù)/失敗率達(dá)到一定閥值昆庇,就“跳閘”末贾,斷路器打開——次數(shù),請求直接返回整吆,而不去調(diào)用原本調(diào)用的邏輯拱撵。
跳閘一段時間后(例如15秒),斷路器會進(jìn)入半開狀態(tài)表蝙,這是一個瞬間態(tài)拴测,此時允許一個請求調(diào)用該調(diào)的邏輯,如果成功府蛇,則斷路器關(guān)閉集索,應(yīng)用正常調(diào)用;如果調(diào)用依然不成功汇跨,斷路器繼續(xù)回到打開狀態(tài)务荆,過段時間再進(jìn)入半開狀態(tài)嘗試——通過“跳閘”,應(yīng)用可以保護(hù)自己穷遂,而且避免資源浪費(fèi)蛹含;而通過半開的設(shè)計,可以實現(xiàn)應(yīng)用的“自我修復(fù)”
三塞颁、Sentinel 流量控制浦箱、容錯吸耿、降級
3.1 什么是Sentinel?
A lightweight powerful flow control component enabling reliability and monitoring for microservices.(輕量級的流量控制酷窥、熔斷降級 Java 庫)
github官網(wǎng)地址:https://github.com/alibaba/Sentinel
wiki:https://github.com/alibaba/Sentinel/wiki/
Hystrix 在 Sentinel 面前就是弟弟
Sentinel的初體驗
niuh04-ms-alibaba-sentinel-helloworld
V1版本:
- 第一步:添加依賴包
<!--導(dǎo)入Sentinel的相關(guān)jar包-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
- 第二步:controller
@RestController
@Slf4j
public class HelloWorldSentinelController {
@Autowired
private BusiServiceImpl busiService;
/**
* 初始化流控規(guī)則
*/
@PostConstruct
public void init() {
List<FlowRule> flowRules = new ArrayList<>();
/**
* 定義 helloSentinelV1 受保護(hù)的資源的規(guī)則
*/
//創(chuàng)建流控規(guī)則對象
FlowRule flowRule = new FlowRule();
//設(shè)置流控規(guī)則 QPS
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設(shè)置受保護(hù)的資源
flowRule.setResource("helloSentinelV1");
//設(shè)置受保護(hù)的資源的閾值
flowRule.setCount(1);
flowRules.add(flowRule);
//加載配置好的規(guī)則
FlowRuleManager.loadRules(flowRules);
}
/**
* 頻繁請求接口 http://localhost:8080/helloSentinelV1
* 這種做法的缺點(diǎn):
* 1)業(yè)務(wù)侵入性很大,需要在你的controoler中寫入 非業(yè)務(wù)代碼..
* 2)配置不靈活 若需要添加新的受保護(hù)資源 需要手動添加 init方法來添加流控規(guī)則
* @return
*/
@RequestMapping("/helloSentinelV1")
public String testHelloSentinelV1() {
Entry entity =null;
//關(guān)聯(lián)受保護(hù)的資源
try {
entity = SphU.entry("helloSentinelV1");
//開始執(zhí)行 自己的業(yè)務(wù)方法
busiService.doBusi();
//結(jié)束執(zhí)行自己的業(yè)務(wù)方法
} catch (BlockException e) {
log.info("testHelloSentinelV1方法被流控了");
return "testHelloSentinelV1方法被流控了";
}finally {
if(entity!=null) {
entity.exit();
}
}
return "OK";
}
}
測試效果:http://localhost:8080/helloSentinelV1
V1版本的缺陷如下:
- 業(yè)務(wù)侵入性很大,需要在你的controoler中寫入 非業(yè)務(wù)代碼.
- 配置不靈活 若需要添加新的受保護(hù)資源 需要手動添加 init方法來添加流控規(guī)則
V2版本:基于V1版本咽安,再添加一個依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.7.1</version>
</dependency>
- 編寫controller
// 配置一個切面
@Configuration
public class SentinelConfig {
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
}
/**
* 初始化流控規(guī)則
*/
@PostConstruct
public void init() {
List<FlowRule> flowRules = new ArrayList<>();
/**
* 定義 helloSentinelV2 受保護(hù)的資源的規(guī)則
*/
//創(chuàng)建流控規(guī)則對象
FlowRule flowRule2 = new FlowRule();
//設(shè)置流控規(guī)則 QPS
flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設(shè)置受保護(hù)的資源
flowRule2.setResource("helloSentinelV2");
//設(shè)置受保護(hù)的資源的閾值
flowRule2.setCount(1);
flowRules.add(flowRule2);
}
/**
* 頻繁請求接口 http://localhost:8080/helloSentinelV2
* 優(yōu)點(diǎn): 需要配置aspectj的切面SentinelResourceAspect ,添加注解@SentinelResource
* 解決了v1版本中 sentinel的業(yè)務(wù)侵入代碼問題,通過blockHandler指定被流控后調(diào)用的方法.
* 缺點(diǎn): 若我們的controller中的方法逐步變多,那么受保護(hù)的方法也越來越多,會導(dǎo)致一個問題
* blockHandler的方法也會越來越多 引起方法急劇膨脹 怎么解決
*
* 注意點(diǎn):
* blockHandler 對應(yīng)處理 BlockException 的函數(shù)名稱蓬推,
* 可選項妆棒。blockHandler 函數(shù)訪問范圍需要是 public,返回類型需要與原方法相匹配沸伏,
* 參數(shù)類型需要和原方法相匹配并且最后加一個額外的參數(shù)糕珊,
* 類型為 BlockException。blockHandler 函數(shù)默認(rèn)需要和原方法在同一個類中
* @return
*/
@RequestMapping("/helloSentinelV2")
@SentinelResource(value = "helloSentinelV2",blockHandler ="testHelloSentinelV2BlockMethod")
public String testHelloSentinelV2() {
busiService.doBusi();
return "OK";
}
public String testHelloSentinelV2BlockMethod(BlockException e) {
log.info("testRt流控");
return "testRt降級 流控...."+e;
}
測試效果:http://localhost:8080/helloSentinelV2
V3版本 基于V2缺點(diǎn)改進(jìn)
/**
* 初始化流控規(guī)則
*/
@PostConstruct
public void init() {
List<FlowRule> flowRules = new ArrayList<>();
/**
* 定義 helloSentinelV3 受保護(hù)的資源的規(guī)則
*/
//創(chuàng)建流控規(guī)則對象
FlowRule flowRule3 = new FlowRule();
//設(shè)置流控規(guī)則 QPS
flowRule3.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設(shè)置受保護(hù)的資源
flowRule3.setResource("helloSentinelV3");
//設(shè)置受保護(hù)的資源的閾值
flowRule3.setCount(1);
flowRules.add(flowRule3);
}
/**
* 我們看到了v2中的缺點(diǎn),我們通過blockHandlerClass 來指定處理被流控的類
* 通過testHelloSentinelV3BlockMethod 來指定blockHandlerClass 中的方法名稱
* ***這種方式 處理異常流控的方法必須要是static的
* 頻繁請求接口 http://localhost:8080/helloSentinelV3
* @return
*/
@RequestMapping("/helloSentinelV3")
@SentinelResource(value = "helloSentinelV3",blockHandler = "testHelloSentinelV3BlockMethod",blockHandlerClass = BlockUtils.class)
public String testHelloSentinelV3() {
busiService.doBusi();
return "OK";
}
// 異常處理類
@Slf4j
public class BlockUtils {
public static String testHelloSentinelV3BlockMethod(BlockException e){
log.info("testHelloSentinelV3方法被流控了");
return "testHelloSentinelV3方法被流控了";
}
}
測試效果:http://localhost:8080/helloSentinelV3
缺點(diǎn):不能動態(tài)的添加規(guī)則毅糟。如何解決問題红选?
3.2 如何在工程中快速整合Sentinel
<!--加入sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--加入actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加Sentinel后,會暴露/actuator/sentinel 端點(diǎn)http://localhost:8080/actuator/sentinel
而Springboot默認(rèn)是沒有暴露該端點(diǎn)的姆另,所以我們需要自己配置
server:
port: 8080
management:
endpoints:
web:
exposure:
include: '*'
3.3 我們需要整合Sentinel-dashboard(哨兵流量衛(wèi)兵)
下載地址:https://github.com/alibaba/Sentinel/releases (我這里版本是:1.6.3)
-
第一步:執(zhí)行
java -jar sentinel-dashboard-1.6.3.jar
啟動(就是一個SpringBoot工程) - 第二步:訪問我們的sentinel控制臺(1.6版本加入登陸頁面)http://localhost:8080/ 喇肋,默認(rèn)賬戶密碼:sentinel/sentinel
- 第三步:我們的微服務(wù) niuh04-ms-alibaba-sentinel-order 整合 sentinel,我們也搭建好了Sentinel控制臺迹辐,為微服務(wù)添加sentinel的控制臺地址
spring:
cloud:
sentinel:
transport:
dashboard: localhost:9999
四蝶防、Sentinel監(jiān)控性能指標(biāo)詳解
4.1 實時監(jiān)控面板
在這個面板中我們監(jiān)控我們接口的 通過的QPS 和 拒絕的QPS,在沒有設(shè)置流控規(guī)則明吩,我們是看不到拒絕的QPS间学。
4.2 簇點(diǎn)鏈路
用來線上微服務(wù)的所監(jiān)控的API
4.3 流控設(shè)置
簇點(diǎn)鏈路 選擇具體的訪問的API,然后點(diǎn)擊“流控按鈕”
含義:
- 資源名:為我們接口的API /selectOrderInfoById/1
- 針對來源:這里是默認(rèn)的 default(標(biāo)識不針對來源)印荔,還有一種情況就是假設(shè)微服務(wù)A需要調(diào)用這個資源菱鸥,微服務(wù)B也需要調(diào)用這個資源,那么我們就可以單獨(dú)的為微服務(wù)A和微服務(wù)B進(jìn)行設(shè)置閥值躏鱼。
-
閥值類型:分為QPS和線程數(shù),假設(shè)閥值為2
- QPS類型:指的是每秒鐘訪問接口的次數(shù) > 2 就進(jìn)行限流
- 線程數(shù):為接受請求該資源殷绍,分配的線程數(shù) > 2 就進(jìn)行限流
流控模式
- 直接:這種很好理解染苛,就是達(dá)到設(shè)置的閥值后直接被流控拋出異常
瘋狂的請求這個路徑
- 關(guān)聯(lián)
業(yè)務(wù)場景:我們現(xiàn)在有兩個API,第一個是保存訂單主到,一個是查詢訂單茶行,假設(shè)我們希望有限操作“保存訂單”
測試:寫兩個讀寫測試接口
/**
* 方法實現(xiàn)說明:模仿 流控模式【關(guān)聯(lián)】 讀接口
* @author:hejianhui
* @param orderNo
* @return:
* @exception:
* @date:2019/11/24 22:06
*/
@RequestMapping("/findById/{orderNo}")
public Object findById(@PathVariable("orderNo") String orderNo) {
log.info("orderNo:{}","執(zhí)行查詢操作"+System.currentTimeMillis());
return orderInfoMapper.selectOrderInfoById(orderNo);
}
/**
* 方法實現(xiàn)說明:模仿流控模式【關(guān)聯(lián)】 寫接口(優(yōu)先)
* @author:hejianhui
* @return:
* @exception:
* @date:2019/11/24 22:07
*/
@RequestMapping("/saveOrder")
public String saveOrder() throws InterruptedException {
//Thread.sleep(500);
log.info("執(zhí)行保存操作,模仿返回訂單ID");
return UUID.randomUUID().toString();
}
測試代碼:寫一個for循環(huán)一直調(diào)用我們的寫接口,讓寫接口QPS達(dá)到閥值
public class TestSentinelRule {
public static void main(String[] args) throws InterruptedException {
RestTemplate restTemplate = new RestTemplate();
for(int i=0;i<1000;i++) {
restTemplate.postForObject("http://localhost:8080/saveOrder",null,String.class);
Thread.sleep(10);
}
}
}
此時訪問我們的讀接口:此時被限流了登钥。
- 鏈路
用法說明畔师,本地實驗沒成功,用alibaba 未畢業(yè)版本0.9.0可以測試出效果牧牢,API級別的限制流量
代碼:
@RequestMapping("/findAll")
public String findAll() throws InterruptedException {
orderServiceImpl.common();
return "findAll";
}
@RequestMapping("/findAllByCondtion")
public String findAllByCondtion() {
orderServiceImpl.common();
return "findAllByCondition";
}
@Service
public class OrderServiceImpl {
@SentinelResource("common")
public String common() {
return "common";
}
}
根據(jù)流控規(guī)則來說: 只會限制/findAll的請求看锉,不會限制/findAllByCondtion規(guī)則
流控效果
- 快速失敗(直接拋出異常)每秒的QPS 操作過1 就直接拋出異常
源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
-
預(yù)熱(warmUp)
源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController>
當(dāng)流量突然增大的時候姿锭,我們常常會希望系統(tǒng)從空閑狀態(tài)到繁忙狀態(tài)的切換的時間長一些。即如果系統(tǒng)在此之前長期處于空閑的狀態(tài)伯铣,我們希望處理請求的數(shù)量是緩步增加呻此,經(jīng)過預(yù)期的時間后,到達(dá)系統(tǒng)處理請求個數(shù)的最大值腔寡。Warm Up (冷啟動焚鲜,預(yù)熱)模式就是為了實現(xiàn)這個目的。
冷加載因子:codeFacotr 默認(rèn)是3
- 默認(rèn) coldFactor 為3放前,即請求 QPS 從 threshold / 3 開始忿磅,經(jīng)預(yù)熱時長逐漸升至設(shè)定的 QPS 閥值。
上圖設(shè)置:就是QPS從100/3=33開始算凭语, 經(jīng)過10秒鐘葱她,達(dá)到一百的QPS 才進(jìn)行限制流量。
-
排隊等待
源碼:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
這種方式適合用于請求以突刺狀來到叽粹,這個時候我們不希望一下子把所有的請求都通過览效,這樣可能會把系統(tǒng)壓垮;同時我們也期待系統(tǒng)以穩(wěn)定的速度虫几,逐步處理這些請求锤灿,以起到“削峰填谷”的效果,而不是拒絕所有請求辆脸。
選擇排隊等待的閥值類型必須是****QPS
上圖設(shè)置:單機(jī)閥值為10但校,表示每秒通過的請求個數(shù)是10,也就是每個請求平均間隔恒定為 1000 / 10 = 100 ms啡氢,每一個請求的最長等待時間(maxQueueingTimeMs)為 20 * 1000ms = 20s状囱。,超過20s就丟棄請求倘是。
4.4 降級規(guī)則
rt(平均響應(yīng)時間)
平均響應(yīng)時間(DEGRADE_GRADE_RT):當(dāng) 1s 內(nèi)持續(xù)進(jìn)入5個請求亭枷,對應(yīng)時刻的平均響應(yīng)時間(秒級)均超過閥值(count,以 ms 為單位)搀崭,那么在接下來的時間窗口(DegradeRule 中的 timeWindow叨粘,以 s 為單位)之內(nèi),對這個方法的調(diào)用都會自動地熔斷(拋出 DegradeException)瘤睹。
注意:Sentinel 默認(rèn)同級的 RT 上限是4900ms升敲,超出此閥值都會算做4900ms,若需要變更此上限可以通過啟動配置項:-Dcsp.sentinel.statistic.max.rt=xxx 來配置
異常比例(DEGRADE_GRADE_EXCEPTION_RATIO)
當(dāng)資源的每秒請求量 >= 5轰传,并且每秒異陈康常總數(shù)占通過量的比值超過閥值(DegradeRule 中的 count)之后,資源進(jìn)入降級狀態(tài)获茬,即在接下的時間窗口(DegradeRule 中的 timeWindow港庄,以 s 為單位)之內(nèi)倔既,對這個方法的調(diào)用都會自動地返回。異常比例的閥值范圍是 [0.0, 1.0]攘轩,代表 0% ~ 100% 叉存。
異常數(shù)(DEGRADE_GRADE_EXCEPTION_COUNT)
當(dāng)資源近千分之的異常數(shù)目超過閥值之后會進(jìn)行熔斷。注意由于統(tǒng)計時間窗口是分鐘級別的度帮,若 timeWindow 小于 60s歼捏,則結(jié)束熔斷狀態(tài)后仍可能再進(jìn)入熔斷狀態(tài)。
4.5 熱點(diǎn)參數(shù)
業(yè)務(wù)場景:秒殺業(yè)務(wù)笨篷,比如商場做促銷秒殺瞳秽,針對蘋果11(商品id=1)進(jìn)行9.9秒殺活動,那么這個時候率翅,我們?nèi)フ埱笥唵谓涌冢ㄉ唐穒d=1)的請求流量十分大练俐,我們就可以通過熱點(diǎn)參數(shù)規(guī)則來控制 商品id=1 的請求的并發(fā)量。而其他正常商品的請求不會受到限制冕臭。那么這種熱點(diǎn)參數(shù)規(guī)則使用腺晾。
五、Sentinel-dashboard 控制臺 和 我們的微服務(wù)通信原理
5.1 控制臺如何獲取到微服務(wù)的監(jiān)控信息辜贵?
5.2 在控制臺配置規(guī)則悯蝉,如何把規(guī)則推送給微服務(wù)的?
我們通過觀察到sentinel-dashboard的機(jī)器列表上觀察注冊服務(wù)微服務(wù)信息托慨。我們的 控制臺就可以通過這些微服務(wù)的注冊信息跟我們的具體的微服務(wù)進(jìn)行通信.
5.3 微服務(wù)整合sentinel時候的提供的一些接口API地址: http://localhost:8720/api
5.4 我們可以通過代碼設(shè)置規(guī)則(我們這里用流控規(guī)則為例)
@RestController
public class AddFlowLimitController {
@RequestMapping("/addFlowLimit")
public String addFlowLimit() {
List<FlowRule> flowRuleList = new ArrayList<>();
FlowRule flowRule = new FlowRule("/testAddFlowLimitRule");
//設(shè)置QPS閾值
flowRule.setCount(1);
//設(shè)置流控模型為QPS模型
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRuleList.add(flowRule);
FlowRuleManager.loadRules(flowRuleList);
return "success";
}
@RequestMapping("/testAddFlowLimitRule")
public String testAddFlowLimitRule() {
return "testAddFlowLimitRule";
}
}
添加效果截圖: 執(zhí)行:http://localhost:8080/addFlowLimit
Sentinel具體配置項:https://github.com/alibaba/Sentinel/wiki/啟動配置項
5.5 對SpringMVC端點(diǎn)保護(hù)關(guān)閉(一般應(yīng)用場景是做壓測需要關(guān)閉)
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:9999
filter:
enabled: true #關(guān)閉Spring mvc的端點(diǎn)保護(hù)
那么我們的這種類型的接口 不會被sentinel保護(hù)
只有加了
@SentinelResource
的注解的資源才會被保護(hù)六鼻由、Ribbon整合Sentinel
6.1 第一步:加配置
<!--加入ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!--加入sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--加入actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
6.2 第二步:加注解
在我們的RestTemplate組件上添加@SentinelRestTemplate注解。并且我們可以通過在@SentinelRestTemplate 同樣的可以指定我們的 blockHandlerClass厚棵、fallbackClass蕉世、blockHandler、fallback 這四個屬性
@Configuration
public class WebConfig {
@Bean
@LoadBalanced
@SentinelRestTemplate(
blockHandler = "handleException",blockHandlerClass = GlobalExceptionHandler.class,
fallback = "fallback",fallbackClass = GlobalExceptionHandler.class
)
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
*****************全局異常處理類*****************
@Slf4j
public class GlobalExceptionHandler {
/**
* 限流后處理方法
* @param request
* @param body
* @param execution
* @param ex
* @return
*/
public static SentinelClientHttpResponse handleException(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductName("被限制流量拉");
productInfo.setProductNo("-1");
ObjectMapper objectMapper = new ObjectMapper();
try {
return new SentinelClientHttpResponse(objectMapper.writeValueAsString(productInfo));
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
/**
* 熔斷后處理的方法
* @param request
* @param body
* @param execution
* @param ex
* @return
*/
public static SentinelClientHttpResponse fallback(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductName("被降級拉");
productInfo.setProductNo("-1");
ObjectMapper objectMapper = new ObjectMapper();
try {
return new SentinelClientHttpResponse(objectMapper.writeValueAsString(productInfo));
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
}
6.3 第三步:添加配置
什么時候關(guān)閉:一般在我們的自己測試業(yè)務(wù)功能是否正常的情況婆硬,關(guān)閉該配置
#是否開啟@SentinelRestTemplate注解
resttemplate:
sentinel:
enabled: true
七狠轻、OpenFeign整合我們的Sentinel
7.1 第一步:加配置
在niuh05-ms-alibaba-feignwithsentinel-order上 pom.xml中添加配置
<!--加入sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--加入actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.niuh</groupId>
<artifactId>niuh03-ms-alibaba-feign-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
7.2 第二步:在Feign的聲明式接口上添加fallback屬性或者 fallbackFactory屬性
- 為我們添加fallback屬性的api
@FeignClient(name = "product-center",fallback = ProductCenterFeignApiWithSentinelFallback.class)
public interface ProductCenterFeignApiWithSentinel {
/**
* 聲明式接口,遠(yuǎn)程調(diào)用http://product-center/selectProductInfoById/{productNo}
* @param productNo
* @return
*/
@RequestMapping("/selectProductInfoById/{productNo}")
ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo) throws InterruptedException;
}
我們feign的限流降級接口(通過fallback沒有辦法獲取到異常的)
@Component
public class ProductCenterFeignApiWithSentinelFallback implements ProductCenterFeignApiWithSentinel {
@Override
public ProductInfo selectProductInfoById(String productNo) {
ProductInfo productInfo = new ProductInfo();
productInfo.setProductName("默認(rèn)商品");
return productInfo;
}
}
- 為我們添加fallbackFactory屬性的api
package com.niuh.feignapi.sentinel;
import com.niuh.entity.ProductInfo;
import com.niuh.handler.ProductCenterFeignApiWithSentielFallbackFactoryasdasf;
import com.niuh.handler.ProductCenterFeignApiWithSentinelFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* Created by hejianhui on 2019/11/22.
*/
@FeignClient(name = "product-center",fallbackFactory = ProductCenterFeignApiWithSentielFallbackFactoryasdasf.class)
public interface ProductCenterFeignApiWithSentinel {
/**
* 聲明式接口,遠(yuǎn)程調(diào)用http://product-center/selectProductInfoById/{productNo}
* @param productNo
* @return
*/
@RequestMapping("/selectProductInfoById/{productNo}")
ProductInfo selectProductInfoById(@PathVariable("productNo") String productNo) throws InterruptedException;
}
通過FallbackFactory屬性可以處理我們的異常
@Component
@Slf4j
public class ProductCenterFeignApiWithSentielFallbackFactoryasdasf implements FallbackFactory<ProductCenterFeignApiWithSentinel> {
@Override
public ProductCenterFeignApiWithSentinel create(Throwable throwable) {
return new ProductCenterFeignApiWithSentinel(){
@Override
public ProductInfo selectProductInfoById(String productNo) {
ProductInfo productInfo = new ProductInfo();
if (throwable instanceof FlowException) {
log.error("流控了....{}",throwable.getMessage());
productInfo.setProductName("我是被流控的默認(rèn)商品");
}else {
log.error("降級了....{}",throwable.getMessage());
productInfo.setProductName("我是被降級的默認(rèn)商品");
}
return productInfo;
}
};
}
}
八、Sentinel 規(guī)則持久化
Sentinel-dashboard 配置的規(guī)則彬犯,在我們的微服務(wù)以及控制臺重啟的時候就清空了向楼,因為它是基于內(nèi)存的。
8.1 原生模式
Dashboard 的推送規(guī)則方式是通過 API 將規(guī)則推送至客戶端并直接更新到內(nèi)存躏嚎。
優(yōu)缺點(diǎn):這種做法的好處是簡單,無依賴菩貌;壞處是應(yīng)用重啟規(guī)則就會消失卢佣,僅用于簡單測試,不能用于生產(chǎn)環(huán)境箭阶。
8.2 Pull拉模式
首先 Sentinel 控制臺通過 API 將規(guī)則推送至客戶端并更新到內(nèi)存中虚茶,接著注冊的寫數(shù)據(jù)源會將新的規(guī)則保存到本地的文件中戈鲁。使用 pull 模式的數(shù)據(jù)源時一般不需要對 Sentinel 控制臺進(jìn)行改造。
這種實現(xiàn)方法好處是簡單嘹叫,不引入新的依賴婆殿,壞處是無法保證監(jiān)控數(shù)據(jù)的一致性
客戶端Sentinel的改造(拉模式)
通過SPI擴(kuò)展機(jī)制進(jìn)行擴(kuò)展,我們寫一個拉模式的實現(xiàn)類 com.niuh.persistence.PullModeByFileDataSource 罩扇,然后在工廠目錄下創(chuàng)建 META-INF/services/com.alibaba.csp.sentinel.init.InitFun文件婆芦。
文件的內(nèi)容就是寫我們的拉模式的實現(xiàn)類:
代碼在niuh05-ms-alibaba-sentinelrulepersistencepull-order 工程的persistence包下。
8.3 Push推模式(以Nacos為例喂饥,生產(chǎn)推薦使用)
原理簡述
- 控制臺推送規(guī)則:
- 將規(guī)則推送至Nacos獲取其他遠(yuǎn)程配置中心
- Sentinel客戶端連接Nacos消约,獲取規(guī)則配置;并監(jiān)聽Nacos配置變化员帮,如果發(fā)送變化或粮,就更新本地緩存(從而讓本地緩存總是和Nacos一致)
- 控制臺監(jiān)聽Nacos配置變化,如果發(fā)送變化就更新本地緩存(從而讓控制臺本地緩存和Nacos一致)
改造方案
微服務(wù)改造方案
- 第一步:在niuh05-ms-alibaba-sentinelrulepersistencepush-order工程加入依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 第二步:加入yml的配置
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:9999
#namespace: bc7613d2-2e22-4292-a748-48b78170f14c #指定namespace的id
datasource:
# 名稱隨意
flow:
nacos:
server-addr: 47.111.191.111:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
rule-type: flow
degrade:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade
system:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-system-rules
groupId: SENTINEL_GROUP
rule-type: system
authority:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-authority-rules
groupId: SENTINEL_GROUP
rule-type: authority
param-flow:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: SENTINEL_GROUP
rule-type: param-flow
Sentinel-dashboard改造方案
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- <scope>test</scope>--> // 需要把test注釋掉
</dependency>
控制臺改造主要是為規(guī)則實現(xiàn):
- DynamicRuleProvider :從Nacos上讀取配置
- DynamicRulePublisher :將規(guī)則推送到Nacis上
在sentinel-dashboard工程目錄com.alibaba.csp.sentinel.dashboard.rule 下創(chuàng)建一 個Nacos的包捞高,然后把我們的各個場景的配置規(guī)則類寫到該包下.
我們以ParamFlowRuleController(熱點(diǎn)參數(shù)流控類作為修改作為演示)
/**
* @author Eric Zhao
* @since 0.2.1
*/
@RestController
@RequestMapping(value = "/paramFlow")
public class ParamFlowRuleController {
private final Logger logger = LoggerFactory.getLogger(ParamFlowRuleController.class);
@Autowired
private SentinelApiClient sentinelApiClient;
@Autowired
private AppManagement appManagement;
@Autowired
private RuleRepository<ParamFlowRuleEntity, Long> repository;
@Autowired
@Qualifier("niuhHotParamFlowRuleNacosPublisher")
private DynamicRulePublisher<List<ParamFlowRuleEntity>> rulePublisher;
@Autowired
@Qualifier("niuhHotParamFlowRuleNacosProvider")
private DynamicRuleProvider<List<ParamFlowRuleEntity>> ruleProvider;
@Autowired
private AuthService<HttpServletRequest> authService;
private boolean checkIfSupported(String app, String ip, int port) {
try {
return Optional.ofNullable(appManagement.getDetailApp(app))
.flatMap(e -> e.getMachine(ip, port))
.flatMap(m -> VersionUtils.parseVersion(m.getVersion())
.map(v -> v.greaterOrEqual(version020)))
.orElse(true);
// If error occurred or cannot retrieve machine info, return true.
} catch (Exception ex) {
return true;
}
}
@GetMapping("/rules")
public Result<List<ParamFlowRuleEntity>> apiQueryAllRulesForMachine(HttpServletRequest request,
@RequestParam String app,
@RequestParam String ip,
@RequestParam Integer port) {
AuthUser authUser = authService.getAuthUser(request);
authUser.authTarget(app, PrivilegeType.READ_RULE);
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app cannot be null or empty");
}
if (StringUtil.isEmpty(ip)) {
return Result.ofFail(-1, "ip cannot be null or empty");
}
if (port == null || port <= 0) {
return Result.ofFail(-1, "Invalid parameter: port");
}
if (!checkIfSupported(app, ip, port)) {
return unsupportedVersion();
}
try {
/* return sentinelApiClient.fetchParamFlowRulesOfMachine(app, ip, port)
.thenApply(repository::saveAll)
.thenApply(Result::ofSuccess)
.get();*/
List<ParamFlowRuleEntity> rules = ruleProvider.getRules(app);
rules = repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (ExecutionException ex) {
logger.error("Error when querying parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when querying parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private boolean isNotSupported(Throwable ex) {
return ex instanceof CommandNotFoundException;
}
@PostMapping("/rule")
public Result<ParamFlowRuleEntity> apiAddParamFlowRule(HttpServletRequest request,
@RequestBody ParamFlowRuleEntity entity) {
AuthUser authUser = authService.getAuthUser(request);
authUser.authTarget(entity.getApp(), PrivilegeType.WRITE_RULE);
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(null);
entity.getRule().setResource(entity.getResource().trim());
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
try {
entity = repository.save(entity);
//publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
publishRules(entity.getApp());
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when adding new parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when adding new parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private <R> Result<R> checkEntityInternal(ParamFlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "bad rule body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getIp())) {
return Result.ofFail(-1, "ip can't be null or empty");
}
if (entity.getPort() == null || entity.getPort() <= 0) {
return Result.ofFail(-1, "port can't be null");
}
if (entity.getRule() == null) {
return Result.ofFail(-1, "rule can't be null");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource name cannot be null or empty");
}
if (entity.getCount() < 0) {
return Result.ofFail(-1, "count should be valid");
}
if (entity.getGrade() != RuleConstant.FLOW_GRADE_QPS) {
return Result.ofFail(-1, "Unknown mode (blockGrade) for parameter flow control");
}
if (entity.getParamIdx() == null || entity.getParamIdx() < 0) {
return Result.ofFail(-1, "paramIdx should be valid");
}
if (entity.getDurationInSec() <= 0) {
return Result.ofFail(-1, "durationInSec should be valid");
}
if (entity.getControlBehavior() < 0) {
return Result.ofFail(-1, "controlBehavior should be valid");
}
return null;
}
@PutMapping("/rule/{id}")
public Result<ParamFlowRuleEntity> apiUpdateParamFlowRule(HttpServletRequest request,
@PathVariable("id") Long id,
@RequestBody ParamFlowRuleEntity entity) {
AuthUser authUser = authService.getAuthUser(request);
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
authUser.authTarget(oldEntity.getApp(), PrivilegeType.WRITE_RULE);
Result<ParamFlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
if (!checkIfSupported(entity.getApp(), entity.getIp(), entity.getPort())) {
return unsupportedVersion();
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
//publishRules(entity.getApp(), entity.getIp(), entity.getPort()).get();
publishRules(entity.getApp());
return Result.ofSuccess(entity);
} catch (ExecutionException ex) {
logger.error("Error when updating parameter flow rules, id=" + id, ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when updating parameter flow rules, id=" + id, throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
@DeleteMapping("/rule/{id}")
public Result<Long> apiDeleteRule(HttpServletRequest request, @PathVariable("id") Long id) {
AuthUser authUser = authService.getAuthUser(request);
if (id == null) {
return Result.ofFail(-1, "id cannot be null");
}
ParamFlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE);
try {
repository.delete(id);
/*publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort()).get();*/
publishRules(oldEntity.getApp());
return Result.ofSuccess(id);
} catch (ExecutionException ex) {
logger.error("Error when deleting parameter flow rules", ex.getCause());
if (isNotSupported(ex.getCause())) {
return unsupportedVersion();
} else {
return Result.ofThrowable(-1, ex.getCause());
}
} catch (Throwable throwable) {
logger.error("Error when deleting parameter flow rules", throwable);
return Result.ofFail(-1, throwable.getMessage());
}
}
private CompletableFuture<Void> publishRules(String app, String ip, Integer port) {
List<ParamFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
return sentinelApiClient.setParamFlowRuleOfMachine(app, ip, port, rules);
}
private void publishRules(String app) throws Exception {
List<ParamFlowRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
private <R> Result<R> unsupportedVersion() {
return Result.ofFail(4041,
"Sentinel client not supported for parameter flow control (unsupported version or dependency absent)");
}
private final SentinelVersion version020 = new SentinelVersion().setMinorVersion(2);
}
8.4 阿里云的 AHAS
- 開通地址:https://ahas.console.aliyun.com/
- 開通規(guī)則說明:https://help.aliyun.com/document_detail/90323.html
第一步:訪問 https://help.aliyun.com/document_detail/90323.html
第二步:免費(fèi)開通
第三步:開通
第四步:接入應(yīng)用
第五步:點(diǎn)擊接入SDK
第六步:加入我們的應(yīng)用
以niuh05-ms-alibaba-sentinelrulepersistence-ahas-order工程為例
- 加入ahas的依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>spring‐boot‐starter‐ahas‐sentinel‐client</artifactId> 4 <version>1.5.0</version>
</dependency>
- 加入配置:yml的配置
ahas.namespace: default
project.name: order-center
ahas.license: b833de8ab5f34e4686457ecb2b60fa46
- 測試接口
@SentinelResource("hot-param-flow-rule")
@RequestMapping("/testHotParamFlowRule")
public OrderInfo testHotParamFlowRule(@RequestParam("orderNo") String orderNo) {
return orderInfoMapper.selectOrderInfoById(orderNo);
}
第一次訪問接口:
添加我們直接的流控規(guī)則
瘋狂刷新我們的測試接口:
九氯材、Sentinel 線上環(huán)境的優(yōu)化
9.1 優(yōu)化錯誤頁面
- 流控錯誤頁面
- 降級錯誤頁面
發(fā)現(xiàn)這兩種錯誤都是醫(yī)院,顯然這里我們需要優(yōu)化
UrlBlockHandler
提供了一個接口硝岗,我們需要實現(xiàn)這個接口
/**
* @vlog: 高于生活氢哮,源于生活
* @desc: 類的描述:處理流控,降級規(guī)則
* @author: hejianhui
* @createDate: 2019/12/3 16:40
* @version: 1.0
*/
@Component
public class NiuhUrlBlockHandler implements UrlBlockHandler {
public static final Logger log = LoggerFactory.getLogger(NiuhUrlBlockHandler.class);
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
if(ex instanceof FlowException) {
log.warn("觸發(fā)了流控");
warrperResponse(response,ErrorEnum.FLOW_RULE_ERR);
}else if(ex instanceof ParamFlowException) {
log.warn("觸發(fā)了參數(shù)流控");
warrperResponse(response,ErrorEnum.HOT_PARAM_FLOW_RULE_ERR);
}else if(ex instanceof AuthorityException) {
log.warn("觸發(fā)了授權(quán)規(guī)則");
warrperResponse(response,ErrorEnum.AUTH_RULE_ERR);
}else if(ex instanceof SystemBlockException) {
log.warn("觸發(fā)了系統(tǒng)規(guī)則");
warrperResponse(response,ErrorEnum.SYS_RULE_ERR);
}else{
log.warn("觸發(fā)了降級規(guī)則");
warrperResponse(response,ErrorEnum.DEGRADE_RULE_ERR);
}
}
private void warrperResponse(HttpServletResponse httpServletResponse, ErrorEnum errorEnum) throws IOException {
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
httpServletResponse.setContentType("application/json;charset=utf-8");
ObjectMapper objectMapper = new ObjectMapper();
String errMsg =objectMapper.writeValueAsString(new ErrorResult(errorEnum));
httpServletResponse.getWriter().write(errMsg);
}
}
優(yōu)化后:
- 流控規(guī)則提示:
- 降級規(guī)則提示:
9.2 針對來源編碼實現(xiàn)
Sentinel 提供了一個
RequestOriginParser
接口,我們可以在這里實現(xiàn)編碼從請求頭中區(qū)分來源
/**
* @vlog: 高于生活辈讶,源于生活
* @desc: 類的描述:區(qū)分來源接口
* @author: hejianhui
* @createDate: 2019/12/4 13:13
* @version: 1.0
*/
/*@Component*/
@Slf4j
public class NiuhRequestOriginParse implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String origin = request.getHeader("origin");
if(StringUtils.isEmpty(origin)) {
log.warn("origin must not null");
throw new IllegalArgumentException("request origin must not null");
}
return origin;
}
}
配置設(shè)置區(qū)分來源為:yijiaoqian
9.3 解決RestFul風(fēng)格的請求
例如:/selectOrderInfoById/2 命浴、 /selectOrderInfoById/1 需要轉(zhuǎn)為/selectOrderInfoById/{number}
/**
* @vlog: 高于生活,源于生活
* @desc: 類的描述:解決RestFule風(fēng)格的請求
* eg: /selectOrderInfoById/2 /selectOrderInfoById/1 需要轉(zhuǎn)為/selectOrderInfoById/{number}
* @author: hejianhui
* @createDate: 2019/12/4 13:28
* @version: 1.0
*/
@Component
@Slf4j
public class NiuhUrlClean implements UrlCleaner {
@Override
public String clean(String originUrl) {
log.info("originUrl:{}",originUrl);
if(StringUtils.isEmpty(originUrl)) {
log.error("originUrl not be null");
throw new IllegalArgumentException("originUrl not be null");
}
return replaceRestfulUrl(originUrl);
}
/**
* 方法實現(xiàn)說明:把/selectOrderInfoById/2 替換成/selectOrderInfoById/{number}
* @author:hejianhui
* @param sourceUrl 目標(biāo)url
* @return: 替換后的url
* @exception:
* @date:2019/12/4 13:46
*/
private String replaceRestfulUrl(String sourceUrl) {
List<String> origins = Arrays.asList(sourceUrl.split("/"));
StringBuffer targetUrl = new StringBuffer("/");
for(String str:origins) {
if(NumberUtils.isNumber(str)) {
targetUrl.append("/{number}");
}else {
targetUrl.append(str);
}
}
return targetUrl.toString();
}
}
PS:以上代碼提交在 Github :https://github.com/Niuh-Study/niuh-cloud-alibaba.git
文章持續(xù)更新贱除,可以公眾號搜一搜「 一角錢技術(shù) 」第一時間閱讀生闲, 本文 GitHub org_hejianhui/JavaStudy 已經(jīng)收錄,歡迎 Star月幌。