spring cloud之集成sentinel

写在前面

源码

本文一起看下spring cloud的sentinel组件的使用。

1:准备

1.1:理论

对于一个系统来说,最重要的就是高可用,那么如何实现高可用呢?你可能会说,集群部署不就可以了,但事实并非这么简单,假定一个不那么靠谱的开发,写了一个不那么靠谱的sql语句,上线之后,DB资源很快耗尽,那么所有需要查询DB的服务都无法处理请求,进而导致这些服务挂掉,而上游的服务因为无法快速得到响应,也会很快的挂掉,进而整个系统都会无法对外提供服务,这其实就是发生了非常可怕的服务雪崩,这个过程可以简单参考下图:

假定那个不那么靠谱的开发,是在服务D开发了那个不那么靠谱的sql语句,则上线后的后果就是D导致B和C不可用,B和C的不可用又会导致A的不可用,最终整个系统不可用。所以想要真正的实现高可用,除了多节点的集群部署外,我们还需要预防服务雪崩,而本文要学习的sentinel正是这样的一个组件,我们可以叫做容错组件

基本上,导致服务雪崩发生的原因在2个方面,第一个方面是高并发导致的请求量增大,第二方面就是应用内部的异常和错误(就像那个不那么靠谱的sql)。其实sentinel也正是从这两方面来预防服务雪崩的,对于高并发,sentinel可以从外部来限制并发量,对于应用内部异常和错误,sentinel可以进行降级和熔断。我们也会从这方面来展开sentinel的实战环节。

在java中,万物皆对象,在Linux中,万物皆文件,而在sentinel中,万物皆资源,而这里的资源我们可以认为就是接口地址,而对这些资源作用的过程是通过其内部的一个责任链,可以参考下图:

比如有用来收集数据的StaticSlot,构建调用链的NodeSelectorSlot(形成一个树状的调用关系图),如果业务有需要,我们也可以增加自定义的额slot到这个调用链上。

1.2:安装sentinel

首先在这里 下载可运行的jar包,接着如下操作:

复制代码
$ java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.2.jar
INFO: Sentinel log output type is: file
INFO: Sentinel log charset is: utf-8
INFO: Sentinel log base directory is: C:\Users\dell9020\logs\csp\
INFO: Sentinel log name use pid is: false

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.0.5.RELEASE)
 ...

成功后访问http://localhost:8080/#/login,进入登陆页面:

账号sentinel/sentinel,成功后进入如下页面:

2:限流实战

限流,或者叫流量整形,是sentinel从外部预防服务雪崩的重要手段。

2.1:基础配置

这部分我们直接来使用sentiniel对我们项目做流量整形,首先,在custom和template模块引入依赖:

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

然后在custom和template模块的application.yml中配置sentinel信息:

yml 复制代码
spring:
  jpa:
    ...
  cloud:
    sentinel:
      eager: true 
      transport:
        port: 8719
        dashboard: localhost:8080

接着使用@SentinelResource注解标记要进行流量整形的接口,如下:

  • custom模块
java 复制代码
dongshi.daddy.sentinel.controller.CouponCustomerController#requestCoupon
@PostMapping("requestCoupon")
@SentinelResource(value = "requestCoupon")
public Coupon requestCoupon(@Valid @RequestBody RequestCoupon request) {
    ...
}
@PostMapping("findCoupon")
@SentinelResource(value = "customer-findCoupon")
public List<CouponInfo> findCoupon(@Valid @RequestBody SearchCoupon request) {
    return customerService.findCoupon(request);
}
  • template模块
java 复制代码
// 读取优惠券
@GetMapping("/getTemplate")
@SentinelResource(value = "getTemplate")
public CouponTemplateInfo getTemplate(@RequestParam("id") Long id){
    log.info("Load template, id={}", id);
    /*try {
        // 休眠二十秒模拟超时
        TimeUnit.SECONDS.sleep(20);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }*/
    return couponTemplateService.loadTemplateInfo(id);
}

// 批量获取
@GetMapping("/getBatch")
// 降级要执行的方法
@SentinelResource(value = "getTemplateInBatch", blockHandler = "getTemplateInBatch_block")
public Map<Long, CouponTemplateInfo> getTemplateInBatch(@RequestParam("ids") Collection<Long> ids) {
    log.info("getTemplateInBatch: {}", JSON.toJSONString(ids));
    return couponTemplateService.getTemplateInfoMap(ids);
}
// 降级要执行的方法
public Map<Long, CouponTemplateInfo> getTemplateInBatch_block(
        Collection<Long> ids, BlockException exception) {
    log.info("接口被限流");
    return Maps.newHashMap();
}

接着我们启动服务,如果一切正常的话,就可以看到custom和template这两个模块注册到sentinel上了:

2.2:通过流控规则进行限流

sentinel支持通过三种方式来进行流量整形:

复制代码
直接流控:
    针对特定接口(sentinel称为资源)限流
关联流控:
    当某接口(sentinel称为资源)达到限流条件时对关联的接口(sentinel称为资源)进行限流
链路流控:

2.2.1:直接流控

我们对资源getTemplateInBatch进行流控,在sentinel选择template对应的微服务,点击子菜单流控规则,然后点击右上角 添加流控规则,如下:

为了测试我们使用jmeter来进行测试,jmeter配置可以从这里 下载,配置如下:

通过按钮 启动,之后查看template模块日志输出如下:

复制代码
2024-01-08 15:34:44.411  INFO 26504 --- [io-20006-exec-7] d.d.s.c.CouponTemplateController         : getTemplateInBatch: [2,3]
Hibernate: select coupontemp0_.id as id1_0_, coupontemp0_.available as availabl2_0_, coupontemp0_.type as type3_0_, coupontemp0_.created_time as created_4_0_, coupontemp0_.description as descript5_0_, coupontemp0_.name as name6_0_, coupontemp0_.rule as rule7_0_, coupontemp0_.shop_id as shop_id8_0_ from coupon_template coupontemp0_ where coupontemp0_.id in (? , ?)
2024-01-08 15:34:44.904  INFO 26504 --- [io-20006-exec-5] d.d.s.c.CouponTemplateController         : 接口被限流

可以看到第二次执行了熔断逻辑被限流了。

2.2.2:关联流控

jmeter配置下载

我们来设置当getTempalte接口qps超过1时限制getBatch接口,配置如下:

首先我们使用jmeter模拟qps 2不间断访问getTemplate接口,如下:

启动后,这样,肯定就会触发限流规则了,这样,我们先来启动jmeter,

之后访问getBatch接口,如下:

复制代码
http://localhost:20006/template/getBatch?ids=2,3

查看后台输出:

然后我们来停止jmeter,再来请求接口,就会恢复正常了:

2.2.3:链路流控

jmeter配置下载

对于同一个资源访问可以来自于不同的链路,针对特定的链路进行流控,就是链路流控了,如下图,上方链路就是被流控的:

这里我们模拟从custom模块访问template模块的getBatch接口,为了测试,需要首先在custom模块中增加如下接口:

java 复制代码
// 批量获取
@GetMapping("/getBatchFromCustomer")
public Map<Long, CouponTemplateInfo> getTemplateInBatch(@RequestParam("ids") Collection<Long> ids) {
    log.info("getTemplateInBatch111: {}", JSON.toJSONString(ids));
    return templateService.getTemplateInBatch(ids);
}

因为需要进行限流的资源需要知道当前的调用者是谁,才能知道是否需要触发,所以,首先需要在custom模块添加openfeign的拦截器,在openfeign的请求中添加来源头

java 复制代码
@Configuration
public class OpenfeignSentinelInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        template.header("SentinelSource", "coupon-customer-serv");
    }
}

接着在tempalte模块中获取这个来源的信息:

java 复制代码
@Component
@Slf4j
public class SentinelOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest request) {
        log.info("request {}, header={}", request.getParameterMap(), request.getHeaderNames());
        return request.getHeader("SentinelSource");
    }
}

接着我们来配置sentinel针对来源coupon-customer-serv进行限流,如下:

接着开启jmeter,正常的话会看到被限流了:

为了测试来源不匹配的情况不限流,我们将sentinel的目标资源名称随便改为其他的,如下:

此时再开启jmeter测试就正常了:

2.3:三种流控效果

sentinel支持三种流控效果,快速失败,warm up,排队等待,在上述的实战环节我们使用就是快速失败,这也是sentinel默认的流控效果。

  • 快速失败
    直接失败。
  • warm up
    这是一种从低水位逐渐拉高的一种限流方式,比如如下的配置:

    上图设置在5秒内拉高到10进行限流,而非开始就是10,开始的限流值需要除以冷却因子,这里冷却因子是3,所以就是10/3=3,也就是当qps到达3时开始限流,然后在5秒内将这个限流值拉高到最大值10,

使用的场景,如采用缓存+DB读的场景,如果接口一段时间内都处于很低的水位,导致大量的缓存都失效了,此时突然发生了突发流量(某明星出轨了,某漂亮country被原子弹攻击了),此时缓存还没有完全构建起来,为了避免突发流量全部打到DB,把DB打穿,就可以考虑使用warm up,在一段时间内从低水位逐渐拉到高水位,同时在这段时间内完成缓存的构建工作。

  • 排队等待
    被限流的请求在一个任务队列中排队等待,并按照超时等待时间从队列中删除任务,如下500内还没有处理则移除任务的配置:

3:降级、熔断实战

jmeter配置

降级、熔断,是sentinel从服务内部预防服务雪崩的重要手段。这部分我们一起来看下如何使用sentinel来实现降级和熔断。为了方便测试我们首先在template模块添加一个新的接口并设置为sentinel的资源:

java 复制代码
// 批量获取
@GetMapping("/getBatchV1")
// 降级要执行的方法
@SentinelResource(value = "getTemplateInBatchV1", fallback = "getTemplateInBatch_fallback")
public Map<Long, CouponTemplateInfo> getTemplateInBatchV1(@RequestParam("ids") Collection<Long> ids) {
    log.info("getTemplateInBatch: {}", JSON.toJSONString(ids));
    int i = 1 / 0;
    return couponTemplateService.getTemplateInfoMap(ids);
}

public Map<Long, CouponTemplateInfo> getTemplateInBatch_fallback(Collection<Long> ids) {
    log.info("接口被fallback");
    return Maps.newHashMap();
}

这里注意指定了fallback = "getTemplateInBatch_fallback",则当抛出了RuntimeException时就会进入执行降级逻辑,接着我们就需要在sentinel中配置熔断逻辑了,如下:

降级熔断的判断过程可以参考下图(注意时示例)

接着执行jemter,日志输出如下:

复制代码
2024-01-10 14:01:47.813  INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController         : getTemplateInBatch: [2,3] #
2024-01-10 14:01:47.813  INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController         : 接口被fallback 
2024-01-10 14:01:47.813  INFO 13112 --- [o-20006-exec-10] d.daddy.sentinel.SentinelOriginParser    : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@7d50964c
2024-01-10 14:01:47.814  INFO 13112 --- [o-20006-exec-10] d.d.s.c.CouponTemplateController         : 接口被fallback
2024-01-10 14:01:47.815  INFO 13112 --- [io-20006-exec-9] d.daddy.sentinel.SentinelOriginParser    : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@1f47105b
2024-01-10 14:01:47.816  INFO 13112 --- [io-20006-exec-9] d.d.s.c.CouponTemplateController         : 接口被fallback
....
2024-01-10 14:01:47.813  INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController         : getTemplateInBatch: [2,3] # 1s后半开启,后又重新进入熔断
2024-01-10 14:01:47.813  INFO 13112 --- [io-20006-exec-2] d.d.s.c.CouponTemplateController         : 接口被fallback 
2024-01-10 14:01:47.813  INFO 13112 --- [o-20006-exec-10] d.daddy.sentinel.SentinelOriginParser    : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@7d50964c
2024-01-10 14:01:47.814  INFO 13112 --- [o-20006-exec-10] d.d.s.c.CouponTemplateController         : 接口被fallback
2024-01-10 14:01:47.815  INFO 13112 --- [io-20006-exec-9] d.daddy.sentinel.SentinelOriginParser    : request org.apache.catalina.util.ParameterMap@39d7b9e0, header=org.apache.tomcat.util.http.NamesEnumerator@1f47105b
2024-01-10 14:01:47.816  INFO 13112 --- [io-20006-exec-9] d.d.s.c.CouponTemplateController         : 接口被fallback
...

可以看到降级后,满足条件就会进入熔断。一段时间后会尝试恢复,这个状态叫做半熔断,完整的状态转换参考下图:

写在后面

参考文章列表

jmeter之简单使用

相关推荐
蓝色天空的银码星12 小时前
SpringCloud微服务架构下的日志可观测解决方案(EFK搭建)
spring cloud·微服务·架构
.生产的驴17 小时前
SpringBoot 服务器监控 监控系统开销 获取服务器系统的信息用户信息 运行信息 保持稳定
服务器·spring boot·分布式·后端·spring·spring cloud·信息可视化
hqxstudying20 小时前
Kafka
java·spring cloud·kafka
康小庄21 小时前
AQS独占模式——资源获取和释放源码分析
java·开发语言·jvm·spring boot·spring·spring cloud·nio
码不停蹄的玄黓1 天前
通关JUC:Java并发工具包从入门到精通 | 深度源码解析
java·jvm·spring boot·spring·spring cloud
异常君1 天前
Dubbo 与 Spring Cloud Gateway 技术对比:微服务架构中的协同实践
spring cloud·微服务·dubbo
草履虫建模1 天前
Tomcat 和 Spring MVC
java·spring boot·spring·spring cloud·tomcat·mvc·intellij-idea
未来并未来2 天前
Sentinel 流量控制安装与使用
开发语言·python·sentinel
shangjg32 天前
Eureka 心跳续约机制
java·分布式·spring cloud·eureka
·云扬·2 天前
【PmHub面试篇】集成 Sentinel+OpenFeign实现网关流量控制与服务降级相关面试题解答
面试·职场和发展·sentinel