栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Redis + Gateway 实现动态网关路由,放弃yml

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Redis + Gateway 实现动态网关路由,放弃yml

Gateway 简介

字面意思就是网关,它的主要作用是路由转发,鉴权,限流等等。它的主要核心是Route,Predicate,Filter。这三大核心呢也是后面实现动态路由的关键。

Gateway 引入

这里呢我项目中使用的是spring cloud gateway。

        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
Gateway 配置 Gateway 基于yml配置

要想学会动态路由,那我们首先要知道Gateway相关的组成部分

spring:
  application:
    name: smart-cms-gateway
  profiles:
    active: dev
  cloud:
    nacos:
      discovery:
#        namespace: ${config.register.namespace} # 命名空间
        server-addr: http://localhost:8848/ # 配置nacos注册中心
    gateway:
      discovery:
        locator:
          enabled: true # 让网关可以发现nacos中的微服务
          lower-case-service-id: true
      # 跨域配置,vue前端需要加changeOrigin=true
      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: '*'
            exposedHeaders:
              - content-type
            allowCredentials: true
            allowedMethods:
              - GET
              - OPTIONS
              - PUT
              - DELETe
              - POST
        routes:
        - id: smart-auth
          # lb代表从注册中心获取服务,且已负载均衡方式转发
          uri: lb://smart-cms-auth
          predicates:
            - Path=/smart-auth/**
#          # 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
          filters:
            - StripPrefix=1
            - name: RequestRateLimiter #请求数限流
              args:
                key-resolver: "#{@pathKeyResolver}" # 系统配置文件 com.smart.cms.ratelimiter.RateConfiguration 下有多种限流方式,也可以自己写实现
                redis-rate-limiter.replenishRate: 1  #令牌桶每秒填充平均速率
                redis-rate-limiter.burstCapacity: 10  #令牌桶总容量

由上图可知,在到达路由前的定位是locator,这个词很重要,然后代码中也有注释,自行解读。

Gateway 底层源码分析

先看下图

 RouteDefinitionLocator 是路由定义定位器的顶级接口,它的主要作用就是读取路由的配置信息,会发现他有五种实现方式,其中四种是实现类,一个是接口类,为什么这么设计呢,是因为Spring对于开发人员来说有一点是非常好的,那就是扩展性,可以让程序员随自己发挥。RouteDefinitionRepository也是今天的主角了,其他的四种实现方式呢,就不做过多介绍了,字面翻译过来就能理解,尤其是和yml配置一起解读的话会更通俗易懂。RouteDefinitionRepository继承了RouteDefinitionLocator和RouteDefinitionWriter,因此实现了路由的增删改查。所以如果我们要实现动态路由就需要实现RouteDefinitionRepository,然后重写它的增删改查方法。

动态路由具体实现

实现动态路由就需要实现RouteDefinitionRepository,本文是基于redis实现,读者可自行选择其他方式

@Component
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public Flux getRouteDefinitions() {
        // 也可以采用map存储,没有做过分布式下redis和内存的对比,有空可以试试
        List routeDefinitions = new ArrayList<>();
        redisTemplate.opsForHash().values(GlobalConstants.GATEWAY_ROUTES).stream().forEach(routeDefinition -> {
            routeDefinitions.add(JSON.parseObject(routeDefinition.toString(), RouteDefinition.class));
        });
        return Flux.fromIterable(routeDefinitions);
    }

    @Override
    public Mono save(Mono route) {
        return route
                .flatMap(routeDefinition -> {
                    redisTemplate.opsForHash().put(GlobalConstants.GATEWAY_ROUTES, routeDefinition.getId(),
                            JSON.toJSONString(routeDefinition));
                    return Mono.empty();
                });
    }

    @Override
    public Mono delete(Mono routeId) {
        return routeId.flatMap(id -> {
            if (redisTemplate.opsForHash().hasKey(GlobalConstants.GATEWAY_ROUTES, id)) {
                redisTemplate.opsForHash().delete(GlobalConstants.GATEWAY_ROUTES, id);
                return Mono.empty();
            }
            return Mono.defer(() -> Mono.error(new RuntimeException("路由文件没有找到: " + routeId)));
        });
    }
}

实现了基本的代码后,我们需要将它进行发布,这里是设置的重点,就是我们开头说到的三大核心Route,Predicate,Filte

@Component
public class GatewayRouteAdvice implements ApplicationEventPublisherAware, CommandLineRunner {
    @Autowired
    private RedisRouteDefinitionRepository routeDefinitionWriter;

    private ApplicationEventPublisher publisher;
    @Autowired
    private GatewayRouteService gatewayRouteService;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    @Override
    public void run(String... args){
        //从数据库拿到路由配置
        List gatewayRouteList = gatewayRouteService.lambdaQuery().eq(GatewayRateLimiter::getEnabled, 1).list();

        log.info("网关配置信息:=====>"+ JSON.toJSONString(gatewayRouteList));
        gatewayRouteList.forEach(gatewayRoute -> {
            RouteDefinition definition = new RouteDefinition();
            // 该参数必须唯一
            definition.setId(gatewayRoute.getPath());
            URI uri = UriComponentsBuilder.fromUriString("lb://"+gatewayRoute.getServiceId()).build().toUri();
            definition.setUri(uri);

            PredicateDefinition predicate = new PredicateDefinition();
            predicate.setName("Path");

            Map predicateParams = new HashMap<>(8);
            predicateParams.put("pattern", "/"+gatewayRoute.getPath()+"/**");
            predicate.setArgs(predicateParams);

            FilterDefinition filterDefinition2 = new FilterDefinition();
            // 一个 -属性名称为一个FilterDefinition,下面的参数为HashMap
            filterDefinition2.setName("StripPrefix");
            Map filterParams2 = new HashMap<>(8);
            // 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
            filterParams2.put("_genkey_0", "1");
            filterDefinition2.setArgs(filterParams2);
            // 路径去前缀
            FilterDefinition filterDefinition = new FilterDefinition();
            // 该参数为唯一值,不可变,且下列配置为限流配置
            filterDefinition.setName("RequestRateLimiter");
            Map filterParams = new HashMap<>(8);
            // 令牌桶流速
            filterParams.put("redis-rate-limiter.replenishRate", gatewayRoute.getLimiterRate());
            //令牌桶容量
            filterParams.put("redis-rate-limiter.burstCapacity", gatewayRoute.getLimiterCapacity());
            // 限流策略(#{@BeanName})
            filterParams.put("key-resolver", "#{@"+gatewayRoute.getKeyResolver()+"}");
            filterDefinition.setArgs(filterParams);

            definition.setFilters(Arrays.asList(filterDefinition, filterDefinition2));
            definition.setPredicates(Arrays.asList(predicate));
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        });
        // 配置信息加载后后,发起一次RefreshRoutesEvent事件即可让配置生效,这个步骤是最重要的,也是核心
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }


    public void deleteRoute(String routeId){
        routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
    }
}

以上就是动态路由实现的具体过程了,欢迎大家来探讨。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1035641.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号