系列文章导航: Spring Cloud Alibaba微服务解决方案
目录
Sentinel引入
Sentinel规则
流控规则
降级规则
热点规则
系统规则
授权规则
集群流控
Sentinel API及@SentinelResource注解
Sentinel扩展
RestTemplate整合Sentinel
Feign整合Sentinel
Sentinel规则持久化
Sentinel异常提示优化
Sentinel支持Restful URL
Sentinel的CommonFilter
项目引入sentinel依赖
org.springframework.cloud
spring-cloud-starter-alibaba-sentinel
0.9.0.RELEASE
应用端连接控制台配置项
示例:
spring:
cloud:
sentinel:
transport:
#指定sentinel控制台地址
dashboard: localhost:8099
#开启饥饿加载
eager: true
启动Sentinel控制台
https://github.com/alibaba/Sentinel/releases 官方下载对应版本的控制台jar
控制台相关配置项如下,可以根据需要调整配置:
启动示例: nohup java >log.out 2>&1 -Dserver.port=8099 -Dsentinel.dashboard.auth.username=xiawei -Dsentinel.dashboard.auth.password=xiawei -jar sentinel-dashboard-1.7.1.jar &
访问sentinel控制台,localhost:8099,用设置的用户密码登录
由于sentinel默认是懒加载的,如果应用没有流量,sentinel控制台不会显示任何内容。可以配置开启饥饿加载。Sentinel的详细配置可以参考这篇文章。
下列各种规则都是基于Sentinel的控制台进行配置,代码中仅使用@SentinelResource指定了资源。Sentinel也支持基于代码配置规则,详情参见这篇文章。
在sentinel控制台,选择簇点链路中针对特定的资源可以配置流控规则,如下图:
针对来源:可以针对不同的微服务来源的调用,配置不同的流控规则,默认为default,不区分来源。支持针对来源需要在代码中 扩展,详情可以参见本文授权规则一节。
阈值类型:QPS每秒请求数、线程数
是否集群:本文暂不讨论集群流控
流控模式:
直接模式:达到设定阈值直接限流。
关联模式:当关联的资源达到阈值,限流自身。适用于使用共同资源的API,有资源倾斜需求的场景。
链路模式:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就限流),API级别的针对来源。
链路模式需要使用@SentinelResource指定需要控制的API级资源,代码示例如下:
@RestController
@RequestMapping
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
private final @NonNull TestServiceImpl testService;
@GetMapping("/testa")
public String testa() {
testService.common();
return "test-a";
}
@GetMapping("/testb")
public String testb() {
testService.common();
return "test-b";
}
}
@Slf4j
@Service
public class TestServiceImpl {
@SentinelResource("common")
public String common() {
log.info("common...");
return "common";
}
}
注意:Sentinel 1.6.3版本以后,Sentinel Web过滤器默认收敛所有URL的入口上下文,因此互连限流不生效。1.7.0和1.7.1版本可以使用配置类的方式关闭URL PATH聚合,示例如下:
spring:
cloud:
sentinel:
filter:
enabled: false
@Configuration
public class FilterContextConfig {
/**
* @NOTE 在spring-cloud-alibaba v2.1.1.RELEASE及前,sentinel1.7.0及后,关闭URL PATH聚合需要通过该方式
* spring-cloud-alibaba v2.1.1.RELEASE后,可以通过配置关闭:spring.cloud.sentinel.web-context-unify=false
* 手动注入Sentinel的过滤器,关闭Sentinel注入CommonFilter实例,修改配置文件中的 spring.cloud.sentinel.filter.enabled=false
* 入口资源聚合问题:https://github.com/alibaba/Sentinel/issues/1024 或 https://github.com/alibaba/Sentinel/issues/1213
* 入口资源聚合问题解决:https://github.com/alibaba/Sentinel/pull/1111
*/
@Bean
public FilterRegistrationBean sentinelFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new CommonFilter());
registration.addUrlPatterns("/*");
// 入口资源关闭聚合
registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
registration.setName("sentinelFilter");
registration.setOrder(1);
return registration;
}
}
在版本1.7.2及以后,可以直接修改配置spring.cloud.sentinel.web-context-unify为false来关闭URL PATH聚合。
流控效果:
快速失败:超出规则配置的阈值时,直接拒绝请求,抛出异常。
Warm Up:预热/冷启动当流量突然增大的时候,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。默认冷启动因子coldFactor
为 3,即请求 QPS 从 阈值/ 3
开始,经预热时长逐渐升至设定的 QPS 阈值。详情参见官方文档。
排队等待:匀速排队,让请求以均匀的速度通过,阈值类型必须设置成QPS
,否则无效。
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException
)。
降级策略
DEGRADE_GRADE_RT
):当 1s 内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值(count
,以 ms 为单位),那么在接下的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException
)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx
来配置。DEGRADE_GRADE_EXCEPTION_RATIO
):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule
中的 count
)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow
小于 60s,则结束熔断状态后仍可能再进入熔断状态。降级配置目前针对RT、异常比例、异常数有不同级别的统计粒度,无法配置,使用还不足够灵活。sentinel的后续版本已经计划处理相关问题,可以在这里关注进展。
热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。
热点规则可以设置API参数级的流量控制,还可以针对不同的参数值设置不同的限流阈值。设置的热点参数必须是基本类型或String。
对API进行热点规则控制,仅需要将API上加入@SentinelResource注解,然后在sentinel控制台针对指定的资源进行配置即可。
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
上述阈值类型的说明如下:
授权规则可以在微服务的粒度级别控制是否允许调用方使用资源,可以配置白名单或黑名单。
授权规则和流控规则的针对来源功能,需要在代码中进行扩展,示例如下:
@Component
public class MyRequestOriginParserConfig implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//指定参数origin为来源参数,必填
//实际项目中为了不侵入请求URL,可以设置在header中
String origin = httpServletRequest.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must not be null .");
}
return origin;
}
}
要想使用集群流控功能,我们需要在应用端配置动态规则源,并通过 Sentinel 控制台实时进行推送。
详细扩展方式参见这篇文章。
Sentinel中的核心API主要有3个:SphU用于定义资源、Tracer用于指定统计异常、ContextUtil用于定义来源。
大致的使用方式如下:
@Slf4j
@RestController
@RequestMapping
public class TestController {
@GetMapping("/test-sentinel-api")
public String testSentinelApi(@RequestParam(required = false) String a) {
String resourceName = "test-sentinel-api";
//定义来源,针对流控规则中的针对来源
ContextUtil.enter(resourceName, "test-wfw");
//定义一个sentinel保护的资源,名称是test-sentinel-api
Entry entry = null;
try {
entry = SphU.entry(resourceName);
//被保护的业务逻辑
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("参数a为空");
}
return a;
} catch (BlockException block) {
//限流或者降级
log.warn("被限流或者降级");
return "被限流或者降级";
} catch (IllegalArgumentException illeaga) {
//将异常加入限流/降级监控统计
//统计IllegalArgumentException的发生次数、占比...
Tracer.trace(illeaga);
return "参数非法。";
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
}
在每个需要保护的业务逻辑中加入上述的模板代码对开发体验来说非常不友好,我们可以使用@SentinelResource来实现相应的功能。示例如下:
@Slf4j
@RestController
@RequestMapping
public class TestController {
@GetMapping("/test-sentinel-api")
@SentinelResource(
//定义资源名称
value = "test-sentinel-api",
//定义限流/降级异常处理方法,需在本类中实现该方法,且方法签名与资源一致,可以在参数最后添加异常类参数
//也可以将限流/降级处理方法抽出,创建独立的类实现,并将方法加上 static修饰
//在此处指定blockHandlerClass 或 fallbackClass类即可
//blockHandlerClass = x.class
//fallbackClass = y.class
blockHandler = "block",
fallback = "fallback"
//设置忽略的异常
//exceptionsToIgnore = {IllegalMonitorStateException.class}
//设置需要trace的异常
//exceptionsToTrace = {IllegalArgumentException.class}
)
public String testSentinelApi(@RequestParam(required = false) String a) {
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("参数a为空");
}
return a;
}
public String block(String a, BlockException b) {
log.warn("限流或者降级了...block...", b);
return "限流或者降级了...block...";
}
public String fallback(String a, Throwable throwable) {
log.warn("限流或者降级了...fallback...", throwable);
return "限流或者降级了...fallback...";
}
}
RestTemplate整合Sentinel,只需要在RestTemplate创建时加入@SentinelRestTemplate注解即可:
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
同样,@SentinelRestTemplate注解也可以支持blockHandler和fallback,类似于@SentinelResource的使用方式。
Feign整合Sentinel,只需要在配置文件中加入属性配置feign.sentinel.enable=true即可。
注意:使用spring cloud alibaba 2.2.1RELEASE版本时,对应的spring cloud版本为 Hoxton.SR3 ,但是将feign整合Sentinel时,加入feign.sentinel.enable=true会导致项目启动报出Caused by: java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidateMetadata(Ljava/lang/Class;)Ljava/util/List错误。这是因为版本兼容性问题导致,feign在2.2.2RELEASE版本修改了一处接口的方法名称的命名错误,而Sentinel并没有进行对应修改。可以在pom中手动配置open feign的版本,将其降级为2.2.0RELEASE解决该问题。
org.springframework.cloud
spring-cloud-openfeign-dependencies
2.2.0.RELEASE
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
如需实现自定义异常处理逻辑,需要改造FeignClient,指定fallback调用类。
示例如下:
feign:
sentinel:
#为feign整合sentinel
enable: true
httpclient:
enabled: true
#最大连接数
max-connections: 200
#单个路径最大连接数
max-connections-per-route: 50
@FeignClient(name = "user-center",
//使用fallbackFactory可以获取异常,使用fallbackFactory时就不要使用fallback
fallbackFactory = FeignClientFallbackFactory.class)
public interface UserCenterFeignClient {
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
@Slf4j
@Component
public class FeignClientFallbackFactory implements FallbackFactory {
@Override
public UserCenterFeignClient create(Throwable throwable) {
return id -> {
log.warn("远程调用被限流/降级了...",throwable);
return null;
};
}
}
Sentinel默认将规则存储在内存中,在应用重启后已配置的规则会丢失,不适用于生产环境。
Sentinel提供了两种模式的扩展,用以支持规则持久化:
pull模式
push模式
生产环境推荐使用push模式。需要改造sentinel-dashboard控制台。详情参见这篇文章。
在实际项目使用中,我们期望流控、降低、授权、系统、热点等规则控制抛出的异常能够被区分,让应用知晓是因为何种控制规则不通过进行的处理。我们可以使用@SentinelResource、@FeignClient、@SentinelRestTemplate等注解中的异常处理回调来支持不同细粒度的异常处理。也可以使用UrlBlockHandler来统一处理,示例如下:
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws IOException {
JSONObject res = new JSONObject();
if (e instanceof FlowException) {
res.put("code", "100");
res.put("msg", "限流了...");
} else if (e instanceof DegradeException) {
res.put("code", "101");
res.put("msg", "降级了...");
} else if (e instanceof ParamFlowException) {
res.put("code", "102");
res.put("msg", "热点参数限流...");
} else if (e instanceof SystemBlockException) {
res.put("code", "103");
res.put("msg", "系统规则(负载/...)不满足要求");
} else if (e instanceof AuthorityException) {
res.put("code", "104");
res.put("msg", "授权规则不通过...");
}
httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setHeader("Content-Type", "application/json;charset=utf-8");
httpServletResponse.setContentType("application/json;charset=utf-8");
JSON.writeJSONString(httpServletResponse.getWriter(), res);
}
}
由于sentinel不支持占位符,在使用restful风格的接口时,不同的PathVariable参数会在sentinel中当做不同的资源,这样会导致无法配置统一的流控规则。
针对这种情况,Sentinel提供了UrlCleaner接口,支持用户将url统一处理,示例如下:
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String originUrl) {
String[] split = originUrl.split("/");
return Arrays.stream(split).map(str -> {
if (NumberUtils.isNumber(str)) {
return "{number}";
}
return str;
}).reduce((prefix, replace) -> prefix + "/" + replace).orElse("");
}
}
经过UrlCleaner处理后,sentinel将含有路径参数的url进行统一,可以针对统一后的资源配置规则
前度扩展Sentinel时使用的UrlBlockHandler(异常处理)、UrlCleaner(资源统一)、RequestOriginParser(支持针对来源),核心逻辑都是使用的Sentinel Api完成,对源码感兴趣的朋友可以查看CommonFilter类。