Spring cloud 整合Sentinel 实现zuul网关(gateway)限流redis缓存限流

关键词 Spring cloud Sentinel zuul gateway redis dashboard Sentinel redis 限流 

开始使用

修改的地方有两个,一个是Sentinel dashboard项目,一个是你自己的项目(客户端)

(不难,不难,不难,只要把文章看完就懂了,文章后面有修改dashboard的代码下载)

修改Sentinel dashboard项目

首先你需要down下来Sentinel整个项目源码,本文使用的为Sentinel 1.8.4的源码,源码下载路径为:官方Sentinel ,下载后请导入到开发工具(Eclipse 或者 idea)中准备修改源码并编译。

导入的项目结构如下:

我们将将要修改的代码就在sentinel-dashboard项目中。

此项目中要修改得地方有

  • application.properties(增加Redis配置参数)

  • pom.xml(增加Redis的依赖)

  • GatewayFlowRuleRedisProvider.java(新增,自定义的Redis拉取规则)

  • GatewayFlowRuleRedisPublisher.java(新增,自定义的Redis推送规则)

  • RedisConfig.java(新增,定义的Redis配置类)

  • RuleConstants.java(新增,Redis的常量)

  • GatewayApiController.java(修改,修改api推送逻辑和拉取逻辑)
  • GatewayFlowRuleController.java(修改,修改规则推送逻辑和拉取逻辑)

所有的修改内容已经罗列在上面了,下面咱们就详细说说如何修改:

application.properties文件

此文件在sentinel-dashboard项目下的resource中,在此文件最下面添加下面的配置:

# redis配置 
spring.redis.database=0
spring.redis.host=localhost
spring.redis.password=密码
spring.redis.port=6379
spring.redis.timeout=3000

#spring.redis.jedis.pool.max-active=8
#spring.redis.jedis.pool.max-wait=1
#spring.redis.jedis.pool.max-idle=8
#spring.redis.jedis.pool.min-idle=2
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=2

注意,上面的配置中,前四个配置为redis的配置,不在解释,自己修改成自己的就可以

pom.xml

此文件在sentinel-dashboard项目下,请添加以下依赖:

        <!-- 集成redis -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-redis</artifactId>
            <version>1.8.4</version>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.5.12</version>
        </dependency>
        <!-- lettuce pool 缓存连接池-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>2.6.2</version>
        </dependency>

注意,此处的${spring.boot.version}表示使用此项目本身配置的版本,以适应他的jar要求,防止jar依赖出问题。

GatewayApiRedisProvider.java

此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis(注意,redis路径需要新增),如图:

上面所说的几个新增的类同样需要放在此路径下,这个类的作用是自定义基于Redis实现拉取限流规则的逻辑

package com.alibaba.csp.sentinel.dashboard.rule.redis;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRuleProvider;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义实现基于redis的拉取规则
 *
 * @autheor lingguoxiong
 * @date 2022/10/9 23:08
 */
@Component("gatewayApiRedisProvider")
public class GatewayApiRedisProvider implements DynamicRuleProvider<List<ApiDefinitionEntity>> {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public List<ApiDefinitionEntity> getRules(String appName) {
        System.out.println("Sentinel 从Redis拉取api begin >>>>>>>>>>>>>>>>>>>>");
        String value = JSON.toJSONString(redisTemplate.opsForValue().get(RuleConstants.gatewayApi + appName));
        if (StringUtils.isEmpty(value)) {
            return new ArrayList<>();
        }
        System.out.println("Sentinel 从Redis拉取api end >>>>>>>>>>>>>>>>>>>>");
        return JSONObject.parseArray(value, ApiDefinitionEntity.class);
    }
}

GatewayApiRedisPublisher.java

此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis,这个类的作用是自定义基于Redis实现推送限流规则的逻辑

package com.alibaba.csp.sentinel.dashboard.rule.redis;

import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.rule.DynamicRulePublisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 自定义实现限流配置推送规则
 *
 * @autheor lingguoxiong
 * @date 2022/10/9 23:11
 */
@Component("gatewayApiRedisPublisher")
public class GatewayApiRedisPublisher implements DynamicRulePublisher<List<ApiDefinitionEntity>> {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void publish(String app, List<ApiDefinitionEntity> rules) {
        System.out.println("Sentinel 向Redis推送api begin >>>>>>>>>>>>>>>>>>>>" + rules);
        if (rules == null) {
            return;
        }
        System.out.println("rules:" + rules);

        //  使用RedisTemplate 的默认配置,即不开启事务支持。但是,我们可以通过使用 SessionCallback,
        //  该接口保证其内部所有操作都是在同一个Session中
        List<Object> txResults = redisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.multi();
                operations.opsForValue().set(RuleConstants.gatewayApi + app, rules);
                operations.convertAndSend(RuleConstants.gatewayApiChannel + app, rules);
                // This will contain the results of all operations in the transaction
                return operations.exec();
            }
        });

        System.out.println("Sentinel 向Redis推送api end >>>>>>>>>>>>>>>>>>>>" + txResults);
    }
}

RedisConfig.java

此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis,这个类的作用是定义Redis的配置(这个类可用可不用)

package com.alibaba.csp.sentinel.dashboard.rule.redis;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author lingguoxiong
 * @date 2021/12/20 18:08
 */
@Configuration
public class RedisConfig {

    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }

    @Bean(name = "stringRedisTemplate")
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate redisTemplate = new StringRedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    private RedisSerializer<Object> valueSerializer() {
        return new Jackson2JsonRedisSerializer(Object.class);
    }

}

RuleConstants.java

此文件新增,新增的位置com.alibaba.csp.sentinel.dashboard.rule.redis,这个类的作用是定义Redis中使用的常量

package com.alibaba.csp.sentinel.dashboard.rule.redis;

import org.springframework.stereotype.Component;

/**
 * @autheor lingguoxiong
 * @date 2022/10/9 23:19
 */
@Component
public class RuleConstants {
    /**
     * 流控规则key前缀
     */
    public static final String gatewayRuleFlow = "sentinel_gateway_rule_flow_";
    public static final String gatewayRuleFlowChannel = "sentinel_gateway_rule_flow_channel_";

    public static final String gatewayApi = "sentinel_gateway_api_";
    public static final String gatewayApiChannel = "sentinel_gateway_api_channel_";
}

GatewayApiController.java

注意,这个类原本就存在,位于com.alibaba.csp.sentinel.dashboard.controller路径下,我们需要修改它的推送逻辑和拉取逻辑(此类基于sentinel 1.8.4修改,如果未来的您使用的版本比这个高,或者地,请谨慎修改---只动推送和拉取的逻辑)

/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.csp.sentinel.dashboard.controller.gateway;

import com.alibaba.csp.sentinel.dashboard.auth.AuthAction;
import com.alibaba.csp.sentinel.dashboard.auth.AuthService;
import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiDefinitionEntity;
import com.alibaba.csp.sentinel.dashboard.datasource.entity.gateway.ApiPredicateItemEntity;
import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo;
import com.alibaba.csp.sentinel.dashboard.domain.Result;
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.AddApiReqVo;
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.ApiPredicateItemVo;
import com.alibaba.csp.sentinel.dashboard.domain.vo.gateway.api.UpdateApiReqVo;
import com.alibaba.csp.sentinel.dashboard.repository.gateway.InMemApiDefinitionStore;
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.beans.factory.annotation.Qualifier;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*;

/**
 * Gateway api Controller for manage gateway api definitions.
 *
 * @author cdfive
 * @since 1.7.0
 */
@RestController
@RequestMapping(value = "/gateway/api")
public class GatewayApiController {

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

    @Autowired
    private InMemApiDefinitionStore repository;

    @Autowired
    private SentinelApiClient sentinelApiClient;

    //-----------新增逻辑,服务于redis持久化---------------
    @Autowired
    @Qualifier("gatewayApiRedisProvider")
    private DynamicRuleProvider<List<ApiDefinitionEntity>> ruleProvider;
    @Autowired
    @Qualifier("gatewayApiRedisPublisher")
    private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher;
    //-----------新增逻辑,服务于redis持久化---------------

    @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");
        }
        // 修改的地方 获取redis里面api的数据
        try {
            List<ApiDefinitionEntity> apis = ruleProvider.getRules(app);
            repository.saveAll(apis);
            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");
        }

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

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

        return Result.ofSuccess(id);
    }

    private boolean publishApis(String app, String ip, Integer port) {
        //-----------新增逻辑,保存redis数据---------------
        List<ApiDefinitionEntity> apis = repository.findAllByApp(app);
        try {
            //修改了redis中的存储及发布修改的规则,客户端app订阅channel后将规则保存在redis中
            rulePublisher.publish(app, apis);
//            GatewayRuleManager
            logger.info("网关添加api成功{}");
        } catch (Exception e) {
            e.printStackTrace();
            logger.warn("publishRules failed Sentinel 推送api到Redis失败>>>>>>>>>>>>>>>>>>>>>>>>", e);
            return false;
        }
        //-----------新增逻辑,保存redis数据---------------
        //核心代码,sentinel-dashboard通过http的形式进行数据推送,客户端接收后将规则保存在本地内存中
        return sentinelApiClient.modifyApis(app, ip, port, apis);
    }
}

在这个类中,我做了以下修改:

  • 引入了以下依赖

//-----------新增逻辑,服务于redis持久化---------------
@Autowired
@Qualifier("gatewayApiRedisProvider")
private DynamicRuleProvider<List<ApiDefinitionEntity>> ruleProvider;
@Autowired
@Qualifier("gatewayApiRedisPublisher")
private DynamicRulePublisher<List<ApiDefinitionEntity>> rulePublisher;
//-----------新增逻辑,服务于redis持久化---------------

这两个就是上文中创建的Redis推送和拉取的类

  • 修改queryApis方法

  • 修改publishApis方法

这两出很重要,修改的时候需要重点关注,本类中其他的修改项不多,可以直接使用比对工具参考修改(如果您使用的是Sentinel 1.8.4,可以直接覆盖)。

至此,sentinel dashboard端的源码就修改完了(你可以执行mvn install 把这个源码编译成jar,然后运行方式和官方推荐方式相同,如果您不知道如何运行,请参考Spring cloud gateway 整合 sentinel 做限流和熔断(最新)解决。

客户端修改(你自己的项目)

这里说明下,网上很多资料写的乱七八糟,啥样的都有,更坑的是官方的文档写的也是很烂(要自己继承AbstractDataSource类,其实不用,因为最新的依赖中已经有一个实现类了,完全可以拿来用)

对于客户端,你需要做的是:

  • application.yml(增加sentinel和Redis配置)

  • pom.xml(增加redis和sentinel的依赖)

  • RedisDataSourceConfig(新增,应用启动增加Sentinel 和 redis 监听)

  • 启动的时候添加  -Dcsp.sentinel.app.type=1

application.yml

请在此文件新增您的redis和sentinel配置,示例如下:

spring:
  cloud:
    sentinel:
      transport: #dashboard地址
        dashboard: localhost:8080  #对应自己的sentinel控制台端口
        port: 8719  #您的项目和sentinel交互使用的端口
    redis:
      database: 0
      host: 127.0.0.1
      port: 6379
      password:
      #jedis:
      lettuce:
        pool:
          max-active: 8
          max-idle: 8
          min-wait: 0
          timeout: 3000

配置这块不多说,redis客户端在于你自己的选择,sentinel配置请按照你自己的sentinel地址来配置

pom.xml

 <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
            <version>2.2.8.RELEASE</version>
        </dependency>
        <!-- 控制台显示 类似 hystrix-dashboard -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
            <version>2.2.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-redis</artifactId>
            <version>1.8.4</version>
        </dependency>

ApiDefinitionDto.java类

package com.xdh.yjmthird.gateway.api;

import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem;

import java.util.Objects;
import java.util.Set;

/**
 * @autheor lingguoxiong
 * @date 2022/10/15 21:41
 */
public class ApiDefinitionDto {
    private String apiName;
    private Set<ApiPathPredicateItem> predicateItems;

    public String getApiName() {
        return this.apiName;
    }

    public ApiDefinitionDto setApiName(String apiName) {
        this.apiName = apiName;
        return this;
    }

    public Set<ApiPathPredicateItem> getPredicateItems() {
        return this.predicateItems;
    }

    public ApiDefinitionDto setPredicateItems(Set<ApiPathPredicateItem> predicateItems) {
        this.predicateItems = predicateItems;
        return this;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        } else if (o != null && this.getClass() == o.getClass()) {
            ApiDefinitionDto that = (ApiDefinitionDto) o;
            return !Objects.equals(this.apiName, that.apiName) ? false : Objects.equals(this.predicateItems, that.predicateItems);
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        int result = this.apiName != null ? this.apiName.hashCode() : 0;
        result = 31 * result + (this.predicateItems != null ? this.predicateItems.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "ApiDefinition{apiName='" + this.apiName + '\'' + ", predicateItems=" + this.predicateItems + '}';
    }
}

RedisDataSourceConfig.java

这里是唯一新增的类(类的位置你想放哪就放那,我不管,只要你老板不打你)

package com.xdh.yjmthird.gateway.config;

import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition;
import com.alibaba.csp.sentinel.adapter.gateway.common.api.GatewayApiDefinitionManager;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
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.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xdh.yjmthird.gateway.api.ApiDefinitionDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @autheor lingguoxiong
 * @date 2022/10/18 22:25
 */
@Slf4j
@Component
public class RedisDataSourceConfig implements ApplicationRunner {

    @Value("${spring.redis.host}")
    public String redisHost;

    @Value("${spring.redis.port}")
    public int redisPort;

    @Value("${spring.redis.password}")
    public String redisPass;

    public Integer database = 0;

    //限流规则key前缀
    public final String GATEWAY_RULE_FLOW = "sentinel_gateway_rule_flow_gateway";
    public final String GATEWAY_RULE_FLOW_CHANNEL = "sentinel_gateway_rule_flow_channel_gateway";

    public final String GATEWAY_API = "sentinel_gateway_api_gateway";
    public final String GATEWAY_API_CHANNEL = "sentinel_gateway_api_channel_gateway";

    //降级规则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 {
        RedisConnectionConfig config = RedisConnectionConfig.builder().withHost(redisHost).withPort(redisPort)
                .withPassword(redisPass).withDatabase(database).build();
        initApis(config);
        initRules(config);
        // Converter<String, List<DegradeRule>> parserDegrade = source -> JSON.parseObject(source,
        // new TypeReference<List<DegradeRule>>() {
        // });
        // ReadableDataSource<String, List<DegradeRule>> redisDataSourceDegrade = new
        // RedisDataSource<>(config, RULE_DEGRADE + SentinelConfig.getAppName(),
        // RULE_DEGRADE_CHANNEL, parserDegrade);
        // DegradeRuleManager.register2Property(redisDataSourceDegrade.getProperty());

        // Converter<String, List<SystemRule>> parserSystem = source -> JSON.parseObject(source, new
        // TypeReference<List<SystemRule>>() {
        // });
        // ReadableDataSource<String, List<SystemRule>> redisDataSourceSystem = new
        // RedisDataSource<>(config, RULE_SYSTEM + SentinelConfig.getAppName(), RULE_SYSTEM_CHANNEL,
        // parserSystem);
        // SystemRuleManager.register2Property(redisDataSourceSystem.getProperty());
        log.info(">>>>>>>>>执行sentinel规则初始化 end。。。");
    }

    private void initApis(RedisConnectionConfig config) throws Exception {
        log.info("执行sentinel网关api初始化 start >>>>>>>>>>>>>");
        Converter<String, Set<ApiDefinition>> parser = source -> JSON.parseObject(source,
                new TypeReference<Set<ApiDefinition>>() {
                });

        ReadableDataSource<String, Set<ApiDefinition>> redisDataSource = new RedisDataSource<>(config, GATEWAY_API,
                GATEWAY_API_CHANNEL, parser);
        GatewayApiDefinitionManager.register2Property(redisDataSource.getProperty());
        // 把redis数据重新拿出来转换 并设置(不然不会生效,里面是有数据的,不知道为什么不生效,可能是末尾资料文章说的没有LISTENER导致的,这个有时间大家自己研究下)
        Set<ApiDefinitionDto> apiDefinitionDtos = JSON.parseObject(redisDataSource.readSource(),
                new TypeReference<Set<ApiDefinitionDto>>() {
                });
        if (apiDefinitionDtos != null) {
            Set<ApiDefinition> apiDefinitions = apiDefinitionDtos.stream().map(apiDefinitionDto -> {
                ApiDefinition apiDefinition = new ApiDefinition();
                BeanUtils.copyProperties(apiDefinitionDto, apiDefinition);
                return apiDefinition;
            }).collect(Collectors.toSet());

            GatewayApiDefinitionManager.loadApiDefinitions(apiDefinitions);
        }

        log.info("执行sentinel网关api初始化 end >>>>>>>>>>>>>");
    }

    private void initRules(RedisConnectionConfig config) throws Exception {
        log.info("执行sentinel规则初始化 start >>>>>>>>>>>>>");
        Converter<String, Set<GatewayFlowRule>> parser = source -> JSON.parseObject(source,
                new TypeReference<Set<GatewayFlowRule>>() {
                });

        ReadableDataSource<String, Set<GatewayFlowRule>> redisDataSource = new RedisDataSource<>(config, GATEWAY_RULE_FLOW,
                GATEWAY_RULE_FLOW_CHANNEL, parser);
        GatewayRuleManager.register2Property(redisDataSource.getProperty());
        // 把redis数据重新拿出来转换 并设置(不然不会生效,里面是有数据的,不知道为什么不生效,可能是末尾资料文章说的没有LISTENER导致的,这个有时间大家自己研究下)
        GatewayRuleManager.register2Property(redisDataSource.getProperty());
        // 下面的这个需要去掉,附件源码里面的也需要去掉,之前其他文章说的不生效是上面 // ApiDefinition转换的问题
       // Set<GatewayFlowRule> sets1 = GatewayRuleManager.getRules();
       // Set<GatewayFlowRule> sets2 = new HashSet();
       // GatewayRuleManager.loadRules(sets2);
       // GatewayRuleManager.loadRules(sets1);

        log.info("执行sentinel规则初始化 end >>>>>>>>>>>>>");
    }
}

这个类没有什么东西,最主要的代码就是初始化一个RedisConnectionConfig,并监听redis中的规则变化情况(注意,注释中的部分分别是熔断规则监听和数据规则监听,他们在sentinel dashboard中的修改本文没涉及,但是完全和限流规则使用方法完全一样,请自己实现),如果您有需要,可以放开此部分注释。

需要注意的是,这个类中引用的redis限流的KEY必须和sentinel dashboard中配置的一致,否则(呵呵,等自己骂娘吧)。

至此,客户端配置完成,分别启动自己的应用和编译好的sintinel dashboard项目,就能正常给你的项目添加限流配置,同时在redis中也能看到规则信息,并且重启您的项目后,sentinel dashboard也能加载出redis中缓存到规则(注意:此处会有一定的延迟,因为sentinel dashboard 需要定时任务去轮询redis中的规则,虽然不想放图毒害大家,但是还是要放上一两张证明我确实搞通了。

 

dashboard 修改的代码 (不需要积分)

https://download.csdn.net/download/Micheal_Bear/86780044

参考文章:

Sentinel的gateway规则持久化改造 - 灰信网(软件开发博客聚合)

sentinel实现gateway网关限流规则持久化 - 灰信网(软件开发博客聚合)

Spring cloud 整合Sentinel 实现redis缓存限流规则(最新一)|Redis 持久化 SentinelCDCN-码上中国博客

Spring cloud 整合Sentinel 实现redis缓存限流规则(最新二)|sentinel 持久化redisCDCN-码上中国博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值