前言:
今天朋友们对“apache路由配置”大约比较重视,看官们都需要分析一些“apache路由配置”的相关知识。那么小编同时在网摘上收集了一些对于“apache路由配置””的相关资讯,希望你们能喜欢,大家快快来学习一下吧!在前文中提到,HeartbeatEvent事件会频繁触发,每次都需要去查询数据库。而且ZuulRefreshListener监听的其余四个刷新事件也不会经常触发。所以这里就可以做一下小优化,因为系统上线稳定后,路由一般是不会经常变动的。所以我们可以把路由规则配置给缓存起来,这样就不会每次的心跳都去查询数据库。
这里有分两种情况:
单机Zuul网关下的本地缓存Zuul网关集群下的分布式缓存2.1 本地缓存
直接上代码:
public class DynamicZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator { private final ZuulProperties zuulProperties; private final RoutingRuleService routingRuleService; private static final ConcurrentHashMap<String, ZuulRoute> routesCache = new ConcurrentHashMap<>(); public DynamicZuulRouteLocator(String servletPath, ZuulProperties properties, RoutingRuleService routingRuleService) { super(servletPath, properties); this.zuulProperties = properties; this.routingRuleService = routingRuleService; } @Override public void refresh() { doRefresh(); } @Override protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() { Map<String, ZuulProperties.ZuulRoute> allRoutes = null; if (routesCache.isEmpty()) { allRoutes = routingRuleService.findAllRoutes(); routesCache.putAll(allRoutes); } else { allRoutes = routesCache; } LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>(); // 父类的 SimpleRouteLocator的locateRoutes()读取的是配置文件中的路由规则配置 routesMap.putAll(super.locateRoutes()); routesMap.putAll(allRoutes); LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>(); routesMap.forEach((key, value) -> { String path = key; if (!path.startsWith("/")) { path = "/" + path; } if (StringUtils.hasText(this.zuulProperties.getPrefix())) { path = this.zuulProperties.getPrefix() + path; if (!path.startsWith("/")) { path = "/" + path; } } values.put(path, value); }); return values; } public static void clearCache() { routesCache.clear(); }}复制代码
这是简单地使用了ConcurrentHashMap作为本地缓存。当然也可以使用caffeine、Guava Cache、Ehcache等本地缓存框架。使用ConcurrentHashMap主要是更加方便快捷,简单明了。例如像RocketMQ也有使用了ConcurrentHashMap来缓存路由信息。
这样在数据库面前就挡了层缓存,优化了查询性能,不需要每次的心跳都去查数据库。
2.2 Redis分布式缓存
一般情况下为了高可用,甚至是高并发的情况下,我们的微服务网关都是以集群的形式在线上部署的。上面的本地缓存并不适合在分布式的环境下使用。当然也已使用Redis作为一级缓存,然后本地缓存作为二级缓存。
这时候情况就复杂起来了。这里我画了一个简单的架构图设计图:
有两种设计方案:
方案1:Zuul集成路由规则的CRUD接口。对于路由管理或者说网关管理的接口集成在Zuul网关中,Zuul网关配置数据源。对路由CRUD的操作通过负载均衡到Zuul网关,然后更新Redis缓存。这样的话便不能使用本地缓存了,容易出现数据不一致的情况,当然如果能够维护好一级缓存和二级缓存的话,也是可以。 这种设计缺点较多,首先以来了Redis,提高了整个网关的复杂度,可用性也随之降低。 其次业务耦合度较高,依赖数据源,因为需要对外提供了系列接口。方案2:单独一个管理后台应用,在这里对数据源进行操作。当路由信息发生改变的时候通过WebSocket、Zookeeper、Nacos或者Consul等手段来通知刷新本地缓存的路由信息。 这样的话Zuul网关专注做路由转发,把业务解耦出来,设计更加的轻量级。Apache Shenyu网关就是这样做的。三、实现手动触发路由刷新
看到这里应该知道,对于动态路由配置,我们是要提供一系列的接口来进行维护和操作的。当我们对路由进行了增删改后,是要去刷新Zuul中的路由信息的。按照这样手动修改路由信息触发的方式可以实现的有两种:全量和增量。
接下来的实现是以上面章节所说的方案1来做的,也没有使用分布式缓存,把源码中的本地缓存替换成Redis就行了,主要是因为篇幅受限以及我们主要学习的是实现的原理。请勿直接用在生产上,仅供学习。
3.1 全量触发
OK,直接入正题。这里准备一些接口,从Controller到Service。
RoutingRuleController:
@RestController@RequestMapping("route")public class RoutingRuleController { @Autowired private RoutingRuleService routingRuleService; @PostMapping("add") public Object addRoute(@RequestBody RoutingRule routingRule) { routingRuleService.save(routingRule); return "success"; } @PostMapping("update") public Object updateRoute(@RequestBody RoutingRule routingRule) { routingRuleService.save(routingRule); return "success"; } @DeleteMapping("delete") public Object updateRoute(Long id) { routingRuleService.delete(id); return "success"; }}复制代码
接口:
public interface RoutingRuleService { Map<String, ZuulProperties.ZuulRoute> findAllRoutes(); void save(RoutingRule routingRule); void delete(Long id);}复制代码
接口实现:
@Servicepublic class RoutingRuleServiceImpl implements RoutingRuleService { @Autowired private RoutingRuleDao routingRuleDao; @Autowired private ApplicationEventPublisher publisher; @Override public Map<String, ZuulProperties.ZuulRoute> findAllRoutes() { RoutingRule routingRule = new RoutingRule(); routingRule.setEnabled(1); Example<RoutingRule> example = Example.of(routingRule); List<RoutingRule> routingRuleList = routingRuleDao.findAll(example); Map<String, ZuulProperties.ZuulRoute> zuulRouteMap = new LinkedHashMap<>(); routingRuleList.stream().filter(item -> StringUtils.isNotBlank(item.getPath())) .forEach(item -> { ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute(); BeanUtils.copyProperties(item, zuulRoute); zuulRoute.setId(String.valueOf(item.getId())); zuulRouteMap.put(item.getPath(), zuulRoute); }); return zuulRouteMap; } @Override public void save(RoutingRule routingRule) { RoutingRule rule = routingRuleDao.saveAndFlush(routingRule); publisher.publishEvent(new RefreshRouteEvent(rule)); } @Override public void delete(Long id) { routingRuleDao.deleteById(id); RoutingRule routingRule = new RoutingRule(); routingRule.setId(id); publisher.publishEvent(new RefreshRouteEvent(routingRule)); }}复制代码
从代码可以看到,在路由信息发生变更的时候,我们是通过Spring的事件发布,去通知Listener去完成剩下的操作的。这样可以实现业务上的解耦。
其实也不一定是要用Spring的事件发布,可以使用Disruptor框架来实现高性能内存队列;当然也可以参考Apache Shenyu里面实现的disruptor。我这里为了方便就使用了Spring的事件发布了。
定义一个事件RefreshRouteEvent:
public class RefreshRouteEvent extends ApplicationEvent { public RefreshRouteEvent(Object source) { super(source); }}复制代码
还有监听器RefreshRouteListener: 这里的监听器做法有很多
像下面代码里面那样直接清空DynamicZuulRouteLocator里的本地缓存,然后发布路由刷新事件,让ZuulRefreshListener去刷新缓存。
@Componentpublic class RefreshRouteListener implements ApplicationListener<ApplicationEvent> { @Autowired private ApplicationEventPublisher publisher; @Autowired private RouteLocator routeLocator; @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof RefreshRouteEvent) { DynamicZuulRouteLocator.clearCache(); publisher.publishEvent(new RoutesRefreshedEvent(routeLocator)); } }}复制代码可以在RefreshRouteListener里面把数据库里面的路由查询出来,然后设置到DynamicZuulRouteLocator的本地缓存中。3.2 增量触发
每次修改单个路由配置就触发全量的路由加载,这很明显不符合我们程序开发的追求。但是SimpleRouteLotor的方法里面并没有添加单个或删除单个路由配置的方法。我们再看一下之前的类图:
显然还有DiscoveryClientRouteLocator和CompositeRouteLocator,去这两个的源码会看到DiscoveryClientRouteLocator就是我们想要找的那个类。至于CompositeRouteLocator是整合Spring容器中所有的RouteLocator,它的操作就是遍历所有的RouteLocator去操作路由。
ZuulRefreshListener监听器的刷新执行的方法是org.springframework.cloud.netflix.zuul.filters.CompositeRouteLocator#refresh,可以看到是遍历所有的RouteLocator执行refresh操作。
public class CompositeRouteLocator implements RefreshableRouteLocator { ... @Override public void refresh() { for (RouteLocator locator : routeLocators) { if (locator instanceof RefreshableRouteLocator) { ((RefreshableRouteLocator) locator).refresh(); } } }}复制代码
DiscoveryClientRouteLocator在ZuulProxyAutoConfiguration被注入到了Spring容器。使用的时候直接@Autowired引用就行。
所以有两种写法:
使用DiscoveryClientRouteLocator修改DynamicZuulRouteLocator
这里使用第二种写法,接下来修改代码:
DynamicZuulRouteLocator添加方法removeRoute(String id)和addRoute(ZuulRoute zuulRoute):
public class DynamicZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator { ...同上 public void removeRoute(String id) { for (String path : routesCache.keySet()) { ZuulRoute zuulRoute = routesCache.get(path); if (org.apache.commons.lang3.StringUtils.equals(id, zuulRoute.getId())) { routesCache.remove(path); // 刷新 SimpleRouteLocator 里的路由信息 refresh(); } } } public void addRoute(ZuulRoute zuulRoute) { routesCache.put(zuulRoute.getPath(), zuulRoute); // 刷新 SimpleRouteLocator 里的路由信息 refresh(); }}复制代码
因为最终所有的路由信息是保存在SimpleRouteLocator里的private AtomicReference<Map<String, ZuulRoute>> routes = new AtomicReference<>();,所以子类修改了路由信息需要刷新到SimpleRouteLocator;
修改事件:
public class RefreshRouteEvent extends ApplicationEvent { private boolean isDelete = false; public RefreshRouteEvent(Object source) { super(source); } public boolean isDelete() { return isDelete; } public void setDelete(boolean delete) { isDelete = delete; }}复制代码
修改service的delete方法:
@Servicepublic class RoutingRuleServiceImpl implements RoutingRuleService { ... @Override public void delete(Long id) { routingRuleDao.deleteById(id); RoutingRule routingRule = new RoutingRule(); routingRule.setId(id); RefreshRouteEvent refreshRouteEvent = new RefreshRouteEvent(routingRule); refreshRouteEvent.setDelete(true); publisher.publishEvent(refreshRouteEvent); }}复制代码
最后修改监听器:
@Componentpublic class RefreshRouteListener implements ApplicationListener<ApplicationEvent> { @Autowired private DynamicZuulRouteLocator dynamicZuulRouteLocator; @Override public void onApplicationEvent(ApplicationEvent event) { if (event instanceof RefreshRouteEvent) { // 全量加载路由信息 //DynamicZuulRouteLocator.clearCache(); //publisher.publishEvent(new RoutesRefreshedEvent(routeLocator)); // 增量修改路由信息 RefreshRouteEvent refreshRouteEvent = (RefreshRouteEvent) event; RoutingRule routingRule = (RoutingRule) event.getSource(); if (refreshRouteEvent.isDelete()) { dynamicZuulRouteLocator.removeRoute(String.valueOf(routingRule.getId())); } else { ZuulRoute zuulRoute = new ZuulRoute(); BeanUtils.copyProperties(routingRule, zuulRoute); zuulRoute.setId(String.valueOf(routingRule.getId())); dynamicZuulRouteLocator.addRoute(zuulRoute); } } }}复制代码四、总结
通过两篇文章讲述了Zuul网关的动态路由配置的原理和实战,RouteLocator接口的类图并不复杂,很多Zuul的组件在ZuulServerAutoConfiguration和ZuulProxyAutoConfiguration里面注入到了Spring容器,可以看一下里面都有什么组件,并分析一下其功能,能够很快地理解Zuul的一些功能原理。
作者:
链接:
标签: #apache路由配置 #路由转发首页apache