美文网首页SpringFramework
Spring Cloud Zuul 从源码理解如何实现动态路由(

Spring Cloud Zuul 从源码理解如何实现动态路由(

作者: 飞阁流云 | 来源:发表于2019-10-21 22:30 被阅读0次

    如果你正在使用Spring Cloud 那么网关层必不可少,那么必定会遇到这样的一个情况,如果新增一个服务如何去更新网关和新服务之间的映射关系呢?修改配置文件然后重启服务,这是最简单也是最常见的方法,难道每次新增一个服务就要重启一次吗?那服务不允许重启呢?像电商项目这种是绝对不允许在生产环境频繁重启服务的,那就引出下面我们要说的。

    Spring Cloud Zuul 可以动态加载路由配置,新增一个服务,只需要更改数据库表,zuul会去动态加载,如果您正在使用zuul那么可以花点时间来学学原理和实现

    以下分为原理讲解和如何实现,不想看原理可以直接看如何实现的

    实现原理

    不管学习什么源码, 我们需要找到入口,Zuul的入口就是@EnableZuulProxy
    以下截取重点代码:

    @Configuration
    public class ZuulProxyMarkerConfiguration {
        @Bean
        public Marker zuulProxyMarkerBean() {
            return new Marker();//这里new 了个对象 里面也是new 个对象
        }
        class Marker {
    
        }
    }
    

    看到这里,可能有同学就有点一头雾水的感觉,啥意思呢,熟悉springboot的同学知道这里其实用到springboot的自动配置,再截取一段代码就明白了

    @Configuration
    @Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
            RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
            HttpClientConfiguration.class })
    @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)//其实这里才是真正的入口
    // springboot 条件注解使用
    public class ZuulProxyAutoConfiguration extends ZuulServerAutoConfiguration {
    ...
    }
    

    既然知道入口,接下来这里面的东西我就挑重点来说了,ZuulProxyAutoConfiguration这个类主要是发现服务用的,我们重点看下它的父类ZuulServerAutoConfiguration
    还是来波关键代码

    @Configuration
    @EnableConfigurationProperties({ ZuulProperties.class })
    @ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
    @ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
    // Make sure to get the ServerProperties from the same place as a normal web app would
    // FIXME @Import(ServerPropertiesAutoConfiguration.class)
    public class ZuulServerAutoConfiguration {
    ...
            /**
              * 路由定位器,这是Zuul 执行转发的关键,如何转发请求就是靠它
            */
            @Bean
        @ConditionalOnMissingBean(SimpleRouteLocator.class)
        public SimpleRouteLocator simpleRouteLocator() {
            return new SimpleRouteLocator(this.server.getServlet().getContextPath(),
                    this.zuulProperties);
        }
    
    ...
    
    /**
       * 看名字都猜到一大半了,对,这就是Zuul刷新路由的监听器
     */
    private static class ZuulRefreshListener
                implements ApplicationListener<ApplicationEvent> {
    
            @Autowired
            private ZuulHandlerMapping zuulHandlerMapping;
    
            private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
    
            @Override
            public void onApplicationEvent(ApplicationEvent event) {
                if (event instanceof ContextRefreshedEvent
                        || event instanceof RefreshScopeRefreshedEvent
                        || event instanceof RoutesRefreshedEvent
                        || event instanceof InstanceRegisteredEvent) {
                    reset();
                }
                else if (event instanceof ParentHeartbeatEvent) {
                    ParentHeartbeatEvent e = (ParentHeartbeatEvent) event;
                    resetIfNeeded(e.getValue());
                }
                else if (event instanceof HeartbeatEvent) {
                    HeartbeatEvent e = (HeartbeatEvent) event;
                    resetIfNeeded(e.getValue());
                }
            }
    
            private void resetIfNeeded(Object value) {
                if (this.heartbeatMonitor.update(value)) {
                    reset();
                }
            }
     // 下面这个方法就是Zuul 实现动态路由刷新的关键方法
     // 每次请求zuul都会检查dirt这个属性,如果为true 则会重新注册一次路由规则
    //其实Spring Cloud 有一个守护线程会轮询检测服务健康状态,也就是HeartbeatEvent事件
            private void reset() {
                this.zuulHandlerMapping.setDirty(true);
            }
    
        }
    ...
    }
    

    其实看到这里,大概也明白该怎么做了,我们再进setDirty 方法看看

    public void setDirty(boolean dirty) {
            this.dirty = dirty;
            if (this.routeLocator instanceof RefreshableRouteLocator) {
                ((RefreshableRouteLocator) this.routeLocator).refresh();
            }
        }
    

    划重点,必须要是RefreshableRouteLocator这个类的子类才会刷新路由,至此, 我们已经理清刷新逻辑,接下来我们来看看如何实现

    如何实现

    我们就先来实现RefreshableRouteLocator 这个类

    public abstract class AbstractRefreshRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
        
        private ZuulProperties zuulProperties;
        
        public AbstractRefreshRouteLocator(String servletPath, ZuulProperties properties) {
            super(servletPath, properties);
            this.zuulProperties = properties;
        }
    
        @Override
        public void refresh() {
                    //这里调用的是父类方法,其实调用的是下面locateRoutes 方法
            doRefresh();
        }
        
           // 核心方法
        @Override
        protected Map<String, ZuulRoute> locateRoutes() {
            LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
            routesMap.putAll(super.locateRoutes());//加载父类路由
            routesMap.putAll(this.loadRoute());//加载自定义路由
            
            LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
    //下面只是特殊情况处理
            for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
                String path = entry.getKey();
                if (!path.startsWith("/")) {
                    path = "/" + path;
                }
                if (StringUtils.hasText(this.zuulProperties.getPrefix())) {
                    path = this.zuulProperties.getPrefix() + path;
                    if (!path.startsWith("/")) {
                        path = "/" + path;
                    }
                }
                values.put(path, entry.getValue());
            }
            
            return values;
        }
        
    //这里使用了模板方法模式
    // 因为加载路由的方式有很多,我们就留给子类去实现(我们用数据库实现)
        public abstract Map<String, ZuulRoute> loadRoute();
    
    }
    

    然后我们看看子类如何写

    public class RefreshRouteFromDBLocator extends AbstractRefreshRouteLocator {
        
        private JdbcTemplate jdbcTemplate;
        
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        public RefreshRouteFromDBLocator(String servletPath, ZuulProperties properties) {
            super(servletPath, properties);
        }
            
    //一目了然
        @Override
        public Map<String, ZuulRoute> loadRoute() {
            LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<>();
            String sql = "SELECT * FROM route";
            List<Map<String, Object>> routes = jdbcTemplate.queryForList(sql);
            
            for (Map<String, Object> map : routes) {
                ZuulRoute zuulRoute = new ZuulRoute();
                zuulRoute.setId(map.get("route_id").toString());
                zuulRoute.setPath(map.get("path").toString());
                zuulRoute.setServiceId(map.get("service_id").toString());
                
                routesMap.put(map.get("path").toString(), zuulRoute);
            }
            return routesMap;
        }
    
    }
    

    写到这里,核心就完了,就这么点代码就可以实现动态路由,上面说了Spring Cloud 有个守护线程一直轮询,但我们为了保险起见,还是发布一个刷新事件

    我这里使用http请求方式发布刷新事件,可以根据自己业务需求更改

    @RequestMapping("/route")
    @RestController
    public class RouteController {
        
        @Autowired
        private ApplicationEventPublisher publisher;
        
        @Autowired
        private RefreshRouteFromDBLocator locator;
        
        @GetMapping("/refresh")
        public String refresh() {
            publisher.publishEvent(new RoutesRefreshedEvent(locator));
            return "success";
        }
    
    }
    

    结束语

    其实只要理解到位从源码到实现也没多少东西,最近在研究网关层的技术,我写这篇博客也是为了巩固学习使用,希望能帮助到大家,有什么问题欢迎私信。

    相关文章

      网友评论

        本文标题:Spring Cloud Zuul 从源码理解如何实现动态路由(

        本文链接:https://www.haomeiwen.com/subject/uuvxvctx.html