Springboot使用Sentinel限流,集成zookeeper完成规则的持久化
上一篇简单介绍了sentinel限流的基本配置和使用,这一篇我们来稍微深入一点,看看如何将zookeeper继承进来,用以保存添加的流控规则。上一篇中我们启动了dashboard.jar,然后在客户端中指定了dashboard的地址。之后启动项目,随便访问个接口,之后就能在dashboard的界面上看到相应的请求了,并且能在控制台上添加一些规则,保存后客户端就能生效了。基于内存的推...
上一篇简单介绍了sentinel限流的基本配置和使用,这一篇我们来稍微深入一点,看看如何将zookeeper继承进来,用以保存添加的流控规则。
上一篇中我们启动了dashboard.jar,然后在客户端中指定了dashboard的地址。之后启动项目,随便访问个接口,之后就能在dashboard的界面上看到相应的请求了,并且能在控制台上添加一些规则,保存后客户端就能生效了。
基于内存的推送
那么它的内部原理是什么呢?来简单了解一下。
从官方文档可以看到,客户端在引入了Sentinel后,并指定dashboard的地址,启动后,将会在客户端启动一个http服务,默认占用8719端口。由于我们是引入的SpringCloud的模块,就已经包含了下面的引入。
引入这个transport模块的原因就是为了接收dashboard推送过来的配置规则。可以看看官方文档的介绍,默认就是“原始模式”。所谓的原始模式,就是指客户端启动web服务,连上dashboard后,在dashboard配置的规则,由dashboard发起http请求来修改。修改后的规则,直接保存在客户端内存中,并即时生效。
这种方式原理简单,一般用于入门测试使用,生产环境不能用。基于内存存储,在客户端重启后,所有规则都会丢失,需要重新配置。而且不适用于客户端多个实例,因为彼此之间不共享规则,倘若启动多个实例,需要多次重复配置。很明显,这不是我们想要的那种结果。
官方提供了三种模式,上面的“原始模式”、“pull模式”、“push模式”。pull模式就是搞个文件存着,隔一会去请求一下,看看有没有变化,如果变了,就更新到内存,很明显这种模式存在延迟,也不建议上生产。那就来看看“push模式”吧。
基于zookeeper的推送
从上面可以看到,要想能持久化规则的存储,并且在多个实例间共享,就需要一个第三方的存储。让dashboard对规则的修改能及时存储到第三方并及时通知客户端完成修改。官方给了三种示例推荐,Apollo、Nacos、Zookeeper,它们的使用类似,我们以zookeeper为例来看看怎么使用。
客户端pom文件添加zookeeper的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-zookeeper</artifactId>
</dependency>
然后客户端修改获取规则的地方为从zookeeper获取规则。
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.zookeeper.ZookeeperDataSource;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
/**
* @author wuweifeng wrote on 2019/7/1.
*/
@Component
public class ZookeeperSentinelConfig {
@Value("${spring.application.name}")
private String appName;
@PostConstruct
public void loadRules() {
final String remoteAddress = "127.0.0.1:2181";
final String path = "/sentinel_rule_config/" + appName;
ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new ZookeeperDataSource<>(remoteAddress, path,
source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
}));
FlowRuleManager.register2Property(flowRuleDataSource.getProperty());
}
}
这一步很简单,大家自行去下载个zookeeper的安装文件,启动即可。在方法里指定zookeeper的地址和要监听变化的path,然后注册一下就好了。客户端到这里就完毕了。
重新启动客户端后,就会变成从zookeeper的固定path里获取rule规则。之后对该path做的变化,都会即时更新到客户端,并应用新的规则。
这里我们测试一下:
@RestController
public class TestController {
@GetMapping(value = "/hello")
public String hello() {
return "Hello Sentinel";
}
@GetMapping(value = "/test")
@SentinelResource(value = "TestResource", blockHandler = "handleException")
public String test() {
return "Hello TestResource";
}
// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String handleException(BlockException ex) {
return "handleException";
}
}
上面是一个简单的Controller,里面定义了一个resource。之后,我们通过对zookeeper的path推送该resource的规则,来测试是否生效。
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
/**
* @author wuweifeng wrote on 2019/7/1.
*/
public class ZookeeperConfigSender {
private static final int RETRY_TIMES = 3;
private static final int SLEEP_TIME = 1000;
public static void main(String[] args) throws Exception {
final String remoteAddress = "localhost:2181";
final String rule = "[\n"
+ " {\n"
+ " \"resource\": \"TestResource\",\n"
+ " \"controlBehavior\": 0,\n"
+ " \"count\": 1.0,\n"
+ " \"grade\": 1,\n"
+ " \"limitApp\": \"default\",\n"
+ " \"strategy\": 0\n"
+ " }\n"
+ "]";
CuratorFramework zkClient = CuratorFrameworkFactory.newClient(remoteAddress, new ExponentialBackoffRetry
(SLEEP_TIME, RETRY_TIMES));
zkClient.start();
String appName = "your-app-name";
String path = "/sentinel_rule_config/" + appName;
Stat stat = zkClient.checkExists().forPath(path);
if (stat == null) {
zkClient.create().creatingParentContainersIfNeeded().withMode(CreateMode.PERSISTENT).forPath(path, null);
}
zkClient.setData().forPath(path, rule.getBytes());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
zkClient.close();
}
}
这就是测试代码,运行后,就会往zookeeper写入一个rule,设置名为TestResource的qps为1,并应用到客户端。无论启动多少个客户端实例,都会生效这个rule。
看到这里,其实已经完成了基于push的动态规则功能了,可以通过zkui这种zookeeper界面工具,或者通过代码来查询、修改zookeeper里的rule配置(json)来完成对客户端规则的控制。
那么可能有人会问了,dashboard呢?用那个界面操作不是更方便吗?
事实上,对客户端的限流,与dashboard没一点关系,只用zookeeper就能完成了。那么这时,你再启动dashboard,当然也是能用的,因为客户端的web服务还是启动着的,也能接收到来自于dashboard的推送。只是来自于dashboard的在客户端重启后会失效,在zookeeper里的会仍然存在。
那么我们应该改造一下dashboard,让在界面上的操作也推送到zookeeper里去,这样就方便多了。
在GitHub上下载Sentinel的源码,里面有dashboard的工程,我们来修改一下它的代码就好了。
先修改一下pom文件,把scope注释掉。
找到rule包,添加个zookeeper文件夹,里面有4个类。
可以直接从工程的test测试代码里,直接把zookeeper包抄过去就行,并把rule下原来的FlowRuleApiProvider和FlowRuleApiPublisher给注释掉。
test源码里已经提供了基于三种中间件的配置代码了,抄过去就行。
抄过去后,修改一下RULE_ROOT_PATH,保持和客户端配置的是一致的。
之后找到Controller包下的v2包,如果你设置的FlowRuleZookeeperProvider和publisher两个bean有名字,可以在autowired时指定为你设置的名字,或者用@Resource。
最后修改一下sidebar.html,将原来的flowV1改为如图。
这样就ok了。
重新启动dashboard项目,重启客户端。这样dashboard就已经和zookeeper关联起来了,dashboard的操作就由原来的操作客户端的api,变成了操作zookeeper。你所有在dashboard界面上做的配置,都会存储到zookeeper中,并实时推送到客户端。客户端重启后,dashboard不受影响。这样就完成了多实例共享流控规则。
更多推荐
所有评论(0)