Spring Cloud Alibaba-Sentinel(十五)
阿新 • • 發佈:2019-12-31
簡介
A lightweight powerful flow control component enabling reliability and monitoring for microservices. (輕量級的流量控制、熔斷降級 Java 庫).中文檔案
雪崩效應
雪崩效應又稱cascading failure(級聯故障),指基礎服務故障導致上層服務故障並且故障像雪球一樣越滾越大。
常見容錯方案
- 超時
- 限流
- 艙壁模式:每個controller獨立配置執行緒池,互不幹擾。
- 斷路器模式:服務低於閾值(錯誤次數/錯誤率等構成),斷路器開啟,過了一段時間後允許一個服務呼叫,如果成功,斷路器恢復。
整合Sentinel
- 加依賴
<!--sentinel-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sentinel</artifactId>
</dependency>
複製程式碼
- 寫註解
無
複製程式碼
- 寫配置
無
複製程式碼
actuator/sentinel節點
開啟所有端點檢視是否生效
# actuator
management:
endpoints:
web:
exposure:
# 生產不能全部放開且需配置安全措施
include: '*'
複製程式碼
出現以下內容證明整合成功了
Sentinel控制檯
下載我們pom檔案定義的相同版本的Sentinel。下載地址
#啟動
java -jar XXX.jar
複製程式碼
- 訪問登陸介面http://localhost:8080/#/login
- 預設賬號密碼sentinel/sentinel
- 專案整合
spring:
cloud:
sentinel:
transport:
# 指定sentinel控制檯地址
dashboard: localhost:8080
複製程式碼
- 懶載入 由於Sentinel預設是懶載入的,所以請求任意一個介面後才會又檢視
功能說明
-
流控規則
- 流控模式
-
直接
-
關聯:關聯資源達到閾值,限流自己
-
鏈路:指定鏈路上的流量
- 程式碼
// 定義一個common服務 package com.virgo.user.service; import com.virgo.entity.TblCar; /** * @author zhaozha * @date 2019/10/15 下午4:27 */ public interface CommonService { TblCar common(); } package com.virgo.user.service; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.virgo.entity.TblCar; import org.springframework.stereotype.Service; /** * @author zhaozha * @date 2019/10/15 下午4:28 */ @Service public class CommonServiceImpl implements CommonService { @Override @SentinelResource("common") public TblCar common() { return TblCar.builder().id(1L).build(); } } // controller層同時呼叫這個服務 @GetMapping("/test/a") public String testA() { commonServiceImpl.common(); return "testA"; } @GetMapping("/test/b") public String testB() { commonServiceImpl.common(); return "testB"; } 複製程式碼
- 設定:只對/test/a限流,對/test/b無影響
-
- 流控效果
- 快速失敗
- 直接失敗,丟擲異常
- 原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
- Warm Up
- 初始為閾值/codeFactor(預設為3),經過預熱時長,才達到閾值
- 官方檔案
- 原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
- 排隊等待
- 勻速排隊,閾值必須設定為QPS
- 官方檔案
- 原始碼:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
- 快速失敗
- 流控模式
-
降級規則
- 降級策略
- RT(平均響應時間,秒級別)
- 平均響應時間超出閾值且在時間視窗內通過的請求>=5
- 視窗期過後關閉斷路器
- RT最大4900(更大的需要通過-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
- 異常比列(秒級別)
- 異常數(分鐘級別)
- RT(平均響應時間,秒級別)
- 降級策略
-
熱點規則
- 引數級別的流控規則
- 可以對指定引數/指定引數的指定值限流
- 需要帶 @SentinelResource
-
系統規則
- LOAD
- 當系統load1(1分鐘的load)超過閥值,且併發執行緒數超過系統容量時觸發,建議設定為cpu核心數*2(Linux/Unix-like機器生效)
- 系統容量=maxQps(秒級最大QPS)*minRt(秒級最小響應時間)
- uptime命令-> 1 5 15(系統平均負載/load)
- RT
- 所有入口流量的平均RT達到閾值
- 執行緒數
- 所有入口流量的併發執行緒數達到閾值
- 入口QPS
- 所有入口流量的QPS達到閾值
- LOAD
-
授權規則
- 對訪問的微服務設定黑白名單
微服務和控制檯通訊原理
微服務註冊/心跳到控制檯,控制檯通過api獲取/推送訊息
yml配置
spring:
cloud:
sentinel:
transport:
控制檯地址
dashboard:
和控制檯通訊的ip
client-ip:
和控制通訊埠(預設8719,如果已經佔用,+1直到找到)
port:
心跳毫秒
heartbeat-interval-ms:
複製程式碼
控制檯啟動引數
配置項 | 預設值 | 描述 |
---|---|---|
server.port | 8080 | 指定埠 |
csp.sentinel.dashboard.server | localhost:8080 | 指定地址 |
project.name | - | - |
sentinel.dashboard.auth.username[1.6] | sentinel | Dashboard登陸賬號 |
sentinel.dashboard.auth.password[1.6] | sentinel | Dashboard登陸密碼 |
server.servlet.session.timeout[1.6] | 30分鐘 | 登陸session過期時間(7200:7200秒/60m:60分鐘) |
API(可以保護任意資源)
@GetMapping("/test/sentinel/api")
public String testSentinelApi(@RequestParam(required = false) String a) {
String resourceName = "test-sentinel-api";
ContextUtil.enter(resourceName,"test");
Entry entry = null;
try {
entry = SphU.entry(resourceName);
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("引數不可為空");
}
return a;
} catch (BlockException e) {
return "限流/降級了";
} catch (IllegalArgumentException e) {
Tracer.trace(e);
return "引數非法";
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
複製程式碼
@SentinelResource
通過@SentinelResource簡化程式碼
package com.virgo.user.sentinel;
import com.alibaba.csp.sentinel.slots.block.BlockException;
/**
* @author zhaozha
* @date 2019/10/16 下午3:11
*/
public class ControllerBlockHandlerClass {
/**
* 處理限流/降級
*
* @param a
* @param e
* @return
*/
public static String block(String a,BlockException e) {
return "降級/限流了";
}
}
@GetMapping("/test/sentinel/api")
@SentinelResource(
value = "test-sentinel-api",blockHandler = "block",blockHandlerClass = com.virgo.user.sentinel.ControllerBlockHandlerClass.class
)
public String testSentinelApi(@RequestParam(required = false) String a) {
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("引數不可為空");
}
return a;
}
複製程式碼
整合RestTemplate
- 註解
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate(){
return new RestTemplate();
}
複製程式碼
- 開關
resttemplate:
sentinel:
enabled:
複製程式碼
Feign整合Sentinel
feign:
#feign整合sentinel
sentinel
enable: true
複製程式碼
自定義返回報文
- fallback
package com.virgo.user.feignclient;
import com.virgo.entity.TblCar;
import com.virgo.user.configuration.GlobalFeignConfiguration;
import com.virgo.user.feignclient.fallback.LockCenterFeignClientFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author zhaozha
* @date 2019/10/11 下午12:52
*/
@FeignClient(name = "lock-center",configuration = GlobalFeignConfiguration.class,fallback = LockCenterFeignClientFallback.class)
public interface LockCenterFeignClient {
@GetMapping("/lock/test/{id}")
TblCar findById(@PathVariable(value="id") Long id);
}
package com.virgo.user.feignclient.fallback;
import com.virgo.entity.TblCar;
import com.virgo.user.feignclient.LockCenterFeignClient;
import org.springframework.stereotype.Component;
/**
* @author zhaozha
* @date 2019/10/16 下午3:36
*/
@Component
public class LockCenterFeignClientFallback implements LockCenterFeignClient {
@Override
public TblCar findById(Long id) {
return TblCar.builder().level(1).build();
}
}
複製程式碼
- fallbackFactory(可以捕獲異常)
package com.virgo.user.feignclient;
import com.virgo.entity.TblCar;
import com.virgo.user.configuration.GlobalFeignConfiguration;
import com.virgo.user.feignclient.fallbackFactory.LockCenterFeignClientFallbackFactory;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author zhaozha
* @date 2019/10/11 下午12:52
*/
@FeignClient(name = "lock-center",fallbackFactory = LockCenterFeignClientFallbackFactory.class)
public interface LockCenterFeignClient {
@GetMapping("/lock/test/{id}")
TblCar findById(@PathVariable(value="id") Long id);
}
package com.virgo.user.feignclient.fallbackFactory;
import com.virgo.entity.TblCar;
import com.virgo.user.feignclient.LockCenterFeignClient;
import feign.hystrix.FallbackFactory;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author zhaozha
* @date 2019/10/16 下午4:02
*/
@Component
@Slf4j
public class LockCenterFeignClientFallbackFactory implements FallbackFactory<LockCenterFeignClient> {
@Override
public LockCenterFeignClient create(Throwable throwable) {
return new LockCenterFeignClient() {
@Override
public TblCar findById(Long id) {
log.info("限流/降級",throwable);
return TblCar.builder().level(1).build();
}
};
}
}
複製程式碼
使用方式
使用方式 | 使用方式 | 使用方法 |
---|---|---|
編碼方式 | API | try...catch...finally |
註解方式 | @SentinelResource | blockHandler/fallback |
RestTemplate | @SentinelRestTemplate | blockHandler/fallback |
Feign | feign.sentinel.enabled | fallback/fallbackFactory |
配置持久化
-
阿里雲服務(AHAS)
叢集流控
優化
- 錯誤頁優化(UrlBlockHandler)
package com.virgo.user.sentinel;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlBlockHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.virgo.dto.CommonResult;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author zhaozha
* @date 2019/10/17 上午9:49
*/
@Component
public class VirgoUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse,BlockException e) throws IOException {
// 統一返回物件
CommonResult commonResult = null;
// 流控
if(e instanceof FlowException){
commonResult = CommonResult.builder().status(100).msg("流控異常").build();
}
// 降級
else if(e instanceof DegradeException){
commonResult = CommonResult.builder().status(101).msg("降級異常").build();
}
// 熱點
else if(e instanceof ParamFlowException){
commonResult = CommonResult.builder().status(102).msg("熱點異常").build();
}
// 系統
else if(e instanceof SystemBlockException){
commonResult = CommonResult.builder().status(102).msg("系統異常").build();
}
// 授權
else if (e instanceof AuthorityException){
commonResult = CommonResult.builder().status(102).msg("授權異常").build();
}
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("Content-Type","application/json;charset=utf-8");
httpServletResponse.setContentType("application/json;charset=utf-8");
new ObjectMapper()
.writeValue(
httpServletResponse.getWriter(),commonResult
);
}
}
複製程式碼
- 區分來源(RequestOriginParser)
package com.virgo.user.sentinel;
import com.alibaba.csp.sentinel.adapter.servlet.callback.RequestOriginParser;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author zhaozha
* @date 2019/10/17 上午11:51
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// todo 改成header
String origin = httpServletRequest.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must be specified");
}
return origin;
}
}
複製程式碼
- Restful url支援(UrlCleaner)
package com.virgo.user.sentinel;
import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @author zhaozha
* @date 2019/10/17 下午12:05
*/
@Component
public class VirgoUrlCleaner implements UrlCleaner {
@Override
public String clean(String originUrl) {
// todo 多引數
String[] split = originUrl.split("/");
return Arrays.stream(split)
.map(string -> {
if (NumberUtils.isNumber(string)) {
return "{number}";
}
return string;
})
.reduce((a,b) -> a + "/" + b)
.orElse("");
}
}
複製程式碼
配置
生產環境
- 阿里AHAS