简介
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", configuration = GlobalFeignConfiguration.class,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