Zuul动态路由及动态Filter实现

本文介绍如何使用Zuul实现动态路由和动态过滤器功能,包括配置路由信息并实现实时刷新,以及利用Groovy脚本动态加载过滤器。

一, Zuul动态路由实现

动态路由需要达到可持久化配置,动态刷新的效果。不仅要能满足从spring的配置文件properties加载路由信息,还需要从Redis加载我们的配置。另外一点是,路由信息在容器启动时就已经加载进入了内存,我们希望配置完成后,实施发布,动态刷新内存中的路由信息,达到不停机维护路由信息的效果。

为了避免Eureka的侵入性设计,这里没有使用spring-cloud的服务的注册与发现的Eureka,而直接使用了Zuul。

需要配置如下属性使Zuul不依赖Eureka:

Icon

#关闭zuul的eureka注册
eureka.client.enabled=false

这里采取的方式修改路由定位器,借鉴DiscoveryClientRouteLocator去改造SimpleRouteLocator使其具备刷新能力。

DiscoveryClientRouteLocator比SimpleRouteLocator多了两个功能,第一是从DiscoveryClient(如Eureka)发现路由信息,我们不想使用eureka这种侵入式的网关模块,所以忽略它,第二是实现了RefreshableRouteLocator接口,能够实现动态刷新。

路由定位器的实现:

路由定位器的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@Override
public void refresh() {
    doRefresh();
}
 
@Override
protected Map<String, ZuulRoute> locateRoutes() {
    LinkedHashMap<String, ZuulRoute> routesMap = new LinkedHashMap<String, ZuulRoute>();
    //从application.properties中加载路由信息
    routesMap.putAll(super.locateRoutes());
    //从redis中加载路由信息
    routesMap.putAll(locateRoutesFromRedis());
    //优化一下配置
    LinkedHashMap<String, ZuulRoute> values = new LinkedHashMap<>();
    for (Map.Entry<String, 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;
}
locateRoutesFromRedis方法,则是从Redis中获取路由信息。

然后通过调用路由定位器的doRefresh方法,刷新路由。

刷新路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class RefreshRouteService {
 
    @Autowired
    ApplicationEventPublisher publisher;
 
    @Autowired
    RouteLocator routeLocator;
 
    public void refreshRoute() {
        RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
        publisher.publishEvent(routesRefreshedEvent);
    }
 
}

二, Zuul动态Filter实现

Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。过滤器之间没有直接的相互通信。他们是通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。

过滤器是由Groovy写成。这些过滤器文件被放在Zuul Server上的特定目录下面。Zuul会定期轮询这些目录。修改过的过滤器会动态的加载到Zuul Server中以便于request使用。

需要在启动方法中追加如下代码,动态载入Groovy脚本:

动态载入Groovy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 启动时加载groovy filter
@Component
public static class GroovyLoadLineRunner implements CommandLineRunner {
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    @Value("${ecej.groovy.path}")
    private String groovyPath;
 
    @Override
    public void run(String... args) throws Exception {
        MonitoringHelper.initMocks();
        FilterLoader.getInstance().setCompiler(new GroovyCompiler());
        try {
            FilterFileManager.setFilenameFilter(new GroovyFileFilter());
            logger.info(groovyPath);
            FilterFileManager.init(1, groovyPath + "pre", groovyPath + "post");
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
FilterFileManager会根据设定的pollingIntervalSeconds,去扫描指定目录的文件到内存中。

在指定目录放入如下的脚本,即可以实现动态Filter的功能。

Groovy脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.ecej.zuul.filter.pre
 
import org.slf4j.Logger
import org.slf4j.LoggerFactory;
 
import javax.servlet.http.HttpServletRequest;
 
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
 
class PreRequest extends ZuulFilter {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Override
    String filterType() {
        return "pre"
    }
 
    @Override
    int filterOrder() {
        return 1000
    }
 
    @Override
    boolean shouldFilter() {
        return true
    }
 
    @Override
    Object run() {
        try {
            RequestContext ctx = RequestContext.getCurrentContext();
            HttpServletRequest request = ctx.getRequest();
            logger.info(String.format("#####Groovy Pre Filter#####send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
        catch (Exception e) {
            logger.error("",e);
        }
 
        return null
    }
}

之后只要修改这个文件,Filter的就会动态跟着改变。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值