安装
Sentinel 提供一个轻量级的控制台, 它提供机器发现、单机资源实时监控以及规则管理等功能,其控制台
- 安装步骤如下:
- 打开sentinel下载网址
https://github.com/alibaba/Sentinel/releases - 下载
jar
包
- 运行控制台
java -Dserver.port=8180 -Dcsp.sentinel.dashboard.server=localhost:8180 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.1.jar
-
登陆控制台
sentinel/sentinel
http://localhost:8180/
能看到这个界面说明sentinel
启动起来了 -
在项目里面添加依赖
此依赖会在服务中添加一个拦截器对象,这个对象会对前端的请求进行拦截,分析请求是否在sentinel控制台规则的允许范围之内,假如在范围之内就放行,不在则拒绝访问
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 要连接控制台,需要在
bootstrap.yml
里面添加连接sentinel
控制台的地址
spring:
application:
name: sca-provider
cloud:
sentinel:
transport:
dashboard: localhost:8180
此时启动项目后,可以在sentinel
控制台里面看到如下效果
图里面的sca-provider
就是我们的项目yml配置文件里面的name
属性
限流方式
QPS
直接模式
限定/se/te
路径的请求频率为每秒1次,就是1QPS,超过了就根据流控模式
和流控效果
确定的方式进行处理
访问过于频繁时就会出现如下提示
这个提示也是可以修改的。
修改限流提示
查源码,这个部分不重要
下面说如何找到可以修改提示的那个接口
- 从SpringBoot启动时打印的信息里面可以看到这么一条,就是注册了一个拦截器
SentinelWebInterceptor
2021-12-24 14:47:37.519 INFO 20120 --- [ main] c.a.c.s.SentinelWebAutoConfiguration : [Sentinel Starter] register SentinelWebInterceptor with urlPatterns: [/**].
- 找到这个类,发现它继承了
AbstractSentinelInterceptor
package com.alibaba.csp.sentinel.adapter.spring.webmvc;
public class SentinelWebInterceptor extends AbstractSentinelInterceptor {}
- 找到
AbstractSentinelInterceptor
这个类,里面有一个preHandle
方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
try {
String resourceName = getResourceName(request);
if (StringUtil.isEmpty(resourceName)) {
return true;
}
if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) {
return true;
}
// Parse the request origin using registered origin parser.
String origin = parseOrigin(request);
String contextName = getContextName(request);
ContextUtil.enter(contextName, origin);
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;
}
}
再点开 这个handleBlockException
的定义,看到它调用了handle
方法来处理,我们点中这个handle
按Ctrl + Alt + B
组合键打开实现类的方法
protected void handleBlockException(HttpServletRequest request, HttpServletResponse response, BlockException e)
throws Exception {
if (baseWebMvcConfig.getBlockExceptionHandler() != null) {
baseWebMvcConfig.getBlockExceptionHandler().handle(request, response, e);
} else {
// Throw BlockException directly. Users need to handle it in Spring global exception handler.
throw e;
}
}
就可以看到这个类了
package com.alibaba.csp.sentinel.adapter.spring.webmvc.callback;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.util.StringUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* 被限流的请求的默认处理类.
*
* @author kaizi2009
*/
public class DefaultBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
// Return 429 (Too Many Requests) by default.
response.setStatus(429);
PrintWriter out = response.getWriter();
out.print("Blocked by Sentinel (flow limiting)");
out.flush();
out.close();
}
}
可以看到这个类实现了一个接口,实现了里面的handle方法,就是我们刚才在页面上看到的提示了,所以我们也写一个类,实现这个接口,并交由Spring容器进行管理,就可以了。
4. 比如我们创建这么一个类
package vip.sjxq.provider.test;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class MyBlocakExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(425);
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("你访问的太频繁了");
response.getWriter().close();
}
}
重启动项目后再被限流时,提示就已经换成我们的了
关联模式
需要保证/provider/sentinel02
的访问时,可以这样设置,就是02的访问超过阈值时,会限制01的访问
如图所示,如果02每秒访问次数超过一次,02仍然可以正常访问,但01无论每秒访问几次都是显示被限流。
链路模式(针对SentinelResource标识的方法的限流)
使用@SentinelResource
注解标识一个方法,表示对此方法进行流控管理,可以对各种不同的访问路径分别做流控。这里所说的不同的访问路径,可以简单的理解为被@RequestMapping
、@GetMapping
、@PostMapping
等注解标识的方法、且方法里面有调用被@SentinelResource
注解的方法时,此方法上面的诸如@RequestMapping
的注解里面的请求路径值,就是上面说的链路的路径。
比如创建一个类,定义获取资源的方法,打上@SentinelResource
注解,并把该类交由Spring
容器进行管理
package vip.sjxq.provider.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
import vip.sjxq.provider.pojo.User;
@Service
public class UserService {
//名字可以随便取,但是一定要有,会显示在sentinel控制台里面
@SentinelResource("queryUser")
public User getUser(){
User user = new User();
user.setName("张三").setAge(18).setAddress("山东济南");
return user;
}
}
然后定义一个UserController
定义两个请求处理方法,调用这个userService.getUser()
方法 ,模拟访问资源
package vip.sjxq.provider.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import vip.sjxq.provider.pojo.User;
import vip.sjxq.provider.service.UserService;
@RestController
@RequestMapping("/user")
public class ProviderUserController {
@Autowired
private UserService userService;
//该请求路径会访问资源,则/user/one就是一条链路
@RequestMapping("/one")
public User showUser() {
return userService.getUser();
}
//该请求路径会访问资源,则/user/two也是一条链路
@RequestMapping("/two")
public User showUser2(){
return userService.getUser();
}
}
重启服务,并分别访问一次两个链路后,就可以在sentinel-簇点链路中看到如下的情况
默认情况下会把这两个链路显示到同一个 sentinel_spring_web_context
下
如果要对/user/one
和/user/two
这两个链路都进行限流,则
这样就是把所有当前项目的请求都做了限流。
- 如果想要分别对各个链路做不同的限流的话,需要在配置里面
spring:
application:
name: sca-provider
cloud:
sentinel:
transport:
dashboard: localhost:8180
web-context-unify: false
# 代表不再自动合并链路
这样再次重启,并分别访问两个链路后,就可以看到两个链路下都有queryUser
这个资源了
此时就可以分别对指定的链路进行限流了
但是限流后显示的页面都是500页面,如何自定义这个页面呢
自定义限流后的页面
- 创建一个
BlockHandler
类,并创建被限流时的处理方法
- 要求:
该方法的返回值类型要与被处理的路径的那个方法的返回值类型一致
该方法必须是静态的
该方法的参数必须是BlockException
异常
package cn.tedu.handler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class BlockHandler {
//方法必须为静态的,方法名随便取
//异常类型必须为BlockException
public static User call(BlockException e){
log.error("限流了{}",e.getMessage());
User user = new User();
user.setName("你访问的太频繁了");
return user;
}
}
- 修改
UserService
里面的@SentinelResource
注解内部的值
参数 | 意义 |
---|---|
value | 资源的名称,会显示在sentinel 控制台的簇点里面 |
blockHandlerClass | 用于指定处理链路限流异常的类型 |
blockHandler | 用于指定blockHandlerClass 指定的类里面的用来处理指定异常的方法 |
@SentinelResource(value ="userResource",blockHandlerClass = BlockHandler.class,blockHandler = "call")
public User getUser(){
User user =new User();
user.setName("张三").setAge(18).setAddr("山东济南");
return user;
}
- 重启项目、在sentinel簇点链路里面对
userResource
资源进行限流后,再执行高频刷新页面后就可以看到我们修改的被限流后的结果信息了
{
"name": "你访问的太频繁了",
"age": null,
"addr": null
}
自定义拦截器,实现仅限定时间段内可访问
- 实现
HandlerInterceptor
接口,里面有很多默认方法,我们可以根据需要对其进行重写
package cn.tedu.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalTime;
public class MyTimeInterceptor implements HandlerInterceptor {
/**
* 此方法会在实际方法执行之前执行,返回为:true 放行;false 拦截掉此请求
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
LocalTime lt = LocalTime.now();
System.out.println("进入了时间拦截器");
final int hour = lt.getHour();
if (hour < 8 || hour > 21) {
throw new RuntimeException("请在 8:00 ~ 21:00 期间访问");
}
return true;
}
}
如果请求被拦截器拦截掉,则客户端的请求会被立即响应掉,Content-Length:0
,此请求不再向后执行
- 把拦截器注册到Spring中
package cn.tedu.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyConfig implements WebMvcConfigurer {
/**
* 注册拦截器,并定义拦截规则,负责注册拦截器的对象
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyTimeInterceptor()).addPathPatterns("/user/*");
}
}
降级
规则设置
慢调用比例
在指定的统计时长
内,有超过最小请求数
的请求,其中有比例阈值
点比的请求数的响应时间大于或等于最大RT
,则在接下来的熔断时长
的时间内,请求会被
例如下面这个规则的意思是:1000ms内,5个请求的30%的请求的响应时间超过了200ms,则熔断5s
通过断点可以看到被降级时得到的异常是DegradeException
,而限流的话是FlowException
因此可以使用instanceof
来判断是异常产生后,是被限流了还是被降级了。然后根据不同的异常来对应的向前端返回信息
package cn.tedu.config;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler{
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
response.setCharacterEncoding("utf-8");
PrintWriter out = response.getWriter();
String reson = "限流";
if(e instanceof DegradeException){
reson = "降级";
} else if (e instanceof FlowException){
reson = "限流";
}
out.print(String.format("该服务已经被:%s",reson));
out.flush();
out.close();
}
}
热点
可以设置该热点的参数位置,这里使用索引来指示
在热点规则
里面点击编辑
总结:
- 流控有直接、关联和链路三种模式;直接流控是指对指定的API或路径进行流量访问频率的控制;关联是指为了保障另外一个API流量的正常访问,而限制本身API的访问量。链路是针对
@SentinelResource
注解标识的资源访问的来源进行流控,当然了,此注解标识的资源也可以使用前两种流控方式。可能 - 降级是某API或路径资源的访问极其耗时,或响应时间不稳定时,为保证不会出现调用堆积的情况而进行的一种流控。通过监控指定时间窗口内、请求数达到了最小请求数且响应超出指定时间的请求数占比超过了阀值,则进行控制。
- 热点更加精细一些,可以针对API或是路径资源的某个参数存在否或是其参数的值来进行流控,就是都是同一个API,但是给其传的值可能并不全是热点数据,热点只存在于一小部分值中,就可以使用热点流控的方式来保障热点数据的访问。
- 系统流控,就更加的粗犷了,可以说是从系统环境层面来进行的流控,关注的是服务器的性能,比如load(没搞懂是啥)、CPU占用率(太高时可以限流)、线程数等