• SpringCloud实战十三:Gateway之 Spring Cloud Gateway 动态路由


    前面分别对 Spring Cloud Zuul 与 Spring Cloud Gateway 进行了简单的说明,它门是API网关,API网关负责服务请求路由、组合及协议转换,客户端的所有请求都首先经过API网关,然后由它将匹配的请求路由到合适的微服务,是系统流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,如果有新的服务要上线时,可以通过动态路由配置功能上线。

    本篇拿 Spring Cloud Gateway 为例,对网关的动态路由进行简单分析,下一篇将分享动态路由的进阶实现

    1.gateway配置路由主要有两种方式,1.用yml配置文件,2.写在代码里。而无论是 yml,还是代码配置,启动网关后将无法修改路由配置,如有新服务要上线,则需要先把网关下线,修改 yml 配置后,再重启网关
    • yml配置方式
      在这里插入图片描述
    • 代码配置方式
      在这里插入图片描述
    2.gateway网关启动时,路由信息默认会加载内存中,路由信息被封装到 RouteDefinition 对象中,配置多个RouteDefinition组成gateway的路由系统,仔细的同学可能看到RouteDefinition中的字段与上面代码配置方式比较对应
    • RouteDefinition对象在 org.springframework.cloud.gateway.route包下,其定义如下:
      在这里插入图片描述
    • RouteDefinitionLocator是个接口,在org.springframework.cloud.gateway.route包下,如果想查看网关中所有的路由信息,调用此接口方法是一个办法,需要从先注入到容器,后面还有另外一种查看方式,是Spring Cloud Gateway 的Endpoint端点提供的方法
      在这里插入图片描述
    3.Spring Cloud Gateway 提供了 Endpoint 端点,暴露路由信息,有获取所有路由、刷新路由、查看单个路由、删除路由等方法,源码在 org.springframework.cloud.gateway.actuate.GatewayControllerEndpoint 中,想访问端点中的方法需要添加 spring-boot-starter-actuator 注解,并在配置文件中暴露所有端点
    # 暴露端点
    management:
      endpoints:
        web:
          exposure:
            include: '*'
      endpoint:
        health:
          show-details: always
    
    4.动态路由代码实现

    前提:需要启动 3个服务,eureka、gateway、consumer-service

    • 1.eureka使用前面博客中的代码
    • 2.consumer-service是个web项目,提供一个hello方法,需注册到eureka上
    • 3.新建gateway,添加引用
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.cloud</groupId>
    	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    • 4.添加基本配置和注册到eureka,不要配置路由信息映射到consumer-service,由后面的动态路由功能路由过去
    • 5.根据Spring Cloud Gateway的路由模型定义数据传输模型,分别是:路由模型、过滤器模型、断言模型
    //1.创建路由模型
    public class GatewayRouteDefinition {
        //路由的Id
        private String id;
        //路由断言集合配置
        private List<GatewayPredicateDefinition> predicates = new ArrayList<>();
        //路由过滤器集合配置
        private List<GatewayFilterDefinition> filters = new ArrayList<>();
        //路由规则转发的目标uri
        private String uri;
        //路由执行的顺序
        private int order = 0;
        //此处省略get和set方法
    }
    
    //2.创建过滤器模型
    public class GatewayFilterDefinition {
        //Filter Name
        private String name;
        //对应的路由规则
        private Map<String, String> args = new LinkedHashMap<>();
        //此处省略Get和Set方法
    }
    
    //3.路由断言模型
    public class GatewayPredicateDefinition {
        //断言对应的Name
        private String name;
        //配置的断言规则
        private Map<String, String> args = new LinkedHashMap<>();
        //此处省略Get和Set方法
    }
    
    • 6.编写动态路由实现类,需实现ApplicationEventPublisherAware接口
    /**
     * 动态路由服务
     */
    @Service
    public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware{
    
        @Autowired
        private RouteDefinitionWriter routeDefinitionWriter;
        private ApplicationEventPublisher publisher;
    
        @Override
        public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
            this.publisher = applicationEventPublisher;
        }
    
    
        //增加路由
        public String add(RouteDefinition definition) {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        }
        //更新路由
        public String update(RouteDefinition definition) {
            try {
                delete(definition.getId());
            } catch (Exception e) {
                return "update fail,not find route  routeId: "+definition.getId();
            }
            try {
                routeDefinitionWriter.save(Mono.just(definition)).subscribe();
                this.publisher.publishEvent(new RefreshRoutesEvent(this));
                return "success";
            } catch (Exception e) {
                return "update route  fail";
            }
        }
        //删除路由
        public Mono<ResponseEntity<Object>> delete(String id) {
            return this.routeDefinitionWriter.delete(Mono.just(id)).then(Mono.defer(() -> {
                return Mono.just(ResponseEntity.ok().build());
            })).onErrorResume((t) -> {
                return t instanceof NotFoundException;
            }, (t) -> {
                return Mono.just(ResponseEntity.notFound().build());
            });
        }
    }
    
    • 7.编写 Rest接口,通过这些接口实现动态路由功能,注意SpringCloudGateway使用的是WebFlux不要引用WebMvc
    @RestController
    @RequestMapping("/route")
    public class RouteController {
    
        @Autowired
        private DynamicRouteServiceImpl dynamicRouteService;
    
        //增加路由
        @PostMapping("/add")
        public String add(@RequestBody GatewayRouteDefinition gwdefinition) {
            String flag = "fail";
            try {
                RouteDefinition definition = assembleRouteDefinition(gwdefinition);
                flag = this.dynamicRouteService.add(definition);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return flag;
        }
        //删除路由
        @DeleteMapping("/routes/{id}")
        public Mono<ResponseEntity<Object>> delete(@PathVariable String id) {
            try {
                return this.dynamicRouteService.delete(id);
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
        //更新路由
        @PostMapping("/update")
        public String update(@RequestBody GatewayRouteDefinition gwdefinition) {
            RouteDefinition definition = assembleRouteDefinition(gwdefinition);
            return this.dynamicRouteService.update(definition);
        }
    
        //把传递进来的参数转换成路由对象
        private RouteDefinition assembleRouteDefinition(GatewayRouteDefinition gwdefinition) {
            RouteDefinition definition = new RouteDefinition();
            definition.setId(gwdefinition.getId());
            definition.setOrder(gwdefinition.getOrder());
    
            //设置断言
            List<PredicateDefinition> pdList=new ArrayList<>();
            List<GatewayPredicateDefinition> gatewayPredicateDefinitionList=gwdefinition.getPredicates();
            for (GatewayPredicateDefinition gpDefinition: gatewayPredicateDefinitionList) {
                PredicateDefinition predicate = new PredicateDefinition();
                predicate.setArgs(gpDefinition.getArgs());
                predicate.setName(gpDefinition.getName());
                pdList.add(predicate);
            }
            definition.setPredicates(pdList);
    
            //设置过滤器
            List<FilterDefinition> filters = new ArrayList();
            List<GatewayFilterDefinition> gatewayFilters = gwdefinition.getFilters();
            for(GatewayFilterDefinition filterDefinition : gatewayFilters){
                FilterDefinition filter = new FilterDefinition();
                filter.setName(filterDefinition.getName());
                filter.setArgs(filterDefinition.getArgs());
                filters.add(filter);
            }
            definition.setFilters(filters);
    
            URI uri = null;
            if(gwdefinition.getUri().startsWith("http")){
                uri = UriComponentsBuilder.fromHttpUrl(gwdefinition.getUri()).build().toUri();
            }else{
                // uri为 lb://consumer-service 时使用下面的方法
                uri = URI.create(gwdefinition.getUri());
            }
            definition.setUri(uri);
            return definition;
        }
    }
    
    • 8.启动项目,查看网关路由信息,访问 localhost:9999/actuator/gateway/routes,因没有配置路由信息,因此返回结果为空数组
      在这里插入图片描述
    • 9.通过Postman发一个 post 请求新增路由,接口地址:http://localhost:9999/route/update,路由到 consumer-service 上,然后通过网关访问查看是否转发请求了(这里直接调用的update,有就会覆盖,没有则新增)
      在这里插入图片描述
    • 10.再访问 localhost:9999/actuator/gateway/routes ,可以看到新的路由信息已经配置进去了,这就是动态路由配置,还可以调用删除、修改接口,操作动态操作路由信息
      在这里插入图片描述
    • 11.配置路由信息后,访问consumer-service服务,正常返回,说明路由已经生效,请求转发到consumer-service服务
      在这里插入图片描述
      好了,动态路由的简单实现了,一般在生产环境不使用此方式,因为网关都是多实例部署,还可能随时增加实例,需要已调用接口的方式一一调用网关所有的实例

    本篇博客参考了许进的文章,地址:http://springcloud.cn/view/407 ,还有他写的 《重新定义SpringCloud》比较不错

    代码已上传至码云,源码,项目使用的版本信息如下:

    - SpringBoot 2.0.6.RELEASE
    - SpringCloud Finchley.SR2
    

    下篇将介绍动态路由的进阶实现,实现方式:

    • 创建一个路由信息维护的项目
    • 实现增删改查路由信息到mysql
    • 然后发布,发布后将路由信息与版本信息保存到redis中,对外提供 rest 接口
    • 网关开启定时任务,定时拉取 rest 接口中最新版本的路由信息,这样网关发布多个实例后,都会单独的去拉取维护的路由信息
      参考了https://blog.csdn.net/tianyaleixiaowu/article/details/83412301
     转自:https://blog.csdn.net/zhuyu19911016520/article/details/86557165
  • 相关阅读:
    动态联编学习:一、多态性和动态联编
    客户信用评分参考
    top、postop、scrolltop、scrollHeight、offsetHeight
    XPath 简单语法
    系统表
    彩信MO,MT的业务流程
    Short Message Peer to Peer
    解决IIS中网站按浏览没反应问题
    比较详细的win2003 IIS6.0 301重定向带参数的问题解决方法
    Fleaphp函数用法举例
  • 原文地址:https://www.cnblogs.com/javalinux/p/14265522.html
Copyright © 2020-2023  润新知