前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springCloud --- 高级篇(2)

springCloud --- 高级篇(2)

作者头像
贪挽懒月
发布2020-06-08 12:06:26
1.1K0
发布2020-06-08 12:06:26
举报
文章被收录于专栏:JavaEEJavaEEJavaEE

本系列笔记涉及到的代码在GitHub上,地址:https://github.com/zsllsz/cloud

本文涉及知识点:

  • sentinel降级;
  • sentinel熔断;
  • sentinel规则持久化;

一、springCloud Alibaba sentinel 之降级规则

上一篇已经说了sentinel的流控,接下来看看sentinel的降级。 1、基本介绍: sentinel的降级没有半开状态,有如下3种策略:

sentinel降级

  • RT(平均响应时间):平均响应时间超出阈值且在时间窗口期内通过的大于等于5,两个条件同时满足后触发降级。窗口期过后关闭断路器。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。
  • 异常比例(秒级):QPS大于等于5且异常比例超过阈值时,触发降级。时间窗口结束后,关闭降级。
  • 异常数(分钟级):异常数超过阈值时触发降级,时间窗口结束后关闭降级。

2、RT策略实例:

  • 在8401的controller中加上一个方法,如下:
@GetMapping("/testC")
public String testC() {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "=========== test RT ==========";
}
  • sentinel降级配置:

sentinel RT降级配置

这里配置的意思就是,1秒钟内有超过5个请求进入时,要求每个请求testC在200毫秒内响应,如果没有响应,那就跳闸1秒中,接下来的1秒内的请求都会被降级,1秒后恢复。用jmeter请求testC,用10个线程去请求,每1秒请求1次。然后再在浏览器请求testC,就会发现访问不了,返回Blocked by Sentinel (flow limiting)

3、异常比例策略实例:

  • 在8401的controller中加上一个方法,如下:
@GetMapping("/testD")
public String testD() {
    int x = 10 / 0;
    return "============ test 异常比例 ============";
}
  • sentinel降级配置:

sentinel异常比例降级配置

这个配置意思就是,1秒中内超过5个请求的时候,如果有超过5*0.2=1个请求异常了,那么在接下来的2秒内都会拉闸断电。降级后访问结果如下:

sentinel异常比例降级

4、异常数策略实例: 最近1分钟内请求发生异常的数量超过阈值时会触发降级。由于这里的异常数是分钟级别统计的,所以如果时间窗口期设置的小于60秒,则结束熔断后可能再次进入熔断状态。所以,时间窗口期要大于等于60秒。

  • 仍旧用testD进行测试
  • sentinel降级配置:

sentinel异常数降级配置

这个配置表示,1分钟内对testD的请求发生异常的次数超过3次,那么接下来的66秒内都会拉闸断电。我们在1分钟内访问3次,然后再去访问,就会返回Blocked by Sentinel (flow limiting)

二、springCloud Alibaba sentinel 之热点规则

1、是什么? 就是针对热点数据做限流。比如id为1的商品是热点数据,那么可以针对id为1的这个商品做限流。

2、热点限流实例配置:

  • 在8401的controller中加一个方法,如下:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") // 这个value值随意,只要唯一即可,但是一般和@GetMapping中的一致
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2) {
        return "test hot key";
}

/**
* 兜底方法,参数除了原方法的参数,还要加上BlockException
* @param p1
* @param p2
* @param e
* @return
*/
public String deal_testHotKey(String p1, String p2, BlockException e) {
    return "兜底方法";
}
  • 热点规则配置:

热点规则配置

这里配置的意思就是,testHotKey(就是@SentinelResource中的value值)这个资源,我对索引为0的参数(p1)进行监控,如果访问testHotKey带上了p1,并且QPS超过了1,那么接下来的1秒中内这个方法都会被降级。注意:索引为0的参数是p1,是controller中接收参数的顺序的索引。你访问http://192.168.0.104:8401/sentinel/testHotKey?p2=1,这里只有一个参数p2,在url中它是第0个参数,但是在controller中不是,所以这样访问并不会被降级。

热点配置的高级选项:

  • 参数例外项:上面的配置对p1进行限流,不管p1的值是多少,只要QPS超过1,就降级。现在的需求是如果p1的值是5,我就搞特殊的,因为它充了钱,所以让它QPS超过100才限流。配置如下图:

参数例外项配置

当参数不是5时,QPS超过1就会被限流降级,p1的值为5时,你狂点都可以正常访问。 注意:@SentinelResource只管我们控制台配置的违规情况,才会进行兜底,假如程序异常了,它是管不了的。比如我们在return 前加一行int a = 10 / 0,它还是会返回error page的,而不是兜底方法。

三、springCloud Alibaba sentinel 之系统规则

上面的限流降级都是针对某个具体方法而言的,系统规则就是针对整个微服务系统来说的。比如配置了QPS阈值为100,也就是说,这个微服务的QPS超过100,整个服务都不可用了。就是控制的粒度更粗了,生产中不建议用这种方式。

1、系统规则的阈值类型:

  • LOAD(对Linux和Unix机器生效):当系统装载数超过阈值就会限流,阈值一般设置为cpu核心数 * 2.5
  • RT(平均响应时间):单台机器上所有入口流量平均RT超过阈值时进行限流降级
  • 线程数:单台机器上的所有入口流量的并发线程数超过阈值时进行限流降级
  • 入口QPS:当单台机器所有入口流量的QPS超过阈值时进行限流降级
  • CPU使用率:当系统cpu使用率超过阈值时进行限流降级

2、配置全局QPS:

入口QPS

之前testB是没配置任何限流规则的,1秒点n次都可以,现在配了系统规则后,发现testB也是只能1秒钟点1次了,说明配置生效。

四、sentinel的@SentinelResource详细用法

上面做限流时用到过这个注解,但是没说其用法,下面来学习一下它的用法。 1、按资源名称限流 + 后续处理:

  • 修改8401的pom,添加自己定义的common模块,如下:
<dependency>
    <groupId>com.zhu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>
  • 8401中新加一个controller,如下:
@RestController
@RequestMapping("/ratelimit")
public class RateLimitController {

    @GetMapping("/bySource")
    @SentinelResource(value = "bySource", blockHandler = "handleException")
    public JsonResult<?> bySource() {
        return new JsonResult<Payment>(200, "按资源名称限流测试通过", new Payment(1L, "6666"));
    }
    
    public JsonResult<?> handleException(BlockException e){
        return new JsonResult<>(400, e.getClass().getCanonicalName() + "\t服务不可用");
    }
}
  • 然后新增流控规则,阈值类型选择QPS,阈值设置为1。然后访问bySource,点击快一点,就返回如下内容:
{
  code: 400,
  message: "com.alibaba.csp.sentinel.slots.block.flow.FlowException 服务不可用",
  data: null
}
  • 现在关闭8401,然后刷新sentinel dashboard,发现刚才添加的流控规则没了。所以后续得做持久化处理。

2、按url进行限流: 什么叫按资源名称,什么叫按url?看下图:

簇点链路

第一个/ratelimit/bySource就是url,第二个bySource就是资源名称。上面演示了按资源名称来限流,下面演示按url来限流。

  • 在RateLimitController 中添加如下方法:
@GetMapping("/byUrl")
@SentinelResource(value = "byUrl")
public JsonResult<?> byUrl(){
    return new JsonResult<Payment>(200, "按url限流测试通过", new Payment(1L, "6666"));
}
  • 配置的时候,选择url,添加流控,阈值类型QPS,阈值1。QPS超过1就会返回错误提示Blocked by Sentinel (flow limiting)

这两个配置案例主要是两个知识点:

  • 可以按url配,也可以按资源名成配
  • 自己写了blockHandler就会走自己写的,没写就返回默认提示

上面的配置案例存在的问题:

  • 兜底方案与业务代码耦合
  • 每个方法都要写一个兜底方法,即没有做全局的兜底方法
  • 服务一关闭,配置就没有了,即没有做持久化

下面就来解决这些问题。

3、自定义限流处理逻辑: 这是为了解决上面的第一第二个问题的。

  • 自定义限流处理类:
public class CustomerBlockHandler {
    
    public static JsonResult<?> handlerException1(BlockException e){
        return new JsonResult<Payment>(444, "自定义返回信息1");
    }
    
    public static JsonResult<?> handlerException2(BlockException e){
        return new JsonResult<Payment>(444, "自定义返回信息2");
    }
}
  • RateLimitController:
@GetMapping("/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException1")
public JsonResult<?> customerBlockHandler(){
    return new JsonResult<Payment>(200, "自定义限流处理测试通过", new Payment(1L, "6666"));
}
  • sentinel控制台配置:也是配置QPS、1就好了
  • 测试:1秒点击两次,发现返回:
{
  code: 444,
  message: "自定义返回信息1",
  data: null
}

说明自定义返回信息成功了。

五、sentinel的熔断功能

1、Ribbon系类:

新建名为cloudalibaba-provider-payment9003和cloudalibaba-provider-payment9004的module,两个module只是端口不一致:

  • pom.xml:
<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>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
</dependency>
<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
    <groupId>com.zhu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>
<!-- sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  • application.yml:
server:
  port: 9003
  
spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
management:
  endpoints:
    web:
      exposure:
        include:
        - "*"
  • 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(PaymentMain9003.class, args);
    }
}
  • 业务类:
@RestController
@RequestMapping("/payment")
public class PaymentController {
    
    @Value("${server.port}")
    private int port;
    
    public static Map<Long, Payment> map = new HashMap<>();
    // 偷懒,不去连数据库查记录了
    static {
        map.put(1L, new Payment(1L,"111"));
        map.put(1L, new Payment(2L,"222"));
        map.put(1L, new Payment(3L,"333"));
    }
    
    @GetMapping("/{id}")
    public JsonResult<?> payment(@PathVariable("id") Long id){
        return new JsonResult<>(200, "success from " + port, map.get(id));
    }
}

新建名为cloudalibaba-consumer-nacos-order84的消费者,通过ribbon去调用9003和9004:

  • pom.xml:
<dependency>
    <groupId>com.zhu.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>
<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>
<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>
<!-- nacos -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  • application.yml:
server:
  port: 84
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
    sentinel:
      transport:
        client-ip: 192.168.0.104
        dashboard: 192.168.0.106:8080
        port: 8719
service-url:
  nacos-user-service: http://nacos-payment-provider
  • 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
public class OrderMain84 {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(OrderMain84.class, args);
    }
}
  • ribbon配置类:
@Configuration
public class RibbonConfig {
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
  • controller:
@RestController
@RequestMapping("/order")
public class OrderController {

    @Value("${service-url.nacos-user-service}")
    private String url;
    
    @Autowired
    private RestTemplate restTemplate;
    
    @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback")
    public JsonResult<?> fallback(@PathVariable("id") Long id){
        JsonResult<?> result = restTemplate.getForObject(url + "/payment/" + id, JsonResult.class);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数");
        } else if (result.getData() == null) {
            throw new NullPointerException("没有该id对应的记录");
        }
        return result;
    }
}

现在访问http://192.168.0.104:84/order/fallback/1,可以成功返回信息,并且一次9003一次9004。如果传的id是4,就会返回error page,非法参数,因为我们没有任何配置。要解决这个问题,可以在@SentinelResource中加上fallback属性,属性值就是发生异常时要调用的方法名,如下:

public JsonResult<?> handlerFallback(@PathVariable("id") Long id, Throwable e){
    return new JsonResult<>(444, "没有id" + id + "对应的记录,这是兜底方法," + e.getMessage());
}

这样,再去访问http://192.168.0.104:84/order/fallback/4,返回的信息就如下:

{
  code: 444,
  message: "没有id4对应的记录,这是兜底方法,非法参数",
  data: null
}

这里说一下fallback和blockHandler的区别:

  • fallback:管运行异常,运行时发生异常了,就走fallback
  • blockHandler:sentinel控制台的配置违规处理,就是前面讲的那些降级规则,违规了就走blockHandler

下面就看一下两个都配置的情况:

    @GetMapping("/fallback/{id}")
    @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
    public JsonResult<?> fallback(@PathVariable("id") Long id){
        JsonResult<?> result = restTemplate.getForObject(url + "/payment/" + id, JsonResult.class);
        if (id == 4) {
            throw new IllegalArgumentException("非法参数");
        } else if (result.getData() == null) {
            throw new NullPointerException("没有该id对应的记录");
        }
        return result;
    }
    
    
    public JsonResult<?> handlerFallback(@PathVariable("id") Long id, Throwable e){
        return new JsonResult<>(444, "没有id" + id + "对应的记录,这是兜底方法," + e.getMessage());
    }
    
    
    public JsonResult<?> blockHandler(@PathVariable("id") Long id, BlockException e){
        return new JsonResult<>(445, "id" + id + "的记录配置违规了,这是兜底方法," + e.getMessage());
    }

然后在sentinel控制台配置一条流控规则,QPS阈值为1。现在访问http://192.168.0.104:84/order/fallback/4,如果慢悠悠地点,返回的是fallback的内容,如果快快地点,QPS大于1,返回的就是blockHandler的内容。

异常忽略属性:exceptionsToIgnore = {XxxException.class}:@SentinelResource注解还有这个属性可以配置,表示运行时发生了XxxException就忽略掉,不会走兜底的方法。

2、openFeign系列: 上面的order84是通过ribbon去调用9003和9004的,这里再来演示一下通过openFeign调用服务端的时候,如果用sentinel做熔断降级相关配置。新建一个名为cloudalibaba-consumer-nacos-order85的module:

  • pom.xml:相比order84,只多了一个openfeign:
<!-- openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
  • application.yml:相比order84,就是增加了sentinel激活openfeign的配置
server:
  port: 85
spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
    sentinel:
      transport:
        client-ip: 192.168.0.104
        dashboard: 192.168.0.106:8080
        port: 8719
service-url:
  nacos-user-service: http://nacos-payment-provider
  
# 激活sentinel对openfeign的支持
feign:
  sentinel:
    enabled: true
  • 主启动类:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 激活openfeign
public class OrderMain85 {
    public static void main(String[] args) throws Exception {
        SpringApplication.run(OrderMain85.class, args);
    }
}
  • service:
@FeignClient(value = "nacos-payment-provider", fallback = OrderServiceImpl.class)
public interface OrderService {

    @GetMapping("/payment/{id}")
    public JsonResult<?> payment(@PathVariable("id") Long id);
}
  • serviceImpl:
@Component
public class OrderServiceImpl implements OrderService{

    @Override
    public JsonResult<?> payment(Long id) {
        return new JsonResult<>(446, "这是兜底的方法");
    }
}
  • controller:
@RestController
@RequestMapping("/order")
public class OrderController {
    
    @Autowired
    private OrderService orderService;
    
    @GetMapping("/fallback/{id}")
    public JsonResult<?> fallback(@PathVariable("id") Long id){
        return orderService.payment(id);
    }
}

现在访问http://192.168.0.104:85/order/fallback/1可以成功调用9003和9004,现在把9003和9004服务停掉,再次访问,就返回了:

{
  code: 446,
  message: "这是兜底的方法",
  data: null
}

说明降级配置成功。

3、熔断框架比较:

六、sentinel规则持久化

目前我们没有做sentinel规则持久化,也就是说,在sentinel控制台配置的规则,只要我们微服务一重启,配置的规则就消失了。

1、sentinel规则持久化的方案: 将规则配置进nacos进行保存,nacos也做了数据库的持久化,所以相当于间接地把sentinel规则也写进了数据库。

2、步骤: 以8401的那个项目为例,进行如下修改:

  • pom.xml:添加如下依赖:
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  • application.yml:添加nacos数据源配置
server:
  port: 8401
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.106:8848
    sentinel:
      transport: 
        dashboard: 192.168.0.106:8080
        client-ip: 192.168.0.104
        port: 8719 # 默认8719,如果被占用会依次加1,直至找到没有被占用的端口
      datasource: # 将规则配置进nacos
        dsl: 
          nacos:
            server-addr: 192.168.0.106:8848
            data-id: cloudalibaba-sentinel-service
            group-id: DEFAULT_GROUP
            rule-type: flow
            data-type: json
# actuator图形化配置
management:
  endpoints:
    web:
      exposure:
        include:
        - "*"
  • 然后在nacos中新建配置:Data ID就是8401的服务名称,group用默认的,配置格式选json,配置内容如下:
[
    {
        "resource":"/ratelimit/byUrl",
        "limitApp":"default",
        "grade":1,
        "count":1,
        "strategy":0,
        "controlBehavior":0,
        "clusterMode":true
    }
]

现在说明一下json中各个字段的意思:

  • resource:资源名称
  • limitApp:来源应用
  • grade:阈值类型,0表示线程数,1表示QPS
  • count:单机阈值
  • strategy:流控模式,0表示直接,1表示关联,2表示链路
  • controlBehavior:流控效果,0表示快速失败,1表示warm up,2表示排队等待
  • clusterMode:是否集群

然后重启8401,访问一下/ratelimit/byUrl,然后发现sentinel流控中就有相关规则了,然后关闭8401,再重启,发现规则还在,并不用重新配置。关于sentinel的持久化还有很多方式,以后再细说。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、springCloud Alibaba sentinel 之降级规则
  • 二、springCloud Alibaba sentinel 之热点规则
  • 三、springCloud Alibaba sentinel 之系统规则
  • 四、sentinel的@SentinelResource详细用法
  • 五、sentinel的熔断功能
  • 六、sentinel规则持久化
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档