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

本文详细介绍了Sentinel作为服务防护组件在Spring Cloud Alibaba微服务中的应用,包括Sentinel的介绍、部署控制台、实验环境准备、Nacos服务启动,以及Sentinel在API、Provider和Consumer工程中的配置和流控、熔断规则的设定。Sentinel提供了注解支持、RestTemplate和OpenFeign的流量控制,并展示了如何处理异常情况。
摘要由CSDN通过智能技术生成

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

1、什么是Sentinel

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

引用官网图片:
在这里插入图片描述
Sentinel 有着丰富的开源生态:
在这里插入图片描述

Sentinel功能支持:引用官网图片:
在这里插入图片描述

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
在这里插入图片描述
登录成功:
在这里插入图片描述

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
在这里插入图片描述

启动成功,访问:http://127.0.0.1:8848/nacos,账号密码都是nacos
在这里插入图片描述
登录成功,来到主页:
在这里插入图片描述

5、创建API工程

使用maven命令

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

也可以自己创建一个maven项目
在这里插入图片描述
编写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 Dubbo、Nacos Service Discovery(Nacos服务注册支持)、Spring Cloud Alibaba Sentinel、Spring Cloud Alibaba Sentinel Dubbo Adaper(Sentinel适配Dubbo的工程)
在这里插入图片描述
依赖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

在这里插入图片描述
在Sentinel控制台可以找到Provider工程的监控:
在这里插入图片描述

在这里插入图片描述

7、创建Consumer工程

新建项目,使用阿里的service url:
在这里插入图片描述
同理选择需要的组件:
在这里插入图片描述

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);
    }

}

在这里插入图片描述

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

在这里插入图片描述

8、Sentinel流控规则

找到刚才的echo方法,新增一条规则:如图代表针对该服务方法的调用每秒钟不能超过 1 次,超出会直接拒绝
在这里插入图片描述

在这里插入图片描述
多刷新几次api

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

在这里插入图片描述
异常调用被Sentinel监控到
在这里插入图片描述

9、Sentinel熔断降级规则

Sentinel提供几种熔断策略:

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

同理在控制台设置规则:设定慢调用临界值为50ms,响应时间超出 50ms 即记为慢调用。当统计时长内的请求数 >=5 且慢调用的比例超出我们配置的阈值(80%)就会触发熔断,熔断时长为 5s,经过熔断时长后会允许一个请求探测通过,若请求正常则恢复,否则继续熔断。
在这里插入图片描述

10、Sentinel注解支持

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

使用@SentinelResource

@SentinelResource(value = "DemoService#echo" )

在这里插入图片描述

自定义异常回调:

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);
    }

}

同理要添加流控规则:
在这里插入图片描述
服务接口,linux系统curl命令

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

Blocked by Sentinel (flow limiting)

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

附录:博客参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

smileNicky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值