首先创建父maven项目springcloudalibaba
,在pom文件中引入如下依赖,需要注意版本对应关系
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.11.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>springcloudalibabaartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>springcloudalibabaname>
<description>springcloudalibabadescription>
<modules>
<module>Order-sentinelmodule>
modules>
<properties>
<java.version>1.8java.version>
<spring.cloud.alibaba.version>2.2.5.RELEASE spring.cloud.alibaba.version>
<spring.cloud.version>Hoxton.SR8spring.cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>${spring.cloud.alibaba.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring.cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
随后在父maven项目下创建子maven项目Order-sentinel
,pom文件如下
<parent>
<groupId>com.examplegroupId>
<artifactId>springcloudalibabaartifactId>
<version>0.0.1-SNAPSHOTversion>
parent>
<groupId>org.examplegroupId>
<artifactId>Order-sentinelartifactId>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
dependencies>
创建一个Controller控制器
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
private static final String USER_RESOURCE_NAME = "user";
private static final String DEGRADE_RESOURCE_NAME = "degrade";
// 进行流控
@RequestMapping("/hello")
public String hello(){
Entry entry = null;
try {
// 通过接口路径定义资源名称,sentinel是通过资源进行限制的
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "hello world";
log.info("========="+str+"===========");
return str;
}catch (BlockException e1){
// 资源阻止访问,被限制或者被降级
// 进行相应的处理操作
log.info("block!");
return "被流控了";
}catch (Exception e2){
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(e2,entry);
}finally {
if(entry!=null){
entry.exit();
}
}
return null;
}
/**
* @description 改善接口中资源定义和被流控后降级的处理方法
* 使用步骤:1、引入依赖
* 2、配置bean
* value 定义资源
* blockHandler 设置流控降级后的处理方法(默认该方法声明在同一个类中)
* 如果不在同一个类中可以通过 blockHandlerClass 设置,且方法需要用static修饰
* @return: com.tuling.sentineldemo.entity.User
*/
@RequestMapping("/user")
@SentinelResource(value = USER_RESOURCE_NAME,blockHandler = "blockHandlerForGetUser")
public User getUser(String id){
return new User("测试");
}
/**
* 注意 :
* 1、一定要public
* 2、返回值一定要和源方法一样,包含源方法参数,可以在参数最后加上异常类
*
*/
public User blockHandlerForGetUser(String id,BlockException e){
e.printStackTrace();
return new User("被流控了!");
}
@PostConstruct
private static void initFlowRules(){
// 流控规则
List<FlowRule> rules = new ArrayList<>();
// 流控
FlowRule rule = new FlowRule();
// 设置受保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则(QPS)
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule.setCount(2);
rules.add(rule);
// 流控
FlowRule rule1 = new FlowRule();
// 设置受保护的资源
rule1.setResource(USER_RESOURCE_NAME);
// 设置流控规则(QPS)
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule1.setCount(1);
rules.add(rule1);
// 加载配置好的规则
FlowRuleManager.loadRules(rules);
}
}
启动项目,浏览器快速访问user和hello接口,会看到被流控的字样,表明流控规则设置生效了
先下载安装好对应版本的dashboard,dashboard控制面板下载安装教程请看这篇《sentinel控制面板dashboard的下载安装教程》
随后在项目yml配置文件中进行相应的配置
server:
port: 8860
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
web-context-unify: false # 打开调用链路
order-sentinel
是对应的服务名,127.0.0.1:8080
为dashboard程序对应的访问ip和端口号
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
System.out.println("BlockExceptionHandler ========="+e.getRule());
String r = null;
if(e instanceof FlowException){
r = "被流控了";
}else if(e instanceof DegradeException){
r = "被降级处理了";
} else if (e instanceof ParamFlowException) {
r = "热点参数限流了";
} else if (e instanceof SystemBlockException) {
r = "触发了系统保护规则";
} else if (e instanceof AuthorityException) {
r = "授权规则不通过";
}
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getWriter(),r);
}
}
配置了全局BlockException异常处理后,当某个接口被流控或者降级处理导致异常会通过该方法进行处理
当前整个项目目录结构如下
controllert层
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private IOrderService orderService;
@GetMapping("/addOrder")
public String addOrder(){
System.out.println("下单成功");
return "下单成功";
}
@GetMapping("/getOrder")
public String getOrder(){
System.out.println("查询订单成功");
return "查询订单成功";
}
@GetMapping("/flow")
// @SentinelResource(value = "flow",blockHandler = "flowBlockHandler")
public String flow(){
return "返回成功";
}
@GetMapping("/flowThread")
@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")
public String flowThread() throws InterruptedException {
Thread.sleep(5000);
return "返回成功";
}
public String flowBlockHandler(BlockException e){
return "被流控了";
}
@RequestMapping("/test1")
public String test1(){
return orderService.getUser();
}
@RequestMapping("/test2")
public String test2(){
return orderService.getUser();
}
}
service层
public interface IOrderService {
String getUser();
}
service实现类
@Service
public class OrderServiceImpl implements IOrderService {
@Override
@SentinelResource(value = "getUser",blockHandler = "blockExceptionGetUser")
public String getUser() {
return "查询用户";
}
public String blockExceptionGetUser(BlockException e) {
return "流控";
}
}
最后,启动项目,依次访问各个接口,让各个接口能被dashboard识别并注册到控制面板中
。
进入dashboard控制面板,点击簇点链路,可以看到各个接口并能做出相应的规则设置
对/order/addOrder资源进行流控规则设置,输入以上对应的值点击新增按钮,随后请求order/addOrder接口,只要在一秒内请求超过两次后会出现流控效果。QPS就表示为每秒请求数
同样为flowThread设置流控规则,由于flowThread接口设置了让线程睡眠5秒,让该接口积攒了超过两条线程后会出现流控效果
现有下单接口addOrder和查询订单接口getOrder,当下单接口请求量高于一定阈值,为了节省服务器性能,保障下单接口正常可用,可以对查询订单接口进行限流,此时可以用到关联模式。对查询订单接口设置如上流控规则,当下单接口每秒请求数超过2次,则查询订单接口不可用。
链路模式是当两个不同的接口调用一个公用的方法,可以对该方法设置流控规则。当公用的方法每秒请求超过一定限制可以通过链路模式对某个调用它的接口进行限流。
这里也可以对test1下的getUser设置规则,效果一样
当getUser请求数每秒超过2次,则/order/test1被流控,/order/test2正常调用
默认使用的是快速失败的效果,快速失败顾名思义就是当此次访问请求不符合设定的规则会直接抛出对应异常,而Warm Up则是预热效果,它是让每秒请求数从一个最小值依次递增到设定的阈值的一个过程,达到对系统的一个预热效果,可以有效防止缓存击穿(例如在系统刚开始启动的时候,缓存中还没有数据,此时涌入大量的请求,这些请求先是从缓存中查询数据,当发现缓存中没有想要的数据时会去请求数据库,数据库由于一次性收到大量的请求导致瘫痪,这就叫缓存击穿,即大量请求跳过缓存,直达数据库层,导致数据库瘫痪),这是因为在预热过程中,由于请求量依次递增,系统有时间去填充缓存中的数据,后续大部分的请求也能从缓存中找到相应的数据,避免了数据库层一次性涌入大量请求的情况。
这里为flow接口设定流控规则,上述规则表示flow接口有两秒的预热时间,在这两秒内进入flow接口的请求依次从最小值递增到阈值10,我们采用测试工具进行测试,设定了15秒执行200次请求,结果如下图,可以发现通过的请求依次递增到设定的阈值。
排队等待效果就是当同一时间访问的请求数超出设定的阈值5,只允许前5个请求访问通过,其他请求进行排队等待,等待时间若超过设定的时常则抛出对应的流控异常,为了体现排队等待的效果,先用压测工具测试快速失败下的效果,并发数填30,压测轮数为2。
每经过5秒进行一次并发测试,快速失败效果如下。可发现,在脉冲流量情况下,快速失败仅在