基于Redis对SpringCloudGateWay实现动态化路由

我的个人网站:等不见天亮等时光
  • SpringCloudGateWay的路由存储默认是InMemory,所以只需要改变数据的保存地址即可很方便的解决动态路由
  • 第一种:基于Redis保存
    – 首先SpringCloudGateWay的路由策略类默认为InMemoryRouteDefinitionRepository,实现了RouteDefinitionRepository接口进行的动态路由,那么我们也可以实现此接口
  • 创建RedisRouteDefinitionRepository类,实现RouteDefinitionRepository接口
  • 第二种基于配置中心(Apollo/nacos),基于Apollo的请转到基于Apollo实现对SpringCloudGagtway的动态化路由
@Component
@Slf4j
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    /**
     * 路由保存Key值
     */
    public static final String ROUTE_KEY="ROUTE_KEY";

    @Autowired
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 获取路由配置信息
     *
     * @return
     */
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        log.info("[网关系统]动态路由刷新 start");
        List<RouteDefinition> routeDefinitions=new ArrayList<>();
        redisTemplate.opsForHash().values(ROUTE_KEY).stream().forEach(routeDefinition -> {
            routeDefinitions.add(JSON.parseObject(routeDefinition.toString(),RouteDefinition.class));
        });
        return Flux.fromIterable(routeDefinitions);
    }

    /**
     * 保存路由信息
     *
     * @param route
     * @return
     */
    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return null;
    }

    /**
     * 删除路由信息
     *
     * @param routeId
     * @return
     */
    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return null;
    }
  • 然后将save和delete方法可以提取到其他的微服务模块中,因为这些操作可能涉及到DB,RouteDefinition类是路由类的参数类,定义了服务的id已经路由的策略,具体含义如下

id:服务的serverId,注册到注册中心的Id信息
uri:lb:使用注册中心方式配置;ws:使用websocket方式配置;http:使用http方式配置,如:ws://cloud-cus;
order:优先级数字类型,越小越优先匹配
predicates:请求路径配置,默认可使用/服务名/**
filters:过滤器配置
	限流配置说明
   		name:限流配置服务类默认使用EpayRateLimiter
        key-resolver:限流方式,支持使用apiKeyResolver[API限流]与hostAddrKeyResolver[用户IP地址限流]
        redis-rate-limiter.replenishRate:每秒允许通过的请求数
        redis-rate-limiter.burstCapacity:令牌桶的最大容量
  • 限流策略类如下
/**
     * 用户IP限流
     *
     * @return KeyResolver
     */
    @Bean("hostAddrKeyResolver")
    @Primary
    public KeyResolver hostAddrKeyResolver() {
        return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName());
    }

    /**
     * API路径限流
     * @return apiKeyResolver
     */
    @Bean("apiKeyResolver")
    public KeyResolver apiKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getPath().value());
    }

  • 参考配置如下所示
{
        "id":"test-cus",
        "uri":"lb://test-cus",
        "order":0,
        "predicates":[
           {
               "args":{
                   "pattern":"/test-cus/**"
               },
               "name":"Path"
           }
        ],
        "filters":[
           {
               "name":"RedisRateLimiter",
               "args":{
                   "key-resolver":"#{@hostAddrKeyResolver}",
                   "redis-rate-limiter.replenishRate":"1000",
                   "redis-rate-limiter.burstCapacity":"1000"
               }
           }
        ]
    }
  • 实例化Bean提取为对象
    – GatewayFilterDefinition保存过滤器等信息
    – GatewayPredicateDefinition保存断言信息
    – GatewayRouteDefinition 路由模型信息
@Data
public class GatewayFilterDefinition {

    /**
     * 过滤器名称
     */
    private String name;

    /**
     * 对应的路由规则
     */
    private Map<String, String> args = new LinkedHashMap<>();

}
@Data
public class GatewayPredicateDefinition {

    /**
     * 断言对应的Name
     */
    private String name;
    /**
     * 配置的断言规则
     */
    private Map<String, String> args = new LinkedHashMap<>();

}
@Data
public class GatewayRouteDefinition {

    /**
     * 路由Id唯一
     */
    private String id;
    /**
     * 路由断言集合配置
     */
    private List<GatewayPredicateDefinition> predicates=new ArrayList<>();
    /**
     * 路由过滤器集合配置
     */
    private List<GatewayFilterDefinition> filters=new ArrayList<>();
    /**
     * 元数据
     */
    private Map<String,Object> metadata=new HashMap<>();
    /**
     * 路由规则转发的目标uri
     */
    private String uri;
    /**
     * 路由执行的顺序
     */
    private int order=0;
}
  • 然后是路由对外提供API,可以集成于管理界面操作
@RestController
public class WebRouterManger {

    public static final String ROUTE_KEY="ROUTE_KEY";
    private final RedisTemplate<String,Object> redisTemplate;

    private final GatewayRoutesMapper routeMapper;

    public WebRouterManger(GatewayRoutesMapper routeMapper,RedisTemplate<String,Object> redisTemplate) {
        this.routeMapper=routeMapper;
        this.redisTemplate=redisTemplate;
    }

    @GetMapping("/router")
    public Flux<GatewayRouteDefinition> list() {
        List<GatewayRouteDefinition> routeDefinitions=new ArrayList<>();
        redisTemplate.opsForHash().values(ROUTE_KEY).forEach(routeDefinition -> routeDefinitions.add(JSON.parseObject(routeDefinition.toString(),GatewayRouteDefinition.class)));
        return Flux.fromIterable(routeDefinitions);
    }


    /**
     * 路由保存
     *
     * @param routeDefinition 路由信息
     * @return CommonResult
     */
    @PostMapping("/router")
    @Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=Exception.class)
    public Mono<CommonResult> save(@RequestBody GatewayRouteDefinition routeDefinition) {
        //判断是否存在
        List<GatewayRoutes> routesList=routeMapper.selectByRouteName(routeDefinition.getId());
        if (!CollectionUtils.isEmpty(routesList)) {
            for (GatewayRoutes gatewayRoutes : routesList) {
                int i=routeMapper.deleteByPrimaryKey(gatewayRoutes.getId());
                if (i < 0) {
                    throw new RouterException(HttpStatus.INTERNAL_SERVER_ERROR.value(),"路由保存失败");
                }
            }
        }
        GatewayRoutes gatewayRoutes=new GatewayRoutes();
        gatewayRoutes.setCreateTime(new Date());
        gatewayRoutes.setFilters(JSONArray.toJSONString(routeDefinition.getFilters()));
        gatewayRoutes.setRouteId(routeDefinition.getId());
        gatewayRoutes.setRouteUri(routeDefinition.getUri());
        gatewayRoutes.setRouteOrder(routeDefinition.getOrder());
        gatewayRoutes.setPredicates(JSONArray.toJSONString(routeDefinition.getPredicates()));
        gatewayRoutes.setRouteStatus("00");
        gatewayRoutes.setMetadata(JSON.toJSONString(routeDefinition.getMetadata()));
        if (1 == routeMapper.insertSelective(gatewayRoutes)) {
            redisTemplate.opsForHash().put(ROUTE_KEY,routeDefinition.getId(),JSON.toJSONString(routeDefinition));
        }
        return Mono.just(CommonResult.success(""));
    }

    /**
     * 路由删除
     *
     * @param routeId 微服务Id
     * @param id      列表查询到的主键id
     * @return CommonResult
     */
    @DeleteMapping("/router")
    @Transactional(propagation=Propagation.REQUIRES_NEW, rollbackFor=Exception.class)
    public Mono<CommonResult> delete(@RequestParam("routeId") String routeId,@RequestParam("id") Long id) {

        if (routeMapper.deleteByPrimaryKey(id) == 1) {
            if (redisTemplate.opsForHash().hasKey(ROUTE_KEY,routeId)) {
                redisTemplate.opsForHash().delete(ROUTE_KEY,routeId);
            }
        }
        return Mono.just(CommonResult.success(""));
    }
  • 路由数据保存到数据库,做二级缓存兜底,数据库SQL
CREATE TABLE `gateway_routes` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `route_id` varchar(64) DEFAULT NULL COMMENT '路由id',
  `route_uri` varchar(128) DEFAULT NULL COMMENT '转发目标uri',
  `route_order` int(11) DEFAULT NULL COMMENT '路由执行顺序',
  `predicates` text DEFAULT NULL COMMENT '断言字符串集合,字符串结构:[{\r\n                "name":"Path",\r\n                "args":{\r\n                   "pattern" : "/zy/**"\r\n                }\r\n              }]',
  `metadata` text DEFAULT NULL COMMENT '元数据 JSON格式',
  `filters` text DEFAULT NULL COMMENT '过滤器字符串集合,字符串结构:{\r\n              	"name":"StripPrefix",\r\n              	 "args":{\r\n              	 	"_genkey_0":"1"\r\n              	 }\r\n              }',
  `route_status` char(2) DEFAULT '' COMMENT '00 正常 99 失效',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8mb4;
  • 路由刷新时间实际上取的是eureka的探活时间,eureka.client.registry-fetch-interval-seconds,设定60秒后会执行Redis的路由刷新
  • 基于Apollo与nacos配置中心这种形式的会更加的便捷,可以考虑使用配置中心做动态化路由,实现方式也比较简单
  • 只需要将json配置保存后转换为RouteDefinition 然后调用RefreshRoutesEvent重加载路由信息即可,推荐使用这种方式,后面有时间会写一个比较详细点的基于Apollo或nacos的动态路由
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring Cloud Gateway中,你可以使用Redis实现动态获取Gateway路由信息。下面是一个基本的实现思路: 1. **配置Redis**:首先,确保你已经正确配置了Redis,并能够通过Spring Boot进行连接和操作。你可以参考Spring Data Redis的官方文档来了解如何配置和使用Redis。 2. **创建自定义的RouteDefinitionLocator**:创建一个自定义的`RouteDefinitionLocator`实现,用于从Redis中获取路由信息。这个实现类需要实现`getRouteDefinitions()`方法,并在该方法中从Redis中获取路由配置信息。 ```java @Component public class RedisRouteDefinitionLocator implements RouteDefinitionLocator { private final RedisTemplate<String, Object> redisTemplate; public RedisRouteDefinitionLocator(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } @Override public Flux<RouteDefinition> getRouteDefinitions() { List<RouteDefinition> routeDefinitions = new ArrayList<>(); // 从Redis中获取路由配置信息,并将其转换为RouteDefinition对象 // 将解析后的RouteDefinition对象添加到routeDefinitions列表中 return Flux.fromIterable(routeDefinitions); } } ``` 3. **配置Gateway使用自定义的RouteDefinitionLocator**:在配置类中,将自定义的`RouteDefinitionLocator`注册为Bean,并将其设置为Gateway的`RouteDefinitionLocator`。 ```java @Configuration public class GatewayConfig { @Bean public RedisTemplate<String, Object> redisTemplate() { // 创建和配置RedisTemplate实例 return redisTemplate; } @Bean public RouteDefinitionLocator routeDefinitionLocator(RedisTemplate<String, Object> redisTemplate) { return new RedisRouteDefinitionLocator(redisTemplate); } @Bean public RouteDefinitionLocatorComposite routeDefinitionLocatorComposite(RouteDefinitionLocator... locators) { return new RouteDefinitionLocatorComposite(Arrays.asList(locators)); } } ``` 在上述配置中,`RedisTemplate`是一个用于与Redis进行交互的Spring Data Redis的组件。你可以根据实际情况进行配置和使用。 4. **配置Redis中的路由信息**:在Redis中存储路由信息,可以使用Hash结构来存储每个路由的详细信息,例如路由ID、URI、谓词、过滤器等。 ```shell HSET gateway_routes route1 '{ "id": "route1", "uri": "http://example.com", "predicates": [{ "name": "Path", "args": { "pattern": "/example" } }] }' HSET gateway_routes route2 '{ "id": "route2", "uri": "http://example.org", "predicates": [{ "name": "Path", "args": { "pattern": "/example2" } }] }' ``` 上述示例中,使用Hash结构存储了两个路由的详细信息,每个路由信息以JSON格式表示。 通过以上步骤,你就可以通过Redis动态获取Gateway路由信息了。每当路由配置信息在Redis中发生变化时,Gateway会自动重新加载路由信息。 需要注意的是,上述示例仅提供了一个基本的思路和示例代码,你可能需要根据实际需求进行适当的调整和扩展。 希望以上内容对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值