SpringCloud Alibaba系列——14Sentinel简介及基本应用(中)
作者 | 一起撸Java
来源 |今日头条
第2章 Sentinel
官网:
https://sentinelguard.io/zh-cn/
2.1 简介
Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。2.1.1 基本概念
- 资源 (需要被保护的东西)
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
资源的定义有两种,可以通过@SentinelResource(value="doTest")设置,如果没有设置的话,就默认取请求后面的一截
- 规则(限流的规则/熔断规则)
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
2.1.2 功能和设计理念
2.1.2.1 流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
不管是什么方式,流控的核心原理是,监控应用流量的QPS或者并发线程数这些指标,去判断指标的阈值来实现对流量的控制,防止瞬时流高峰流量导致系统被压垮,从而保证系统的高可用性。
在Sentinel中,限流的直接表现形式是,在执行Entry nodeA = SphU.entry(resourceName)的时候抛出FlowException异常。FlowException是 BlockException的子类,您可以捕捉BlockException来自定义被限流之后的处理逻辑。
同一个资源可以创建多条限流规则。 FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
- resource :资源名,即限流规则的作用对象
- count : 限流阈值
- grade : 限流阈值类型(QPS 或并发线程数)
- limitApp : 流控针对的调用来源,若为 default 则不区分调用来源
- default:表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。
- {some_origin_name}:表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如 NodeA 配置了一条针对调用者caller1的规则,那么当且仅当来自 caller1 对 NodeA 的请求才会触发流量控制。
- other:表示针对除 {some_origin_name} 以外的其余调用方的流量进行流量控制。例如,资源NodeA配置了一条针对调用者 caller1 的限流规则,同时又配置了一条调用者为 other 的规则,那么任意来自非 caller1 对 NodeA 的调用,都不能超过 other 这条规则定义的阈值。
同一个资源名可以配置多条规则,规则的生效顺序为:{some_origin_name} > other > default
- strategy : 调用关系限流策略
- controlBehavior : 流量控制效果(直接拒绝、Warm Up、匀速排队)
通过这个地址,可以查看实时的统计信息:
http://localhost:8719/cnode?id=doTestthread: 代表当前处理该资源的并发数;
pass: 代表一秒内到来到的请求;
blocked: 代表一秒内被流量控制的请求数量;
success: 代表一秒内成功处理完的请求;
total: 代表到一秒内到来的请求以及被阻止的请求总和;
RT: 代表一秒内该资源的平均响应时间;
1m-pass: 则是一分钟内到来的请求;
1m-block: 则是一分钟内被阻止的请求;
1m-all: 则是一分钟内到来的请求和被阻止的请求的总和;
exception: 则是一秒内业务本身异常的总和。
并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
QPS流量
当 QPS 超过某个阈值的时候,则采取措施进行流量控制。流量控制的效果包括以下几种:直接拒绝、Warm Up、匀速排队。对应 FlowRule 中的 controlBehavior 字段。
基于调用关系
调用关系包含调用方和被调用方,一个方法有可能会调用其他方法,形成一个调用链,所谓的调用关系限流,就是根据不同的调用纬度来触发流量控制。
- 根据调用方限流
- 根据调用链路入口限流
- 具有关系的资源流量控制
根据调用方限流
很多场景下,根据调用方来限流也是非常重要的。比如有两个服务 A 和 B 都向 Service Provider 发起调用请求,我们希望只对来自服务 B 的请求进行限流,则可以设置限流规则的 limitApp 为服务 B 的名称。Sentinel Dubbo Adapter 会自动解析 Dubbo 消费者(调用方)的 application name 作为调用方名称(origin),在进行资源保护的时候都会带上调用方名称。若限流规则未配置调用方(default),则该限流规则对所有调用方生效。若限流规则配置了调用方则限流规则将仅对指定调用方生效。
所谓调用方限流,就是根据请求来源进行流量控制,我们可以设置limitApp属性来配置来源信息,它有三个选项:
- default :表示不区分调用者,来自任何调用者的请求都将进行限流统计。如果这个资源名的调用总和超过了这条规则定义的阈值,则触发限流。
- {some_origin_name} :表示针对特定的调用者,只有来自这个调用者的请求才会进行流量控制。例如 NodeA 配置了一条针对调用者 caller1 的规则,那么当且仅当来自 caller1 对NodeA 的请求才会触发流量控制。
- other :表示针对除 {some_origin_name} 以外的其余调用方的流量进行流量控制。例如,资源 NodeA 配置了一条针对调用者 caller1 的限流规则,同时又配置了一条调用者为 other 的规则,那么任意来自非 caller1 对 NodeA 的调用,都不能超过 other 这条规则定义的阈值。
对于同一个资源,可以配置多条规则,规则的生效顺序为: {some_origin_name} > other >default
根据调用链路入口限流
一个被限流的保护方法,可能来自于不同的调用链路,比如针对资源NodeA,入口 Entrance1 和Entrance2的请求都调用到了资源 NodeA ,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 strategy 为 RuleConstant.STRATEGY_CHAIN ,同时设置 refResource 为Entrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经Entrance2 到来的调用具有关系的资源流量控制
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说, read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db 。这样当写库操作过于频繁时,读数据的请求会被限流
2.1.2.2 控制策略
1、直接拒绝
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT )方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出 FlowException 。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
2、Warm Up
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP )方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
如下图所示,当前系统所能够处理的最大并发数是480,首先在最下面的标记位置,系统一直处于空闲状态,接着请求量突然直线升高,这个时候系统并不是直接将QPS拉到最大值,而是在一定时间内逐步增加阈值,而中间这段时间就是一个系统逐步预热的。
3、匀速排队
匀速排队( RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER )方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
2.1.2.3 熔断降级
1、什么是熔断降级
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
2、熔断降级设计理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。
Sentinel 对这个问题采取了两种手段:
- 通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
2.1.3 如何工作的
Sentinel 的主要工作机制如下:
- 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。(API的使用或者集成Springboot使用)
- 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。(使用之前先定规则)
- Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。(控制台)
2.2 控制台
启动方式:
下载jar包,通过命令方式
在
https://github.com/alibaba/Sentinel/releases下载对应版本的dashboard jar包,进入到该jar所在的目录,然后通过java命令运行该jar包即可:
java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 -Dproject.name=sentinel-dashboard-1.8.0 -jar sentinel-dashboard-1.8.0.jar
访问地址:localhost:7777
默认账号和密码都是:sentinel
2.3 应用方式
官网介绍了基本使用的方式有五种,其实归根结底就两种:1.api的使用;2.主流框架的封装。我们这节课就讲api的使用和集成springboot,在使用过程中包含了动态规则配置
版本对应关系:
https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
API使用只需要引sentinel-core包就可以(版本选择1.8.1)
集成springboot时版本选择:这里选择springboot的版本为2.3.2.RELEASE,所以Spring Cloud Alibaba Version版本选择为2.2.6.RELEASE,sentinel的版本选择为1.8.1
2.3.1 api使用
2.3.1.1 本地配置
1、创建springboot工程,依赖spring web
2、配置pom
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-core</artifactId> <version>1.8.1</version></dependency>
3、初始化流控规则
@SpringBootApplicationpublic class SpringbootSentinelApplication { //在spring容器启动的时候初始化流控规则 public static void main(String[] args) { initFlowRules(); SpringApplication.run(SpringbootSentinelApplication.class, args); } public static void initFlowRules(){ List<FlowRule> rules = new ArrayList<FlowRule>(); //定义一条规则 FlowRule rule1 = new FlowRule(); //规则是针对哪个资源,这个资源名可以任意定义,不定义的时候默认是url的值 rule1.setResource("rule01"); // QPS控制在2以内 rule1.setCount(2); // QPS限流 rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); }}
4、定义请求进来的controller
@RestControllerpublic class UserController { @Autowired private UserServiceImpl01 userServiceImpl01; @GetMapping("/getUser") public String userName(@RequestParam("name") String name){ return userServiceImpl01.getUserName(name); }}
5、定义真正要限流的接口方法(一般在service层)
api的使用也有多种方式:if、try catch的方式
- if的方式
@Servicepublic class UserServiceImpl01 { public String getUserName(String name){ if(SphO.entry("rule01")){ try { return "你好吗?亲爱的"+name; } finally { SphO.exit(); } }else{ return "别问了,烦不烦啊"; } }}
- try catch方式
@Servicepublic class UserServiceImpl01 { public String getUserName(String name){ Entry entry = null; try { entry = SphU.entry("rule01"); return "你好吗?亲爱的"+name; }catch (BlockException e1){ // 接口被限流的时候, 会进入到这里 return "烦不烦啊,降级"; }finally { // SphU.entry(xxx) 需要与 entry.exit() 成对出现,否则会导致调用链记录异常 if (entry != null) { entry.exit(); } } }}
6、日志查看
日志 ~/logs/csp/${appName}-metrics.log.xxx 里看到下面的输出:
时间戳|格式化的时间戳|资源名|通过的请求|被拒绝的请求|成功数量|异常数量|rt平均响应时间1600608724000|2020-09-20 21:32:04|helloWorld|5|6078|5|0|5|0|0|01600608725000|2020-09-20 21:32:05|helloWorld|5|32105|5|0|0|0|0|01600608726000|2020-09-20 21:32:06|helloWorld|5|41084|5|0|0|0|0|01600608727000|2020-09-20 21:32:07|helloWorld|5|72211|5|0|0|0|0|01600608728000|2020-09-20 21:32:08|helloWorld|5|60828|5|0|0|0|0|01600608729000|2020-09-20 21:32:09|helloWorld|5|41696|5|0|0|0|0|0
2.3.1.2 动态配置
1、增加下面的依赖
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.1</version></dependency><dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-extension</artifactId> <version>1.8.1</version></dependency><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version></dependency>
2、初始化流控规则,这里不在通过本地配置,而是通过配置中心去完成
所以在启动spring容器的时候要先连接配置中心,通过spi机制去实现
在resources目录下创建services目录,然后再services目录下创建com.alibaba.csp.sentinel.init.InitFunc文件
com.example.springbootsentinel.spi.DataSourceInitFunc
public class DataSourceInitFunc implements InitFunc { private final String nacosAddress="192.168.8.74:8848"; private final String groupId="SENTINEL_GROUP"; private final String dataId="--flow-rules"; private final String appName="springboot-sentinel"; @Override public void init() throws Exception { ReadableDataSource<String, List<FlowRule>> flowRuleDs= new NacosDataSource<List<FlowRule>>(nacosAddress,groupId,appName+dataId, source-> JSON.parseObject(source,new TypeReference<List<FlowRule>>(){})); FlowRuleManager.register2Property(flowRuleDs.getProperty()); }}
3、启动类
@SpringBootApplicationpublic class SpringbootSentinelApplication { public static void main(String[] args) { SpringApplication.run(SpringbootSentinelApplication.class, args); }}
4、nacos的配置5、测试类跟上面本地配置一样
2.3.2 集成springCloud
2.3.2.1 本地配置
1、配置pom
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-extension</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.6.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.1</version> </dependency></dependencies>
2、初始化流控配置
@SpringBootApplicationpublic class SpringbootSentinelApplication { //在spring容器启动的时候初始化流控规则 public static void main(String[] args) { initFlowRules(); SpringApplication.run(SpringbootSentinelApplication.class, args); } public static void initFlowRules(){ List<FlowRule> rules = new ArrayList<FlowRule>(); //定义一条规则 FlowRule rule1 = new FlowRule(); //规则是针对哪个资源,这个资源名可以任意定义,不定义的时候默认是url的值 rule1.setResource("rule01"); // QPS控制在2以内 rule1.setCount(2); // QPS限流 rule1.setGrade(RuleConstant.FLOW_GRADE_QPS); rule1.setLimitApp("default"); rules.add(rule1); FlowRuleManager.loadRules(rules); }}
3、实现接口
@Servicepublic class UserServiceImpl01 { @SentinelResource(value = "rule01",blockHandler = "blockHandlerDemo",fallback = "fallBackDemo") public String getUserName(String name){ // 模拟接口运行时抛出代码异常 if ("文文".equals(name)) { throw new RuntimeException(); } return "你好吗?亲爱的"+name; } /** * 订单查询接口抛出限流或降级时的处理逻辑 * * 注意: 方法参数、返回值要与原函数保持一致 * @return */ public String blockHandlerDemo(String name, BlockException e) { e.printStackTrace(); return "被限流了, " + name; } /** * 订单查询接口运行时抛出的异常提供fallback处理 * * 注意: 方法参数、返回值要与原函数保持一致 * @return */ public String fallBackDemo(String name, Throwable e) { return "被熔断了: " + name; }}
2.3.2.2 动态配置
1、启动类
@SpringBootApplicationpublic class SpringbootSentinelApplication { public static void main(String[] args) { SpringApplication.run(SpringbootSentinelApplication.class, args); }}
2、可以通过spi机制也可以通过配置文件实现,这里通过配置文件
# 应用名称spring.application.name=springboot-sentinel# 应用服务 WEB 访问端口server.port=8080# spring 静态资源扫描路径spring.resources.static_locations=classpath:/static/# Sentinel 控制台地址spring.cloud.sentinel.transport.dashboard=127.0.0.1:7777# 取消Sentinel控制台懒加载# 默认情况下 Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包# 配置 sentinel.eager=true 时,取消Sentinel控制台懒加载功能spring.cloud.sentinel.eager=truespring.cloud.sentinel.datasource.ds1.nacos.serverAddr=192.168.8.74:8848spring.cloud.sentinel.datasource.ds1.nacos.dataId=springboot-sentinel--flow-rulesspring.cloud.sentinel.datasource.ds1.nacos.groupId=SENTINEL_GROUP# FlowRule , jsonspring.cloud.sentinel.datasource.ds1.nacos.dataType=jsonspring.cloud.sentinel.datasource.ds1.nacos.ruleType=flowspring.cloud.sentinel.datasource.ds1.nacos.username=nacosspring.cloud.sentinel.datasource.ds1.nacos.password=nacos
2.4 集群限流
代码见sentinel-token-server和
springcloud-sentinel-cluster-client
在前面的所有案例中,我们只是基于Sentinel的基本使用和单机限流的使用,假如有这样一个场景,我们现在把provider部署了10个集群,希望调用这个服务的api的总的qps是100,意味着每一台机器的qps是10,理想情况下总的qps就是100.但是实际上由于负载均衡策略的流量分发并不是非常均匀的,就会导致总的qps不足100时,就被限了。在这个场景中,仅仅依靠单机来实现总体流量的控制是有问题的。所以最好是能实现集群限流。
集群流控中共有两种身份:
- Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端结果,决定是否限流。
- Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放 token(是否允许通过)。
下面的演示案例,主要会集成到Dubbo服务层,实现Dubbo服务集群的分布式限流
2.4.1 Token-server
1、创建sentinel-token-server的Maven项目
2、添加依赖
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example.sentinel</groupId> <artifactId>sentinel-token-server</artifactId> <version>1.0-SNAPSHOT</version> <name>sentinel-token-server</name> <!-- FIXME change it to the project's website --> <url>http://www.example.com</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-cluster-server-default</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.8.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency> </dependencies></project>
3、在resource目录下创建log4j.properties文件并配置
log4j.rootLogger=debug, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
4、添加InitFunc实现类
public class FlowRuleInitFunc implements InitFunc { private final String nacosAddress="192.168.8.74:8848"; private final String groupId="DEFAULT_GROUP"; private String dataId="-flow-rules"; //dataId的后缀,它会和namespace组成一个完整的dataID,namespace表示token-client的名字,后续会用到 @Override public void init() throws Exception { ClusterFlowRuleManager.setPropertySupplier(namespace->{ ReadableDataSource<String, List<FlowRule>> flowRuleDs= new NacosDataSource<>(nacosAddress, groupId, namespace + dataId, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() { })); return flowRuleDs.getProperty(); }); }}
5、SPI实现,在META-INF/services目录下创建
com.alibaba.csp.sentinel.init.InitFunc文件
com.example.sentinel.FlowRuleInitFunc
6、创建启动类
public class ClusterServer { public static void main(String[] args) throws Exception { ClusterTokenServer tokenServer=new SentinelDefaultTokenServer(); //手动载入namespace和serverTransportConfig的配置到ClusterServerConfigManager //集群限流服务端通信相关配置 ClusterServerConfigManager.loadGlobalTransportConfig( new ServerTransportConfig().setIdleSeconds(600).setPort(9999)); //加载namespace集合列表() , namespace也可以放在配置中心 ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("cluster-demo")); tokenServer.start(); //Token-client会上报自己的project.name到token-server。Token-server会根据namespace来统计连接数 }}
7、nacos上面增加配置
- Dataid:cluster-demo-flow-rules
- Group:DEFAULT_GROUP
- 格式为JSON
[{"resource":"clusterdemo01","grade":1,"count":5,"clusterMode":true,"clusterConfig":{"flowId":100001,"thresholdType":1,"fallbackToLocalWhenFail":true}}]
8、启动:
-Dcsp.sentinel.dashboard.server=localhost:7777 -Dproject.name=sentinel-token-server -Dcsp.sentinel.log.use.pid=true
服务启动之后,在$user.home$/logs/csp/ 可以找到sentinel-record.log.pid*.date文件,如果看到日志文件中获取到了远程服务的信息,说明token-server启动成功了
2.4.2 cluster-client
1、创建
springcloud-sentinel-cluster-client的springboot项目
2、引包
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>springcloud-sentinel-cluster-client</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springcloud-sentinel-cluster-client</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.6.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.1</version> </dependency> </dependencies></project>
3、通过SPI实现InitFunc接口,实现类
public class FlowRuleInitFunc implements InitFunc { private static final String CLUSTER_SERVER_HOST = "localhost"; private static final int CLUSTER_SERVER_PORT = 9999; //Token-Server的端口和ip private static final int REQUEST_TIME_OUT = 200000; private static final String REMOTE_ADDRESS = "192.168.8.74:8848"; //nacos服务器地址 private static final String GROUP_ID = "DEFAULT_GROUP"; private static final String FLOW_POSTFIX = "-flow-rules"; private static final String APP_NAME = "cluster-demo"; //这个名字需要在Token-Server的namespace中配置 @Override public void init() throws Exception { ClusterStateManager.applyState(ClusterStateManager.CLUSTER_CLIENT); loadClusterClientConfig(); registerClusterFlowRuleProperty(); } //通过硬编码的方式,配置连接到token-server服务的地址,{这种在实际使用过程中不建议,后续可以基于动态配置源改造} private static void loadClusterClientConfig() { ClusterClientAssignConfig assignConfig = new ClusterClientAssignConfig(); assignConfig.setServerHost(CLUSTER_SERVER_HOST); assignConfig.setServerPort(CLUSTER_SERVER_PORT); ClusterClientConfigManager.applyNewAssignConfig(assignConfig); ClusterClientConfig clientConfig = new ClusterClientConfig(); clientConfig.setRequestTimeout(REQUEST_TIME_OUT); //token-client请求token-server获取令牌超时的时间 ClusterClientConfigManager.applyNewConfig(clientConfig); } /** * 注册动态规则Property * 当client与Server连接中断,退化为本地限流时需要用到的该规则 * 该配置为必选项,客户端会从nacos上加载限流规则,请求tokenserver时,会带上要check的规则id */ private static void registerClusterFlowRuleProperty() { ReadableDataSource<String, List<FlowRule>> ds = new NacosDataSource<List<FlowRule>>(REMOTE_ADDRESS, GROUP_ID, APP_NAME + FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() { })); FlowRuleManager.register2Property(ds.getProperty()); }}
4、controller和service复制之前的项目中的类
5、集成Dashboard
spring.application.name=cluster-clientserver.port=8080# 连接Dashboard地址spring.cloud.sentinel.transport.dashboard=localhost:7777# 默认是当触发了限流后,才会连接到dashboard,设置为true启动后就连接spring.cloud.sentinel.eager=true spring.cloud.sentinel.log.switch-pid=true
6、然后启动两个同样的Token-client,端口分别为8080和8081。
7、然后通过Jmeter同时向两个端口发起测试,观察结果