一. 基础解释
- 解释: Zuul 基于数据库实现动态路由的原因,假设当前新建了服务,或已有服务中添加了新的接口,怎么不停机的情况下动态配置该接口通过网关访问,通过数据库进行配置,手动向数据库中存储服务与接口的映射关系,Zuul定时扫描数据库获取最新的服务与接口映射关系,做到动态定时更新
- 实现步骤
- 创建 Zuul 网关服务
- 网关服务中配置连接数据库(添加连接数据库需要的依赖,配置文件等)
- 数据库中创建指定服务与接口路径,路由规则的映射表
- 自定义路由策略类,继承 SimpleRouteLocator并实现 RefreshableRouteLocator接口
- 重写locateRoutes() 与 refresh()方法,locateRoutes()方法中通过数据库获取服务与接口映射关系的数据,存入 Map<String, ZuulProperties.ZuulRoute> 中并返回 继承 SimpleRouteLocator并实现 RefreshableRouteLocator接口 动态路由类注入到容器中
- 创建路由配置类,将自定义路由策略类注入到容器中,设置Zuul使用自定义路由策略
- 创建定时任务,定时发布 RoutesRefreshedEvent 事件
- 每发布一次事件,被对应的监听器监听到执行监听方法,会自动调用重写的 locateRoutes() 方法,获取新添加到数据库中添加的服务接口映射关系
二. 简单实现
创建 Zuul 网关服务
-
数据库中创建存于服务与接口映射关系的表,将来对某个服务添加新接口或改变接口路由规则时,直接向该表中添加服务名称,对应接口的路径,对应的路由规则即可例如:
-
除了SpringCloud,Zuul ,Eureka,等依赖以外,引入连接数据库依赖,例如
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
- 配置当前 Zuul 网关服务连接数据库,对数据库中存有服务与接口映射关系的表生产dto,mapper.xml等
- 自定义路由策略类,继承 继承 SimpleRouteLocator并实现 RefreshableRouteLocator接口,重写locateRoutes() 与 refresh()方法,locateRoutes()方法中通过数据库获取服务与接口映射关系的数据,存入 Map<String, ZuulProperties.ZuulRoute> 中并返回
import com.wuhenjian.microservicescaffolding.zuul.util.StringUtil;
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.util.StringUtils;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 动态路由实现
*/
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
/**
* 是否启用动态路由。
* 在配置文件中:gateway.routes.dynamic.enabled,默认为false
*/
private boolean enabled;
private ZuulProperties properties;
public DynamicRouteLocator(boolean enabled, String servletPath, ZuulProperties properties) {
super(servletPath, properties);
this.enabled = enabled;
}
/**
* 重载路由规则,当插入RoutesRefreshedEvent事件时自动执行该方法
*/
@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
Map<String, ZuulProperties.ZuulRoute> routeMap = new LinkedHashMap<>();
//1.读取 yml 文件中配置的路由关系
routeMap.putAll(super.locateRoutes());
//2.获取通过数据库配置服务与指定接口的映射关系
// 先模拟几个配置
String path = "/log/**";
String serviceId = "service-log";
// 任意一个为空,则不进行动态路由
if (StringUtil.isBlank(path) || StringUtil.isBlank(serviceId)) {
return super.locateRoutes();
}
//3.将通过数据库获取的映射关系封装为ZuulRoute对象
ZuulProperties.ZuulRoute zuulRoute = createZuulRoute(path, serviceId);
//4.将通过数据库获取到的映射关系存入 routeMap 中
routeMap.put(path, zuulRoute);
//5.对路径进行统一处理
LinkedHashMap<String,ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routeMap.entrySet()) {
String p = entry.getKey();
if (!p.startsWith("/")) {
p = "/"+p;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
p = this.properties.getPrefix()+p;
if (!p.startsWith("/")) {
p = "/"+p;
}
}
values.put(path, entry.getValue());
}
//6.将处理后的map返回
return values;
}
/**
* 刷新路由
*/
@Override
public void refresh() {
super.doRefresh();
}
public boolean getEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* 生成ZuulRoute对象
* @param path 映射路径
* @param serviceId 服务Id
*/
private ZuulProperties.ZuulRoute createZuulRoute(String path, String serviceId) {
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
zuulRoute.setId(path);
zuulRoute.setPath(path);
zuulRoute.setServiceId(serviceId);
return zuulRoute;
}
}
- 创建路由配置类,将上面的自定义路由策略类注入到容器中
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 动态路由配置类,将 DynamicRouteLocator 自定义路由策略类
*/
@Configuration
public class DynamicRouteConfig {
//读取 yml 文件中中配置的路由规则
@Value("${gateway.routes.dynamic.enabled}")
private boolean enabledDynamicRoute;
/**
* 使用自定义的路由策略代替默认路由策略
*/
@Bean
public SimpleRouteLocator routeLocator(ZuulProperties zuulProperties) {
//DynamicRouteLocator 为自定义通过数据库获取的路由规则类
return new DynamicRouteLocator(enabledDynamicRoute, zuulProperties.getPrefix(), zuulProperties);
}
}
- 创建定时任务,定时发布 RoutesRefreshedEvent 事件,事件发布后会自动执行DynamicRouteLocator自定义类中的 locateRoutes() 方法
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@Configuration
@EnableScheduling
public class RefreshRouteTask {
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
@Autowired
private RouteLocator routeLocator;
@Scheduled(fixedRate = 5000)
private void refreshRoute(){
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
applicationEventPublisher.publishEvent(routesRefreshedEvent);
}
}