美文网首页
(十一)实现动态路由

(十一)实现动态路由

作者: guessguess | 来源:发表于2021-12-08 19:04 被阅读0次

在实现动态路由之前,有必要先看看ZuulHandlerMapping如何处理请求的过程路径如何映射到对应的处理器

ZuulHandlerMapping会查找处理的时候,去生成url与处理器的映射,具体源码如下。其实在上面的链接中已经有了,如果不想看的话,从下面的代码链路中也可以看出来,路由数据是如何加载的。

public class ZuulHandlerMapping extends AbstractUrlHandlerMapping {
    @Override
    protected Object lookupHandler(String urlPath, HttpServletRequest request)
            throws Exception {
        if (this.errorController != null
                && urlPath.equals(this.errorController.getErrorPath())) {
            return null;
        }
        if (isIgnoredPath(urlPath, this.routeLocator.getIgnoredPaths())) {
            return null;
        }
        RequestContext ctx = RequestContext.getCurrentContext();
        if (ctx.containsKey("forward.to")) {
            return null;
        }
        if (this.dirty) {
            synchronized (this) {
                if (this.dirty) {
                    registerHandlers();
                    this.dirty = false;
                }
            }
        }
        return super.lookupHandler(urlPath, request);
    }

    private void registerHandlers() {
        Collection<Route> routes = this.routeLocator.getRoutes();
        if (routes.isEmpty()) {
            this.logger.warn("No routes found from RouteLocator");
        }
        else {
            for (Route route : routes) {
                registerHandler(route.getFullPath(), this.zuul);
            }
        }
    }
}

public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping implements MatchableHandlerMapping {
    protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
        Assert.notNull(urlPath, "URL path must not be null");
        Assert.notNull(handler, "Handler object must not be null");
        Object resolvedHandler = handler;

        // Eagerly resolve handler if referencing singleton via name.
        if (!this.lazyInitHandlers && handler instanceof String) {
            String handlerName = (String) handler;
            ApplicationContext applicationContext = obtainApplicationContext();
            if (applicationContext.isSingleton(handlerName)) {
                resolvedHandler = applicationContext.getBean(handlerName);
            }
        }

        Object mappedHandler = this.handlerMap.get(urlPath);
        if (mappedHandler != null) {
            if (mappedHandler != resolvedHandler) {
                throw new IllegalStateException(
                        "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath +
                        "]: There is already " + getHandlerDescription(mappedHandler) + " mapped.");
            }
        }
        else {
            if (urlPath.equals("/")) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Root mapping to " + getHandlerDescription(handler));
                }
                setRootHandler(resolvedHandler);
            }
            else if (urlPath.equals("/*")) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Default mapping to " + getHandlerDescription(handler));
                }
                setDefaultHandler(resolvedHandler);
            }
            else {
                this.handlerMap.put(urlPath, resolvedHandler);
                if (logger.isTraceEnabled()) {
                    logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
                }
            }
        }
    }
}

如何实现动态路由?

  • 1.路由加载器是如何加载的。
    其实从前面已经知道,ZuulHandlerMapping加载路由数据的时候,其实用的是复合的路由加载器。
@Configuration
@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass({ ZuulServlet.class, ZuulServletFilter.class })
@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
public class ZuulServerAutoConfiguration {
    上面这个用法是比较骚气的,其实就是将多个类型的路由加载器,整合到复合的路由加载器中。
    @Bean
    @Primary
    public CompositeRouteLocator primaryRouteLocator(
            Collection<RouteLocator> routeLocators) {
        return new CompositeRouteLocator(routeLocators);
    }
    这个时候ZuulHandlerMapping中注入的RouteLocator 便是CompositeRouteLocator 
    @Bean
    public ZuulHandlerMapping zuulHandlerMapping(RouteLocator routes) {
        ZuulHandlerMapping mapping = new ZuulHandlerMapping(routes, zuulController());
        mapping.setErrorController(this.errorController);
        mapping.setCorsConfigurations(getCorsConfigurations());
        return mapping;
    }

}
  • 2 那么如何构建一个自己的路由加载器呢?
    其实只要实现RouteLocator,注入容器即可。
  • 3 路由加载器要实现的功能
  • 3.1 动态路由加载
    既然是动态的,那么可以通过一个服务获取,也可以通过数据库。
  • 3.1.1环境准备
    这里由于是通过数据库来动态管理路由数据。
    所以先按zuul保存路由数据的格式来定义一个表
CREATE TABLE "route" (
  "id" bigserial,
  "routeid" VARCHAR(32),--路由规则id
  "path" VARCHAR(32),--路径
  "serviceId" VARCHAR(32),--服务id
  "url" VARCHAR(128),--url
  "stripPrefix" BOOLEAN,--校验前缀
  "retryable" BOOLEAN,--重试
  "updatetime" int8,--更新时间
  "createtime" int8,--创建时间
  "gatewayGroup" VARCHAR(32)--网关组,自定义字段
);
  • 3.1.2 持久层
    主要是用于数据的获取
    DAO
public interface RouteDAO {
    List<Route> getRouteByGroup(String group);
}

DAO的实现

@Repository
public class RouteDAOImpl implements RouteDAO{
    
    @Autowired
    private RouteMapper routeMapper;

    @Override
    public List<Route> getRouteByGroup(String group) {
        RouteExample re = new RouteExample();
        re.createCriteria().andGatewaygroupEqualTo(group);
        return routeMapper.selectByExample(re);
    }
}

接口

public interface RouteService {
    获取某个网关组的路由数据
    public List<ZuulRoute> getRoutes(String group);
}

接口实现

@Service
public class RouteServieImpl implements RouteService{
    
    @Autowired
    private RouteDAO routeDAO;
    
    @Override
    public List<ZuulRoute> getRoutes(String group) {
        if (StringUtils.isBlank(group)) {
            return null;
        }
        List<Route> dbData = routeDAO.getRouteByGroup(group);
        if (CollectionUtils.isEmpty(dbData)) {
            return null;
        }
        return dbData.stream().map(item -> {
            return genZuulRoute(item);
        }).collect(Collectors.toList());
    }
    
    private ZuulRoute genZuulRoute(Route src) {
        ZuulRoute destObj = new ZuulRoute();
        destObj.setId(src.getRouteid());
        destObj.setPath(src.getPath());
        destObj.setRetryable(src.getRetryable());
        destObj.setServiceId(src.getServiceid());
        destObj.setStripPrefix(src.getStripprefix());
        destObj.setUrl(src.getUrl());
        return destObj;
    }
}
  • 3.1.3 路由加载器的构建
    自定义的配置类,用于加载网关组配置,需要加配置文件中添加网关组。
@Configuration
@ConfigurationProperties(prefix = "custom.zuul")
@Data
public class CustomZuulConfigurationProperties {
    private String group;
}

路由加载器的实现

@Slf4j
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator{
    使用LoadingCache作为缓存,具有定时清理缓存的作用
    private LoadingCache<String, List<ZuulRoute>> routeCache;
    用于从Db中读取路由数据
    @Autowired
    private RouteService routeService;
    网关组
    private String group;
    
    private Callable<? extends List<ZuulRoute>> callable;
    
    public DynamicRouteLocator(ZuulProperties properties,
            CustomZuulConfigurationProperties customZuulConfigurationProperties) {
        super(properties.getServletPath(), properties);
        this.group =  customZuulConfigurationProperties.getGroup();
        缓存最大为128,30秒数据没有更新自动过期
        this.routeCache = CacheBuilder.newBuilder().initialCapacity(128).expireAfterWrite(30, TimeUnit.SECONDS)
                .build(new CacheLoader<String, List<ZuulRoute>>() {
                    @Override
                    public List<ZuulRoute> load(String key) throws Exception {
                        缓存不存在自动从DB中获取
                        return getZuulRoutes(key);
                    }
                });
        callable = new Callable<List<ZuulRoute>>() {
            @Override
            public List<ZuulRoute> call() throws Exception {
                return new ArrayList<ZuulRoute>(0);
            }
        };
    }

    private List<ZuulRoute> getZuulRoutes(String group) {
        return routeService.getRoutes(group);
    }
    
    private List<ZuulRoute> getZuulRoutesFromCache(String group) {
        List<ZuulRoute> routesFromDB = null;
        try {
            routesFromDB = routeCache.get(this.group);
        } catch (ExecutionException e) {
            log.error("getZuulRoutesFromCache error");
        }
        return routesFromDB;
    }

    这个方法应该是最重要的,用于实现加载路由的过程
    @Override
    protected Map<String, ZuulRoute> locateRoutes() {
        通过数据库模拟获取路由. 通过group获取对应路由
        List<ZuulRoute> routesFromDB = getZuulRoutesFromCache(this.group);
        LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
        if (CollectionUtils.isEmpty(routesFromDB)) {
            return values;
        }
        Map<String, ZuulRoute> routesMap = routesFromDB.stream().collect(Collectors.groupingBy(ZuulRoute::getPath,
                Collectors.collectingAndThen(Collectors.toList(), v -> v.get(0))));
        for (Entry<String, ZuulRoute> entry : routesMap.entrySet()) {
            String path = entry.getKey();
            if (!path.startsWith("/")) {
                path = "/" + path;
            }
            values.put(path, entry.getValue());
        }

        return values;
    }

    刷新方法,使用父类的实现即可,也是调用locateRoutes()。
    至于刷新机制的触发,其实只有容器刷新的时候,会发送刷新事件,这个时候路由会重新加载。
    但是由于使用了LoadingCache,可以让缓存定时失效,也就不用手动去刷新容器了。
    @Override
    public void refresh() {
        doRefresh();
    }
}

最后,通过配置类将路由加载器注入容器

@Configuration
public class CustomZuulServerAutoConfiguration{

    @Bean
    public DynamicRouteLocator dynamicRouteLocator(ZuulProperties properties,
            CustomZuulConfigurationProperties customZuulConfigurationProperties) {
        return new DynamicRouteLocator(properties, customZuulConfigurationProperties);
    }
}

这样子其实就完成了动态路由的功能了。
利用数据库以及LoadingCache,可以做到数据自动更新,就可以达到动态路由的效果了。
如果追求时效性,想让数据即可更新,可以通过手动触发容器刷新的接口,一样可以做到立马刷新的效果。

相关文章

网友评论

      本文标题:(十一)实现动态路由

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