限流组件Sentinel生产应用实践(一) : 动态规则源持久化(Zookeeper)改造接入

介绍

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模块的接入改造生产实践。

Dashboad介绍

从github仓库下载最新的代码到本地,启动dashoard模块应用入口类DashboardApplication, 该引用是基于springboot 的web应用, 并加入启动参数-Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard 参数具体意义可参考文档。访问http://localhost:8080即可 用户密码均为sentinel, 如下所示
限流组件Sentinel生产应用实践(一) : 动态规则源持久化(Zookeeper)改造接入_第1张图片

可以看到这里可以配置多种规则,比如流控规则
限流组件Sentinel生产应用实践(一) : 动态规则源持久化(Zookeeper)改造接入_第2张图片
但是这里的规则设置,都是存储在对应的客户端内存中的,这样有两个缺点: 1.规则会随着客户端的重启而丢失 2.若同一个app有多个实例(多个实例微服务很常见),就需要每个客户端实例都得手动设置规则,非常痛苦,因此不能直接用于生产环境,需要做相应的改造。目前的sentinel已经支持多种持久化组件了,比如zookeeper,nacos,apollo,redis,rdbms等,而且留有相应的扩展,只需对dashboard做些改造即可。

Dashboard改造

其实sentinel dashboard源码中已经有了一个针对流控规则改造的简单例子,就是com.alibaba.csp.sentinel.dashboard.controller.v2.FlowControllerV2这个controller
限流组件Sentinel生产应用实践(一) : 动态规则源持久化(Zookeeper)改造接入_第3张图片
com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider 动态规则源提获取接口以及com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher动态规则源提变更接口,实现这两个接口即可。

Zookeeper接入改造

这里先做个zookeeper的接入,其它数据源举一反三即可。

1. 实现DynamicRuleProvider和DynamicRulePublisher这两个接口
  • 先在dashboard模块中引入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>
  • 实现DynamicRuleProvider
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));
    }
}
  • 实现DynamicRulePublisher
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);
    }

}
  • 相应的bean初始化
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
2. 改造FlowControllerV2

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);
    }
}

3. 规则全局id改造

由于规则持久化,所以规则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();
    }
}
4. 前端改造

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改造后客户端接入

在dashboard改造完成后,客户端的接入也有些改变。就以普通的web servlet应用为例,原有的接入方式可以参考文档,除了
这些还得以下步骤

核心的原理就是需要客户端根据规则的改变来动态更新内存规则。

  • 引入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>
  • 简单的例子
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仓库中(流控规则和授权规则均已改造)。

你可能感兴趣的:(Sentinel,限流,java)