Spring Cloud 网关服务 zuul 三 动态路由
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 版权协议,转载请附上原文出处链接和本声明。转载请附带公众号二维码
Spring Cloud 网关服务 zuul 三 动态路由的更多相关文章
- Spring Cloud 网关服务 zuul 二
有一点上篇文章忘了 讲述,nacos的加载优先级别最高.服务启动优先拉去配置信息.所以上一篇服务搭建我没有讲述在nacos 中心创建的配置文件 可以看到服务端口和注册中心都在配置文件中配置化 属性信息 ...
- spring cloud 2.x版本 Gateway动态路由教程
摘要 本文采用的Spring cloud为2.1.8RELEASE,version=Greenwich.SR3 本文基于前面的几篇Spring cloud Gateway文章的实现. 参考 Gatew ...
- spring cloud 网关服务
微服务 网关服务 网关服务是微服务体系里面重要的一环. 微服务体系内,各个服务之间都会有通用的功能比如说:鉴权.安全.监控.日志.服务调度转发.这些都是可以单独抽象出来做一个服务来处理.所以微服务网关 ...
- spring cloud网关通过Zuul RateLimit 限流配置
目录 引入依赖 配置信息 RateLimit源码简单分析 RateLimit详细的配置信息解读 在平常项目中为了防止一些没有token访问的API被大量无限的调用,需要对一些服务进行API限流.就好比 ...
- Spring Cloud Gateway实战之三:动态路由
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- spring cloud微服务实践三
上篇文章里我们实现了spring cloud中的服务提供者和使用者.接下来我们就来看看spring cloud中微服务的其他组件. 注:这一个系列的开发环境版本为 java1.8, spring bo ...
- Spring Cloud微服务中网关服务是如何实现的?(Zuul篇)
导读 我们知道在基于Spring Cloud的微服务体系中,各个微服务除了在内部提供服务外,有些服务接口还需要直接提供给客户端,如Andirod.IOS.H5等等. 而一个很尴尬的境地是,如果直接将提 ...
- spring cloud 学习(6) - zuul 微服务网关
微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService.ProductService.UserService...,为了让调用更简单,一般会在这些服务前端再封装一层,类似下 ...
- Spring Cloud 微服务二:API网关spring cloud zuul
前言:本章将继续上一章Spring Cloud微服务,本章主要内容是API 网关,相关代码将延续上一章,如需了解请参考:Spring Cloud 微服务一:Consul注册中心 Spring clou ...
随机推荐
- Flask中的路由、实例化参数和config配置文件
Flask中的路由 endpoint 别名不能重复,对应的视图函数,默认是视图函数名.endpoint 才是路由的核心.视图函数与路由的对应关系.可以通过url_for 反向创建url # metho ...
- C++:Memory Management
浅谈C++内存管理 new和delete 在C++中,我们习惯用new申请堆中的内存,配套地,使用delete释放内存. class LiF; LiF* lif = new LiF(); // 分配内 ...
- Python学习笔记整理总结【Django】【MVC/MTV/路由分配系统(URL)/视图函数 (views)/表单交互】
一.Web框架概述 Web框架本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python # -*- coding:utf-8 ...
- XLNet预训练模型,看这篇就够了!(代码实现)
1. 什么是XLNet XLNet 是一个类似 BERT 的模型,而不是完全不同的模型.总之,XLNet是一种通用的自回归预训练方法.它是CMU和Google Brain团队在2019年6月份发布的模 ...
- quartz-scheduler定时器实现
第一步,在pom.xml中引入quartz-scheduler. <dependency> <groupId>org.quartz-scheduler</groupId& ...
- LAMP模拟搭建wordpress,phpmyadmin环境
1.安装需要的环境,centos7的mysql叫mariadb,所有的配置文件默认就可以 [root@test ~]# yum install httpd php mariadb mariadb-se ...
- 在Linux系统下有一个目录/usr/share/dict/ 这个目录里包含了一个词典的文本文件,我们可以利用这个文件来辨别单词是否为词典中的单词。
#!/bin/bash s=`cat /usr/share/dict/linux.words` for i in $s; do if [ $1 = $i ];then echo "$1 在字 ...
- Java 学习笔记之 父子类Synchronized
父子类Synchronized: 我们通过一个例子来验证下,父类和子类的Synchronized方法被同时调用,是否是同步的. public class FatherClass { synchroni ...
- IDEA 学习笔记之 Java项目开发深入学习(2)
Java项目开发深入学习(2): 查找变量被用到的地方 编译当前文件 增加变量watch 注意:我使用了keymap (eclipse模板),所以很多快捷键和eclipse一样. F5单步调试进入函数 ...
- 快学Scala 第十五课 (二进制读取文件,写文件,访问目录,序列化)
二进制读取文件: val file = new File("F:\\scalaWorkspace\\ScalaLearning\\files\\test.txt") val in ...