springcloud 整合sentinel
一、參考官網:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
1. 搭建sentinel Dashborad
1. 下載jar包: Releases · alibaba/Sentinel (github.com)
2. 啟動:java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
3. 如果8080端口沖突,就更改端口

2. springcloud項目接入:
1.引入依賴:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
2.在application.yml中配置:
spring: cloud: sentinel: transport: port: 8719 dashboard: localhost:8080
隨便訪問Controller:

點擊流控:配置流控規則:每秒只允許一個請求

請求超過限制報錯:

3. 自定義報錯:
/** * author: yangxiaohui * date: 2023/8/7 */ @Component public class SentinelBlockExceptionHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception { httpServletResponse.setCharacterEncoding("utf-8"); JSONObject jsonObject = new JSONObject(); jsonObject.put("ErrorCode","500"); jsonObject.put("Msg","To Many Request"); httpServletResponse.getWriter().write(JSON.toJSONString(jsonObject)); } }

源碼分析:sentinel為何能夠對http請求進行限流以及為何能自定義返回對象:(省略部分代碼)
public abstract class AbstractSentinelInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { String origin = parseOrigin(request); String contextName = getContextName(request); ContextUtil.enter(contextName, origin);
//sentinel 限流核心代碼,在這里不分析 如果限流不通過就會拋異常BlockException Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry);
return true; } catch (BlockException e) { try { handleBlockException(request, response, e); //限流失敗的返回值 } finally { ContextUtil.exit(); } return false; } } protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { if (baseWebMvcConfig.getBlockExceptionHandler() != null) { //限流失敗最終是交給 BaseWebMvcConfig的一個接口處理 baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e); } else { // Throw BlockException directly. Users need to handle it in Spring global exception handler. throw e; } } }
通過源碼分析,如果了解過springMvc源碼可以知道,要攔截請求只要定義一個攔截器HandlerInterceptor 即可,在攔截的方法里,根據請求路徑,校驗請求有沒達到限流的要求,達到就拋異常,捕捉到異常后,在異常里面處理響應邏輯:
之后我們再看看SentinelWebAutoConfiguration這個配置類:
@Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnProperty( name = {"spring.cloud.sentinel.enabled"}, matchIfMissing = true ) @ConditionalOnClass({SentinelWebInterceptor.class}) @EnableConfigurationProperties({SentinelProperties.class}) public class SentinelWebAutoConfiguration implements WebMvcConfigurer { private static final Logger log = LoggerFactory.getLogger(SentinelWebAutoConfiguration.class); @Autowired private SentinelProperties properties; @Autowired private Optional<UrlCleaner> urlCleanerOptional; @Autowired private Optional<BlockExceptionHandler> blockExceptionHandlerOptional; @Autowired private Optional<RequestOriginParser> requestOriginParserOptional; @Autowired private Optional<SentinelWebInterceptor> sentinelWebInterceptorOptional; public SentinelWebAutoConfiguration() { } //將攔截器也就是SentinelWebInterceptor(繼承了AbstractSentinelInterceptor)注冊到Springmvc中 public void addInterceptors(InterceptorRegistry registry) { if (this.sentinelWebInterceptorOptional.isPresent()) { Filter filterConfig = this.properties.getFilter(); registry.addInterceptor((HandlerInterceptor)this.sentinelWebInterceptorOptional.get()).order(filterConfig.getOrder()).addPathPatterns(filterConfig.getUrlPatterns()); log.info("[Sentinel Starter] register SentinelWebInterceptor with urlPatterns: {}.", filterConfig.getUrlPatterns()); } } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true )
//攔截器 public SentinelWebInterceptor sentinelWebInterceptor(SentinelWebMvcConfig sentinelWebMvcConfig) { return new SentinelWebInterceptor(sentinelWebMvcConfig); } @Bean @ConditionalOnProperty( name = {"spring.cloud.sentinel.filter.enabled"}, matchIfMissing = true )
//SentinelWebMvcConfig 繼承了BaseWebMvcConfig public SentinelWebMvcConfig sentinelWebMvcConfig() { SentinelWebMvcConfig sentinelWebMvcConfig = new SentinelWebMvcConfig(); sentinelWebMvcConfig.setHttpMethodSpecify(this.properties.getHttpMethodSpecify()); sentinelWebMvcConfig.setWebContextUnify(this.properties.getWebContextUnify()); if (this.blockExceptionHandlerOptional.isPresent()) { //如果容器中提供了限流異常處理器,就用提供的 this.blockExceptionHandlerOptional.ifPresent(sentinelWebMvcConfig::setBlockExceptionHandler); } else if (StringUtils.hasText(this.properties.getBlockPage())) { //如果提供了限流異常跳轉頁面 sentinelWebMvcConfig.setBlockExceptionHandler((request, response, e) -> { response.sendRedirect(this.properties.getBlockPage()); }); } else { //都沒提供就用默認的 sentinelWebMvcConfig.setBlockExceptionHandler(new DefaultBlockExceptionHandler()); } this.urlCleanerOptional.ifPresent(sentinelWebMvcConfig::setUrlCleaner); this.requestOriginParserOptional.ifPresent(sentinelWebMvcConfig::setOriginParser); return sentinelWebMvcConfig; } }
通過代碼分析,如果沒有提供異常處理器,就會有個默認的異常處理,提供了,就用提供的,我們看看默認的 DefaultBlockExceptionHandler

所以,我們自定義的異常處理器可以替換默認的異常處理器

4. 前面分析,sentinel整合springcloud默認會對http請求進行攔截,本質是springmvc的攔截器導致的,那么如何對feign進行限流配置呢?
1. 在application.yaml中開啟配置:
feign:
sentinel:
enabled: true
2. 簡單demo示例
@FeignClient(name = "service-provider", fallback = EchoServiceFallback.class, configuration = FeignConfiguration.class) public interface EchoService { @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET) String echo(@PathVariable("str") String str); } class FeignConfiguration { @Bean public EchoServiceFallback echoServiceFallback() { return new EchoServiceFallback(); } } class EchoServiceFallback implements EchoService { @Override public String echo(@PathVariable("str") String str) { return "echo fallback"; } }
5.如果我們想攔截非http請求的方法:基于 @SentinelResource注解實現攔截
/** * author: yangxiaohui * date: 2023/8/7 */ @Service public class TestService { @SentinelResource(value = "sayHello",blockHandler = "blockHandler",blockHandlerClass ={BlockClass.class} ) public String sayHello(String name) { return "Hello, " + name; } public static class BlockClass{ //這個方法必須是靜態方法 public static String blockHandler(String name, BlockException blockException){ System.out.println("被阻塞了。。。。。。。。。。。。"); return "haha"; } } }
實現原理是AOP:
@Aspect public class SentinelResourceAspect extends AbstractSentinelAspectSupport { @Pointcut("@annotation(com.alibaba.csp.sentinel.annotation.SentinelResource)") public void sentinelResourceAnnotationPointcut() { } @Around("sentinelResourceAnnotationPointcut()") public Object invokeResourceWithSentinel(ProceedingJoinPoint pjp) throws Throwable { Method originMethod = resolveMethod(pjp); SentinelResource annotation = originMethod.getAnnotation(SentinelResource.class); if (annotation == null) { // Should not go through here. throw new IllegalStateException("Wrong state for SentinelResource annotation"); } String resourceName = getResourceName(annotation.value(), originMethod); EntryType entryType = annotation.entryType(); int resourceType = annotation.resourceType(); Entry entry = null; try { entry = SphU.entry(resourceName, resourceType, entryType, pjp.getArgs()); Object result = pjp.proceed(); return result; } catch (BlockException ex) { return handleBlockException(pjp, annotation, ex); } catch (Throwable ex) { Class<? extends Throwable>[] exceptionsToIgnore = annotation.exceptionsToIgnore(); // The ignore list will be checked first. if (exceptionsToIgnore.length > 0 && exceptionBelongsTo(ex, exceptionsToIgnore)) { throw ex; } if (exceptionBelongsTo(ex, annotation.exceptionsToTrace())) { traceException(ex); return handleFallback(pjp, annotation, ex); } // No fallback function can handle the exception, so throw it out. throw ex; } finally { if (entry != null) { entry.exit(1, pjp.getArgs()); } } } }
6. gateWay網關配置:
1.引入依賴:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2. 配置yml
spring: cloud: sentinel: filter: enabled: false transport: port: 8719 dashboard: localhost:8080
調用后:


3. 限流失敗后,返回默認錯誤
/** * author: yangxiaohui * date: 2023/8/7 */ @Configuration public class SentinelGatewayConfig { public SentinelGatewayConfig() { GatewayCallbackManager.setBlockHandler(new BlockRequestHandler() { @Override public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) { JSONObject jsonObject = new JSONObject(); jsonObject.put("ErrorCode","500"); jsonObject.put("Msg","To Many Request"); return ServerResponse.ok().body(Mono.just(JSON.toJSONString(jsonObject)), String.class); } }); } }

7.前面各種框架的整合,核心代碼,其實還是:
try (Entry entry = SphU.entry("HelloWorld")) { // Your business logic here. System.out.println("hello world"); } catch (BlockException e) { // Handle rejected request. e.printStackTrace(); }


源碼大概:


用官網的一張原理圖展示:

具體源碼過程

8.持久化流控規則 使用nacos
1.引入依賴 在自己項目中引入
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
2. 在yaml中配置:這里以流控規則Flow為例,其他的規則如熱點規則等,只要改下名稱、dataId 、rule-type
spring: cloud: sentinel: transport: dashboard: localhost:8080 datasource: # 名稱隨意 flow: nacos: server-addr: 192.168.50.161:8848 namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99 dataId: sentinel-flow groupId: DEFAULT_GROUP rule-type: flow
對應nacos的值:


規則是一個數組,可以配置多個:
Flow規則:
[ { // 資源名 "resource": "/test", // 針對來源,若為 default 則不區分調用來源 "limitApp": "default", // 限流閾值類型(1:QPS;0:并發線程數) "grade": 1, // 閾值 "count": 1, // 是否是集群模式 "clusterMode": false, // 流控效果(0:快速失敗;1:Warm Up(預熱模式);2:排隊等待) "controlBehavior": 0, // 流控模式(0:直接;1:關聯;2:鏈路) "strategy": 0, // 預熱時間(秒,預熱模式需要此參數) "warmUpPeriodSec": 10, // 超時時間(排隊等待模式需要此參數) "maxQueueingTimeMs": 500, // 關聯資源、入口資源(關聯、鏈路模式) "refResource": "ddd" } ]
降級規則:
[ { // 資源名 "resource": "/test1", "limitApp": "default", // 熔斷策略(0:慢調用比例,1:異常比率,2:異常計數) "grade": 0, // 最大RT、比例閾值、異常數 "count": 200, // 慢調用比例閾值,僅慢調用比例模式有效(1.8.0 引入) "slowRatioThreshold": 0.2, // 最小請求數 "minRequestAmount": 5, // 當單位統計時長(類中默認1000) "statIntervalMs": 1000, // 熔斷時長 "timeWindow": 10 } ]
熱點規則:
[ { // 資源名 "resource": "/test1", // 限流模式(QPS 模式,不可更改) "grade": 1, // 參數索引 "paramIdx": 0, // 單機閾值 "count": 13, // 統計窗口時長 "durationInSec": 6, // 是否集群 默認false "clusterMode": 默認false, // "burstCount": 0, // 集群模式配置 "clusterConfig": { // "fallbackToLocalWhenFail": true, // "flowId": 2, // "sampleCount": 10, // "thresholdType": 0, // "windowIntervalMs": 1000 }, // 流控效果(支持快速失敗和勻速排隊模式) "controlBehavior": 0, // "limitApp": "default", // "maxQueueingTimeMs": 0, // 高級選項 "paramFlowItemList": [ { // 參數類型 "classType": "int", // 限流閾值 "count": 222, // 參數值 "object": "2" } ] } ]
系統規則:
[ { // RT "avgRt": 1, // CPU 使用率 "highestCpuUsage": -1, // LOAD "highestSystemLoad": -1, // 線程數 "maxThread": -1, // 入口 QPS "qps": -1 } ]
授權規則:
[ { // 資源名 "resource": "sentinel_spring_web_context", // 流控應用 "limitApp": "/test", // 授權類型(0代表白名單;1代表黑名單。) "strategy": 0 } ]
注意:配置時,去掉上面的注釋,保證json格式正確
配置多個規則時,在yml中:
spring: cloud: sentinel: transport: dashboard: localhost:8080 datasource: # 名稱隨意 flow: nacos: server-addr: 192.168.50.161:8848 namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99 dataId: sentinel-flow groupId: DEFAULT_GROUP rule-type: flow #系統規則 system: nacos: server-addr: 192.168.50.161:8848 namespace: e5aa487a-9717-4738-8c0a-40cf58bd3a99 dataId: sentinel-system groupId: DEFAULT_GROUP rule-type: system
rule-type 配置表示該數據源中的規則屬于哪種類型的規則(flow, degrade, authority, system, param-flow, gw-flow, gw-api-group)。注意網關流控規則 (GatewayFlowRule) 對應 gw-flow。


浙公網安備 33010602011771號