zuul動態路由
網關服務是流量的唯一入口。不能隨便停服務。所以動態路由就顯得尤為必要。
數據庫動態路由基於事件刷新機制熱修改zuul的路由屬性。
DiscoveryClientRouteLocator
可以看到DiscoveryClientRouteLocator 是默認的刷新的核心處理類。
//重新加載路由信息方法 protected方法。需要子方法重新方法。
protected LinkedHashMap<String, ZuulRoute> locateRoutes()
//觸發刷新的方法 RefreshableRouteLocator 接口
public void refresh() {
this.doRefresh();
}
而這倆個方法都是繼承與SimpleRouteLocator 類,並進行了重新操作。其實官方的方法注釋說明了。如果需要動態讀取加載映射關系。則需要子類重寫這倆個方法。
進行具體的實現
首先pom jar包導入 需要連接mysql 數據庫
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
路由實體 ZuulRouteEntity
package com.xian.cloud.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* <Description> 路由實體類
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/10/30 15:00
*/
@Data
public class ZuulRouteEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* router Id
*/
private Integer id;
/**
* 路由路徑
*/
private String path;
/**
* 服務名稱
*/
private String serviceId;
/**
* url代理
*/
private String url;
/**
* 轉發去掉前綴
*/
private String stripPrefix;
/**
* 是否重試
*/
private String retryable;
/**
* 是否啟用
*/
private String enabled;
/**
* 敏感請求頭
*/
private String sensitiveheadersList;
/**
* 創建時間
*/
private Date createTime;
/**
* 更新時間
*/
private Date updateTime;
/**
* 刪除標識(0-正常,1-刪除)
*/
private String delFlag;
}
新建DiscoveryRouteLocator 類 父類 接口 都不變化
package com.xian.cloud.router;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.xian.cloud.entity.ZuulRoute;
import lombok.extern.slf4j.Slf4j;
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.*;
/**
* <Description>
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/10/30 18:57
*/
@Slf4j
public class DiscoveryRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
private ZuulProperties properties;
private JdbcTemplate jdbcTemplate;
public DiscoveryRouteLocator(String servletPath, ZuulProperties properties, JdbcTemplate jdbcTemplate) {
super(servletPath, properties);
this.properties = properties;
this.jdbcTemplate = jdbcTemplate;
log.info("servletPath:{}",servletPath);
}
@Override
public void refresh() {
doRefresh();
}
@Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routesMap = new LinkedHashMap<String, ZuulProperties.ZuulRoute>();
//從配置文件中加載路由信息
routesMap.putAll(super.locateRoutes());
//自定義加載路由信息
routesMap.putAll(getRouteList());
//優化一下配置
LinkedHashMap<String, ZuulProperties.ZuulRoute> values = new LinkedHashMap<>();
for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : routesMap.entrySet()) {
String path = entry.getKey();
// Prepend with slash if not already present.
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());
}
return values;
}
/**
* 從數據庫讀取zuul路由規則
* @return
*/
private LinkedHashMap<String, ZuulProperties.ZuulRoute> getRouteList() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> zuulRoutes = new LinkedHashMap<>();
List<ZuulRoute> sysZuulRoutes = jdbcTemplate.query("select * from sys_zuul_route where del_flag = 0", new BeanPropertyRowMapper<>(ZuulRoute.class));
for (ZuulRoute route: sysZuulRoutes) {
// 為空跳過
if (Strings.isNullOrEmpty(route.getPath()) && Strings.isNullOrEmpty(route.getUrl())) {
continue;
}
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
try {
zuulRoute.setId(route.getServiceId());
zuulRoute.setPath(route.getPath());
zuulRoute.setServiceId(route.getServiceId());
zuulRoute.setRetryable(Objects.equals("0", route.getRetryable()) ? Boolean.FALSE : Boolean.TRUE);
zuulRoute.setStripPrefix(Objects.equals("0", route.getStripPrefix()) ? Boolean.FALSE : Boolean.TRUE);
zuulRoute.setUrl(route.getUrl());
List<String> sensitiveHeadersList = Arrays.asList(route.getSensitiveheadersList().split(","));
if (sensitiveHeadersList != null) {
Set<String> sensitiveHeaderSet = Sets.newHashSet();
sensitiveHeadersList.forEach(sensitiveHeader -> sensitiveHeaderSet.add(sensitiveHeader));
zuulRoute.setSensitiveHeaders(sensitiveHeaderSet);
zuulRoute.setCustomSensitiveHeaders(true);
}
} catch (Exception e) {
log.error("數據庫加載配置異常", e);
}
log.info("自定義的路由配置,path:{},serviceId:{}", zuulRoute.getPath(), zuulRoute.getServiceId());
zuulRoutes.put(zuulRoute.getPath(), zuulRoute);
}
return zuulRoutes;
}
}
我們還需要一個事件的生產者 和 消費者 直接圖方便 集成到一個類中
package com.xian.cloud.event;
import com.xian.cloud.router.DiscoveryRouteLocator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.cloud.client.discovery.event.HeartbeatMonitor;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;
/**
* <Description> 路由刷新事件發布,與事件監聽者
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/10/30 15:27
*/
@Service
public class RefreshRouteService implements ApplicationListener<ApplicationEvent> {
@Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private HeartbeatMonitor heartbeatMonitor = new HeartbeatMonitor();
@Autowired
ApplicationEventPublisher publisher;
@Autowired
private DiscoveryRouteLocator dynamicRouteLocator;
/**
* 動態路由實現 調用refreshRoute() 發布刷新路由事件
*/
public void refreshRoute() {
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(dynamicRouteLocator);
publisher.publishEvent(routesRefreshedEvent);
}
/**
* 事件監聽者。監控檢測事件刷新
* @param event
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
if(event instanceof ContextRefreshedEvent || event instanceof RefreshScopeRefreshedEvent || event instanceof RoutesRefreshedEvent){
//主動手動刷新。上下文刷新,配置屬性刷新
zuulHandlerMapping.setDirty(true);
}else if(event instanceof HeartbeatEvent){
//心跳觸發,將本地映射關系。關聯到遠程服務上
HeartbeatEvent heartbeatEvent = (HeartbeatEvent)event;
if(heartbeatMonitor.update(heartbeatEvent.getValue())){
zuulHandlerMapping.setDirty(true);
}
}
}
}
對外提供觸發接口
package com.xian.cloud.controller;
import com.xian.cloud.event.RefreshRouteService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* <Description> 手動刷新對外接口
*
* @author xianliru@100tal.com
* @version 1.0
* @createDate 2019/10/30 20:23
*/
@RestController
public class RefreshController {
@Autowired
private RefreshRouteService refreshRouteService;
@GetMapping("/refresh")
public String refresh() {
refreshRouteService.refreshRoute();
return "refresh";
}
}
數據庫表結構
CREATE TABLE `sys_zuul_route` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'router Id',
`path` varchar(255) NOT NULL COMMENT '路由路徑',
`service_id` varchar(255) NOT NULL COMMENT '服務名稱',
`url` varchar(255) DEFAULT NULL COMMENT 'url代理',
`strip_prefix` char(1) DEFAULT '1' COMMENT '轉發去掉前綴',
`retryable` char(1) DEFAULT '1' COMMENT '是否重試',
`enabled` char(1) DEFAULT '1' COMMENT '是否啟用',
`sensitiveHeaders_list` varchar(255) DEFAULT NULL COMMENT '敏感請求頭',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創建時間',
`update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
`del_flag` char(1) DEFAULT '0' COMMENT '刪除標識(0-正常,1-刪除)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='動態路由配置表'
將配置文件client 消費者服務 路由配置注釋掉。設置數據源。從數據庫中讀取
啟動服務打印日志
2019-10-30 20:49:39.946 INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator : 添加數據庫自定義的路由配置,path:/client/**,serviceId:cloud-discovery-client
2019-10-30 20:49:40.397 INFO 63449 --- [TaskScheduler-1] c.xian.cloud.router.DynamicRouteLocator : 添加數據庫自定義的路由配置,path:/client/**,serviceId:cloud-discovery-client
postman 請求client 接口 看看是否能轉發成功
基於zuul 動態網關路由完成。
后續還會更新網關的灰度方案、swagger2 整合的調試源服務。敬請期待!
摘自參考 spring cloud 官方文檔
參考書籍 重新定義spring cloud實戰
服務器nacos 地址 http://47.99.209.72:8848/nacos
往期地址 spring cloud alibaba 地址
Spring Cloud Alibaba (nacos 注冊中心搭建)
Spring Cloud Alibaba 使用nacos 注冊中心
Spring Cloud Alibaba nacos 配置中心使用
如何喜歡可以關注分享本公眾號。
版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。轉載請附帶公眾號二維碼