<dependency> <artifactId>sentinel-api</artifactId> <groupId>com.lucas.sentinel</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> </dependency>
public interface SentinelService { String sayHello(String name); }
@Service public class SentinelServiceImpl implements SentinelService{ @Override public String sayHello(String name) { System.out.println("begin execute sayHello:"+name); return "Hello World:"+name+"->timer:"+LocalDateTime.now(); } }
@Configuration @DubboComponentScan("com.lucas.sentinel") public class ProviderConfig { @Bean public ApplicationConfig applicationConfig() { ApplicationConfig config = new ApplicationConfig(); config.setName("sentinel-web"); config.setOwner("Lucas"); return config; } @Bean public RegistryConfig registryConfig() { RegistryConfig registryConfig = new RegistryConfig(); registryConfig.setAddress("zookeeper://192.168.0.102:2181"); return registryConfig; } @Bean public ProtocolConfig protocolConfig() { ProtocolConfig protocolConfig = new ProtocolConfig(); protocolConfig.setName("dubbo"); protocolConfig.setPort(20880); return protocolConfig; } }
public class BootstrapApp { public static void main(String[] args) throws IOException { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ProviderConfig.class); ((AnnotationConfigApplicationContext) applicationContext).start(); System.in.read(); } }
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.1</version> </dependency> <dependency> <groupId>com.lucas.sentinel</groupId> <artifactId>sentinel-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <!-- zookeeper 须要使用3.5+版本 -> curator 4.0+ zookeeper 须要使用3.4.*版本 -> curator 2.0+ 采坑了--> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> </dependency>
@RestController public class SentinelDubboController { @Reference SentinelService sentinelService; @GetMapping("/say") public String sayHello(){ String result=sentinelService.sayHello("hello word"); return result; } }
dubbo.registry.address=zookeeper://192.168.0.102:2181 dubbo.application.name=sentinel-web dubbo.scan.base-packages=com.lucas.sentinel.demo.study
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-dubbo-adapter</artifactId> <version>1.6.2</version> </dependency>
Service Provider 用于向外界提供服务,处理各个消费者的调用请求。为了保护 Provider 不被激增的流量拖垮影响稳定性,能够给 Provider 配置 QPS 模式的限流,这样当每秒的请求量超过设定的阈值时会自动拒绝多的请求。限流粒度能够是服务接口和服务方法两种粒度。若但愿整个服务接口的 QPS 不超过必定数值,则能够为对应服务接口资源(resourceName 为接口全限定名)配置 QPS 阈值;若但愿服务的某个方法的 QPS 不超过必定数值,则能够为对应服务方法资源(resourceName 为接口全限定名:方法签名)配置 QPS 阈值html
public class BootstrapApp { public static void main(String[] args) throws IOException { initFlowRule();//初始化限流规则 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(ProviderConfig.class); ((AnnotationConfigApplicationContext) applicationContext).start(); System.in.read(); } private static void initFlowRule() { FlowRule flowRule = new FlowRule(); //针对具体的方法限流 flowRule.setResource("com.lucas.sentinel.api.SentinelService:sayHello(java.lang.String) "); flowRule.setCount(10);//限流阈值 qps=10 flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);//限流阈值类型(QPS 或并发线程数) flowRule.setLimitApp("default");//流控针对的调用来源,若为 default 则不区分调用来源 // 流量控制手段(直接拒绝、Warm Up、匀速排队) flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT); FlowRuleManager.loadRules(Collections.singletonList(flowRule)); } }
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=localhost:8080
指定控制台地址和端口java
这个是sentinel提供的一个web可视化页面,咱们下载这个sentinel-dashboard.jar
,下载地址https://github.com/alibaba/Sentinel/releasesgit
下载完成之后,直接用java -jar 启动,默认是8080端口github
java -jar sentinel-dashboard-1.6.3.jar
启动以后的浏览器进入http://localhost:8080web
默认的用户密码为sentinel/sentinel算法
进入以后sentinel首页以下图spring
由于尚未请求相应,没有数据shell
100个qpsapache
由于咱们配置的限流阈值 qps=10json
不少场景下,根据调用方来限流也是很是重要的。好比有两个服务 A 和 B 都向 Service Provider 发起调用请求,咱们但愿只对来自服务 B 的请求进行限流,则能够设置限流规则的 limitApp 为服务 B 的名称。Sentinel Dubbo Adapter 会自动解析 Dubbo 消费者(调用方)的 application name 做为调用方名称(origin),在进行资源保护的时候都会带上调用方名称。若限流规则未配置调用方(default),则该限流规则对全部调用方生效。若限流规则配置了调用方则限流规则将仅对指定调用方生效。
注:Dubbo 默认通讯不携带对端 application name 信息,所以须要开发者在调用端手动将 applicationname 置入 attachment 中,provider 端进行相应的解析。Sentinel Dubbo Adapter 实现了一个 Filter 用于自动从 consumer 端向 provider 端透传 application name。若调用端未引入 Sentinel Dubbo Adapter,又但愿根据调用端限流,能够在调用端手动将 application name 置入 attachment 中,key 为dubboApplication
演示流程
修改provider中限流规则:flowRule.setLimitApp(“springboot-study”);
在consumer工程中,作以下处理。其中一个经过attachment传递了一个消费者的 application.name,另外一个没有传,经过jemeter工具进行测试
@GetMapping("/say") public String sayHello(){ RpcContext.getContext().setAttachment("dubboApplication","springbootstudy"); String result=sentinelService.sayHello("Lucas"); return result; } @GetMapping("/say2") public String say2Hello(){ String result=sentinelService.sayHello("Lucas"); return result; }
/say
的压测,限流生效
/say2
的压测,限流未生效
当 QPS 超过某个阈值的时候,则采起措施进行流量控制。流量控制的手段包括如下几种:
直接拒绝
、Warm Up
、匀速排队
。
直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被当即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的状况下,好比经过压测肯定了系统的准确水位时
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式,当系统长期处于低并发的状况下,流量忽然增长到qps的最高峰值,可能会形成系统的瞬间流量过大把系统压垮。因此warmup,至关于处理请求的数量是缓慢增长,通过一段时间之后,到达系统处理请求个数的最大值
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求经过的间隔时间,也便是让请求以均匀的速度经过,对应的是漏桶算法它的原理是,以固定的间隔时间让请求经过。当请求过来的时候,若是当前请求距离上个经过的请求经过的时间间隔不小于预设值,则让当前请求经过;不然,计算当前请求的预期经过时间,若是该请求的预期经过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来经过;反之,则立刻抛出阻塞异常。
能够设置一个最长排队等待时间: flowRule.setMaxQueueingTimeMs(5 * 1000); // 最长排队等待时间:5s这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,咱们但愿系统可以在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
在前面的全部案例中,咱们只是基于Sentinel的基本使用和单机限流的使用,假若有这样一个场景,咱们如今把provider部署了10个集群,但愿调用这个服务的api的总的qps是100,意味着每一台机器的qps是10,理想状况下总的qps就是100.可是实际上因为负载均衡策略的流量分发并非很是均匀的,就会致使总的qps不足100时,就被限了。在这个场景中,仅仅依靠单机来实现整体流量的控制是有问题的。因此最好是能实现集群分布式限流。
要想使用集群流控功能,咱们须要在应用端配置动态规则源,并经过 Sentinel 控制台实时进行推送。以下图所示:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-cluster-server-default</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.25</version> </dependency>
public class ClusterServer { public static void main(String[] args) throws Exception { ClusterTokenServer tokenServer = new SentinelDefaultTokenServer(); ClusterServerConfigManager.loadGlobalTransportConfig(new ServerTransportConfig(). setIdleSeconds(600).setPort(9999)); ClusterServerConfigManager.loadServerNamespaceSet(Collections.singleton("App-Lucas")); tokenServer.start(); } }
public class DataSourceInitFunc implements InitFunc { //nacos 远程服务host private final String remoteAddress = "192.168.0.102"; //nacos groupId private final String groupId = "SENTINEL_GROUP"; //namespace不一样,限流规则也不一样 private static final String FLOW_POSTFIX = "-flow-rules"; @Override public void init() throws Exception { ClusterFlowRuleManager.setPropertySupplier(namespace -> { ReadableDataSource<String, List<FlowRule>> rds = new NacosDataSource<>(remoteAddress, groupId, namespace + FLOW_POSTFIX, source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() { })); return rds.getProperty(); }); } }
resource目录添加扩展点
/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc = 自定义扩展点
添加log4j.properties文件
启动Sentinel dashboard
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.6.3.jar
启动nacos服务: nohup sh startup.sh -m standalone &
增长限流配置
[ { "resource":"com.lucas.sentinel.api.SentinelService:sayHello(java.lang.String)",//限流资源 "grade":1,//限流模式:qps "count":10,//限流总阈值 "clusterMode":true, "clusterConfig":{ "flowId":111111,//全局惟一ID "thresholdType":1,//阈值模式,全局阈值 "fallbackToLocalWhenFail":true //clinet链接失败或者通讯失败时,是否退化到本地限流模式 } } ]
配置以下jvm启动参数,链接到sentinel dashboard
-Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <artifactId>sentinel-api</artifactId> <groupId>com.lucas.sentinel</groupId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.25</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-framework</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.0</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-dubbo-adapter</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-dubbo-adapter</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-cluster-client-default</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-transport-simple-http</artifactId> <version>1.6.3</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> <version>1.6.3</version> </dependency>
扩展点须要在resources/META-INF/services/增长扩展的配置
com.alibaba.csp.sentinel.init.InitFunc = 自定义扩展点
public class DataSourceInitFunc implements InitFunc { private static final String CLUSTER_SERVER_HOST = "localhost";//token-server的ip private static final int CLUSTER_SERVER_PORT = 8999;//token-server 端口 private static final int REQUEST_TIME_OUT = 200000; //请求超时时间 private static final String APP_NAME = "App-Lucas"; private static final String REMOTE_ADDRESS = "192.168.0.102"; //nacos服务的ip private static final String GROUP_ID = "SENTINEL_GROUP";//group id private static final String FLOW_POSTFIX = "-flow-rules";//限流规则后缀 @Override public void init() throws Exception { loadClusterClientConfig(); registerClusterFlowRuleProperty(); } //经过硬编码的方式,配置链接到token-server服务的地址,{这种在实际使用过程当中不建议,后续能够基于动态配置源改造} public 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() { // 使用 Nacos 数据源做为配置中心,须要在 REMOTE_ADDRESS 上启动一个 Nacos 的服务 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()); } }
这里的project-name要包含在token-server中配置的namespace中,token server 会根据客户端对应的 namespace(默认为 project.name 定义的应用名)下的链接数来
计算总的阈值
-Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true
志文件中获取到了token-server的信息,说明链接成功了
所谓集群限流,就是多个服务节点使用同一个限流规则。从而对多个节点的总流量进行限制,添加一个sentinel-server。同时运行两个程序
-Dserver.port=8082 -Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true -Ddubbo.protocol.port=20881
-Dserver.port=8081 -Dproject.name=App-Lucas -Dcsp.sentinel.dashboard.server=192.168.0.102:8080 -Dcsp.sentinel.log.use.pid=true
使用jemeter建立1000个线程,进行压测,而后关注sentinel dashboard的变化。
咱们改一下nacos中心的配置,qps改成20,在压测一次
图上出现了21,多是统计的偏差。