Sentinel

概念

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

下载地址:https://github.com/alibaba/Sentinel/releases

Sentinel 分为两个部分:

  • 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
  • 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。

下载好后,来到jar包位置打开cmd,java -jar sentinel-dashboard-1.8.2.jar运行,sentinel默认是8080端口,访问http://localhost:8080查看控制台,登录账号密码默认都是sentinel,启动nacos服务,http://localhost:8848/nacos/#/login

环境

  1. jdk11
  2. maven3.8.1
  3. SpringBoot 2.2.2
  4. SpringCloud Hoxton.SR1

案例

新建module cloudalibaba-sentinel-service8401

pom

 <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- SpringBoot整合Web组件+actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>4.6.3</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

yml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
  public static void main(String[] args) {
    SpringApplication.run(MainApp8401.class,args);
  }
}

controller

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FlowLimitController {

  @GetMapping("/test1")
  public String test1(){
    return "test1";
  }

  @GetMapping("/test2")
  public String test2(){
    return "test2";
  }
}

由于sentinel是懒加载机制,刚开始什么都没有

 

 访问几次接口的方法即可,http://localhost:8401/testA,此时发现sentinel正在监控8401

 

 流控规则

1 系统默认,快速失败

 

 

 

 2 当关联的资源达到阈值时,就限流自己

 

 

 

 

 

 链路关联

 pom

  <!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-web-servlet -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-web-servlet</artifactId>
            <version>1.8.2</version>
        </dependency>

yml

spring.cloud.sentinel.filter.enabled=false

配置类

import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterContextConfig {
  @Bean
  public FilterRegistrationBean sentinelFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setFilter(new CommonFilter());
    registration.addUrlPatterns("/*");
    // 入口资源关闭聚合
    registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false");
    registration.setName("sentinelFilter");
    registration.setOrder(1);
    return registration;
  }
}

service

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;

@Service
public class OrderServiceImpl {

  @SentinelResource(value = "hello")  //value值为资源名称
  public void hello(){
    System.out.println("hello");
  }

}

controller

@Resource
  private OrderServiceImpl service;

@GetMapping("/test3")
public String test3(){
service.hello();
return "test3";
}

访问  http://localhost:8401/test3

 

 

 

 

 此时访问 http://localhost:8401/test3,快速刷新就会被限流

 

 Warm Up

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值。默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值,官网地址:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8

 

 刚开始访问过快不行,5秒后阈值达到10

 匀速排队

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

 降级

1 RT

 

 

 

 

 

 

 使用jmeter测试每秒发送5个以上请求,发现断路器打开,微服务不可用,Sentinel的断路器是没有半开状态的。

2 异常比例

异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

代码

 @GetMapping("/test5")
  public String test5(){
    int age = 6 / 0;
    return "test2";
  }

 

 使用jmeter直接高并发发送请求,多次调用断路器就开启了,启用服务降级。

异常数

异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断

代码还是test5

 访问 http://localhost:8401/test5报错五次后进入熔断

热点参数限流

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

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

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

controller

  @GetMapping("/hot")
  @SentinelResource(value = "hot",blockHandler = "deal_testHot")  //一旦出了问题使用指定的方法解决
  public String testHot(@RequestParam(value = "p1",required = false) String p1,
                        @RequestParam(value = "p2",required = false) String p2){
    return "testHot";
  }

  public String deal_testHot(String p1, String p2, BlockException blockException){
    return "hot fail";
  }

访问 http://localhost:8401/hot?p1=1,快速刷新,报错页面不再是默认的

特殊情况:假如当p1的值等于6时,它的阈值可以达到200,参数必须是基本类型或者String

 

 

 此时访问 http://localhost:8401/hot?p1=6,阈值为100,@SentinelResource处理的是Sentinel控制台配置的违规情况。

自定义限流处理类

pom引进通用api包

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fly.springcloud.entity.CommonResult;

public class CustomerBlockHandler {

  public static CommonResult handlerException(BlockException exception){
    return new CommonResult(444,"自定义设置");
  }

}

controller

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.fly.alibaba.myhandler.CustomerBlockHandler;
import com.fly.springcloud.entity.CommonResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RateLimitController {

  @GetMapping("/rateLimit/customerBlockHandler")
  @SentinelResource(value = "customerBlockHandler",blockHandlerClass = CustomerBlockHandler.class,
                    blockHandler = "handlerException")
  public CommonResult customerBlockHandler(){
    return new CommonResult(200,"自定义成功");
  }

}

访问 http://localhost:8401/rateLimit/customerBlockHandler发现自定义的有效

服务熔断

与ribbo整合

新建服务提供者cloudalibaba-provider-payment9003和cloudalibaba-provider-payment9004,这两个除了端口号其他的都一样

pom

 <dependencies>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

yml

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #配置Nacos地址

management:
  endpoints:
    web:
      exposure:
        include: '*'

主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
  public static void main(String[] args) {
    SpringApplication.run(PaymentMain9003.class,args);
  }
}

controller

import com.fly.springcloud.entity.CommonResult;
import com.fly.springcloud.entity.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;

@RestController
public class PaymentController {

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

  public static HashMap<Long,Payment> hashMap = new HashMap<>();
  static
  {
    hashMap.put(1L,new Payment(1L,"payment1"));
    hashMap.put(2L,new Payment(2L,"payment2"));
    hashMap.put(3L,new Payment(3L,"payment3"));
  }

  @GetMapping(value = "/paymentSQL/{id}")
  public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
  {
    Payment payment = hashMap.get(id);
    CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort:  "+serverPort,payment);
    return result;
  }

}

自测地址:http://localhost:9003/paymentSQL/1

 新建服务消费者 cloudalibaba-consumer-nacos-order84

pom

dependencies>
        <!--SpringCloud openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--SpringCloud ailibaba nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--SpringCloud ailibaba sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>org.fly</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- SpringBoot整合Web组件 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--日常通用jar包配置-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

yml

server:
  port: 84


spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719


#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者)
service-url:
  nacos-user-service: http://nacos-payment-provider

主启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84
{
  public static void main(String[] args) {
    SpringApplication.run(OrderNacosMain84.class, args);
  }
}

config

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;

@Configuration
public class ApplicationContextConfig {

  @Bean
  @LoadBalanced
  public RestTemplate getRestTemplate()
  {
    return new RestTemplate();
  }

}

controller

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.fly.alibaba.service.PaymentService;
import com.fly.springcloud.entity.CommonResult;
import com.fly.springcloud.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
@Slf4j
public class CircleBreakerController
{
  public static final String SERVICE_URL = "http://nacos-payment-provider";

  @Resource
  private RestTemplate restTemplate;

  @RequestMapping("/consumer/fallback/{id}")
  @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
  public CommonResult<Payment> fallback(@PathVariable Long id)
  {
    CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id,CommonResult.class,id);

    if (id == 4) {
      throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
    }else if (result.getData() == null) {
      throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
    }

    return result;
  }
  public CommonResult handlerFallback(@PathVariable  Long id,Throwable e) {
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(444,"兜底异常handlerFallback,exception内容  "+e.getMessage(),payment);
  }

  public CommonResult blockHandler(@PathVariable  Long id, BlockException blockException) {
    Payment payment = new Payment(id,"null");
    return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException  "+blockException.getMessage(),payment);
  }

}

这样无论java报错还是sentinel配置报错都有方法解决,如果blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。

 与Feign整合

修改服务消费者84

pom

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

yml

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true  

主启动类

添加@EnableFeignClients注解

service

import com.fly.springcloud.entity.CommonResult;
import com.fly.springcloud.entity.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
  @GetMapping(value = "/paymentSQL/{id}")
  CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
import com.fly.springcloud.entity.CommonResult;
import com.fly.springcloud.entity.Payment;
import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "====="));
}
}

启动9003、9004、84,让84调用9003,模拟故障停掉9003,发现84自动降级

规则持久化

如果8401重启后,发现配置的规则全没了,需要改一下8401

pom

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

yml

 
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'

feign:
  sentinel:
    enabled: true # 激活Sentinel对Feign的支持

登录进去nacos添加配置文件

 

 

 这时重启8401服务,在访问 http://localhost:8401/test1过一会就会发现配置出现了

posted @ 2021-07-18 20:30  翻蹄亮掌一皮鞋  阅读(218)  评论(0)    收藏  举报