sentinel-实战笔记
Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。
Sentinel主要特性:
控制台(下载地址在这里)
在使用Sentinel
之前,我们首先需部署Sentinel Dashborad
,通过以下命令运行
java -Dserver.port=9999 -Dcsp.sentinel.dashboard.server=localhost:9999 -Dproject.name=sentinel-dashboard -jar D:\server\sentinel\sentinel-dashboard-1.7.0.jar
启动成功可以访问:http://localhost:9999
默认账号密码:sentinel/sentinel
登录后可以看到dashboard
此时可以当sentinel监控到当前自己服务的信息。我们可以启动自己的应用。
Sentinel针对各个主流框架都提供了适配(包括Servlet,Dubbo,SpringBoot/SpringCloud,gRPC,RocketMQ等),本文以SpringBoot2
举例(通过笔者测试发现,SpringBoot 1.x支持不好,自定义流控规则不可用)。
首先我们需要在SpringBoot2
的配置文件中指定Sentinel连接的控制台地址和项目名,即application.yml
文件,如下
spring: application: name: sentinel-project cloud: sentinel: transport: dashboard: localhost:9999
<!--sentinel--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--sentinel end-->
此时import依赖你会发现报错:
Could not find artifact com.alibaba.csp:sentinel-web-servlet:pom:unknown in nexus-aliyun (http://maven.aliyun.com/nexus/content/groups/public)
需要在pom文件中添加如下配置即可:
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>0.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
开发一个控制器用于测试。
@RestController @RequestMapping("/sen") public class HelloController { @RequestMapping("/hello") public String hello() { return "success"; } }
至此,启动项目。观察sentinel后台:
可以看到我们的应用被sentinel拦截,链路已被追踪到。
直接限流:直接对关联的url资源限流
开启sentinel,开启provider服务
对/index资源做流控,设置QPS为1(表示一秒钟只允许访问一次)
此时我们短时间内多次访问http://localhost:7788/sen/hello
接口,观察返回结果:
出现Blocked by Sentinel (flow limiting)
很幸运,我们成功了!!!说明我们的接口被拦截了。
但是我们思考一个问题,在现在前后端分离的开发模式下,这样的异常抛给前端,肯定是不合适的!这时我们可以自定义异常来处理这个问题。
/** * 自定义sentinel异常返回信息 * @author user */ @Component public class CustomUrlBlockHandler implements UrlBlockHandler { @Override public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException e) throws IOException { R r = null; // BlockException 异常接口,其子类为Sentinel五种规则异常的实现类 // AuthorityException 授权异常 // DegradeException 降级异常 // FlowException 限流异常 // ParamFlowException 参数限流异常 // SystemBlockException 系统负载异常 if (e instanceof FlowException) { r = R.error(100, "访问速度过快"); } else if (e instanceof DegradeException) { r = R.error(100, "接口已被降级,暂时无法访问"); } else if (e instanceof AuthorityException) { r = R.error(100, "无权访问"); } else if (e instanceof ParamFlowException) { r = R.error(100, "热点参数限流了"); } else if (e instanceof SystemBlockException) { r = R.error(100, "系统靠不住了,稍等吧"); } response.setStatus(500); response.setCharacterEncoding("utf-8"); response.setHeader("Content-type","application/json;charset=utf-8"); response.setContentType("application/json;charset=utf-8"); new ObjectMapper().writeValue(response.getWriter(), r); } }
实现UrlBlockHandler
接口,重写blocked方法,对BlockException
子异常进行处理。
重启项目再次访问:
这样抛给前端的异常就比较友好了。
RT: 单个请求的响应时间超过阈值,则进入准降级状态,接下来 1 S 内连续 5 个请求响应时间均超过阈值,就进行降级,持续时间为时间窗口的值。
异常比例:每秒异常数量占通过量的比例大于阈值,就进行降级处理,持续时间为时间窗口的值。
异常数:1 分钟内的异常数超过阈值就进行降级处理,时间窗口的值要大于 60S,否则刚结束熔断又进入下一次熔断了。
热点规则是流控规则的更细粒度操作,可以具体到对某个热点参数的限流,设置限流之后,如果带着限流参数的请求量超过阈值,则进行限流,时间为统计窗口时长。
必须要添加@SentinelResource
,即对资源进行流控。
@GetMapping("/hot") @SentinelResource("hot") public R hot( @RequestParam(value = "num1", required = false) Integer num1, @RequestParam(value = "num2", required = false) Integer num2) { log.info("num1:{}, num2:{}", num1, num2); return R.ok(); }
当num1
参数被携带多次访问接口,抛出以上异常。
发现该异常没用被捕获,我们定义全局异常处理器来处理。
@Slf4j @Component @RestControllerAdvice @Order(Ordered.HIGHEST_PRECEDENCE) public class GlobalExceptionHandler { /** * 功能描述: 处理ParamFlowException<br> * @param ex * @return: {@link R} * @Author: Edward * @Date: 2021/1/20 10:13 */ @ExceptionHandler(value = ParamFlowException.class) protected R handleParamFlowException(ParamFlowException ex) { //获取所有错误信息 return R.error(4000,"热点参数限流了"); } }
再次访问,返回结果如下:
给指定的资源设置流控应用(追加参数),可以对流控应用进行访问权限的设置,具体就是添加白名单和黑名单。
如何给请求指定流控应用,通过实现RequestOriginParser
接口来完成,代码如下所示。
public class RequestOriginParserDefinition implements RequestOriginParser { @Override public String parseOrigin(HttpServletRequest httpServletRequest) { String serviceName = httpServletRequest.getParameter("appType"); return serviceName; } }
将RequestOriginParserDefinition
类交给Spring容器关联。
/** * @version 1.0 * @Author: Edward * @Date: 2021/1/21 15:21 * @Description: */ @Configuration public class SentinelConfiguration { @PostConstruct public void init() { WebCallbackManager.setRequestOriginParser(new RequestOriginParserDefinition()); } }
我们配置值运行应用类型为pc
应用源可以访问。
当以http://localhost:7788/sen/hello?appType=pc
访问,
此时可以正常访问。
我们更改appType
,再次访问http://localhost:7788/sen/hello?appType=app
存中…(img-wjogFRIm-1611222474344)]
我们配置值运行应用类型为pc
应用源可以访问。
当以http://localhost:7788/sen/hello?appType=pc
访问,
[外链图片转存中…(img-akAWxZ6L-1611222474345)]
此时可以正常访问。
我们更改appType
,再次访问http://localhost:7788/sen/hello?appType=app
[外链图片转存中…(img-1v9XwvpR-1611222474346)]
此时接口已被拦截,无法访问。
源码:https://gitee.com/edwarder/sentinel-project.git