SpringCloudAlibaba整合Sentinel

发布时间 2023-09-21 12:35:21作者: xclyydss

Sentinel是阿里巴巴开源的一款面向分布式系统的实时流量控制、熔断降级框架。它可以帮助开发人员在微服务架构中更好地管理服务的流量,防止因为流量激增而导致系统崩溃,提供了更好的服务保障和稳定性。
Sentinel的主要功能和作用包括:
流量控制: Sentinel可以对服务的入口流量进行实时监控和控制,通过设定阈值和规则,可以限制每个服务的请求流量,防止服务因为过载而导致性能下降或崩溃。
熔断降级: Sentinel可以根据服务的实际情况,自动开启熔断降级策略。当服务出现异常或故障时,Sentinel会及时地关闭该服务的访问,避免故障扩散,保护整个系统的稳定性。
实时监控: Sentinel提供实时的监控功能,可以查看各个服务的流量、成功率、响应时间等关键指标,帮助开发人员及时发现问题并进行处理。
规则配置: Sentinel支持动态的规则配置,可以在运行时实时修改流量控制和熔断降级的规则,方便快速适应业务场景的变化。
统计报表: Sentinel可以生成详细的统计报表,帮助开发人员了解服务的运行情况,发现潜在的性能瓶颈和问题。
总的来说,Sentinel提供了一套完善的流量控制、熔断降级和实时监控机制,帮助开发人员更好地保障分布式系统的稳定性和可靠性。它在微服务架构中起到了非常重要的作用,尤其在高并发、复杂业务场景下,Sentinel能够保护服务免受过载和故障的影响,提供更好的用户体验。

sentinel有两种配置方式,一种是在代码里面配置,这个代码入侵性太强,一般不建议这么用,所以这里不讲,有兴趣的自己了解下
我这里只演示控制台的方式来实现流量控制和熔断降级等

首先我们需要部署一套控制台服务,这个可以去https://github.com/alibaba/Sentinel/releases下载,注意下载版本要和springcloud版本一致

 

我们先在/usr/local下面建一个文件夹sentinel,然后把jar包传上去

 

然后通过下面这个命令启动(-jar后面接自己的jar包信息,端口号可以改)
建议设置成开机自启动,还有nacos和mysql这些也是,都设置成开机自动启动,不懂的自己搜一下很简单

java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar
1
启动成功后我们在浏览器输入http://192.168.43.128:9100就可以看到下面这页面了(注意9100是我自己配置的端口号)

 

然后我们在order服务里面集成sentinel
首先还是在pom文件里面加入依赖,直接覆盖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.sakura.springcloud</groupId>
<artifactId>springcloudalibaba</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>order</artifactId>
<packaging>jar</packaging>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>

<!-- nacos配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!-- 新版本已不再默认开启bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>


<!-- loadbalancer启动器 新版本的springcloud去掉了ribbon自带的负载均衡器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>

<!-- openfeign启动器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- sentinel启动器 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

</dependencies>
</project>


然后在application.yml文件里面加上服务地址

server:
port: 8010

logging:
level:
com.sakura.order.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

feign:
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.order.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器


直接启动服务,然后我们在浏览器里面多调几次http://localhost:8010/order/add
刷新一下sentinel控制台,就可以在实时监控里面看到刚才的请求了

 

我再在OrderController里面加个get方法

package com.sakura.order.controller;

import com.sakura.order.feign.ProductFeignService;
import com.sakura.order.feign.StockFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:25
*/
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {

@Autowired
StockFeignService stockFeignService;
@Autowired
ProductFeignService productFeignService;

@Value("${user.name}")
private String userName;

@Value("${user.age}")
private String userAge;

@Value("${user.sex}")
private String userSex;

@RequestMapping("/add")
public String add(){
System.out.println("下单成功");
String msg = stockFeignService.reduct();
String productMsg = productFeignService.get(1);
return userName + userAge + userSex + "下单成功" + msg + "-" + productMsg;
}

@RequestMapping("/get")
public String get(){
return "获取订单成功";
}

}


同样的在浏览器里面请求几次http://localhost:8010/order/get,再去sentinel控制台看一下
可以看到里面有两个接口了

 

接下来就是流控了
我们在簇点链路里面选择order/get这个接口点击操作里面的流控

 

可以看到默认是根据QPS(每秒请求数量)来的,我们在单机阈值里面填上2,也就是每秒超过2次就会流控
直接点新增(其它模式和效果大家自己研究一下)

 

然后我们在浏览器里面请求http://localhost:8010/order/get,手速要快一秒三连即可,手速慢的建议用压测工具
下面这个就是被流控了


上面那个是默认的流控效果,我们也可以自定义流控效果
我们在里面加上getBlockHandler方法来处理流控效果

package com.sakura.order.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.sakura.order.feign.ProductFeignService;
import com.sakura.order.feign.StockFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:25
*/
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {

@Autowired
StockFeignService stockFeignService;
@Autowired
ProductFeignService productFeignService;

@Value("${user.name}")
private String userName;

@Value("${user.age}")
private String userAge;

@Value("${user.sex}")
private String userSex;

@RequestMapping("/add")
public String add(){
System.out.println("下单成功");
String msg = stockFeignService.reduct();
String productMsg = productFeignService.get(1);
return userName + userAge + userSex + "下单成功" + msg + "-" + productMsg;
}

@RequestMapping("/get")
@SentinelResource(value = "get", blockHandler = "getBlockHandler")
public String get(){
return "获取订单成功";
}

// 流控方法必须和原方法类型一致参数一致
// 一定要加上BlockException
public String getBlockHandler(BlockException blockException){
// 我们可以在这个方法里面处理流控后的业务逻辑
return "get接口被流控";
}

}

重启order服务,注意因为sentinel没有做持久化处理,所以重启服务之前的配置会丢失(怎么持久化大家自己研究一下),我们需要重新请求一下http://localhost:8010/order/get
我们再看一下sentinel控制台会发现有两个get接口,一个是order/get,还有一个get,而我们要使用刚才配置的流控效果就需要给get接口配置流控规则

 


我们检查一下流控规则那里,如果order/get还在就删掉,不然下面那个不会生效

 

删掉order/get的流控规则后我们再快速请求http://localhost:8010/order/get三次,就可以看到自定义的效果了


当然要是觉得一个个的配置这个很麻烦我们还有全局的异常处理
我们加一个MyBlockExceptionHandler用来处理全局异常

package com.sakura.order.exception;

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.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.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author Sakura
* @date 2023/7/27 16:07
*/
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {

Logger log = LoggerFactory.getLogger(this.getClass());

@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
log.info("MyBlockExceptionHandler ---------------" + e.getRule());

// 处理返回参数
JSONObject json = new JSONObject();
json.put("code", 500);

if (e instanceof FlowException) {
log.info("接口限流+++++++++++++++++++");
json.put("msg", "接口限流");
} else if (e instanceof DegradeException) {
log.info("服务降级+++++++++++++++++++");
json.put("msg", "服务降级");
} else if (e instanceof ParamFlowException) {
log.info("热点参数限流+++++++++++++++++++");
json.put("msg", "热点参数限流");
} else if (e instanceof SystemBlockException) {
log.info("触发系统保护规则+++++++++++++++++++");
json.put("msg", "触发系统保护规则");
} else if (e instanceof AuthorityException) {
log.info("授权规则不通过+++++++++++++++++++");
json.put("msg", "授权规则不通过");
} else {
log.info("未知异常+++++++++++++++++++");
json.put("msg", "未知异常");
}

httpServletResponse.setStatus(500);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(httpServletResponse.getWriter(), json);
}
}


重启下服务,因为没有做持久化我们再请求一下http://localhost:8010/order/add
我们去sentinel控制台给order/add接口添加流控规则

 


然后我们继续快速请求http://localhost:8010/order/add
可以看到返回的是全局的异常处理了

 

我们再去请求http://localhost:8010/order/get
可以看到之前配置的自定义流控效果依然还在

 

流控规则就讲到这里了,其它的一些流控模式和流控效果大家自己了解一下就好

 

接下来我们来试一下熔断降级规则
我们先在OrderController里面加一个测试方法flow

package com.sakura.order.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.sakura.order.feign.ProductFeignService;
import com.sakura.order.feign.StockFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:25
*/
@RestController
@RequestMapping("/order")
@RefreshScope
public class OrderController {

@Autowired
StockFeignService stockFeignService;
@Autowired
ProductFeignService productFeignService;

@Value("${user.name}")
private String userName;

@Value("${user.age}")
private String userAge;

@Value("${user.sex}")
private String userSex;

@RequestMapping("/add")
public String add(){
System.out.println("下单成功");
String msg = stockFeignService.reduct();
String productMsg = productFeignService.get(1);
return userName + userAge + userSex + "下单成功" + msg + "-" + productMsg;
}

@RequestMapping("/get")
@SentinelResource(value = "get", blockHandler = "getBlockHandler")
public String get(){
return "获取订单成功";
}

// 流控方法必须和原方法类型一致参数一致
// 一定要加上BlockException
public String getBlockHandler(BlockException blockException){
// 我们可以在这个方法里面处理流控后的业务逻辑
return "get接口被流控";
}

@RequestMapping("/flow")
public String flow() throws Exception {
Thread.sleep(3000);
return "正常访问";
}

}


重启一下服务,然后先请求一下http://localhost:8010/order/flow
接着我们去sentinel控制台给order/flow配置熔断规则

 


然后我们再快速请求几次http://localhost:8010/order/flow
可以看到已经降级了,这里走的都是刚才全局异常配置的,你也可以和流控一样配置自定义的降级规则,这个自己研究一下这里不讲
注意上面配置的那个熔断时长,一旦出现这个降级,那么在这一段时间内所有的请求都会提示服务降级,直到这个时间过去了接口又会恢复的

 

好了,熔断降级规则就讲到这里,其它的熔断策略大家自己研究一下

 

最后我们来看下怎么在openfeign中来实现降级

首先我们需要在openfeign中开启sentinel
直接覆盖order服务的application.yml文件

server:
port: 8010

logging:
level:
com.sakura.order.feign: debug # 这里我们可以单独配置feign的日志级别

spring:
profiles:
active: dev
cloud:
sentinel:
transport:
dashboard: 192.168.43.128:9100

feign:
sentinel:
enabled: true
client:
config:
product-service:
logger-level: basic
connect-timeout: 5000 # 连接超时时间,默认2s
read-timeout: 6000 #请求处理超时时间,默认5s
request-interceptors:
- com.sakura.order.interceptor.feign.CustomFeignInterceptor # 开启自定义拦截器


然后我们在order服务feign里面加一个StockFeignServiceFallback类实现StockFeignService接口

package com.sakura.order.feign;

import org.springframework.stereotype.Component;

/**
* @author Sakura
* @date 2023/7/27 17:30
*/
@Component
public class StockFeignServiceFallback implements StockFeignService {

@Override
public String reduct() {
// 我们可以在这里处理降级逻辑,比如记录日志或者发短信提示管理用户等
return "stock服务异常降级";
}
}

我们在StockFeignService加上fallback

package com.sakura.order.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
* @description: stock服务接口
* @author: Sakura
* @date: 2023/7/20 14:38
*/
@FeignClient(name = "stock-service", path = "/stock", fallback = StockFeignServiceFallback.class)
public interface StockFeignService {

@RequestMapping("/reduct")
public String reduct();
}

我们改一下stock服务StockController里面的reduct方法,让它报异常

package com.sakura.stock.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* @author Sakura
* @date 2023/7/19 11:35
*/
@RestController
@RequestMapping("/stock")
public class StockController {

@Value("${server.port}")
String port;

@RequestMapping("/reduct")
public String reduct(){
int a = 1/0;
System.out.println("扣减库存");
return "扣减库存" + port;
}

}

我们请求一下http://localhost:8010/order/add
可以看到已经走了降级方法,但是因为没有抛异常所以接口还是能正常使用,不会影响整个服务

图片: