SpringCloud Alibaba Sentinel

之前我们有提到服务降级、熔断等概念,而在SpringCloud和Netflix生态中,需要打组合拳,并且Netflix甚至停止更新,于是在SpringCloud Alibaba生态中出现了Sentinel(最早是拿来做流量监控的),可以做到流量控制、实时监控、限流、熔断降级等功能

Sentinel具有如下特性:

  • 丰富的应用场景:承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀,可以实时熔断下游不可用应用;
  • 完备的实时监控:同时提供实时的监控功能。可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况;
  • 广泛的开源生态:提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合;
  • 完善的 SPI 扩展点:提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。

总体框架设计(官网:https://sentinelguard.io/zh-cn/docs/basic-implementation.html

我们来快速安装一下,我们不使用官网的安装方式,太繁琐,我们直接使用docker run的方式

使用docker hub里打包好的镜像,版本号是1.8.0(当前最新是1.8.5)

docker pull bladex/sentinel-dashboard:latest
docker run --name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard

如果使用官方注意启动端口是8080,此处是8858,同样如果是使用云服务器记得开放端口,账号/密码 sentinel/sentinel

引用nacos和sentinel

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
server:
    port: 8401
spring:
    application:
        name: sentinel-service
    cloud:
        nacos:
            server-addr: 127.0.0.1:8848 #配置Nacos地址
        sentinel:
            transport:
                dashboard: 127.0.0.1:8858 #配置sentinel dashboard地址
                port: 8719
management:
    endpoints:
        web:
            exposure:
                include: '*'

启动项目后,查看nacos能看到服务已经注册成功了,接下来我们测试一下功能

1、流量控制

现在到sentinel中添加流量规则(1)根据名称注解@SentinelResource配置的value;(2)根据访问路径;

编写一下测试接口

@RestController
@RequestMapping("/rate-limit")
public class RateLimitController {

    /**
     * 按资源名称限流,需要指定限流处理逻辑
     */
    @GetMapping("/by-resource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public String byResource() {
        return "按资源名称限流";
    }

    /**
     * 按URL限流,有默认的限流处理逻辑
     */
    @GetMapping("/by-url")
    @SentinelResource(value = "by-url", blockHandler = "handleException")
    public String byUrl() {
        return "按url限流";
    }

    public String handleException(BlockException exception) {
        return "错误异常" + exception.getClass().getCanonicalName();
    }

}

多次访问会提示下面的错误

或者可以自定义异常

注意:我们是直接访问项目的,而流量控制是项目与sentinel通信,所以sentinel需要可以连通项目,application.yml中可以配置客户端ip和端口,默认端口为8719,所以在本地测试的情况下,如果你的sentinel是部署云服务器上是无法对本地进行监控的,因此如果需要本地运行项目就需要映射到外网,当然这对于没弄过的同学来说很麻烦,所以最简单的办法就是在本地运行sentinel,或者在内网可以互通的情况下使用,连通的情况下,在机器列表里会有对应的客户端信息

2、熔断

增加一个biz-service模块,我这边使用之前的nacos-biz-service,添加一个接口用作测试

@RestController
@RequestMapping("/biz")
public class BizController {

    @GetMapping("/{id}")
    public String getDataById(@PathVariable Long id) {
        System.out.println("获取数据" + id);
        return "获取信息";
    }

}

启动服务查看nacos是否注册成功

然后给sentinel-server增加一个RestTemplate的配置

@Configuration
public class RestTemplateConfig {

    @Bean
    @SentinelRestTemplate
    @LoadBalanced//配合nacos发现服务名进行调用
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

在application.yml增加配置

service-url:
    biz-service: http://nacos-biz-service

添加测试代码,测试断路器

@RestController
@RequestMapping("/breaker")
public class CircleBreakerController {

    private Logger LOGGER = LoggerFactory.getLogger(CircleBreakerController.class);

    @Autowired
    private RestTemplate restTemplate;

    @Value("${service-url.biz-service}")
    private String bizServiceUrl;

    @RequestMapping("/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handleFallback")
    public String fallback(@PathVariable Long id) {
        return restTemplate.getForObject(bizServiceUrl + "/biz/{1}", String.class, id);
    }

    @RequestMapping("/fallbackException/{id}")
    @SentinelResource(value = "fallbackException", fallback = "handleFallback2", exceptionsToIgnore = {NullPointerException.class})
    public String fallbackException(@PathVariable Long id) {
        if (id == 1) {
            throw new IndexOutOfBoundsException();
        } else if (id == 2) {
            throw new NullPointerException();
        }
        return restTemplate.getForObject(bizServiceUrl + "/biz/{1}", String.class, id);
    }

    public String handleFallback(Long id) {
        return "服务降级返回";
    }

    public String handleFallback2(@PathVariable Long id, Throwable e) {
        LOGGER.error("handleFallback2 id:{},throwable class:{}", id, e.getClass());
        return "服务降级返回2";
    }
}

(1)启动服务,查看nacos,注册成功后,测试正常服务下调用

(2)关闭nacos-biz-service,再调用一次,此时因为biz-service服务异常,应触发服务降级

(3)重新打开nacos-biz-service,我们写了一个手工路由,用参数1调用,说明:1的情况下抛IndexOutOfBoundsException()异常,因此被降级处理

用参数2调用,说明:2的情况因为配置了exceptionsToIgnore,忽略这种情况下的异常直接返回给客户端,因此没有被降级处理

用任意1和2之外的参数调用,说明:此时服务正常调用到nacos-biz-service

关闭掉nacos-biz-service再调用一次,说明:此时跟参数1情况一致,都被服务降级处理

3、结合openFeign

引入openfeign

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

application.yml新增参数

feign:
  sentinel:
      enabled: true

启动类增加@EnableFeignClients,和之前openFeign一样

//打开Feign
@EnableFeignClients
@SpringBootApplication
public class SentinelServiceApplication {

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

}
@FeignClient(value = "nacos-biz-service", fallback = BizFallbackService.class)
public interface BizService {

    @PostMapping("/biz/create")
    String create();

    @GetMapping("/biz/{id}")
    String getDataById(@PathVariable Long id);

    @GetMapping("/biz/getByUsername")
    String getByUsername(@RequestParam String username);

    @PostMapping("/biz/update")
    String update();

    @PostMapping("/biz/delete/{id}")
    String delete(@PathVariable Long id);
}
@Component
public class BizFallbackService implements BizService {
    @Override
    public String create() {
        return "服务降级处理create";
    }

    @Override
    public String getDataById(Long id) {
        return "服务降级处理getDataById";
    }

    @Override
    public String getByUsername(String username) {
        return "服务降级处理getByUsername";
    }

    @Override
    public String update() {
        return "服务降级处理update";
    }

    @Override
    public String delete(Long id) {
        return "服务降级处理delete";
    }
}
@RestController
@RequestMapping("/biz")
public class BizFeignController {

    @Qualifier("com.jay.cloud.service.BizService")
    @Autowired
    private BizService bizService;

    @GetMapping("/{id}")
    public String getDataById(@PathVariable Long id) {
        return bizService.getDataById(id);
    }

    @GetMapping("/getByUsername")
    public String getByUsername(@RequestParam String username) {
        return bizService.getByUsername(username);
    }

    @PostMapping("/create")
    public String create() {
        return bizService.create();
    }

    @PostMapping("/update")
    public String update() {
        return bizService.update();
    }

    @PostMapping("/delete/{id}")
    public String delete(@PathVariable Long id) {
        return bizService.delete(id);
    }

}

如果服务不能启动可能是包兼容的问题,除了之前引用的,加上以下这些

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-hystrix</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.netflix.archaius/archaius-core -->
        <dependency>
            <groupId>com.netflix.archaius</groupId>
            <artifactId>archaius-core</artifactId>
            <version>2.0.0-rc.7</version>
        </dependency>

最后启动服务成功后,调用一下接口

4、系统保护规则

从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性

系统规则支持以下的阈值类型:

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps * minRt 计算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0)。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

5、热点流量

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

引入依赖

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-parameter-flow-control</artifactId>
</dependency>

6、动态规则扩展

默认情况下,当我们在Sentinel控制台中配置规则时,控制台推送规则方式是通过API将规则推送至客户端并直接更新到内存中。一旦我们重启应用,规则将消失。下面我们介绍下如何将配置规则进行持久化

此处我们把规则配置到nacos中

先引入包

<dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>

增加配置

spring:
  cloud:
    sentinel:
      datasource:
        ds1:
          nacos:
            server-addr: 127.0.0.1:8848
            dataId: ${spring.application.name}-sentinel
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

启动项目,然后到nacos上增加路由规则

调用一下接口让sentinel接收服务的心跳,此时去查看流控规则,里面多了一个规则,之前我们是手动添加的,并且关闭项目后规则就消失了,达到我们的预期

我们再测试一下规则是否生效

Leave a Reply

Your email address will not be published. Required fields are marked *