zoukankan      html  css  js  c++  java
  • Spring Cloud 网关服务 zuul 三 动态路由

    zuul动态路由

    网关服务是流量的唯一入口。不能随便停服务。所以动态路由就显得尤为必要。

    数据库动态路由基于事件刷新机制热修改zuul的路由属性。

    DiscoveryClientRouteLocator

    file

    可以看到DiscoveryClientRouteLocator 是默认的刷新的核心处理类。

    
    //重新加载路由信息方法 protected方法。需要子方法重新方法。
    protected LinkedHashMap<String, ZuulRoute> locateRoutes() 
    
    //触发刷新的方法  RefreshableRouteLocator 接口
     public void refresh() {
    			this.doRefresh();
    	}
    
    

    而这俩个方法都是继承与SimpleRouteLocator 类,并进行了重新操作。其实官方的方法注释说明了。如果需要动态读取加载映射关系。则需要子类重写这俩个方法。
    进行具体的实现

    首先pom jar包导入 需要连接mysql 数据库

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <!-- jdbc -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
    

    路由实体 ZuulRouteEntity

    package com.xian.cloud.entity;
    
    import lombok.Data;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * <Description> 路由实体类
     *
     * @author xianliru@100tal.com
     * @version 1.0
     * @createDate 2019/10/30 15:00
     */
    @Data
    public class ZuulRouteEntity implements Serializable {
        private static final long serialVersionUID = 1L;
    
        /**
         * router Id
         */
        private Integer id;
        /**
         * 路由路径
         */
        private String path;
        /**
         * 服务名称
         */
        private String serviceId;
        /**
         * url代理
         */
        private String url;
        /**
         * 转发去掉前缀
         */
        private String stripPrefix;
        /**
         * 是否重试
         */
        private String retryable;
        /**
         * 是否启用
         */
        private String enabled;
        /**
         * 敏感请求头
         */
        private String sensitiveheadersList;
        /**
         * 创建时间
         */
        private Date createTime;
        /**
         * 更新时间
         */
        private Date updateTime;
        /**
         * 删除标识(0-正常,1-删除)
         */
        private String delFlag;
    }
    
    

    新建DiscoveryRouteLocator 类 父类 接口 都不变化

    
    package com.xian.cloud.router;
    
    
    import com.google.common.base.Strings;
    import com.google.common.collect.Sets;
    import com.xian.cloud.entity.ZuulRoute;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
    import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
    import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.util.StringUtils;
    
    import java.util.*;
    
    /**
     * <Description>
     *
     * @author xianliru@100tal.com
     * @version 1.0
     * @createDate 2019/10/30 18:57
     */
    @Slf4j
    public class DiscoveryRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
    
        private ZuulProperties properties;
    
        private JdbcTemplate jdbcTemplate;
    
        public DiscoveryRouteLocator(String servletPath, ZuulProperties properties, JdbcTemplate jdbcTemplate) {
            super(servletPath, properties);
            this.properties = properties;
            this.jdbcTemplate = jdbcTemplate;
            log.info("servletPath:{}",servletPath);
        }
    
        @Override
        public void refresh() {
            doRefresh();
        }
    
        @Override
        protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
            LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<String, ZuulProperties.ZuulRoute>();
            //从配置文件中加载路由信息
            routesMap.putAll(super.locateRoutes());
            //自定义加载路由信息
            routesMap.putAll(getRouteList());
            //优化一下配置
            LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
            for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
                String path = entry.getKey();
                // Prepend with slash if not already present.
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
                if (StringUtils.hasText(this.properties.getPrefix())) {
                    path = this.properties.getPrefix() + path;
                    if (!path.startsWith("/")) {
                        path = "/" + path;
                    }
                }
                values.put(path, entry.getValue());
            }
            return values;
        }
    
        /**
         * 从数据库读取zuul路由规则
         * @return
         */
       private LinkedHashMap<String, ZuulProperties.ZuulRoute> getRouteList() {
            LinkedHashMap<String, ZuulProperties.ZuulRoute> zuulRoutes = new LinkedHashMap<>();
            List<ZuulRoute> sysZuulRoutes = jdbcTemplate.query("select * from sys_zuul_route where del_flag = 0", new BeanPropertyRowMapper<>(ZuulRoute.class));
    
           for (ZuulRoute route: sysZuulRoutes) {
    
               // 为空跳过
               if (Strings.isNullOrEmpty(route.getPath()) && Strings.isNullOrEmpty(route.getUrl())) {
                   continue;
               }
    
               ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
               try {
                   zuulRoute.setId(route.getServiceId());
                   zuulRoute.setPath(route.getPath());
                   zuulRoute.setServiceId(route.getServiceId());
                   zuulRoute.setRetryable(Objects.equals("0", route.getRetryable()) ? Boolean.FALSE : Boolean.TRUE);
                   zuulRoute.setStripPrefix(Objects.equals("0", route.getStripPrefix()) ? Boolean.FALSE : Boolean.TRUE);
                   zuulRoute.setUrl(route.getUrl());
                   List<String> sensitiveHeadersList = Arrays.asList(route.getSensitiveheadersList().split(","));
                   if (sensitiveHeadersList != null) {
                       Set<String> sensitiveHeaderSet = Sets.newHashSet();
                       sensitiveHeadersList.forEach(sensitiveHeader -> sensitiveHeaderSet.add(sensitiveHeader));
                       zuulRoute.setSensitiveHeaders(sensitiveHeaderSet);
                       zuulRoute.setCustomSensitiveHeaders(true);
                   }
               } catch (Exception e) {
                   log.error("数据库加载配置异常", e);
               }
               log.info("自定义的路由配置,path:{},serviceId:{}", zuulRoute.getPath(), zuulRoute.getServiceId());
               zuulRoutes.put(zuulRoute.getPath(), zuulRoute);
    
           }
            return zuulRoutes;
       }
    }
    
    

    我们还需要一个事件的生产者 和 消费者 直接图方便 集成到一个类中

    package com.xian.cloud.event;
    
    import com.xian.cloud.router.DiscoveryRouteLocator;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
    import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
    import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
    import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
    import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
    import org.springframework.context.ApplicationEvent;
    import org.springframework.context.ApplicationEventPublisher;
    import org.springframework.context.ApplicationListener;
    import org.springframework.context.event.ContextRefreshedEvent;
    import org.springframework.stereotype.Service;
    
    /**
     * <Description> 路由刷新事件发布,与事件监听者
     *
     * @author xianliru@100tal.com
     * @version 1.0
     * @createDate 2019/10/30 15:27
     */
    @Service
    public class RefreshRouteService implements ApplicationListener<ApplicationEvent> {
    
        @Autowired
        private ZuulHandlerMapping zuulHandlerMapping;
    
        private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
    
        @Autowired
        ApplicationEventPublisher publisher;
    
        @Autowired
        private DiscoveryRouteLocator dynamicRouteLocator;
    
        /**
         * 动态路由实现 调用refreshRoute() 发布刷新路由事件
         */
        public void refreshRoute() {
            RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(dynamicRouteLocator);
            publisher.publishEvent(routesRefreshedEvent);
        }
    
        /**
         * 事件监听者。监控检测事件刷新
         * @param event
         */
        @Override
        public void onApplicationEvent(ApplicationEvent event) {
            if(event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent){
                //主动手动刷新。上下文刷新,配置属性刷新
                zuulHandlerMapping.setDirty(true);
            }else if(event instanceof HeartbeatEvent){
                //心跳触发,将本地映射关系。关联到远程服务上
                HeartbeatEvent heartbeatEvent = (HeartbeatEvent)event;
                if(heartbeatMonitor.update(heartbeatEvent.getValue())){
                    zuulHandlerMapping.setDirty(true);
                }
            }
        }
    }
    

    对外提供触发接口

    package com.xian.cloud.controller;
    
    import com.xian.cloud.event.RefreshRouteService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * <Description> 手动刷新对外接口
     *
     * @author xianliru@100tal.com
     * @version 1.0
     * @createDate 2019/10/30 20:23
     */
    @RestController
    public class RefreshController {
    
        @Autowired
        private RefreshRouteService refreshRouteService;
    
        @GetMapping("/refresh")
        public String refresh() {
            refreshRouteService.refreshRoute();
            return "refresh";
        }
    
    }
    
    

    数据库表结构

    CREATE TABLE `sys_zuul_route` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'router Id',
      `path` varchar(255) NOT NULL COMMENT '路由路径',
      `service_id` varchar(255) NOT NULL COMMENT '服务名称',
      `url` varchar(255) DEFAULT NULL COMMENT 'url代理',
      `strip_prefix` char(1) DEFAULT '1' COMMENT '转发去掉前缀',
      `retryable` char(1) DEFAULT '1' COMMENT '是否重试',
      `enabled` char(1) DEFAULT '1' COMMENT '是否启用',
      `sensitiveHeaders_list` varchar(255) DEFAULT NULL COMMENT '敏感请求头',
      `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      `del_flag` char(1) DEFAULT '0' COMMENT '删除标识(0-正常,1-删除)',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='动态路由配置表'
    

    file

    将配置文件client 消费者服务 路由配置注释掉。设置数据源。从数据库中读取

    file

    启动服务打印日志

    2019-10-30 20:49:39.946  INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator  : 添加数据库自定义的路由配置,path:/client/**,serviceId:cloud-discovery-client
    2019-10-30 20:49:40.397  INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator  : 添加数据库自定义的路由配置,path:/client/**,serviceId:cloud-discovery-client
    

    postman 请求client 接口 看看是否能转发成功
    file

    基于zuul 动态网关路由完成。

    后续还会更新网关的灰度方案、swagger2 整合的调试源服务。敬请期待!

    摘自参考 spring cloud 官方文档

    参考书籍 重新定义spring cloud实战

    示例代码地址

    服务器nacos 地址 http://47.99.209.72:8848/nacos

    往期地址 spring cloud alibaba 地址

    spring cloud alibaba 简介

    Spring Cloud Alibaba (nacos 注册中心搭建)

    Spring Cloud Alibaba 使用nacos 注册中心

    Spring Cloud Alibaba nacos 配置中心使用

    spring cloud 网关服务

    Spring Cloud zuul网关服务 一

    Spring Cloud 网关服务 zuul 二

    如何喜欢可以关注分享本公众号。
    file

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。转载请附带公众号二维码

  • 相关阅读:
    Webx5 label的取值和赋值(div)
    webx5 复选框的显示
    WebX5 Data遍历以及获取数组最后一个元素(更新)
    API集市--分享API
    WebX5 Data判断当前行的值是否改变,以及改变当前行的状态
    WebX5手机GPS定位,无需开启网络
    JS:var 变量=变量 && 变量
    jQuery的ready ()的几种写法
    .net 微信支付(公众号支付)遇到的问题
    .net中的Dictionary字典类的使用方法
  • 原文地址:https://www.cnblogs.com/cloudxlr/p/11769195.html
Copyright © 2011-2022 走看看