Spring Cloud Zuul路由动态配置

简介: Spring Cloud Zuul路由动态配置

目录


  • Zuul配置
  • 在mysql中创建路由信息表
  • 定义CustomRouteLocator类
  • 增加CustomZuulConfig类,主要是为了配置CustomRouteLocator
  • RefreshRouteService类,用于实现数据库路由信息的刷新
  • 当然也要提供RefreshController,提供从浏览器访问的刷新功能
  • 问题
  • 后记


上一篇初步记录了Spring Cloud GateWay的动态路由配置,这一篇说一下Zuul的动态路由配置。Zuul 是 Netflix 开源的微服务网关,Spring Cloud 对 Zuul 进行了整合和增强。在 SpringCloud 体系中,Zuul 担任着网关的角色,对发送到服务端的请求进行一些预处理,比如安全验证、动态路由、负载分配等。


还是那句话,由于水平有限,难免有不当或者错误之处,请大家指正,谢谢。


Zuul配置


一般的,我们如果使用Spring Cloud Zuul 进行路由配置,类似于下面的样子:


  1. zuul:
  2.  routes:
  3.    users:
  4.      path:/myusers/**
  5.      stripPrefix:false


当我们要新增或者改变一个网关路由时,我们不得不停止网关服务,修改配置文件,保存再重新启动网关服务,这样才能让我们新的设置生效。


设想一样,如果是在生产环境,为了一个小小的路由变更,这样的停止再重启恐怕谁也受不了吧。接下来,看看我们怎么能做到动态配置网关路由,让网关路由配置在服务不需要重启的情况生效。(废话一堆啊)


在mysql中创建路由信息表,对于类如下:


  1. publicstaticclassZuulRouteVO{

  2.        /**
  3.         * The ID of the route (the same as its map key by default).
  4.         */
  5.        privateString id;

  6.        /**
  7.         * The path (pattern) for the route, e.g. /foo/**.
  8.         */
  9.        privateString path;

  10.        /**
  11.         * The service ID (if any) to map to this route. You can specify a physical URL or
  12.         * a service, but not both.
  13.         */
  14.        privateString serviceId;

  15.        /**
  16.         * A full physical URL to map to the route. An alternative is to use a service ID
  17.         * and service discovery to find the physical address.
  18.         */
  19.        privateString url;

  20.        /**
  21.         * Flag to determine whether the prefix for this route (the path, minus pattern
  22.         * patcher) should be stripped before forwarding.
  23.         */
  24.        privateboolean stripPrefix =true;

  25.        /**
  26.         * Flag to indicate that this route should be retryable (if supported). Generally
  27.         * retry requires a service ID and ribbon.
  28.         */
  29.        privateBoolean retryable;

  30.        privateBoolean enabled;

  31.        publicString getId(){
  32.            return id;
  33.        }

  34.        publicvoid setId(String id){
  35.            this.id = id;
  36.        }

  37.        publicString getPath(){
  38.            return path;
  39.        }

  40.        publicvoid setPath(String path){
  41.            this.path = path;
  42.        }

  43.        publicString getServiceId(){
  44.            return serviceId;
  45.        }

  46.        publicvoid setServiceId(String serviceId){
  47.            this.serviceId = serviceId;
  48.        }

  49.        publicString getUrl(){
  50.            return url;
  51.        }

  52.        publicvoid setUrl(String url){
  53.            this.url = url;
  54.        }

  55.        publicboolean isStripPrefix(){
  56.            return stripPrefix;
  57.        }

  58.        publicvoid setStripPrefix(boolean stripPrefix){
  59.            this.stripPrefix = stripPrefix;
  60.        }

  61.        publicBoolean getRetryable(){
  62.            return retryable;
  63.        }

  64.        publicvoid setRetryable(Boolean retryable){
  65.            this.retryable = retryable;
  66.        }

  67.        publicBoolean getEnabled(){
  68.            return enabled;
  69.        }

  70.        publicvoid setEnabled(Boolean enabled){
  71.            this.enabled = enabled;
  72.        }
  73.    }


定义CustomRouteLocator类


CustomRouteLocator集成SimpleRouteLocator,实现了RefreshableRouteLocator接口


  1. publicclassCustomRouteLocatorextendsSimpleRouteLocatorimplementsRefreshableRouteLocator{

  2.    publicfinalstaticLogger logger =LoggerFactory.getLogger(CustomRouteLocator.class);

  3.    privateJdbcTemplate jdbcTemplate;

  4.    privateZuulProperties properties;

  5.    publicvoid setJdbcTemplate(JdbcTemplate jdbcTemplate){
  6.        this.jdbcTemplate = jdbcTemplate;
  7.    }

  8.    publicCustomRouteLocator(String servletPath,ZuulProperties properties){

  9.        super(servletPath, properties);
  10.        this.properties = properties;
  11.        System.out.println(properties.toString());
  12.        logger.info("servletPath:{}", servletPath);
  13.    }

  14.    @Override
  15.    publicvoid refresh(){
  16.        doRefresh();
  17.    }

  18.    @Override
  19.    protectedMap<String,ZuulProperties.ZuulRoute> locateRoutes(){
  20.        LinkedHashMap<String,ZuulProperties.ZuulRoute> routesMap =newLinkedHashMap<>();
  21.        System.out.println("start "+newDate().toLocaleString());
  22.        //从application.properties中加载路由信息
  23.        routesMap.putAll(super.locateRoutes());
  24.        //从db中加载路由信息
  25.        routesMap.putAll(locateRoutesFromDB());
  26.        //优化一下配置
  27.        LinkedHashMap<String,ZuulProperties.ZuulRoute> values =newLinkedHashMap<>();
  28.        for(Map.Entry<String,ZuulProperties.ZuulRoute> entry : routesMap.entrySet()){
  29.            String path = entry.getKey();
  30.            System.out.println(path);
  31.            // Prepend with slash if not already present.
  32.            if(!path.startsWith("/")){
  33.                path ="/"+ path;
  34.            }
  35.            if(StringUtils.hasText(this.properties.getPrefix())){
  36.                path =this.properties.getPrefix()+ path;
  37.                if(!path.startsWith("/")){
  38.                    path ="/"+ path;
  39.                }
  40.            }
  41.            values.put(path, entry.getValue());
  42.        }
  43.        return values;
  44.    }

  45.    privateMap<String,ZuulProperties.ZuulRoute> locateRoutesFromDB(){
  46.        Map<String,ZuulProperties.ZuulRoute> routes =newLinkedHashMap<>();
  47.        List<ZuulRouteVO> results = jdbcTemplate.query("select * from gateway_api_define where enabled = true ",new
  48.                BeanPropertyRowMapper<>(ZuulRouteVO.class));
  49.        for(ZuulRouteVO result : results){
  50.            if(StringUtils.isEmpty(result.getPath())){
  51.                continue;
  52.            }
  53.            if(StringUtils.isEmpty(result.getServiceId())&&StringUtils.isEmpty(result.getUrl())){
  54.                continue;
  55.            }
  56.            ZuulProperties.ZuulRoute zuulRoute =newZuulProperties.ZuulRoute();
  57.            try{
  58.                BeanUtils.copyProperties(result, zuulRoute);
  59.            }catch(Exception e){
  60.                logger.error("=============load zuul route info from db with error==============", e);
  61.            }
  62.            routes.put(zuulRoute.getPath(), zuulRoute);
  63.        }
  64.        return routes;
  65.    }  
  66. }


主要的是locateRoutes和locateRoutesFromDB这两个函数,locateRoutes是从SimpleRouteLocator Override过来的,先装载配置文件里面的路由信息,在从数据库里面获取路由信息,最后都是保存在SimpleRoteLocator 的AtomicReference<map> routes属性中,注意routes是类型,它是可以保证线程俺去的。</map


增加CustomZuulConfig类,主要是为了配置CustomRouteLocator


  1. @Configuration
  2. publicclassCustomZuulConfig{
  3.    @Autowired
  4.    ZuulProperties zuulProperties;
  5.    @Autowired
  6.    ServerProperties server;
  7.    @Autowired
  8.    JdbcTemplate jdbcTemplate;

  9.    @Bean
  10.    publicCustomRouteLocator routeLocator(){
  11.        CustomRouteLocator routeLocator =newCustomRouteLocator(this.server.getServlet().getPath(),this.zuulProperties);
  12.        routeLocator.setJdbcTemplate(jdbcTemplate);
  13.        return routeLocator;
  14.    }
  15. }


CustomerRouteLocator 去数据库获取路由配置信息,需要一个JdbcTemplate Bean。


this.zuulProperties 就是配置文件里面的路由配置,应该是网关服务启动时自动就获取过来的。


RefreshRouteService类,用于实现数据库路由信息的刷新


  1. @Service
  2. publicclassRefreshRouteService{
  3.    @Autowired
  4.    ApplicationEventPublisher publisher;

  5.    @Autowired
  6.    RouteLocator routeLocator;

  7.    publicvoid refreshRoute(){
  8.        RoutesRefreshedEvent routesRefreshedEvent =newRoutesRefreshedEvent(routeLocator);
  9.        publisher.publishEvent(routesRefreshedEvent);

  10.    }
  11. }


当然也要提供RefreshController,提供从浏览器访问的刷新功能


  1. @RestController
  2. publicclassRefreshController{
  3.    @Autowired
  4.    RefreshRouteService refreshRouteService;

  5.    @Autowired
  6.    ZuulHandlerMapping zuulHandlerMapping;

  7.    @GetMapping("/refreshRoute")
  8.    publicString refresh(){
  9.        refreshRouteService.refreshRoute();
  10.        return"refresh success";
  11.    }

  12.    @RequestMapping("/watchRoute")
  13.    publicObject watchNowRoute(){
  14.        //可以用debug模式看里面具体是什么
  15.        return zuulHandlerMapping.getHandlerMap();
  16.    }
  17. }

上面两个实现的功能是,在数据库里面新增或者修改路由信息,通过上面的功能进行刷新。

问题

网关服务跑起来了,也能实现正常的路由功能。但是,等等,查看日志,发现每隔30秒,服务自动从数据库再次加载路由配置,这是为什么呢?

这个问题在于ZuulRefreshListener 这个类,这个类j实现了ApplicationListener 接口,监听系统的Event,然后进行刷新。

让我们来更改这个类的代码:

  1. privatestaticclassZuulRefreshListenerimplementsApplicationListener<ApplicationEvent>{
  2.        @Autowired
  3.        privateZuulHandlerMapping zuulHandlerMapping;
  4.        privateHeartbeatMonitor heartbeatMonitor;

  5.        privateZuulRefreshListener(){
  6.            this.heartbeatMonitor =newHeartbeatMonitor();
  7.        }

  8.        @Override
  9.        publicvoid onApplicationEvent(ApplicationEventevent){
  10.            if(!(eventinstanceofContextRefreshedEvent)&&!(eventinstanceofRefreshScopeRefreshedEvent)&&!(eventinstanceofRoutesRefreshedEvent)&&!(eventinstanceofInstanceRegisteredEvent)){
  11.                if(eventinstanceofParentHeartbeatEvent){
  12.                    ParentHeartbeatEvent e =(ParentHeartbeatEvent)event;
  13.                    this.resetIfNeeded(e.getValue());

  14.                }elseif(eventinstanceofHeartbeatEvent){
  15.                    HeartbeatEvent e =(HeartbeatEvent)event;
  16.                    this.resetIfNeeded(e.getValue());

  17.                }
  18.            }else{
  19.                /**
  20.                 * 原来代码
  21.                 * this.reset();
  22.                 */
  23.                if((eventinstanceofContextRefreshedEvent)||(eventinstanceofRefreshScopeRefreshedEvent)||(eventinstanceofRoutesRefreshedEvent)){

  24.                    if(eventinstanceofContextRefreshedEvent){
  25.                        ContextRefreshedEvent contextRefreshedEvent =(ContextRefreshedEvent)event;
  26.                        ApplicationContext context = contextRefreshedEvent.getApplicationContext();

  27.                        String eventClassName = context.getClass().getName();

  28.                        /**
  29.                         * 为了服务启动只执行一次从数据库里面获取路由信息,这儿进行判断
  30.                         */
  31.                        if(eventClassName.equals("org.springframework.context.annotation.AnnotationConfigApplicationContext")){
  32.                            this.reset();
  33.                        }
  34.                    }else{
  35.                        this.reset();
  36.                    }
  37.                }
  38.            }

  39.        }

  40.        privatevoid resetIfNeeded(Object value){
  41.            /**
  42.             * 发送监控心态信息接收到注册服务中心的数据后,只更新心态的相关信息,不再从新load整个路由
  43.             * 原来是从新load路由信息,可以把新注册的服务都动态load进来。
  44.             * 现在要求新的服务的路由在数据库里面配置。
  45.             *
  46.             * 否则的话每30秒发送心态检测,就会更新一次路由信息,没有必要
  47.             *
  48.             */
  49.            if(!this.heartbeatMonitor.update(value)){
  50.                return;
  51.            }
  52.            /* 原来代码
  53.            if (this.heartbeatMonitor.update(value)) {
  54.                this.reset();
  55.            }*/

  56.        }


为什么会30秒一次频繁的获取路由配置,上面的注释已经说的很清楚了。 测试,一切顺利!


后记


写博客很累,主要是没有经验,又担心有的地方理解错误,误导大家。出现问题,有的时候需要去从源码哪里找到答案。本文如果在实践中出现任何问题,欢迎留言指正。

相关文章
|
1天前
|
Java 数据安全/隐私保护 Sentinel
微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流
微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流
|
2天前
|
Java API Nacos
第十二章 Spring Cloud Alibaba Sentinel
第十二章 Spring Cloud Alibaba Sentinel
12 0
|
2天前
|
监控 Java 微服务
第八章 Spring Cloud 之 Hystrix
第八章 Spring Cloud 之 Hystrix
|
2天前
|
监控 Java API
第七章 Spring Cloud 之 GateWay
第七章 Spring Cloud 之 GateWay
|
2天前
|
负载均衡 前端开发 Java
第六章 Spring Cloud 之 OpenFeign
第六章 Spring Cloud 之 OpenFeign
|
2天前
|
消息中间件 Java Nacos
第三章 Spring Cloud简介
第三章 Spring Cloud简介
|
2天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
7天前
|
存储 安全 Java
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(下)
16 0
|
7天前
|
安全 Java 数据库
第2章 Spring Security 的环境设置与基础配置(2024 最新版)(上)
第2章 Spring Security 的环境设置与基础配置(2024 最新版)
29 0