Sentinel是阿里巴巴去年开源的一款轻量级限流组件。引用它官网的一段简要介绍:
As distributed systems become increasingly popular, the reliability between services is becoming more important than ever before. Sentinel takes “flow” as breakthrough point, and works on multiple fields including flow control, circuit breaking and system adaptive protection, to guarantee reliability of microservices.
Sentinel has the following features:
- Rich applicable scenarios: Sentinel has been wildly used in Alibaba, and has covered almost all the core-scenarios in Double-11 (11.11) Shopping Festivals in the past 10 years, such as “Second Kill” which needs to limit burst flow traffic to meet the system capacity, message peak clipping and valley fills, circuit breaking for unreliable downstream services, cluster flow control, etc.
- Real-time monitoring: Sentinel also provides real-time monitoring ability. You can see the runtime information of a single machine in real-time, and the aggregated runtime info of a cluster with less than 500 nodes.
Widespread open-source ecosystem: Sentinel provides out-of-box integrations with commonly-used frameworks and libraries such as Spring Cloud, Dubbo and gRPC. You can easily use Sentinel by simply add the adapter dependency to your services.- Various SPI extensions: Sentinel provides easy-to-use SPI extension interfaces that allow you to quickly customize your logic, for example, custom rule management, adapting data sources, and so on.
如何使用Sentinel这里就不介绍了, 可以参考官方文档快速入门。这里主要介绍的是动态规则源zookeeper持久化的Dashboard模块的接入改造生产实践。
从github仓库下载最新的代码到本地,启动dashoard模块应用入口类DashboardApplication, 该引用是基于springboot 的web应用, 并加入启动参数-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard
参数具体意义可参考文档。访问http://localhost:8080即可 用户密码均为sentinel, 如下所示
可以看到这里可以配置多种规则,比如流控规则
但是这里的规则设置,都是存储在对应的客户端内存中的,这样有两个缺点: 1.规则会随着客户端的重启而丢失 2.若同一个app有多个实例(多个实例微服务很常见),就需要每个客户端实例都得手动设置规则,非常痛苦,因此不能直接用于生产环境,需要做相应的改造。目前的sentinel已经支持多种持久化组件了,比如zookeeper,nacos,apollo,redis,rdbms等,而且留有相应的扩展,只需对dashboard做些改造即可。
其实sentinel dashboard源码中已经有了一个针对流控规则改造的简单例子,就是com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2
这个controller
com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider
动态规则源提获取接口以及com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher
动态规则源提变更接口,实现这两个接口即可。
这里先做个zookeeper的接入,其它数据源举一反三即可。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-zookeeperartifactId>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
exclusion>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
<exclusion>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
exclusion>
exclusions>
dependency>
package com.alibaba.csp.sentinel.dashboard.rule.zookeeper.flow;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.zookeeper.ZookeeperConfigUtils;
import com.alibaba.csp.sentinel.datasource.Converter;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Autowired;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* @author rodbate
*/
public class FlowRuleZookeeperProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
@Autowired
private CuratorFramework zkClient;
@Autowired
private Converter<String, List<FlowRuleEntity>> converter;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
byte[] data = zkClient.getData().forPath(ZookeeperConfigUtils.getFlowRuleZkPath(appName));
if (data == null || data.length == 0) {
return new ArrayList<>();
}
return converter.convert(new String(data, StandardCharsets.UTF_8));
}
}
package com.alibaba.csp.sentinel.dashboard.rule.zookeeper.flow;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.dashboard.rule.zookeeper.ZookeeperConfigUtils;
import com.alibaba.csp.sentinel.datasource.Converter;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.data.Stat;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* @author rodbate
*/
public class FlowRuleZookeeperPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
@Autowired
private CuratorFramework zkClient;
@Autowired
private Converter<List<FlowRuleEntity>, String> converter;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
String zkPath = ZookeeperConfigUtils.getFlowRuleZkPath(app);
Stat stat = zkClient.checkExists().forPath(zkPath);
if (stat == null) {
zkClient.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).forPath(zkPath, null);
}
byte[] data = null;
if (!CollectionUtils.isEmpty(rules)) {
data = converter.convert(rules).getBytes(StandardCharsets.UTF_8);
}
zkClient.setData().forPath(zkPath, data);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.zookeeper;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.zookeeper.flow.FlowRuleZookeeperProvider;
import com.alibaba.csp.sentinel.dashboard.rule.zookeeper.flow.FlowRuleZookeeperPublisher;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @author rodbate
*/
@Slf4j
@ConditionalOnProperty(value = "dynamic.rules.source.type", havingValue = "zookeeper")
@Configuration
@EnableConfigurationProperties(ZookeeperConfigProperties.class)
public class ZookeeperConfig {
private static final int DEFAULT_ZK_SESSION_TIMEOUT = 30000;
private static final int DEFAULT_ZK_CONNECTION_TIMEOUT = 10000;
private static final int RETRY_TIMES = 3;
private static final int SLEEP_TIME = 1000;
public ZookeeperConfig() {
log.info("============== Use Zookeeper Dynamic Rules Source ===================");
}
/**
* flow rule entity encoder
*
* @return Converter, String>
*/
@Bean
public Converter<List<FlowRuleEntity>, String> flowRuleEntityEncoder() {
return JSON::toJSONString;
}
/**
* flow rule entity decoder
*
* @return Converter>
*/
@Bean
public Converter<String, List<FlowRuleEntity>> flowRuleEntityDecoder() {
return s -> JSON.parseArray(s, FlowRuleEntity.class);
}
/**
* authority rule entity encoder
*
* @return Converter, String>
*/
@Bean
public Converter<List<AuthorityRuleEntity>, String> authorityRuleEntityEncoder() {
return JSON::toJSONString;
}
/**
* authority rule entity decoder
*
* @return Converter>
*/
@Bean
public Converter<String, List<AuthorityRuleEntity>> authorityRuleEntityDecoder() {
return s -> JSON.parseArray(s, AuthorityRuleEntity.class);
}
/**
* zk client
*
* @param properties zk properties
* @return zk client
*/
@Bean(destroyMethod = "close")
public CuratorFramework zkClient(ZookeeperConfigProperties properties) {
String connectString = properties.getConnectString();
int sessionTimeout = DEFAULT_ZK_SESSION_TIMEOUT;
int connectionTimeout = DEFAULT_ZK_CONNECTION_TIMEOUT;
if (properties.getSessionTimeout() > 0) {
sessionTimeout = properties.getSessionTimeout();
}
if (properties.getConnectionTimeout() > 0) {
connectionTimeout = properties.getConnectionTimeout();
}
CuratorFramework zkClient = CuratorFrameworkFactory.newClient(connectString,
sessionTimeout, connectionTimeout,
new ExponentialBackoffRetry(SLEEP_TIME, RETRY_TIMES));
zkClient.start();
log.info("Initialize zk client CuratorFramework, connectString={}, sessionTimeout={}, connectionTimeout={}, retry=[sleepTime={}, retryTime={}]",
connectString, sessionTimeout, connectionTimeout, SLEEP_TIME, RETRY_TIMES);
return zkClient;
}
@Bean
public FlowRuleZookeeperProvider flowRuleZookeeperProvider() {
return new FlowRuleZookeeperProvider();
}
@Bean
public FlowRuleZookeeperPublisher flowRuleZookeeperPublisher() {
return new FlowRuleZookeeperPublisher();
}
}
package com.alibaba.csp.sentinel.dashboard.rule.zookeeper;
/**
* @author rodbate
*/
public final class ZookeeperConfigUtils {
public static final String GROUP_ID = "SENTINEL_GROUP";
private static final String ZK_PATH_SEPARATOR = "/";
private static final String FLOW_RULE_DATA_ID_POSTFIX = "-flow-rules";
private static final String AUTHORITY_RULE_DATA_ID_POSTFIX = "-authority-rules";
private ZookeeperConfigUtils() {
}
/**
* /groupId/dataId
*
* @param app app name
* @return zk path
*/
public static String getAuthorityRuleZkPath(String app) {
return ZK_PATH_SEPARATOR + GROUP_ID + ZK_PATH_SEPARATOR + app + AUTHORITY_RULE_DATA_ID_POSTFIX;
}
/**
* /groupId/dataId
*
* @param app app name
* @return zk path
*/
public static String getFlowRuleZkPath(String app) {
return ZK_PATH_SEPARATOR + GROUP_ID + ZK_PATH_SEPARATOR + app + FLOW_RULE_DATA_ID_POSTFIX;
}
}
package com.alibaba.csp.sentinel.dashboard.rule.zookeeper;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author rodbate
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "zookeeper.config")
public class ZookeeperConfigProperties {
private String connectString;
private int sessionTimeout;
private int connectionTimeout;
}
#snowflake id
id.dataCenterId = 0
id.workerId = 0
dynamic.rules.source.type = zookeeper
zookeeper.config.connectString = localhost:2181
zookeeper.config.sessionTimeout = 30000
zookeeper.config.connectionTimeout = 10000
package com.alibaba.csp.sentinel.dashboard.controller.v2;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.AuthUser;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemoryRuleRepositoryAdapter;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
/**
* Flow rule controller (v2).
*
* @author Eric Zhao
* @since 1.4.0
*/
@RestController
@RequestMapping(value = "/v2/flow")
public class FlowControllerV2 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV2.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
@Autowired
private AuthService<HttpServletRequest> authService;
@GetMapping("/rules")
public Result<List<FlowRuleEntity>> apiQueryMachineRules(HttpServletRequest request, @RequestParam String app) {
AuthUser authUser = authService.getAuthUser(request);
authUser.authTarget(app, PrivilegeType.READ_RULE);
if (StringUtil.isEmpty(app)) {
return Result.ofFail(-1, "app can't be null or empty");
}
try {
List<FlowRuleEntity> rules = ruleProvider.getRules(app);
if (rules != null && !rules.isEmpty()) {
for (FlowRuleEntity entity : rules) {
entity.setApp(app);
if (entity.getClusterConfig() != null && entity.getClusterConfig().getFlowId() != null) {
entity.setId(entity.getClusterConfig().getFlowId());
}
}
}
repository.saveAll(rules);
return Result.ofSuccess(rules);
} catch (Throwable throwable) {
logger.error("Error when querying flow rules", throwable);
return Result.ofThrowable(-1, throwable);
}
}
private <R> Result<R> checkEntityInternal(FlowRuleEntity entity) {
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
if (StringUtil.isBlank(entity.getApp())) {
return Result.ofFail(-1, "app can't be null or empty");
}
if (StringUtil.isBlank(entity.getLimitApp())) {
return Result.ofFail(-1, "limitApp can't be null or empty");
}
if (StringUtil.isBlank(entity.getResource())) {
return Result.ofFail(-1, "resource can't be null or empty");
}
if (entity.getGrade() == null) {
return Result.ofFail(-1, "grade can't be null");
}
if (entity.getGrade() != 0 && entity.getGrade() != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + entity.getGrade() + " got");
}
if (entity.getCount() == null || entity.getCount() < 0) {
return Result.ofFail(-1, "count should be at lease zero");
}
if (entity.getStrategy() == null) {
return Result.ofFail(-1, "strategy can't be null");
}
if (entity.getStrategy() != 0 && StringUtil.isBlank(entity.getRefResource())) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
if (entity.getControlBehavior() == null) {
return Result.ofFail(-1, "controlBehavior can't be null");
}
int controlBehavior = entity.getControlBehavior();
if (controlBehavior == 1 && entity.getWarmUpPeriodSec() == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && entity.getMaxQueueingTimeMs() == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
if (entity.isClusterMode() && entity.getClusterConfig() == null) {
return Result.ofFail(-1, "cluster config should be valid");
}
return null;
}
@PostMapping("/rule")
public Result<FlowRuleEntity> apiAddFlowRule(HttpServletRequest request, @RequestBody FlowRuleEntity entity) {
AuthUser authUser = authService.getAuthUser(request);
authUser.authTarget(entity.getApp(), PrivilegeType.WRITE_RULE);
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(null);
Date date = new Date();
entity.setGmtCreate(date);
entity.setGmtModified(date);
entity.setLimitApp(entity.getLimitApp().trim());
entity.setResource(entity.getResource().trim());
try {
entity = repository.save(entity);
publishRules(entity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@PutMapping("/rule/{id}")
public Result<FlowRuleEntity> apiUpdateFlowRule(HttpServletRequest request,
@PathVariable("id") Long id,
@RequestBody FlowRuleEntity entity) {
AuthUser authUser = authService.getAuthUser(request);
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofFail(-1, "id " + id + " does not exist");
}
if (entity == null) {
return Result.ofFail(-1, "invalid body");
}
authUser.authTarget(oldEntity.getApp(), PrivilegeType.WRITE_RULE);
entity.setApp(oldEntity.getApp());
entity.setIp(oldEntity.getIp());
entity.setPort(oldEntity.getPort());
Result<FlowRuleEntity> checkResult = checkEntityInternal(entity);
if (checkResult != null) {
return checkResult;
}
entity.setId(id);
Date date = new Date();
entity.setGmtCreate(oldEntity.getGmtCreate());
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}
publishRules(oldEntity.getApp());
} catch (Throwable throwable) {
logger.error("Failed to update flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/rule/{id}")
public Result<Long> apiDeleteRule(HttpServletRequest request, @PathVariable("id") Long id) {
AuthUser authUser = authService.getAuthUser(request);
if (id == null || id <= 0) {
return Result.ofFail(-1, "Invalid id");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE);
try {
repository.delete(id);
publishRules(oldEntity.getApp());
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
return Result.ofSuccess(id);
}
private void publishRules(/*@NonNull*/ String app) throws Exception {
List<FlowRuleEntity> rules = repository.findAllByApp(app);
rulePublisher.publish(app, rules);
}
}
由于规则持久化,所以规则id必须是全局唯一的,现有的id生成规则是不满足的(内存自增长,dashboard重启之后id就会重复)
@Component
public class InMemFlowRuleStore extends InMemoryRuleRepositoryAdapter<FlowRuleEntity> {
private static AtomicLong ids = new AtomicLong(0);
@Override
protected long nextId() {
return ids.incrementAndGet();
}
@Override
protected FlowRuleEntity preProcess(FlowRuleEntity entity) {
if (entity != null && entity.isClusterMode()) {
ClusterFlowConfig config = entity.getClusterConfig();
if (config == null) {
config = new ClusterFlowConfig();
entity.setClusterConfig(config);
}
// Set cluster rule id.
config.setFlowId(entity.getId());
}
return entity;
}
}
因此id生成使用snowflake id生成规则,保证全局唯一
package com.alibaba.csp.sentinel.dashboard.uniqueid;
import lombok.extern.slf4j.Slf4j;
/**
* @author rodbate
*/
@Slf4j
public class SnowflakeIdGenerator implements IdGenerator<Long> {
private final long startEpoch = 1546300800000L;
private final long workerIdBits = 5L;
private final long datacenterIdBits = 5L;
private final long maxWorkerId = ~(-1L << workerIdBits);
private final long maxDatacenterId = ~(-1L << datacenterIdBits);
private final long sequenceBits = 12L;
private final long workerIdShift = sequenceBits;
private final long datacenterIdShift = sequenceBits + workerIdBits;
private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final long sequenceMask = ~(-1L << sequenceBits);
private final long datacenterId;
private final long workerId;
private long lastTimestamp = -1L;
private long sequence = 0L;
public SnowflakeIdGenerator(long datacenterId, long workerId) {
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
this.datacenterId = datacenterId;
this.workerId = workerId;
}
@Override
public synchronized Long nextId() {
long timestamp = now();
if (timestamp < lastTimestamp) {
log.error("clock is moving backwards. Rejecting requests until {}.", lastTimestamp);
throw new IllegalStateException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
timestamp = tilNextMillis();
}
} else {
sequence = 0;
}
lastTimestamp = timestamp;
return ((timestamp - startEpoch) << timestampLeftShift) |
(datacenterId << datacenterIdShift) |
(workerId << workerIdShift) |
sequence;
}
private long tilNextMillis() {
long timestamp = now();
while (timestamp <= lastTimestamp) {
timestamp = now();
}
return timestamp;
}
private long now() {
return System.currentTimeMillis();
}
}
dashboard是基于angularjs的。由于流控规则前端html js 等资源文件已经有了,所以只需改下页面路由sidebar.html即可。
修改前:
<li ui-sref-active="active">
<a ui-sref="dashboard.flowV1({app: entry.app})">
<i class="glyphicon glyphicon-filter">i> 流控规则a>
li>
修改后:
<li ui-sref-active="active">
<a ui-sref="dashboard.flow({app: entry.app})">
<i class="glyphicon glyphicon-filter">i> 流控规则a>
li>
路由设置在app.js中
.state('dashboard.flow', {
templateUrl: 'app/views/flow_v2.html',
url: '/v2/flow/:app',
controller: 'FlowControllerV2',
resolve: {
loadMyFiles: ['$ocLazyLoad', function ($ocLazyLoad) {
return $ocLazyLoad.load({
name: 'sentinelDashboardApp',
files: [
'app/scripts/controllers/flow_v2.js',
]
});
}]
}
})
如需对其他规则进行改造,参考流控规则既可以。
在dashboard改造完成后,客户端的接入也有些改变。就以普通的web servlet应用为例,原有的接入方式可以参考文档,除了
这些还得以下步骤
核心的原理就是需要客户端根据规则的改变来动态更新内存规则。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-zookeeperartifactId>
<exclusions>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
exclusion>
<exclusion>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
exclusion>
<exclusion>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
exclusion>
exclusions>
dependency>
import com.alibaba.csp.sentinel.config.SentinelConfig;
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 org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;
import java.util.List;
@Configuration
public class ZkDataSourceConfig {
@Value("${zkServerAddress}")
private String zkServerAddress;
@PostConstruct
public void init(){
ZookeeperDataSource<List<FlowRule>> zookeeperDataSource = new ZookeeperDataSource<>(
zkServerAddress,
ZookeeperConfigUtils.getFlowRuleZkPath(SentinelConfig.getAppName()),
s -> JSON.parseArray(s, FlowRule.class)
);
FlowRuleManager.register2Property(zookeeperDataSource.getProperty());
}
}
至此,完整的动态规则源持久化(Zookeeper)改造接入以全部完成了,改造源码在本人github仓库中(流控规则和授权规则均已改造)。