我在使用Spring Cloud 整合Sentines做限流时,想把规则数据存到Redis中,Sentines官方文档是支持持久化到Redis的,但是我按照官方文档继承AbstractDataSource接口后,不知道该如何调用这个类:
我重写的类如下:
package cn.com.xxx.sentinel;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig;
import com.alibaba.csp.sentinel.log.RecordLog;
import cn.com.xxxx.commons.utils.StringUtils;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
import io.lettuce.core.api.sync.RedisCommands;
import io.lettuce.core.pubsub.RedisPubSubAdapter;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands;
/**
* @project: xxxx
* @description: Redis 持久化 Sentinel 数据读取和监听。
* @version 1.0.0
* @errorcode
* 错误码: 错误描述
* @author
* <li>2020-07-16 guopengfei@xxxx.com.cn Create 1.0
* @copyright ©2019-2020 xxxx,版权所有。
*/
public class RedisDataSource<T> extends AbstractDataSource<String, T>
{
/**
* redis 客户端
*/
private final RedisClient redisClient;
/**
* redis中存储的规则的KEY
*/
private final String ruleKey;
// private String ruleKey = "sentinel.rules.flow.ruleKey";
// private String channel = "sentinel.rules.flow.channel";
/**
* 构造方法中添加监听器
*
* @param connectionConfig
* Redis config,不允许为空
* @param ruleKey
* Redis中存储的规则的KEY,不允许为空
* @param channel
* Redis发布者channel,不允许为空
* @param parser
* 自定义数据解析器,不能为空
*/
public RedisDataSource(RedisConnectionConfig connectionConfig, String ruleKey, String channel,
Converter<String, T> parser)
{
super(parser);
System.out.println("开始运行构造方法>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
this.redisClient = getRedisClient(connectionConfig);
this.ruleKey = ruleKey;
loadInitialConfig();
subscribeFromChannel(channel);
}
/**
* 创建redis 客户端
*
* @return a new {@link RedisClient}
*/
private RedisClient getRedisClient(RedisConnectionConfig connectionConfig)
{
if (connectionConfig.getRedisSentinels().size() == 0) {
RecordLog.info("[RedisDataSource] Creating stand-alone mode Redis client");
return getRedisStandaloneClient(connectionConfig);
}
else {
RecordLog.info("[RedisDataSource] Creating Redis Sentinel mode Redis client");
return getRedisSentinelClient(connectionConfig);
}
}
/**
* 单机redis客户端
*
* @param connectionConfig
* @return
*/
private RedisClient getRedisStandaloneClient(RedisConnectionConfig connectionConfig)
{
char[] password = connectionConfig.getPassword();
String clientName = connectionConfig.getClientName();
RedisURI.Builder redisUriBuilder = RedisURI.builder();
redisUriBuilder.withHost(connectionConfig.getHost()).withPort(connectionConfig.getPort())
.withDatabase(connectionConfig.getDatabase()).withTimeout(Duration.ofMillis(connectionConfig.getTimeout()));
if (password != null) {
redisUriBuilder.withPassword(connectionConfig.getPassword());
}
if (StringUtils.isNotBlank(connectionConfig.getClientName())) {
redisUriBuilder.withClientName(clientName);
}
return RedisClient.create(redisUriBuilder.build());
}
/**
* 创建Redis Sentinel模式的Redis客户端
*
* @param connectionConfig
* @return
*/
private RedisClient getRedisSentinelClient(RedisConnectionConfig connectionConfig)
{
char[] password = connectionConfig.getPassword();
String clientName = connectionConfig.getClientName();
RedisURI.Builder sentinelRedisUriBuilder = RedisURI.builder();
for (RedisConnectionConfig config : connectionConfig.getRedisSentinels()) {
sentinelRedisUriBuilder.withSentinel(config.getHost(), config.getPort());
}
if (password != null) {
sentinelRedisUriBuilder.withPassword(connectionConfig.getPassword());
}
if (StringUtils.isNotBlank(connectionConfig.getClientName())) {
sentinelRedisUriBuilder.withClientName(clientName);
}
sentinelRedisUriBuilder.withSentinelMasterId(connectionConfig.getRedisSentinelMasterId())
.withTimeout(connectionConfig.getTimeout(), TimeUnit.MILLISECONDS);
return RedisClient.create(sentinelRedisUriBuilder.build());
}
/**
* 增加监听器
*
* @param channel
*/
private void subscribeFromChannel(String channel)
{
StatefulRedisPubSubConnection<String, String> pubSubConnection = redisClient.connectPubSub();
RedisPubSubAdapter<String, String> adapterListener = new DelegatingRedisPubSubListener();
pubSubConnection.addListener(adapterListener);
RedisPubSubCommands<String, String> sync = pubSubConnection.sync();
sync.subscribe(channel);
}
/**
* 初始化加载规则配置
*/
private void loadInitialConfig()
{
try {
T newValue = loadConfig();
if (newValue == null) {
RecordLog
.warn("[RedisDataSource] WARN: initial config is null, you may have to check your data source");
}
getProperty().updateValue(newValue);
}
catch (Exception ex) {
RecordLog.warn("[RedisDataSource] Error when loading initial config", ex);
}
}
/**
* 从指定数据源读取字符串格式的配置数据
*/
@Override
public String readSource()
{
if (this.redisClient == null) {
throw new IllegalStateException("Redis client has not been initialized or error occurred");
}
RedisCommands<String, String> stringRedisCommands = redisClient.connect().sync();
return stringRedisCommands.get(ruleKey);
}
@Override
public void close()
{
redisClient.shutdown();
}
private class DelegatingRedisPubSubListener extends RedisPubSubAdapter<String, String>
{
DelegatingRedisPubSubListener()
{
}
@Override
public void message(String channel, String message)
{
RecordLog.info(
String.format("[RedisDataSource] New property value received for channel %s: %s", channel, message));
getProperty().updateValue(parser.convert(message));
}
}
}
在启动Spring Cloud项目时,应该如何传入Redis Config 参数和redis 的ruleKey,以及如何让我重写的这个类起作用呢?
1,修改sentinel-dashboard
package com.alibaba.csp.sentinel.dashboard.controller;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
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.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
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 com.alibaba.fastjson.JSON;
/**
* Flow rule controller (v1).
*
* @author Eric Zhao
* @since 1.4.0
*/
@RestController
@RequestMapping(value = "/v1/flow")
public class FlowControllerV1 {
private final Logger logger = LoggerFactory.getLogger(FlowControllerV1.class);
@Autowired
private InMemoryRuleRepositoryAdapter<FlowRuleEntity> repository;
@Autowired
@Qualifier("flowRuleRedisProvider")
private DynamicRuleProvider<List<FlowRuleEntity>> ruleProvider;
@Autowired
@Qualifier("flowRuleRedisPublisher")
private DynamicRulePublisher<List<FlowRuleEntity>> rulePublisher;
@Autowired
private AuthService<HttpServletRequest> authService;
@Autowired
private SentinelApiClient sentinelApiClient;
@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());
}
}
}
rules = 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);
} catch (Throwable throwable) {
logger.error("Failed to add flow rule", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.error("Publish flow rules failed after rule add");
}
return Result.ofSuccess(entity);
}
@PutMapping("/save.json")
public Result<FlowRuleEntity> updateIfNotNull(HttpServletRequest request, Long id, String app,
String limitApp, String resource, Integer grade,
Double count, Integer strategy, String refResource,
Integer controlBehavior, Integer warmUpPeriodSec,
Integer maxQueueingTimeMs) {
AuthUser authUser = authService.getAuthUser(request);
authUser.authTarget(app, PrivilegeType.WRITE_RULE);
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity entity = repository.findById(id);
if (entity == null) {
return Result.ofFail(-1, "id " + id + " dose not exist");
}
if (StringUtil.isNotBlank(app)) {
entity.setApp(app.trim());
}
if (StringUtil.isNotBlank(limitApp)) {
entity.setLimitApp(limitApp.trim());
}
if (StringUtil.isNotBlank(resource)) {
entity.setResource(resource.trim());
}
if (grade != null) {
if (grade != 0 && grade != 1) {
return Result.ofFail(-1, "grade must be 0 or 1, but " + grade + " got");
}
entity.setGrade(grade);
}
if (count != null) {
entity.setCount(count);
}
if (strategy != null) {
if (strategy != 0 && strategy != 1 && strategy != 2) {
return Result.ofFail(-1, "strategy must be in [0, 1, 2], but " + strategy + " got");
}
entity.setStrategy(strategy);
if (strategy != 0) {
if (StringUtil.isBlank(refResource)) {
return Result.ofFail(-1, "refResource can't be null or empty when strategy!=0");
}
entity.setRefResource(refResource.trim());
}
}
if (controlBehavior != null) {
if (controlBehavior != 0 && controlBehavior != 1 && controlBehavior != 2) {
return Result.ofFail(-1, "controlBehavior must be in [0, 1, 2], but " + controlBehavior + " got");
}
if (controlBehavior == 1 && warmUpPeriodSec == null) {
return Result.ofFail(-1, "warmUpPeriodSec can't be null when controlBehavior==1");
}
if (controlBehavior == 2 && maxQueueingTimeMs == null) {
return Result.ofFail(-1, "maxQueueingTimeMs can't be null when controlBehavior==2");
}
entity.setControlBehavior(controlBehavior);
if (warmUpPeriodSec != null) {
entity.setWarmUpPeriodSec(warmUpPeriodSec);
}
if (maxQueueingTimeMs != null) {
entity.setMaxQueueingTimeMs(maxQueueingTimeMs);
}
}
Date date = new Date();
entity.setGmtModified(date);
try {
entity = repository.save(entity);
if (entity == null) {
return Result.ofFail(-1, "save entity fail");
}
} catch (Throwable throwable) {
logger.error("save error:", throwable);
return Result.ofThrowable(-1, throwable);
}
if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) {
logger.info("publish flow rules fail after rule update");
}
return Result.ofSuccess(entity);
}
@DeleteMapping("/delete.json")
public Result<Long> delete(HttpServletRequest request, Long id) {
AuthUser authUser = authService.getAuthUser(request);
if (id == null) {
return Result.ofFail(-1, "id can't be null");
}
FlowRuleEntity oldEntity = repository.findById(id);
if (oldEntity == null) {
return Result.ofSuccess(null);
}
authUser.authTarget(oldEntity.getApp(), PrivilegeType.DELETE_RULE);
try {
repository.delete(id);
} catch (Exception e) {
return Result.ofFail(-1, e.getMessage());
}
if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
logger.info("publish flow rules fail after rule delete");
}
return Result.ofSuccess(id);
}
private boolean publishRules(String app, String ip, Integer port) {
List<FlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
try {
rulePublisher.publish(app, rules);
logger.info("添加限流规则成功{}",JSON.toJSONString(rules));
} catch (Exception e) {
e.printStackTrace();
logger.warn("publishRules failed", e);
return false;
}
//核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中
return sentinelApiClient.setFlowRuleOfMachine(app, ip, port, rules);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.csp.sentinel.util.StringUtil;
import com.alibaba.fastjson.JSONObject;
@Component("flowRuleRedisProvider")
public class FlowRuleRedisProvider implements DynamicRuleProvider<List<FlowRuleEntity>> {
@Autowired
private RedisUtil redisConfigUtil;
@Override
public List<FlowRuleEntity> getRules(String appName) throws Exception {
String rules = redisConfigUtil.getString(RuleConsts.RULE_FLOW + appName);
if (StringUtil.isEmpty(rules)) {
return new ArrayList<>();
}
return JSONObject.parseArray(rules,FlowRuleEntity.class);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.FlowRuleEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import com.alibaba.csp.sentinel.util.AssertUtil;
import com.alibaba.fastjson.JSON;
@Component("flowRuleRedisPublisher")
public class FlowRuleRedisPublisher implements DynamicRulePublisher<List<FlowRuleEntity>> {
@Autowired
private RedisUtil redisConfigUtil;
@Override
public void publish(String app, List<FlowRuleEntity> rules) throws Exception {
AssertUtil.notEmpty(app, "app name cannot be empty");
if (rules == null) {
return;
}
String strs = JSON.toJSONString(rules);
redisConfigUtil.setString(RuleConsts.RULE_FLOW + app, strs);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
@Component
public class RedisUtil {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public StringRedisTemplate getStringRedisTemplate() {
return stringRedisTemplate;
}
/**
* 存放string类型
* @param key
* @param data
* @param timeout
*/
public void setString(String key, String data, Long timeout) {
try {
stringRedisTemplate.opsForValue().set(key, data);
if (timeout != null) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
} catch (Exception e) {
}
}
/**
* 存放string类型
* @param key
* @param data
*/
public void setString(String key, String data) {
setString(key, data, null);
}
/**
* 根据key查询string类型
* @param key
* @return
*/
public String getString(String key) {
String value = stringRedisTemplate.opsForValue().get(key);
return value;
}
/**
* 根据对应的key删除key
* @param key
*/
public Boolean delKey(String key) {
return stringRedisTemplate.delete(key);
}
}
package com.alibaba.csp.sentinel.dashboard.rule.redis;
public interface RuleConsts {
//流控规则key前缀
public final String RULE_FLOW = "sentinel_rule_flow_";
//限流规则key前缀
public final String RULE_DEGRADE = "sentinel_rule_degrade_";
//系统规则key前缀
public final String RULE_SYSTEM = "sentinel_rule_system_";
}
package com.it.sentinel.demo.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.redis.RedisDataSource;
import com.alibaba.csp.sentinel.datasource.redis.config.RedisConnectionConfig;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
public class RedisDataSourceConfig implements ApplicationRunner{
@Value("${spring.redis.host}")
public String redisHost;
@Value("${spring.redis.port}")
public int redisPort;
//限流规则key前缀
public final String RULE_FLOW = "sentinel_rule_flow_";
public final String RULE_FLOW_CHANNEL = "sentinel_rule_flow_channel";
//降级规则key前缀
public final String RULE_DEGRADE = "sentinel_rule_degrade_";
public final String RULE_DEGRADE_CHANNEL = "sentinel_rule_degrade_channel";
//系统规则key前缀
public final String RULE_SYSTEM = "sentinel_rule_system_";
public final String RULE_SYSTEM_CHANNEL = "sentinel_rule_system_channel";
/**
* ApplicationRunner
* 该接口的方法会在服务启动之后被立即执行
* 主要用来做一些初始化的工作
* 但是该方法的运行是在SpringApplication.run(…) 执行完毕之前执行
*/
@Override
public void run(ApplicationArguments args) throws Exception {
log.info(">>>>>>>>>执行sentinel规则初始化");
RedisConnectionConfig config = RedisConnectionConfig.builder().withHost(redisHost).withPort(redisPort).build();
Converter<String, List<FlowRule>> parser = source -> JSON.parseObject(source,new TypeReference<List<FlowRule>>() {});
ReadableDataSource<String, List<FlowRule>> redisDataSource = new RedisDataSource<>(config, RULE_FLOW+SentinelConfig.getAppName(), RULE_FLOW_CHANNEL, parser);
FlowRuleManager.register2Property(redisDataSource.getProperty());
Converter<String, List<DegradeRule>> parser1 = source -> JSON.parseObject(source,new TypeReference<List<DegradeRule>>() {});
ReadableDataSource<String, List<DegradeRule>> redisDataSource1 = new RedisDataSource<>(config, RULE_DEGRADE+SentinelConfig.getAppName(), RULE_DEGRADE_CHANNEL, parser1);
DegradeRuleManager.register2Property(redisDataSource1.getProperty());
Converter<String, List<SystemRule>> parser2 = source -> JSON.parseObject(source,new TypeReference<List<SystemRule>>() {});
ReadableDataSource<String, List<SystemRule>> redisDataSource2 = new RedisDataSource<>(config, RULE_SYSTEM+SentinelConfig.getAppName(), RULE_SYSTEM_CHANNEL, parser2);
SystemRuleManager.register2Property(redisDataSource2.getProperty());
}
}
//设置API
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("account_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/api/v1/account/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
//设置规则
rules.add(new GatewayFlowRule("userplatform")
.setCount(2)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("account_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
.setIntervalSec(1)
);