SpringCloud Alibaba系列之服务防护组件Sentinel

1、什么是Sentinel

​Sentinel​​是阿里巴巴开源的一款高可用的分布式防护组件,主要应用于流量控制、流量整形、熔断降级、系统自适应保护、热点防护等多个维度,原生支持 Java/Go/C++ 等多种语言,并且提供Istio/Envoy 全局流控支持来为 Service Mesh 提供高可用防护的能力

引用官网图片:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Nacos
Sentinel 有着丰富的开源生态:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_02

Sentinel功能支持:引用官网图片:
Spring Cloud Alibaba系列之服务防护组件Sentinel_spring cloud alibaba_03

2、部署Sentinel控制台

下载链接:​​https://github.com/alibaba/Sentinel/releases​

在window系统编写一个bat脚本来启动sentinel-dashboard-1.8.1.jar

@echo off
java -jar -Dserver.port=8080 sentinel-dashboard-1.8.1.jar

访问​​http://localhost:8080​​​ 账号密码 sentinel/sentinel
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_04
登录成功:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Nacos_05

3、实验环境准备

  • 环境准备:
  • 64bit JDK 1.8
  • SpringBoot2.3.7.RELEASE
  • SpringCloud(Hoxton.SR9)
  • SpringCloudAlibaba2.2.2.RELEASE
  • Maven 3.2+
  • 开发工具
  • IntelliJ IDEA
  • smartGit

4、启动Nacos服务

详情可以参考官网:[https://nacos.io/zh-cn/docs/quick-start.html](https://nacos.io/zh-cn/docs/quick-start.html),需要先下载nacos服务端源码,下载源码后编译启动项目:

window+R启动cmd窗口,cd到nacos server的bin目录,linux系统直接使用​​cd ${nacos_server_home}/bin​

./startup.sh -m standalone

window系统使用命令​​startup.cmd -m standalone​​​Spring Cloud Alibaba系列之服务防护组件Sentinel_spring boot_06

启动成功,访问:​​http://127.0.0.1:8848/nacos​​​,账号密码都是nacos
Spring Cloud Alibaba系列之服务防护组件Sentinel_spring cloud alibaba_07
登录成功,来到主页:
Spring Cloud Alibaba系列之服务防护组件Sentinel_spring boot_08

5、创建API工程

使用maven命令

mvn archetype:generate -DgroupId=com.example.springcloud -DartifactId=dubbo-sample-api -Dversion=0.0.1-SNAPSHOT -DinteractiveMode=false

也可以自己创建一个maven项目
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_09
编写API:

package com.example.api.smapleapi.service;

/**
* <pre>
* FooService
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 09:58 修改内容:
* </pre>
*/
public interface FooService {
String echo(String str, boolean slow);
}

6、创建Provider工程

新建项目,使用阿里的service url:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_10
选择需要的组件,Spring Cloud Alibaba Dubbo、Nacos Service Discovery(Nacos服务注册支持)、Spring Cloud Alibaba Sentinel、Spring Cloud Alibaba Sentinel Dubbo Adaper(Sentinel适配Dubbo的工程)
Spring Cloud Alibaba系列之服务防护组件Sentinel_spring cloud alibaba_11
依赖api工程

<dependency>
<groupId>com.example.api</groupId>
<artifactId>smaple-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

编写Dubbo服务:

package com.example.springcloud.sample.service;

import com.example.api.smapleapi.service.FooService;
import org.apache.dubbo.config.annotation.DubboService;

/**
* <pre>
* FooServiceImpl
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 10:03 修改内容:
* </pre>
*/
@DubboService
public class FooServiceImpl implements FooService {

@Override
public String echo(String str, boolean slow) {
if (slow) {
try {
Thread.sleep(100);
} catch (InterruptedException ignored) {
}
}
return String.format("echo:%s", str);
}
}

application.properties,自动生成,我们只要简单修改ip既可:

server.port=9090
# 应用名称
spring.application.name=dubbo-provider-sample
# dubbo 协议
dubbo.protocol.id=dubbo
dubbo.protocol.name=dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
dubbo.protocol.port=-1
# Dubbo 消费端订阅服务端的应用名,多个服务提供者用逗号分隔
# 这里订阅"自己",会被忽略掉,请根据实际情况添加
dubbo.cloud.subscribed-services=dubbo-provider-sample
# dubbo 服务扫描基准包
dubbo.scan.base-packages=com.example.springcloud.sample
# Sentinel 控制台地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 取消Sentinel控制台懒加载
# 默认情况下 Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包
# 配置 sentinel.eager=true 时,取消Sentinel控制台懒加载功能
spring.cloud.sentinel.eager=true
# 如果有多套网络,又无法正确获取本机IP,则需要使用下面的参数设置当前机器可被外部访问的IP地址,供admin控制台使用
# spring.cloud.sentinel.transport.client-ip=
# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public

启动工程:服务注册到Nacos

Spring Cloud Alibaba系列之服务防护组件Sentinel_Nacos_12
在Sentinel控制台可以找到Provider工程的监控:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_13

Spring Cloud Alibaba系列之服务防护组件Sentinel_spring boot_14

7、创建Consumer工程

新建项目,使用阿里的service url:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_10
同理选择需要的组件:
Spring Cloud Alibaba系列之服务防护组件Sentinel_spring boot_16

server.port=9091
# 应用名称
spring.application.name=web-consumer-smaple
# dubbo 协议
dubbo.protocol.id=dubbo
dubbo.protocol.name=dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
dubbo.protocol.port=-1
# Dubbo 消费端订阅服务端的应用名,多个服务提供者用逗号分隔
# 这里订阅"自己",会被忽略掉,请根据实际情况添加
dubbo.cloud.subscribed-services=dubbo-provider-sample

# Nacos帮助文档: https://nacos.io/zh-cn/docs/concepts.html
# Nacos认证信息
spring.cloud.nacos.discovery.username=nacos
spring.cloud.nacos.discovery.password=nacos
# Nacos 服务发现与注册配置,其中子属性 server-addr 指定 Nacos 服务器主机和端口
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 注册到 nacos 的指定 namespace,默认为 public
spring.cloud.nacos.discovery.namespace=public
# Sentinel 控制台地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 取消Sentinel控制台懒加载
# 默认情况下 Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包
# 配置 sentinel.eager=true 时,取消Sentinel控制台懒加载功能
spring.cloud.sentinel.eager=true
# 如果有多套网络,又无法正确获取本机IP,则需要使用下面的参数设置当前机器可被外部访问的IP地址,供admin控制台使用
# spring.cloud.sentinel.transport.client-ip=

Dubbo服务调用:

package com.example.springcloud.web.controller;

import com.example.api.smapleapi.service.FooService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.*;

/**
* <pre>
* DemoController
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 10:15 修改内容:
* </pre>
*/
@RestController
@RequestMapping(value = "/api/demo")
public class DemoController {

@DubboReference
private FooService fooService;

@GetMapping(value = "/echo")
public String echo(@RequestParam(value = "str", required = true) String str, @RequestParam(value = "slow",defaultValue = "false", required = false)Boolean slow) {
return fooService.echo(str , slow);
}

}

Spring Cloud Alibaba系列之服务防护组件Sentinel_Nacos_17

curl http://127.0.0.1:9091/api/demo/echo?str=test

Spring Cloud Alibaba系列之服务防护组件Sentinel_spring cloud alibaba_18

8、Sentinel流控规则

找到刚才的echo方法,新增一条规则:如图代表针对该服务方法的调用每秒钟不能超过 1 次,超出会直接拒绝
Spring Cloud Alibaba系列之服务防护组件Sentinel_微服务_19

Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_20
多刷新几次api

curl http://127.0.0.1:9091/api/demo/echo?str=test

Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_21
异常调用被Sentinel监控到
Spring Cloud Alibaba系列之服务防护组件Sentinel_Nacos_22

9、Sentinel熔断降级规则

Sentinel提供几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置
    允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。
  • 异常比例 (ERROR_RATIO):当单位统计时长内请求数目大于设置的最小请求数目,
    并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行
    熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下
    来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

同理在控制台设置规则:设定慢调用临界值为50ms,响应时间超出 50ms 即记为慢调用。当统计时长内的请求数 >=5 且慢调用的比例超出我们配置的阈值(80%)就会触发熔断,熔断时长为 5s,经过熔断时长后会允许一个请求探测通过,若请求正常则恢复,否则继续熔断。
Spring Cloud Alibaba系列之服务防护组件Sentinel_Nacos_23

10、Sentinel注解支持

Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException 等

使用​​@SentinelResource​

@SentinelResource(value = "DemoService#echo" )

Spring Cloud Alibaba系列之服务防护组件Sentinel_spring boot_24

自定义异常回调:

public String sentinelFallback(Throwable t){
if (BlockException.isBlockException(t)) {
return String.format("Block by sentinel:%s", t.getClass().getSimpleName());
}
return String.format("snetinal fallback method , Oops failed : %s", t.getClass().getCanonicalName());
}
@SentinelResource(value = "DemoService#echo" , defaultFallback = "sentinelFallback")

snetinal fallback method , Oops failed : org.apache.dubbo.rpc.RpcException

11、RestTemplate支持

在之前的学习中,我们知道了微服务调用的两种常用方式​​RestTemplate+@LoadBalanced​​和OpenFeign,而Sentinel也是支持这两种常用方式的监控的

例子进行试验:先要新增一个服务调用api,在Provider工程新增:

@Service
public class EchoServiceImpl{
public String echo(String message) {
return String.format("echo:%s", message);
}
}

向nacos注册服务:

package com.example.springcloud.sample.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.example.springcloud.sample.service.EchoServiceImpl;
import org.apache.dubbo.rpc.service.EchoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping(value = "/api/service")
@RestController public class DemoController {

@Autowired
EchoServiceImpl echoService;

@GetMapping(value = "/echo")
@SentinelResource(value = "service" )
public String echo(@RequestParam(value = "str", required = true) String str) {
return echoService.echo(str);
}


}

RestTemplate配置,通过​​@LoadBalanced​​​配置支持客户端负载均衡,​​@SentinelRestTemplate​​支持Sentinel监控

package com.example.springcloud.web.configuration;

import com.alibaba.cloud.sentinel.annotation.SentinelRestTemplate;
import com.example.springcloud.web.fallback.ExceptionUtil;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

/**
* <pre>
* RestTemplateConfiguration
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 15:51 修改内容:
* </pre>
*/
@Configuration public class RestTemplateConfiguration {

@Bean
@LoadBalanced
@SentinelRestTemplate(blockHandler = "handleException", blockHandlerClass = ExceptionUtil.class)
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

RestTemplate异常工具类

package com.example.springcloud.web.fallback;

import com.alibaba.cloud.sentinel.rest.SentinelClientHttpResponse;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpResponse;

/**
* RestTemplate异常工具类
*/
public class ExceptionUtil {

static Logger logger = LoggerFactory.getLogger(ExceptionUtil.class);

public static ClientHttpResponse handleException(HttpRequest request, byte[] body, ClientHttpRequestExecution execution, BlockException exception) {
logger.info("Oops: {}" , exception.getClass().getCanonicalName());
return new SentinelClientHttpResponse(String.format("custom block info!Oops:%s", exception.getClass().getCanonicalName()));
}

public static SentinelClientHttpResponse fallback(HttpRequest request,
byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
logger.info("fallback:{} " , ex.getClass().getCanonicalName());
return new SentinelClientHttpResponse(String.format("custom fallback info! fallback:%s", ex.getClass().getCanonicalName()));
}
}

服务调用:不使用Dubbo方式,使用RestTemplate

package com.example.springcloud.web.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
* <pre>
* RestTemplateController
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 15:57 修改内容:
* </pre>
*/
@RestController
public class RestTemplateController {

@Autowired
RestTemplate restTemplate;

@GetMapping("/restEcho")
public String restEcho(@RequestParam("str")String str){
return restTemplate.getForObject("http://dubbo-provider-sample/api/service/echo?str="+str,String.class);
}
}

custom block info!Oops:com.alibaba.csp.sentinel.slots.block.flow.FlowException

12、OpenFeign支持

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>

​@EnableFeignClients​​开启Feign支持

package com.example.springcloud.web;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class WebConsumerSmapleApplication {

public static void main(String[] args) {
SpringApplication.run(WebConsumerSmapleApplication.class, args);
}

}

​@FeignClient​​支持异常回调

package com.example.springcloud.web.service;

import com.example.springcloud.web.configuration.FeignConfiguration;
import com.example.springcloud.web.configuration.SentinelWebConfiguration;
import com.example.springcloud.web.fallback.BlockExceptionHandler;
import com.example.springcloud.web.fallback.EchoServiceFallback;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
* <pre>
* FooService
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 16:02 修改内容:
* </pre>
*/
@FeignClient(value = "dubbo-provider-sample", fallback = EchoServiceFallback.class ,configuration = FeignConfiguration.class)
@Service
public interface FeignService {

@GetMapping(value = "/api/service/echo")
String echo(@RequestParam(value = "str", required = true) String str);

}

FeignConfiguration

package com.example.springcloud.web.configuration;

import com.example.springcloud.web.fallback.BlockExceptionHandler;
import com.example.springcloud.web.fallback.EchoServiceFallback;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* <pre>
* FeignConfiguration
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 16:11 修改内容:
* </pre>
*/
@Configuration public class FeignConfiguration {

@Bean
public EchoServiceFallback echoServiceFallback(){
return new EchoServiceFallback();
}
}

BlockExceptionHandler

package com.example.springcloud.web.fallback;

import com.alibaba.csp.sentinel.slots.block.BlockException;

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

public interface BlockExceptionHandler {
void handle(HttpServletRequest request, HttpServletResponse response, BlockException e)
throws Exception;
}

EchoServiceFallback

package com.example.springcloud.web.fallback;

import com.alibaba.csp.sentinel.slots.block.BlockException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;

/**
* <pre>
* EchoServiceFallback
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 15:11 修改内容:
* </pre>
*/
public class EchoServiceFallback implements BlockExceptionHandler{
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
response.setStatus(429);
PrintWriter pw = response.getWriter();
pw.print(String.format("Oops , blocked by sentinel:%s", e.getClass().getSimpleName()));
pw.flush();
pw.close();
}
}

OpenFeign方式服务调用:

package com.example.springcloud.web.controller;

import com.example.springcloud.web.service.FeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* <pre>
* FeignController
* </pre>
*
* <pre>
* @author mazq
* 修改记录
* 修改后版本: 修改人: 修改日期: 2021/02/05 16:00 修改内容:
* </pre>
*/
@RestController
public class FeignController {

@Autowired
private FeignService feignService;

@GetMapping(value = "/feignEcho")
public String echo(@RequestParam String str){
return feignService.echo(str);
}

}

同理要添加流控规则:
Spring Cloud Alibaba系列之服务防护组件Sentinel_Sentinel_25
服务接口,linux系统curl命令

curl http://127.0.0.1:9091/feignEcho?str=test

Blocked by Sentinel (flow limiting)

本博客的例子代码可以在github找到下载链接:​​代码下载​

附录:博客参考资料