描述
Zuul 网关作为 SpringCloud 基础组件之一,最主要的功能就是作为前端请求的入口,自定义路由规则,分配请求到具体的服务上。
所有的请求都要先经过网关,默认使用并不能支持动态路由转发,如果我们新添加一个服务,只能重启Zuul 来使路由规则生效,但这样会影响其他的服务。在生产环境我们希望不重启Zuul服务也可以动态的修改路由规则。
本文将对Zuul进行优化,支持通过数据库动态配置修改路由规则,无需重启服务。
maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
动态路由表
在数据库中创建动态路由表,需要添加或者修改路由信息只需在这张表中修改对应记录即可。
CREATE TABLE `gateway_api_route` (
`id` varchar(50) NOT NULL,
`path` varchar(255) NOT NULL,
`service_id` varchar(50) DEFAULT NULL,
`url` varchar(255) DEFAULT NULL,
`retryable` tinyint(1) DEFAULT NULL,
`enabled` tinyint(1) NOT NULL,
`strip_prefix` int(11) DEFAULT NULL,
`api_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT INTO gateway_api_route (id, path, service_id, retryable, strip_prefix, url, enabled) VALUES ('business-service', '/business/**', 'business-service',0,1, NULL, 1);
代码实现
实现动态路由,需要创建的类 如下:
- GatewayApiRoute 数据库实体
- DynamicRouteLocator 自定义路由管理器
- DynamicRouteConfiguration 自定义路由 Spring 配置类
- RefreshRouteTask 定时从数据库刷新最新的路由信息
具体的代码如下:
DynamicRouteLocator
import org.springframework.beans.BeanUtils;
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.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.StringUtils;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public class DynamicRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
private JdbcTemplate jdbcTemplate;
private ZuulProperties properties;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public DynamicRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
this.properties = properties;
}
@Override
public void refresh() {
doRefresh();
}
@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<>();
// 加载application.yml中的路由表
routesMap.putAll(super.locateRoutes());
// 加载db中的路由表
routesMap.putAll(locateRoutesFromDB());
// 统一处理一下路由path的格式
LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
values.put(path, entry.getValue());
}
System.out.println("路由表:" + values);
return values;
}
private Map<String, ZuulProperties.ZuulRoute> locateRoutesFromDB() {
Map<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<>();
List<GatewayApiRoute> results = jdbcTemplate.query(
"select * from gateway_api_route where enabled = true ",
new BeanPropertyRowMapper<>(GatewayApiRoute.class));
for (GatewayApiRoute result : results) {
if (StringUtils.isEmpty(result.getPath()) ) {
continue;
}
if (StringUtils.isEmpty(result.getServiceId()) && StringUtils.isEmpty(result.getUrl())) {
continue;
}
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
try {
BeanUtils.copyProperties(result, zuulRoute);
} catch (Exception e) {
e.printStackTrace();
}
routes.put(zuulRoute.getPath(), zuulRoute);
}
return routes;
}
}
GatewayApiRoute 数据库映射实体
package com.zhss.demo.zuul.gateway;
public class GatewayApiRoute {
private String id;
private String path;
private String serviceId;
private String url;
private boolean stripPrefix = true;
private Boolean retryable;
private Boolean enabled;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getServiceId() {
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isStripPrefix() {
return stripPrefix;
}
public void setStripPrefix(boolean stripPrefix) {
this.stripPrefix = stripPrefix;
}
public Boolean getRetryable() {
return retryable;
}
public void setRetryable(Boolean retryable) {
this.retryable = retryable;
}
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
}
DynamicRouteConfiguration 自定义路由 Spring 配置类
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
public class DynamicRouteConfiguration {
@Autowired
private ZuulProperties zuulProperties;
@Autowired
private ServerProperties server;
@Autowired
private JdbcTemplate jdbcTemplate;
@Bean
public DynamicRouteLocator routeLocator() {
DynamicRouteLocator routeLocator = new DynamicRouteLocator(
this.server.getServletPrefix(), this.zuulProperties);
routeLocator.setJdbcTemplate(jdbcTemplate);
return routeLocator;
}
}
RefreshRouteTask 路由定时刷新器
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 publisher;
@Autowired
private RouteLocator routeLocator;
@Scheduled(fixedRate = 5000)
private void refreshRoute() {
System.out.println("定时刷新路由表");
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
}
代码及SQL都准备完毕,启动服务,就可以看到路由刷新情况啦