流量控制
sentinel中有几个比较重要的东西:
- 资源 - 针对哪一个方法,可以在接口层面 ,也可以在类层面
- 规则 - 根据资源配置限流的规则
- Entry -> 请求
限流核心因素
- resource 资源
- count 阈值
- grade 限流的类型
- …
并发线程数
我们所有的请求它会去基于业务线程也好,容器分配的线程也好,其都是通过线程的分配来处理。(像dubbo就有默认200的线程大小,其实也就是服务端接收到线程以后,会分配一个业务线程去处理这个请求,其最大的线程数设置的是200。)在sentinel中,关于并发线程数的策略指的是对于并发请求的处理策略。这包括了对于同时处理的请求数量的限制、超时处理、以及资源的保护等方面的策略。这些策略旨在保证系统在高并发情况下能够正常运行,同时避免资源被过度消耗或者出现系统崩溃的情况。通过设置合适的并发线程数策略,可以有效地管理系统的资源和保证系统的稳定性。
QPS
在Sentinel中,关于QPS的策略指的是对于每秒查询次数的限制策略。这些策略可以用来控制系统的并发请求量,防止系统被过度压力而导致性能下降或系统崩溃。通过设置QPS的限制策略,可以限制系统在单位时间内能够处理的请求次数,从而保护系统免受过多请求的影响,确保系统在承受合理负载的情况下能够正常运行。这些策略可以根据系统的实际情况和需求来进行调整,以保证系统的稳定性和可靠性。
当触发了上述的两种限流,就会产生一种行为,也就是所谓流量控制的策略
- 直接拒绝
- warm up(冷启动)
- 匀速排队
这个策略主要是通过ControlBehavior属性来控制策略的
直接拒绝
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
warm up(冷启动)
所谓冷启动,就是我们的系统在启动的时候长期处于低水位(整个系统的吞吐量非常低,并发量很低),当突然出现这种瞬时流量,系统有一个启动的过程,并不是一下子将所有的流量都放进来,而是逐步的拉伸整个系统的水位直到通过一个时间维度达到最高峰的阈值。(因为一下子将所有流量放进来,系统很可能在一瞬间被压垮,所以匀速提升整个系统的预热,可以在一定情况下逐渐增加处理上限)
匀速排队
总体来说,在限流里面,可分为比较重要的就是 资源 和 规则 ,我们可以通过配置不同的资源去设置不同的规则,从而控制整个请求流量的行为。
基于调用关系来做流量控制
在整个调用链路里面,针对不同请求来源去做(FlowRule.limitApp)
- 根据资源
基于资源的流量控制是指根据被调用的具体资源(比如接口、方法等)来进行流量控制。在Sentinel中,可以通过配置规则来限制不同资源的访问流量,比如可以设置某个接口的最大访问次数或者并发数。这种方式适用于对特定资源进行限流的场景,可以保护资源不被过度访问。
- 根据来源
基于来源的流量控制是指根据调用方的来源(比如IP地址、用户ID等)来进行流量控制。在Sentinel中,可以通过配置规则来限制不同来源的访问流量,比如可以设置某个IP地址的最大访问次数或者限制某个用户的访问频率。这种方式适用于对不同来源的调用方进行个性化的限流控制。
- 根据参数
基于参数的流量控制是指根据调用方传递的参数来进行流量控制。在Sentinel中,可以通过配置规则来限制不同参数组合的访问流量,比如可以设置某个接口某个参数的最大访问次数。这种方式适用于对特定参数组合进行限流的场景,可以根据业务需求对不同参数进行限流控制。
Sentinel控制台
java -Dserver.port=7777 -Dcsp.sentinel.dashboard.server=localhost:7777 - Dproject.name=sentinel-dashboard-1.8.0 -jar sentinel-dashboard-1.8.0.jar
上图可以清晰的展示到sentinel可以提供哪些方向上的规则配置。
需要注意的一点就是,单机情况下,如果配置阈值的话,可以通过单机压测 压测出来。
动态限流规则
实际上就是动态的规则感知进行 (pull、push)
Nacos DataSource(接口)
对于整个Sentinel的动态数据源来说,必须要去做扩展。
这个接口提供了动态配置感知的能力 和 动态配置存储的能力。
// sentinel 提供的spi扩展 /* SPI(Service Provider Interface)扩展是Java中一种用于扩展框架的机制。在SPI中,框架定义了一组接口(接口或抽象类),并允许外部的实现者按照这些接口的规范来提供具体的实现。这种机制允许系统的扩展功能可以通过在类路径下放置实现者提供的JAR包来实现,而无需修改框架的源代码。 SPI扩展机制的优点是能够实现框架和扩展的解耦,框架可以在不修改代码的情况下,通过加载外部的实现类来扩展功能。同时,SPI扩展也提供了一种简单的插件机制,允许开发者通过编写实现类来扩展框架的功能。 */ // 扩展之后必须在 resource/META-INF/services/com.alibaba.csp.sentinel.init.initFunc 里面写入com.gupaoedu.example.springbootsentinel.FlowRuleInitFunc 对应的路径 // 扩展之后,sentinel在初始化的时候,就会去加载这个方法 public class FlowRuleInitFunc implements InitFunc{ @Override public void init() throws Exception { List<FlowRule> rules=new ArrayList<>(); FlowRule flowRule=new FlowRule(); flowRule.setResource("read"); //针对那个资源设置规则 flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);//QPS或者并发数 flowRule.setCount(5); //QPS=5 //直接拒绝: flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); rules.add(flowRule); FlowRuleManager.loadRules(rules); } }
# Sentinel 控制台地址 将当前的应用接入到控制台 spring.cloud.sentinel.transport.dashboard=192.168.216.128:7777 # 取消Sentinel控制台懒加载 # 默认情况下 Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包 # 配置 sentinel.eager=true 时,取消Sentinel控制台懒加载功能 spring.cloud.sentinel.eager=true
@RestController public class SentinelController { @Autowired TestService testService; @GetMapping("/hello/{name}") public String sayHello(@PathVariable("name") String name){ return testService.doTest(name); } } @Service public class TestService { @SentinelResource(value = "doTest") //声明限流的资源 public String doTest(String name){ return "hello , "+name; } }
此时我们已经将 sentinel 接入到 sentinel控制台了,然后我们使用jmeter来进行测试
配置http请求 和 线程数
此时就可以看到 在sentinel控制台中对应接口的 监控情况
在前面起到了,只要依赖了 sentinel这个包,那么项目就会自动的去集成流量的一个过滤,比如/hello/{name},因为没有设置规则,所以都能通过。
接下来做一个动态规则。
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.8.0</version> </dependency>
public class FlowRuleInitFunc implements InitFunc{ private final String nacosAddress="192.168.216.128:8848"; private final String groupId="SENTINEL_GROUP"; private final String dataId="-flow-rules"; private final String appName="App-Test"; @Override public void init() throws Exception { registerFlowRule(); } private void registerFlowRule(){ // 从远程服务器上加载规则 ReadableDataSource<String,List<FlowRule>> flowRuleDs= new NacosDataSource<List<FlowRule>>(nacosAddress,groupId,appName+dataId, // 当数据发生变化后,会回调这个方法 source-> JSON.parseObject(source,new TypeReference<List<FlowRule>>(){})); // 这行代码将上面创建的数据源注册到中。是Sentinel中管理流控规则的组件,方法用于将提供的(属性)注册到。这样,每当Nacos中的配置发生变化时,会获取最新的规则,并通知更新内存中的流控规则。 FlowRuleManager.register2Property(flowRuleDs.getProperty()); } }
此时在nacos中修改对应的配置,就能够监听到,然后更新到内存中的流控规则中
- 通过访问测试,即可看到被限流的效果。
- 也可以在 ${用户}/logs/csp/sentinel-record.log.2020-09-22 文件中看到sentinel启动过程中动态数据源的加载过程。
基于配置文件的动态限流
spring.cloud.sentinel.transport.clientIp=192.168.216.128:7777 spring.cloud.sentinel.datasource.nacos.nacos.serverAddr=192.168.216.128:8848 spring.cloud.sentinel.datasource.nacos.nacos.dataId=com.gupaoedu.sentinel.demo.flow.rule spring.cloud.sentinel.datasource.nacos.nacos.groupId=SENTINEL_GROUP spring.cloud.sentinel.datasource.nacos.nacos.dataType=json spring.cloud.sentinel.datasource.nacos.nacos.ruleType=flow spring.cloud.sentinel.datasource.nacos.nacos.username=nacos spring.cloud.sentinel.datasource.nacos.nacos.password=nacos
在这里的配置会出现一个问题,就是修改 sentinel 控制台配置,和nacos上的配置不一致,这样的话最简单的就是修改sentinel的源码,当sentinel控制台变化的时候,动态去修改nacos上的配置即可。
集群限流
token-client代表我们的应用,当一个请求过来以后,经过token-client会进行一个动态的判断,token-server里面采用的是一个动态数据源,此时就能够实现集群限流的思想了,当来一个请求的时候,token-client先去token-server中去判断是否能够通过,如果通过才能执行,否则限流。
当token-server挂了之后,能够直接连接nacos配置中心的规则进行一个处理
token -server
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("App-Test")); tokenServer.start(); //Token-client会上报自己的project.name到token-server。Token-server会根据namespace来统计连接数 } // 主要作用是从 Nacos 配置中心获取流控规则,并将其应用到 Sentinel 中,以实现动态的流控规则管理。 public class FlowRuleInitFunc implements InitFunc{ private final String nacosAddress="192.168.216.128:8848"; private final String groupId="SENTINEL_GROUP"; private String dataId="-flow-rules"; @Override public void init() throws Exception { ClusterFlowRuleManager.setPropertySupplier(namespace->{ ReadableDataSource<String,List<FlowRule>> flowRuleDs= new NacosDataSource<List<FlowRule>>(nacosAddress,groupId,namespace+dataId, source-> JSON.parseObject(source,new TypeReference<List<FlowRule>>(){})); return flowRuleDs.getProperty(); }); } }
token-client
public class FlowRuleInitFunc implements InitFunc{ private final String nacosAddress="192.168.216.128:8848"; private final String groupId="SENTINEL_GROUP"; private final String dataId="-flow-rules"; private final String clusterServerHost="localhost"; private final int clusterServerPort=9999; private final int requestTimeOut=20000; private final String appName="App-Test"; @Override public void init() throws Exception { loadClusterConfig(); registerFlowRule(); } private void loadClusterConfig(){ ClusterClientAssignConfig assignConfig=new ClusterClientAssignConfig(); assignConfig.setServerHost(clusterServerHost); //放到配置中心 assignConfig.setServerPort(clusterServerPort); ClusterClientConfigManager.applyNewAssignConfig(assignConfig); ClusterClientConfig clientConfig=new ClusterClientConfig(); clientConfig.setRequestTimeout(requestTimeOut); //放到配置中心 ClusterClientConfigManager.applyNewConfig(clientConfig); } private void registerFlowRule(){ 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()); } }
以上就是一个集群流控的核心实现了。
总结:集群限流配置一个总的qps,多个节点可以配置权重,或者均摊的方式来起到限流的作用。
深入理解Sentinel系列-2.Sentinel原理及核心源码分析(下):https://developer.aliyun.com/article/1413969