Sentinel全流程分析和网关规则持久化到Nacos

【微服务】(十四)—— Sentinel 规则持久化 目录 一、授权规则 1)授权规则 2)自定义异常结果 二、规则管理模式 1)规则管理模式-原始模式 2)规则管理模式-pull模式 3)规则管理模式-push模式 三、Sentinel 规则持久化 1)修改order-service服务 1.引入依赖 2.配置nacos地址 2)修改sentinel-dashboard源码 1. 解压 2. 修改nacos依赖 3. 添加nacos支持 4. 修改nacos地址 5. 配置nacos数据源 6. 修改前端页... 阅读详情

一、背景与目标

  • 随着微服务架构的普及,系统之间的交互变得日益复杂,流量控制成为了保障系统稳定运行的关键技术之一。微服务网关作为所有外部请求的入口,可以在这一层进行统一的限流管理。这使得限流策略的配置和维护更加集中和方便,减少了在多个微服务中分别配置限流策略的复杂性。
  • 我司之前一直使用的Sentinel企业版AHAS网关限流,但AHAS将在2025年1月5日正式停服,AHAS推荐的方案是MSE微服务治理,但截止2024年5月,MSE暂时不支持API级别的热点参数限流,不满足我司需求。于是需要改造开源Sentinel网关限流,以满足我司需求。

二、改造前流程分析

改造前:

  • gateway客户端引入sentinel
  • sentinel-dashboard不进行任何修改

Sentinel限流流程图 (4).jpg

1、客户端

  1. 初始化sentinel配置(com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration#init)
  2. 上报心跳到sentinel-dashboard,其中包含ip、port、appName、appType等关键信息(com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc#init)

2、sentinel-dashboard

  1. 新增、修改、删除:
    1. 控制台对API或者限流规则进行新增、修改、删除
    2. 对本地内存中的数据进行增、修改、删除
    3. 再次查询本地内存中数据,并将其推送到客户端接口(http://clientIp:8719/setRules)
  2. 列表查询
    1. 远程调用客户端接口查询API或规则(http://clientIp:8719/gateway/getRules)
    2. 保存到本地内存InMemoryRuleRepositoryAdapter
    3. 返回列表数据到后台
  3. 定时拉取监控指标数据
    1. 远程调用客户端接口查询API或规则监控指标(http://clientIp:8719/metric)
    2. 保存到本地内存InMemoryMetricsRepository

3、请求限流

  1. 时间窗口统计请求量(com.alibaba.csp.sentinel.slots.statistic.StatisticSlot#entry)
  2. 获取本地内存中的限流规则,并判断是否达到限流条件(com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot#entry)

三、问题分析与方案设计

1、改造前问题分析

  1. 客户端重启
    1. 此时刷新列表,会读取到客户端的空数据并将控制台内存中的数据进行清空
  2. 控制台重启
    1. 此时刷新列表,会读取到客户端内存中的数据到控制台内存,但如果此时新增规则,会出现数据错误。因为重启后,内存中自增ID会被重置为1,因为新增数据时,会覆盖客户端ID为1的数据。
    2. 此时新增数据,会将新增的这一条数据保存到内存并推送客户端,从而将客户端原有数据给覆盖
  3. 因为不管是客户端重启还是控制台重启,都可能导致数据异常,因此API和规则的持久化是必须要做的

2、方案设计

只对sentinel-dashboard后端代码进行改动,不改动客户端和前端代码。(只针对网关限流进行改造,其它原理类似)

  1. 控制台收到客户端注册时,请求nacos配置中心,并将其配置存入本地内存
  2. 控制台对API或规则进行增删改时,更新本地内存中的数据,并同时推送当前服务下的所有API或者限流规则到nacos
  3. 控制台进行列表查询时,只查询本地内存中的数据
  4. ID生成采用雪花算法,避免重启后出现ID重复数据异常

四、实施方案

1、客户端(无需改造,只需常规配置)

  1. 引入依赖

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
  1. yaml配置参数
spring:
  cloud:
    sentinel:
      enable: true
      filter:
        enabled: false
      eager: true #立即加载
      transport:
        dashboard: localhost:9999
      datasource:
        gw-flow:
          nacos:
            group-id: sentinel_group
            namespace: sentinel
            data-id: ${spring.application.name}-sentinel-gateway-flow-rules # 在修改的sentinel 源码中定义的规则名
            server-addr: 192.168.0.7:8848
            username: xxxx
            password: xxxx
            data-type: json
            rule-type: gw-flow
        gw-api-group:
          nacos:
            group-id: sentinel_group
            namespace: sentinel
            data-id: ${spring.application.name}-sentinel-gateway-api-rules # 在修改的sentinel 源码中定义的规则名
            server-addr: 192.168.0.7:8848
            username: xxxx
            password: xxxx
            data-type: json
            rule-type: gw-api-group

2、sentinel-dashboard

(1)pom.xml去除sentinel-datasource-nacos的scope
        <!-- for Nacos rule publisher sample -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
            <!-- <scope>test</scope> -->
        </dependency>
(2)移动test包下的文件到main包下同级目录

image.png

(3)模仿test包下的文件编写网关API和限流规则的Provider和Publisher
@Component("gateWayFlowRulesNacosProvider")
public class GateWayFlowRulesNacosProvider implements DynamicRuleProvider<List<GatewayFlowRuleEntity>> {

    @Resource
    private ConfigService configService;
    @Resource
    private Converter<String, List<GatewayFlowRuleEntity>> converter;
    @Resource
    private NacosConfigProperties nacosConfigProperties;

    @Override
    public List<GatewayFlowRuleEntity> getRules(String appName) throws Exception {
        String rules = configService.getConfig(appName + NacosConfigUtil.GATEWAY_FLOW_DATA_ID_POSTFIX,
                nacosConfigProperties.getGroup(), 3000);
        if (StringUtil.isEmpty(rules)) {
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }

}
@Component("gateWayFlowRulesNacosPunlisher")
public class GateWayFlowRulesNacosPunlisher implements DynamicRulePublisher<List<GatewayFlowRuleEntity>> {

    @Resource
    private ConfigService configService;
    @Resource
    private Converter<List<GatewayFlowRuleEntity>, String> converter;
    @Resource
    private NacosConfigProperties nacosConfigProperties;


    @Override
    public void publish(String app, List<GatewayFlowRuleEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        configService.publishConfig(app + NacosConfigUtil.GATEWAY_FLOW_DATA_ID_POSTFIX,
                nacosConfigProperties.getGroup(), converter.convert(rules));
    }
}
@Component("getWayApiNacosProvider")
public class GetWayApiNacosProvider implements DynamicRuleProvider<List<ApiDefinitionEntity>> {
    @Resource
    private ConfigService configService;
    @Resource
    private Converter<String , List<ApiDefinitionEntity>> converter;
    @Resource
    private NacosConfigProperties nacosConfigProperties;
    @Override
    public List<ApiDefinitionEntity> getRules(String appName) throws Exception {
        String rules = configService.getConfig(appName+ NacosConfigUtil.GATEWAY_API_DATA_ID_POSTFIX
                ,nacosConfigProperties.getGroup(),3000);
        if(StringUtil.isEmpty(rules)){
            return new ArrayList<>();
        }
        return converter.convert(rules);
    }
}
@Component("getWayApiNacosPublisher")
public class GetWayApiNacosPublisher implements DynamicRulePublisher<List<ApiDefinitionEntity>> {

    @Resource
    private ConfigService configService;
    @Resource
    private Converter<List<ApiDefinitionEntity>, String> converter;
    @Resource
    private NacosConfigProperties nacosConfigProperties;

    @Override
    public void publish(String app, List<ApiDefinitionEntity> rules) throws Exception {
        AssertUtil.notEmpty(app, "app name cannot be empty");
        if (rules == null) {
            return;
        }
        configService.publishConfig(app + NacosConfigUtil.GATEWAY_API_DATA_ID_POSTFIX,
                nacosConfigProperties.getGroup(), converter.convert(rules));
    }
}
(4)修改GatewayApiController和GatewayFlowRuleController
  1. 注入NacosPublisher
  2. 把之前调用客户端的地方修改为调用Nacos
  3. list.json接口改为只从本地内存中获取数据
@RestController
@RequestMapping(value = "/gateway/api")
public class GatewayApiController {

    private final Logger logger = LoggerFactory.getLogger(GatewayApiController.class);

    @Autowired
    private InMemApiDefinitionStore repository;

    @Autowired
    @Qualifier("getWayApiNacosPublisher")
    private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher;


    @GetMapping("/list.json")
    @AuthAction(AuthService.PrivilegeType.READ_RULE)
    public Result<List<ApiDefinitionEntity>> queryApis(String app, String ip, Integer port) {

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }

        try {
            // List<ApiDefinitionEntity> apis = sentinelApiClient.fetchApis(app, ip, port).get();
            // List<ApiDefinitionEntity> apis = ruleProvider.getRules(app);
            //
            // repository.saveAll(apis);
            List<ApiDefinitionEntity> apis = repository.findAllByApp(app);
            return Result.ofSuccess(apis);
        } catch (Throwable throwable) {
            logger.error("queryApis error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    @PostMapping("/new.json")
    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
    public Result<ApiDefinitionEntity> addApi(HttpServletRequest request, @RequestBody AddApiReqVo reqVo) {

        String app = reqVo.getApp();
        if (StringUtil.isBlank(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }

        ApiDefinitionEntity entity = new ApiDefinitionEntity();
        entity.setApp(app.trim());

        String ip = reqVo.getIp();
        if (StringUtil.isBlank(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        entity.setIp(ip.trim());

        Integer port = reqVo.getPort();
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        entity.setPort(port);

        // API名称
        String apiName = reqVo.getApiName();
        if (StringUtil.isBlank(apiName)) {
            return Result.ofFail(-1, "apiName can't be null or empty");
        }
        entity.setApiName(apiName.trim());

        // 匹配规则列表
        List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
        if (CollectionUtils.isEmpty(predicateItems)) {
            return Result.ofFail(-1, "predicateItems can't empty");
        }

        List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
        for (ApiPredicateItemVo predicateItem : predicateItems) {
            ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();

            // 匹配模式
            Integer matchStrategy = predicateItem.getMatchStrategy();
            if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
                return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
            }
            predicateItemEntity.setMatchStrategy(matchStrategy);

            // 匹配串
            String pattern = predicateItem.getPattern();
            if (StringUtil.isBlank(pattern)) {
                return Result.ofFail(-1, "pattern can't be null or empty");
            }
            predicateItemEntity.setPattern(pattern);

            predicateItemEntities.add(predicateItemEntity);
        }
        entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));

        // 检查API名称不能重复
        List<ApiDefinitionEntity> allApis = repository.findAllByMachine(MachineInfo.of(app.trim(), ip.trim(), port));
        if (allApis.stream().map(o -> o.getApiName()).anyMatch(o -> o.equals(apiName.trim()))) {
            return Result.ofFail(-1, "apiName exists: " + apiName);
        }

        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);

        try {
            entity = repository.save(entity);
        } catch (Throwable throwable) {
            logger.error("add gateway api error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        // if (!publishApis(app, ip, port)) {
        //     logger.warn("publish gateway apis fail after add");
        // }
        publishApis(app);

        return Result.ofSuccess(entity);
    }

    @PostMapping("/save.json")
    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
    public Result<ApiDefinitionEntity> updateApi(@RequestBody UpdateApiReqVo reqVo) {
        String app = reqVo.getApp();
        if (StringUtil.isBlank(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }

        Long id = reqVo.getId();
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }

        ApiDefinitionEntity entity = repository.findById(id);
        if (entity == null) {
            return Result.ofFail(-1, "api does not exist, id=" + id);
        }

        // 匹配规则列表
        List<ApiPredicateItemVo> predicateItems = reqVo.getPredicateItems();
        if (CollectionUtils.isEmpty(predicateItems)) {
            return Result.ofFail(-1, "predicateItems can't empty");
        }

        List<ApiPredicateItemEntity> predicateItemEntities = new ArrayList<>();
        for (ApiPredicateItemVo predicateItem : predicateItems) {
            ApiPredicateItemEntity predicateItemEntity = new ApiPredicateItemEntity();

            // 匹配模式
            int matchStrategy = predicateItem.getMatchStrategy();
            if (!Arrays.asList(URL_MATCH_STRATEGY_EXACT, URL_MATCH_STRATEGY_PREFIX, URL_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
                return Result.ofFail(-1, "Invalid matchStrategy: " + matchStrategy);
            }
            predicateItemEntity.setMatchStrategy(matchStrategy);

            // 匹配串
            String pattern = predicateItem.getPattern();
            if (StringUtil.isBlank(pattern)) {
                return Result.ofFail(-1, "pattern can't be null or empty");
            }
            predicateItemEntity.setPattern(pattern);

            predicateItemEntities.add(predicateItemEntity);
        }
        entity.setPredicateItems(new LinkedHashSet<>(predicateItemEntities));

        Date date = new Date();
        entity.setGmtModified(date);

        try {
            entity = repository.save(entity);
        } catch (Throwable throwable) {
            logger.error("update gateway api error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        // if (!publishApis(app, entity.getIp(), entity.getPort())) {
        //     logger.warn("publish gateway apis fail after update");
        // }
        publishApis(app);

        return Result.ofSuccess(entity);
    }

    @PostMapping("/delete.json")
    @AuthAction(AuthService.PrivilegeType.DELETE_RULE)

    public Result<Long> deleteApi(Long id) {
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }

        ApiDefinitionEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }

        try {
            repository.delete(id);
        } catch (Throwable throwable) {
            logger.error("delete gateway api error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        // if (!publishApis(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
        //     logger.warn("publish gateway apis fail after delete");
        // }
        publishApis(oldEntity.getApp());

        return Result.ofSuccess(id);
    }

    // private boolean publishApis(String app, String ip, Integer port) {
    //     List<ApiDefinitionEntity> apis = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    //     return sentinelApiClient.modifyApis(app, ip, port, apis);
    // }

    /**
     * 把配置推给nacos中
     *
     * @param app
     * @throws Exception
     */
    private void publishApis(String app) {
        List<ApiDefinitionEntity> rules = repository.findAllByApp(app);
        try {
            rulePublisher.publish(app, rules);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
@RestController
@RequestMapping(value = "/gateway/flow")
public class GatewayFlowRuleController {

    private final Logger logger = LoggerFactory.getLogger(GatewayFlowRuleController.class);

    @Autowired
    private InMemGatewayFlowRuleStore repository;

    @Autowired
    @Qualifier("gateWayFlowRulesNacosPunlisher")
    private DynamicRulePublisher<List<GatewayFlowRuleEntity>> rulePublisher;


    @GetMapping("/list.json")
    @AuthAction(AuthService.PrivilegeType.READ_RULE)
    public Result<List<GatewayFlowRuleEntity>> queryFlowRules(String app, String ip, Integer port) {

        if (StringUtil.isEmpty(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }
        if (StringUtil.isEmpty(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }

        try {
            // List<GatewayFlowRuleEntity> rules = sentinelApiClient.fetchGatewayFlowRules(app, ip, port).get();
            // List<GatewayFlowRuleEntity> rules = ruleProvider.getRules(app);
            //
            // repository.saveAll(rules);
            List<GatewayFlowRuleEntity> rules = repository.findAllByApp(app);
            return Result.ofSuccess(rules);
        } catch (Throwable throwable) {
            logger.error("query gateway flow rules error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }
    }

    @PostMapping("/new.json")
    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
    public Result<GatewayFlowRuleEntity> addFlowRule(@RequestBody AddFlowRuleReqVo reqVo) {

        String app = reqVo.getApp();
        if (StringUtil.isBlank(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }

        GatewayFlowRuleEntity entity = new GatewayFlowRuleEntity();
        entity.setApp(app.trim());

        String ip = reqVo.getIp();
        if (StringUtil.isBlank(ip)) {
            return Result.ofFail(-1, "ip can't be null or empty");
        }
        entity.setIp(ip.trim());

        Integer port = reqVo.getPort();
        if (port == null) {
            return Result.ofFail(-1, "port can't be null");
        }
        entity.setPort(port);

        // API类型, Route ID或API分组
        Integer resourceMode = reqVo.getResourceMode();
        if (resourceMode == null) {
            return Result.ofFail(-1, "resourceMode can't be null");
        }
        if (!Arrays.asList(RESOURCE_MODE_ROUTE_ID, RESOURCE_MODE_CUSTOM_API_NAME).contains(resourceMode)) {
            return Result.ofFail(-1, "invalid resourceMode: " + resourceMode);
        }
        entity.setResourceMode(resourceMode);

        // API名称
        String resource = reqVo.getResource();
        if (StringUtil.isBlank(resource)) {
            return Result.ofFail(-1, "resource can't be null or empty");
        }
        entity.setResource(resource.trim());

        // 针对请求属性
        GatewayParamFlowItemVo paramItem = reqVo.getParamItem();
        if (paramItem != null) {
            GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
            entity.setParamItem(itemEntity);

            // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie
            Integer parseStrategy = paramItem.getParseStrategy();
            if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER
                    , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
                return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy);
            }
            itemEntity.setParseStrategy(paramItem.getParseStrategy());

            // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填
            if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
                // 参数名称
                String fieldName = paramItem.getFieldName();
                if (StringUtil.isBlank(fieldName)) {
                    return Result.ofFail(-1, "fieldName can't be null or empty");
                }
                itemEntity.setFieldName(paramItem.getFieldName());
            }

            String pattern = paramItem.getPattern();
            // 如果匹配串不为空,验证匹配模式
            if (StringUtil.isNotEmpty(pattern)) {
                itemEntity.setPattern(pattern);
                Integer matchStrategy = paramItem.getMatchStrategy();
                if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
                    return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
                }
                itemEntity.setMatchStrategy(matchStrategy);
            }
        }

        // 阈值类型 0-线程数 1-QPS
        Integer grade = reqVo.getGrade();
        if (grade == null) {
            return Result.ofFail(-1, "grade can't be null");
        }
        if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) {
            return Result.ofFail(-1, "invalid grade: " + grade);
        }
        entity.setGrade(grade);

        // QPS阈值
        Double count = reqVo.getCount();
        if (count == null) {
            return Result.ofFail(-1, "count can't be null");
        }
        if (count < 0) {
            return Result.ofFail(-1, "count should be at lease zero");
        }
        entity.setCount(count);

        // 间隔
        Long interval = reqVo.getInterval();
        if (interval == null) {
            return Result.ofFail(-1, "interval can't be null");
        }
        if (interval <= 0) {
            return Result.ofFail(-1, "interval should be greater than zero");
        }
        entity.setInterval(interval);

        // 间隔单位
        Integer intervalUnit = reqVo.getIntervalUnit();
        if (intervalUnit == null) {
            return Result.ofFail(-1, "intervalUnit can't be null");
        }
        if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) {
            return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit);
        }
        entity.setIntervalUnit(intervalUnit);
        entity.calIntervalSec();
        // 流控方式 0-快速失败 2-匀速排队
        Integer controlBehavior = reqVo.getControlBehavior();
        if (controlBehavior == null) {
            return Result.ofFail(-1, "controlBehavior can't be null");
        }
        if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) {
            return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior);
        }
        entity.setControlBehavior(controlBehavior);

        if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) {
            // 0-快速失败, 则Burst size必填
            Integer burst = reqVo.getBurst();
            if (burst == null) {
                return Result.ofFail(-1, "burst can't be null");
            }
            if (burst < 0) {
                return Result.ofFail(-1, "invalid burst: " + burst);
            }
            entity.setBurst(burst);
        } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) {
            // 1-匀速排队, 则超时时间必填
            Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs();
            if (maxQueueingTimeoutMs == null) {
                return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null");
            }
            if (maxQueueingTimeoutMs < 0) {
                return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs);
            }
            entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
        }

        Date date = new Date();
        entity.setGmtCreate(date);
        entity.setGmtModified(date);

        try {
            entity = repository.save(entity);
        } catch (Throwable throwable) {
            logger.error("add gateway flow rule error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        // if (!publishRules(app, ip, port)) {
        //     logger.warn("publish gateway flow rules fail after add");
        // }
        publishRules(app);

        return Result.ofSuccess(entity);
    }

    @PostMapping("/save.json")
    @AuthAction(AuthService.PrivilegeType.WRITE_RULE)
    public Result<GatewayFlowRuleEntity> updateFlowRule(@RequestBody UpdateFlowRuleReqVo reqVo) {

        String app = reqVo.getApp();
        if (StringUtil.isBlank(app)) {
            return Result.ofFail(-1, "app can't be null or empty");
        }

        Long id = reqVo.getId();
        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }

        GatewayFlowRuleEntity entity = repository.findById(id);
        if (entity == null) {
            return Result.ofFail(-1, "gateway flow rule does not exist, id=" + id);
        }

        // 针对请求属性
        GatewayParamFlowItemVo paramItem = reqVo.getParamItem();
        if (paramItem != null) {
            GatewayParamFlowItemEntity itemEntity = new GatewayParamFlowItemEntity();
            entity.setParamItem(itemEntity);

            // 参数属性 0-ClientIP 1-Remote Host 2-Header 3-URL参数 4-Cookie
            Integer parseStrategy = paramItem.getParseStrategy();
            if (!Arrays.asList(PARAM_PARSE_STRATEGY_CLIENT_IP, PARAM_PARSE_STRATEGY_HOST, PARAM_PARSE_STRATEGY_HEADER
                    , PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
                return Result.ofFail(-1, "invalid parseStrategy: " + parseStrategy);
            }
            itemEntity.setParseStrategy(paramItem.getParseStrategy());

            // 当参数属性为2-Header 3-URL参数 4-Cookie时,参数名称必填
            if (Arrays.asList(PARAM_PARSE_STRATEGY_HEADER, PARAM_PARSE_STRATEGY_URL_PARAM, PARAM_PARSE_STRATEGY_COOKIE).contains(parseStrategy)) {
                // 参数名称
                String fieldName = paramItem.getFieldName();
                if (StringUtil.isBlank(fieldName)) {
                    return Result.ofFail(-1, "fieldName can't be null or empty");
                }
                itemEntity.setFieldName(paramItem.getFieldName());
            }

            String pattern = paramItem.getPattern();
            // 如果匹配串不为空,验证匹配模式
            if (StringUtil.isNotEmpty(pattern)) {
                itemEntity.setPattern(pattern);
                Integer matchStrategy = paramItem.getMatchStrategy();
                if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) {
                    return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy);
                }
                itemEntity.setMatchStrategy(matchStrategy);
            }
        } else {
            entity.setParamItem(null);
        }

        // 阈值类型 0-线程数 1-QPS
        Integer grade = reqVo.getGrade();
        if (grade == null) {
            return Result.ofFail(-1, "grade can't be null");
        }
        if (!Arrays.asList(FLOW_GRADE_THREAD, FLOW_GRADE_QPS).contains(grade)) {
            return Result.ofFail(-1, "invalid grade: " + grade);
        }
        entity.setGrade(grade);

        // QPS阈值
        Double count = reqVo.getCount();
        if (count == null) {
            return Result.ofFail(-1, "count can't be null");
        }
        if (count < 0) {
            return Result.ofFail(-1, "count should be at lease zero");
        }
        entity.setCount(count);

        // 间隔
        Long interval = reqVo.getInterval();
        if (interval == null) {
            return Result.ofFail(-1, "interval can't be null");
        }
        if (interval <= 0) {
            return Result.ofFail(-1, "interval should be greater than zero");
        }
        entity.setInterval(interval);

        // 间隔单位
        Integer intervalUnit = reqVo.getIntervalUnit();
        if (intervalUnit == null) {
            return Result.ofFail(-1, "intervalUnit can't be null");
        }
        if (!Arrays.asList(INTERVAL_UNIT_SECOND, INTERVAL_UNIT_MINUTE, INTERVAL_UNIT_HOUR, INTERVAL_UNIT_DAY).contains(intervalUnit)) {
            return Result.ofFail(-1, "Invalid intervalUnit: " + intervalUnit);
        }
        entity.setIntervalUnit(intervalUnit);
        entity.calIntervalSec();
        // 流控方式 0-快速失败 2-匀速排队
        Integer controlBehavior = reqVo.getControlBehavior();
        if (controlBehavior == null) {
            return Result.ofFail(-1, "controlBehavior can't be null");
        }
        if (!Arrays.asList(CONTROL_BEHAVIOR_DEFAULT, CONTROL_BEHAVIOR_RATE_LIMITER).contains(controlBehavior)) {
            return Result.ofFail(-1, "invalid controlBehavior: " + controlBehavior);
        }
        entity.setControlBehavior(controlBehavior);

        if (CONTROL_BEHAVIOR_DEFAULT == controlBehavior) {
            // 0-快速失败, 则Burst size必填
            Integer burst = reqVo.getBurst();
            if (burst == null) {
                return Result.ofFail(-1, "burst can't be null");
            }
            if (burst < 0) {
                return Result.ofFail(-1, "invalid burst: " + burst);
            }
            entity.setBurst(burst);
        } else if (CONTROL_BEHAVIOR_RATE_LIMITER == controlBehavior) {
            // 2-匀速排队, 则超时时间必填
            Integer maxQueueingTimeoutMs = reqVo.getMaxQueueingTimeoutMs();
            if (maxQueueingTimeoutMs == null) {
                return Result.ofFail(-1, "maxQueueingTimeoutMs can't be null");
            }
            if (maxQueueingTimeoutMs < 0) {
                return Result.ofFail(-1, "invalid maxQueueingTimeoutMs: " + maxQueueingTimeoutMs);
            }
            entity.setMaxQueueingTimeoutMs(maxQueueingTimeoutMs);
        }

        Date date = new Date();
        entity.setGmtModified(date);

        try {
            entity = repository.save(entity);
        } catch (Throwable throwable) {
            logger.error("update gateway flow rule error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        // if (!publishRules(app, entity.getIp(), entity.getPort())) {
        //     logger.warn("publish gateway flow rules fail after update");
        // }

        publishRules(app);

        return Result.ofSuccess(entity);
    }


    @PostMapping("/delete.json")
    @AuthAction(AuthService.PrivilegeType.DELETE_RULE)
    public Result<Long> deleteFlowRule(Long id) {

        if (id == null) {
            return Result.ofFail(-1, "id can't be null");
        }

        GatewayFlowRuleEntity oldEntity = repository.findById(id);
        if (oldEntity == null) {
            return Result.ofSuccess(null);
        }

        try {
            repository.delete(id);
        } catch (Throwable throwable) {
            logger.error("delete gateway flow rule error:", throwable);
            return Result.ofThrowable(-1, throwable);
        }

        // if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) {
        //     logger.warn("publish gateway flow rules fail after delete");
        // }

        publishRules(oldEntity.getApp());

        return Result.ofSuccess(id);
    }

    // private boolean publishRules(String app, String ip, Integer port) {
    //     List<GatewayFlowRuleEntity> rules = repository.findAllByMachine(MachineInfo.of(app, ip, port));
    //     return sentinelApiClient.modifyGatewayFlowRules(app, ip, port, rules);
    // }

    /**
     * 把配置推给nacos中
     *
     * @param app
     * @throws Exception
     */
    private boolean publishRules(String app) {
        List<GatewayFlowRuleEntity> rules = repository.findAllByApp(app);
        try {
            rulePublisher.publish(app, rules);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }

}
(5)修改AppManagement应用注册逻辑,如果是第一次注册,从Nacos拉取配置到本地内存
@Component
public class AppManagement implements MachineDiscovery {

    private final Logger logger = LoggerFactory.getLogger(AppManagement.class);

    @Autowired
    private ApplicationContext context;

    @Autowired
    @Qualifier("gateWayFlowRulesNacosProvider")
    private DynamicRuleProvider<List<GatewayFlowRuleEntity>> flowRuleProvider;

    @Autowired
    private InMemGatewayFlowRuleStore flowRuleRepository;


    @Autowired
    @Qualifier("getWayApiNacosProvider")
    private DynamicRuleProvider<List<ApiDefinitionEntity>> apiProvider;

    @Autowired
    private InMemApiDefinitionStore apiRepository;

    private MachineDiscovery machineDiscovery;

    @PostConstruct
    public void init() {
        machineDiscovery = context.getBean(SimpleMachineDiscovery.class);
    }

    @Override
    public Set<AppInfo> getBriefApps() {
        return machineDiscovery.getBriefApps();
    }

    @Override
    public long addMachine(MachineInfo machineInfo) {
        // 是网关并且是第一次注册
        String app = machineInfo.getApp();
        if (isGateway(machineInfo.getAppType()) && getDetailApp(app) == null) {
            pullGatewayNacosApi(app);
            pullGatewayNacosFlowRules(app);
        }
        return machineDiscovery.addMachine(machineInfo);
    }

    @Override
    public boolean removeMachine(String app, String ip, int port) {
        return machineDiscovery.removeMachine(app, ip, port);
    }

    @Override
    public List<String> getAppNames() {
        return machineDiscovery.getAppNames();
    }

    @Override
    public AppInfo getDetailApp(String app) {
        return machineDiscovery.getDetailApp(app);
    }

    @Override
    public void removeApp(String app) {
        machineDiscovery.removeApp(app);
    }

    public boolean isValidMachineOfApp(String app, String ip) {
        if (StringUtil.isEmpty(app)) {
            return false;
        }
        return Optional.ofNullable(getDetailApp(app))
                .flatMap(a -> a.getMachine(ip))
                .isPresent();
    }

    private boolean isGateway(Integer appType) {
        return appType == 1;
    }

    private void pullGatewayNacosFlowRules(String app) {
        try {
            List<GatewayFlowRuleEntity> rules = flowRuleProvider.getRules(app);
            flowRuleRepository.saveAll(rules);
        } catch (Exception e) {
            logger.error("pull nacos flow rules error, app:{}", app, e);
        }
    }

    private void pullGatewayNacosApi(String app) {
        try {
            List<ApiDefinitionEntity> api = apiProvider.getRules(app);
            apiRepository.saveAll(api);
        } catch (Exception e) {
            logger.error("pull nacos api error, app:{}", app, e);
        }
    }

}
(6)修改内存自增ID为雪花算法(需要引入hutool)
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.15</version>
        </dependency>
@Component
public class InMemApiDefinitionStore extends InMemoryRuleRepositoryAdapter<ApiDefinitionEntity> {


    @Override
    protected long nextId() {
        return IdUtil.getSnowflakeNextId();
    }
}
@Component
public class InMemGatewayFlowRuleStore extends InMemoryRuleRepositoryAdapter<GatewayFlowRuleEntity> {

    @Override
    protected long nextId() {
        return IdUtil.getSnowflakeNextId();
    }
}
(7)还有小BUG修复,完整GIT提交日志如下(完整代码在文末)

image.png

五、改造后流程分析

Sentinel限流流程图 (3).jpg

1、客户端

  1. 初始化Sentinel配置(com.alibaba.cloud.sentinel.custom.SentinelAutoConfiguration#init)
  2. 上报心跳到sentinel-dashboard,其中包含ip、port、appName、appType等关键信息(com.alibaba.csp.sentinel.transport.init.HeartbeatSenderInitFunc#init)
  3. 控制台收到第一次注册时,拉取Nacos配置到本地内存
  4. 初始化Nacos配置监听器,实时监听nacos配置变化(com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource#initNacosListener)
  5. 加载Nacos初始化配置到本地内存(com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource#loadInitialConfig)

2、sentinel-dashboard

  1. 新增、修改、删除:
    1. 后台页面对API或者限流规则进行新增、修改、删除
    2. 对本地内存中的数据进行操作
    3. 再次查询本地内存中数据,并将其推送到nacos
  2. 列表查询
    1. 查询本地内存InMemoryRuleRepositoryAdapter
    2. 返回列表数据到后台
  3. 定时拉取监控指标数据
    1. 远程调用客户端接口查询API或规则(http://clientIp:8719/metric)
    2. 保存到本地内存InMemoryMetricsRepository

3、请求限流(和改造前没有区别)

六、遇到的问题

Q:控制台配置的间隔单位和间隔时间不生效
A:控制台的参数名是interval和intervalUnit,但客户端读取的是intervalSec,需要进行转换并存入Nacos

Q:控制台内存中维护的自增ID,如果控制台重启,自增ID会被重置
A:ID创建修改为雪花算法,ID返回给前端时,格式化为String类型(以防Long传到前端精度丢失)

Q:如果一个链接没有被请求过,控制台修改配置后,客户端会报naco notify-error npe
A:该问题已在1.8.1被修复,可将客户端版本升级到1.8.1及以上,https://github.com/alibaba/Sentinel/pull/1729

Q:控制台是否支持集群部署
A:暂时不支持。因为现在控制台的数据都是存储在本地内存,如果要实现集群部署,需要引入外部数据存储工具,例如:Mysql、Redis

Q:阿里云AHAS在项目中的实际应用
A:网关侧接入AHAS,并将项目中所有需要外部限流的接口路径配置在AHAS中,并根据热点参数对IP、Header、Token进行多维度限流

Q:阿里云MSE微服务治理为什么不能满足需求
A:MSE进行了微服务职责的进一步划分,网关侧只能进行路由级别限流,应用侧才能进行接口级别限流。如果要实现AHAS网关限流同样的功能,需要将所有微服务加入到MSE微服务治理中,对我们来说成本过高

Q:是否还有其它问题
A:还比较多,比如:
  1、控制台监控数据未持久化,只能看到最近5分钟数据,并且可能有内存溢出的风险
  2、控制台未接入nacos配置中心
  3、控制台不支持多账号密码
  4、可能有内存和Nacos双写一致性问题

七、总结

我们目前只用到了Sentinel网关限流中热点参数限流,所以只对网关规则进行了持久化改造。如果有其它需求,大致的修改逻辑应该是差不多的。虽然改造后的Sentinel控制台还存在很多问题,但通过上面的流程分析,控制台只是作为规则可视化配置的工具,对性能要求不高,我们也没有监控持久化的需求,所以暂时就不进行其它改造了。

八、参考资料

Sentinel-官网

优快云-Sentinel的gateway规则持久化改造

Github-文章源码

Sentinel结合Nacos实现配置持久化(全面) 在分布式微服务的开发中,Gateway是必不可少的组件,那么对于服务的配置也会集成到Gateway中,但是我们不能把配置放在内存中,此时就需要结合nacos配置中心来实现配置的持久化了。 阅读详情

相关推荐

Sentinel配置规则持久化至Nacos

文中使用的nacos版本1.4.2、sentinel版本1.8.3 问题 当集成了Sentinel的客户端应用重启之后,在控制台配置的规则全部都没有了,如果需要上生产环境需要将规则配置进行持久化。下面就来实现Sentinel规则配置持久化。 添加Maven依赖 需要持久化至nacos就需要添加相关Maven依赖 <!-- Sentinel Datasource 依赖 --> <dependency> <groupId>com.alibaba.cloud<.

weixin_42270645的博客 8845

sentinel+nacos持久化流控配置

目录 概述 服务端 1、拉取sentinel代码地址 2、打开工程如下,重点关注core和dashboard的微服务 3、启动控制台时需要往jvm中写入参数 4、然后启动微服务 客户端接入 1、客户端引入pom文件 2、限流配置测试 持久化nacos 1、控制台工程修改 2、idea配置jvm参数,启动 3、客户端工程引入nacos 4、限流配置测试 概述 Sentinel控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自...

zchlove137的博客 5225

从ruoyi-gateway配置看Sentinel网关限流规则持久化到Nacos

本文详细解析了如何通过ruoyi-gateway配置实现Sentinel网关限流规则持久化到Nacos,解决了硬编码限流规则带来的运维难题。文章从核心依赖配置到生产环境最佳实践,全面介绍了Nacos与Sentinel的集成方案,帮助开发者构建高可用的微服务架构。

weixin_33875839的博客 308

Sentinel nacos spring cloud 持久化配置---分布式/微服务流量控制

Sentinel nacos spring cloud 持久化配置---分布式/微服务流量控制

北巷东东的专栏 1653

9.Sentinel控制台 配置 nacos持久化

对于 push 模式的数据源,如远程配置中心(ZooKeeper, Nacos, Apollo等等),我们在sentinel Dashboard配置的规则会同步到Nacos中, Sentinel Dashboard也会从Nacos中获取规则,Nacos会讲规则推送给Sentinel客户端。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的。大家可以发现整个过程比较繁重,并且每次都需要更新本地文件,他的性能有一定的影响。

qq_44605317的博客 1656

【白话系列】倍增算法

【序言】         我认为吧,所有能够优化复杂度的算法都是神奇的,所有能够化繁琐为形象的文字都是伟大的。一直觉得倍增算法是个很神奇的东西,所以决定写点东西纪念一下它。但是作为一个非常不称职的OIER,我非常讨厌在看别人的算法解析时整版的i,j,k等我看到鼠标就惯性移到右上角的符号语言,所以我想用最形象的方式来纪念它。 【一】         从前,有一只可爱得不得了的小白兔,它想从

JarjingX 3万+

Sentinel - 集成 Nacos 实现规则动态推送与持久化

本文介绍了如何将Sentinel与Nacos集成,实现流量控制规则的动态推送和持久化存储。主要内容包括: Sentinel与Nacos的核心功能概述 Sentinel提供流量控制、熔断降级等微服务治理能力 Nacos作为配置中心支持动态配置管理 集成方案的优势 规则动态更新无需重启服务 规则持久化存储防止丢失 与微服务生态无缝集成 技术实现要点 环境准备与Maven依赖配置 通过Nacos数据源实现规则动态推送 示例代码展示集成方式 该方案能够有效解决微服务架构中流量规则管理难题,提升系统的弹性和可维护性。

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

NOIP2008年普及组初赛题目答案及解析

NOIP2008年初赛详细题解

城南蝈蝈 926

Sentinel使用Nacos存储规则及同步

因为SentinelDashboard中设置的限流规则在应用重启之后就丢失了,所以需要将Sentinel的规则持久化。 1、Sentinel 动态规则扩展 Sentinel 的理念是开发者只需要关注资源的定义,当资源定义成功后可以动态增加各种流控降级规则。Sentinel 提供两种方式修改规则: 通过 API 直接修改 (loadRules) 通过DataSource适配不同数据源修改 手动通过 API 修改比较直观,可以通过以下几个 API 修改不同的规则: FlowRule...

骏马逸动,心随你动的博客 1万+

微信小程序scroll-view,点击menu,滚动到锚点;滚动到锚点,激活menu。

首先看看效果图1、首先是点击菜单,滚动列表,这个实现比较简单。直接使用scroll-view 的 scroll-into-view属性,小程序文档。&lt;view class='container'&gt; &lt;!-- 菜单 --&gt; &lt;view class='menu'&gt; &lt;ul&gt; &lt;!-- scrollList 修改cate --&g...

weixin_39834214的博客 1万+

【面试经验】京东-技术产品经理一面:从紧张到反思的全流程复盘

本文详细复盘了京东技术产品经理一面的全流程,从面试前的简历打磨、业务理解和技术基础准备,到面试中的高压对话和突发状况应对,再到面试后的深度反思与改进方案。文章特别强调了技术产品经理需要具备的技术理解力、数据敏感度和商业思维,并提供了实用的面试建议和准备技巧。

weixin_28730927的博客 162

Sentinel(五)整合Nacos实现动态规则配置持久化、双向同步

Sentinel(五)整合Nacos实现动态规则配置持久化、双向同步

一恍过去 1万+

OpencvSharp学习笔记(一):图片的打开,保存灰度化,保存

近来萌生了学习OpenCv的想法,经过度娘得知老夫会的C#不能直接用OpenCv,只能用他的儿子OpenCvSharp与EmguCV,奈何奈何,这两玩意的资料少的出奇,只能慢慢的摸索了,EmguCV先不说,老夫找到了牛批的资料,先摸索OpencvSharp吧 首先打开VS通过Nuget安装 选择版本后点击安装就OK了. 安装完成后,引用里面就有以下的玩意儿了… 废话不多说了,在写程序前先 using OpenCvSharp 图片打开,灰度化,保存代码如下: static void Main(strin

猥琐de专注 7186

SpringCloud+Nacos持久化Sentinel

SpringCloud-Alibaba+Nacos+Sentinel整合并持久化集成

qq_42354307的博客 1584

Java开发者的福音:Aspose 25.10全系列(Cells/Words/PDF/Slides)最新版破解与集成保姆级教程

本文为Java开发者提供了Aspose 25.10全系列组件(Cells/Words/PDF/Slides)的最新版破解与集成保姆级教程。通过详细的Maven依赖配置、字节码修改技术绕过限制,以及Spring Boot项目中的实战集成指南,帮助开发者高效实现Office文档处理功能,提升开发效率。

weixin_30919429的博客 344

PHP实现SM2算法(附完整源码)

PHP实现SM2算法(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 4021

改造sentinel-dashboard,使用Nacos持久化规则(详细)

前言 要想改造 sentinel-dashboard,需要修改 dashboard 的源码。(本文基于sentinel 1.8.0) 先通过 Github 拉取 Sentinel 源码,GitHub 地址:https://github.com/alibaba/Sentinel 导入 IDEA,下载依赖,进入 sentinel-dashboard 模块,所有的修改都在该模块下。 修改 POM 想要将 dashboard 的规则持久化到 Nacos,需要添加一个依赖,源码中已经添加,不过只在测试环境下打包进去了

zhangyuanshuai的博客 2116

手把手教你用git上传项目到GitHub(图文并茂,这一篇就够了),相信你一定能成功!!

GitHub是基于git实现的代码托管。git是目前最好用的版本控制系统了,非常受欢迎,比之svn更好。 GitHub可以免费使用,并且快速稳定。即使是付费帐户,每个月不超过10美刀的费用也非常便宜。 利用GitHub,你可以将项目存档,与其他人分享交流,并让其他开发者帮助你一起完成这个项目。优点在于,他支持多人共同完成一个项目,因此你们可以在同一页面对话交流。 创建自己的项目,并备份,代码不需要保存在本地或者服务器,GitHub做得非常理想。 学习Git也有很多好处。他被视为一个预先维护过程,你可以按自己

左手の明天的博客 3万+

Sentinel的gateway规则持久化改造

Sentinel的gateway规则持久化改造 每天多学一点点~ 话不多说,这就开始吧… 文章目录Sentinel的gateway规则持久化改造1. 前言2. 源码改造2.1 老规矩,去掉sentinel-dashboard 工程的pom中test2.2 复制 test下 NacosConfig类并修改2.3 新增 pulisher 推送规则 和 provider 拉取规则2.4 修改GatewayFlowRuleController 和 GatewayApiController2.5 修改端口3

爆裂无球的程序空间 5148

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Spring Cloud Alibaba系列-第12节-GateWay网关使用sentinel限流,规则同步nacos

目录 1. gateway集成到sentinel 1.1 修改pom文件 1.2网关增加sentinel配置 1.3 验证 1.3.1 启动各个服务 1.3.2 发送请求到网关 1.3.2 观察sentinel页面 2. sentinel网关规则持久化nacos 2.1 修改sentinel源码 2.1.1 在rule.nacos下创建gateway包路径并创建类 2.1.2 修改gatewat controller文件 2.2 进行测试 2.2.1 启动服务: 2.2.2 ..

皮卡肉丘 5072

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

Zuul网关集成Sentinel持久化规则数据到nacos中

Zuul网关集成Sentinel持久化规则数据到nacos中 本项目环境: Zuul 1.3.1 SentinelDashBoard 1.8.3 nacos 2.0.4 本篇文章目前已实现功能 网关限流和网关API分组规则数据持久化到nacos 实现了nacos和SentinelDashboard的数据双向互通 目前遗留问题: 链路追踪只能看到通过网关调用其他服务产生的调用路径 SentinelDashboard网关限流规则参数实体类与客户端的网关限流规则参数实例类字段不一致 1.网关POM文件

weixin_42940218的博客 1195

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

sentinel实现gateway网关限流规则持久化

sentinel实现gateway网关限流规则持久化 上一篇文件介绍了怎么实现网关怎么限流 1.网关限流持久化 1.1maven依赖 <dependencies> <<!--sentinel限流熔断 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cl

bright的博客 2883

网关整合sentinel无法读取nacos配置问题分析

网关整合sentinel读取nacos配置原理

Darlight的博客 2107

【Sentinel】授权规则与规则持久化

Sentinel的授权规则与规则的从持久化

少年你真的要止步于此嘛。 1552

Sentinel1.8.6 Gateway网关流控+动态Nacos数据源双向持久化

大家也知道,SpringCloud架构的微服务项目—可以说网关是必不可少的,其中SpringCloud gateway最受欢迎,无缝集成,所有请求都会先进入Gateway,由Gateway进行路由转发。你想想,sentinel默认配置的流控规则都是存储在dashboard服务内存中的,项目一重启,那么你之前配置的流控规则都没了!注意,重点就是在Publisher(Nacos发布者)和Provider(Nacos发布者)这两个类,需要重写,Sentinel是支持网关流控控台的,所以我们需要重写。

jack2350536098的博客 5164

Sentinel使用Nacos存储规则

相信大家对Sentinel已经有了初步的认识。在Spring Cloud Alibaba的整合封装之下,接口限流这件事情可以非常轻易的整合到我们的Spring Cloud应用中。但是,依然还不能完美的满足我们日常的生产需求。其中,非常重要的一点就是限流规则的持久化问题。Dashboard中设置的限流规则在应用重启之后就丢失了的问题。那么,接下来我们就来说说Sentinel的规则持久化如何实现。 ...

穷水叮咚的博客 6903
上一篇: 软件技术选型模板
下一篇: 微服务网关API加解密组件gateway-api-encrypt
AJiong93
博客等级 码龄10年 95粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值