springboot+zuul实现自定义过滤器、动态路由、动态负载。

匿名 (未验证) 提交于 2019-12-02 20:41:15

参考:https://blog.csdn.net/u014091123/article/details/75433656
https://blog.csdn.net/u013815546/article/details/68944039

Zuul是Netflix开源的微服务网关,他的核心是一系列的过滤器,通过这些过滤器我们可以轻松的实现服务的访问认证、限流、路由、负载、熔断等功能。

基于对已有项目代码零侵入的需求,本文没有将zuul网关项目注册到eureka中心,而是将zuul与springboot结合作为一个独立的项目进行请求转发,因此本项目是非spring cloud架构。



pom.xml

1 <dependency> 2     <groupId>org.springframework.cloud</groupId> 3     <artifactId>spring-cloud-starter-zuul</artifactId> 4     <version>1.4.4.RELEASE</version> 5 </dependency>

application.properties

1 server.port=8090 2 eureka.client.enable=false 3 zuul.ribbon.eager-load.enabled=true 4  5 zuul.SendErrorFilter.post.disable=true

由于后续会使用到动态路由,所以这里我们并不需要在application.properties中做网关地址转发映射。

SpringBootZuulApplication.java

 1 package com.syher.zuul;  2   3 import com.google.common.util.concurrent.ThreadFactoryBuilder;  4 import com.syher.zuul.core.zuul.router.PropertiesRouter;  5 import org.springframework.beans.factory.annotation.Autowired;  6 import org.springframework.boot.CommandLineRunner;  7 import org.springframework.boot.SpringApplication;  8 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;  9 import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 10 import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent; 11 import org.springframework.cloud.netflix.zuul.filters.RouteLocator; 12 import org.springframework.context.ApplicationEventPublisher; 13 import org.springframework.context.annotation.ComponentScan; 14  15 import java.io.File; 16 import java.util.concurrent.Executors; 17 import java.util.concurrent.ScheduledExecutorService; 18 import java.util.concurrent.TimeUnit; 19  20 /** 21  * @author braska 22  * @date 2018/06/25. 23  **/ 24 @EnableAutoConfiguration 25 @EnableZuulProxy 26 @ComponentScan(basePackages = { 27         "com.syher.zuul.core", 28         "com.syher.zuul.service" 29 }) 30 public class SpringBootZuulApplication implements CommandLineRunner { 31     @Autowired 32     ApplicationEventPublisher publisher; 33     @Autowired 34     RouteLocator routeLocator; 35  36     private ScheduledExecutorService executor; 37     private Long lastModified = 0L; 38     private boolean instance = true; 39  40     public static void main(String[] args) { 41         SpringApplication.run(SpringBootZuulApplication.class, args); 42     } 43  44     @Override 45     public void run(String... args) throws Exception { 46         executor = Executors.newSingleThreadScheduledExecutor( 47                 new ThreadFactoryBuilder().setNameFormat("properties read.").build() 48         ); 49         executor.scheduleWithFixedDelay(() -> publish(), 0, 1, TimeUnit.SECONDS); 50     } 51  52     private void publish() { 53         if (isPropertiesModified()) { 54             publisher.publishEvent(new RoutesRefreshedEvent(routeLocator)); 55         } 56     } 57  58     private boolean isPropertiesModified() { 59         File file = new File(this.getClass().getClassLoader().getResource(PropertiesRouter.PROPERTIES_FILE).getPath()); 60         if (instance) { 61             instance = false; 62             return false; 63         } 64         if (file.lastModified() > lastModified) { 65             lastModified = file.lastModified(); 66             return true; 67         } 68         return false; 69     } 70 }



要自定义一个过滤器,只需要要继承ZuulFilter,然后指定过滤类型、过滤顺序、是否执行这个过滤器、过滤内容就OK了。


AbstractZuulFilter.java

 1 package com.syher.zuul.core.zuul.filter;  2   3 import com.netflix.zuul.ZuulFilter;  4 import com.netflix.zuul.context.RequestContext;  5 import com.syher.zuul.core.zuul.ContantValue;  6   7 /**  8  * @author braska  9  * @date 2018/06/29. 10  **/ 11 public abstract class AbstractZuulFilter extends ZuulFilter { 12  13     protected RequestContext context; 14  15     @Override 16     public boolean shouldFilter() { 17         RequestContext ctx = RequestContext.getCurrentContext(); 18         return (boolean) (ctx.getOrDefault(ContantValue.NEXT_FILTER, true)); 19     } 20  21     @Override 22     public Object run() { 23         context = RequestContext.getCurrentContext(); 24         return doRun(); 25     } 26  27     public abstract Object doRun(); 28  29     public Object fail(Integer code, String message) { 30         context.set(ContantValue.NEXT_FILTER, false); 31         context.setSendZuulResponse(false); 32         context.getResponse().setContentType("text/html;charset=UTF-8"); 33         context.setResponseStatusCode(code); 34         context.setResponseBody(String.format("{\"result\":\"%s!\"}", message)); 35         return null; 36     } 37  38     public Object success() { 39         context.set(ContantValue.NEXT_FILTER, true); 40         return null; 41     } 42 }


AbstractPreZuulFilter.java

 1 package com.syher.zuul.core.zuul.filter.pre;  2   3 import com.syher.zuul.core.zuul.FilterType;  4 import com.syher.zuul.core.zuul.filter.AbstractZuulFilter;  5   6 /**  7  * @author braska  8  * @date 2018/06/29.  9  **/ 10 public abstract class AbstractPreZuulFilter extends AbstractZuulFilter { 11     @Override 12     public String filterType() { 13         return FilterType.pre.name(); 14     } 15 }


RateLimiterFilter.java

 1 package com.syher.zuul.core.zuul.filter.pre;  2   3 import com.google.common.util.concurrent.RateLimiter;  4 import com.syher.zuul.core.zuul.FilterOrder;  5 import org.slf4j.Logger;  6 import org.slf4j.LoggerFactory;  7   8 import javax.servlet.http.HttpServletRequest;  9  10 /** 11  * @author braska 12  * @date 2018/06/29. 13  **/ 14 public class RateLimiterFilter extends AbstractPreZuulFilter { 15  16     private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterFilter.class); 17  18     /** 19      * 每秒允许处理的量是50 20      */ 21     RateLimiter rateLimiter = RateLimiter.create(50); 22  23     @Override 24     public int filterOrder() { 25         return FilterOrder.RATE_LIMITER_ORDER; 26     } 27  28     @Override 29     public Object doRun() { 30         HttpServletRequest request = context.getRequest(); 31         String url = request.getRequestURI(); 32         if (rateLimiter.tryAcquire()) { 33             return success(); 34         } else { 35             LOGGER.info("rate limit:{}", url); 36             return fail(401, String.format("rate limit:{}", url)); 37         } 38     } 39 }

其他类型的过滤器也一样。创建不同的抽象类,比如AbstractPostZuulFilter,指定filterType,然后具体的postFilter只要继承该抽象类即可。


ZuulConfigure.java

 1 package com.syher.zuul.core.config;  2   3 import com.netflix.loadbalancer.IRule;  4 import com.netflix.zuul.ZuulFilter;  5 import com.syher.zuul.core.ribbon.ServerLoadBalancerRule;  6 import com.syher.zuul.core.zuul.filter.pre.RateLimiterFilter;  7 import com.syher.zuul.core.zuul.filter.pre.TokenAccessFilter;  8 import com.syher.zuul.core.zuul.filter.pre.UserRightFilter;  9 import com.syher.zuul.core.zuul.router.PropertiesRouter; 10 import org.springframework.beans.factory.annotation.Autowired; 11 import org.springframework.boot.autoconfigure.web.ServerProperties; 12 import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; 13 import org.springframework.context.annotation.Bean; 14 import org.springframework.context.annotation.Configuration; 15  16 /** 17  * @author braska 18  * @date 2018/07/05. 19  **/ 20 @Configuration 21 public class ZuulConfigure { 22  23     @Autowired 24     ZuulProperties zuulProperties; 25     @Autowired 26     ServerProperties server; 27  28     /** 29      * 动态路由 30      * @return 31      */ 32     @Bean 33     public PropertiesRouter propertiesRouter() { 34         return new PropertiesRouter(this.server.getServletPrefix(), this.zuulProperties); 35     } 36  37     /** 38      * 动态负载 39      * @return 40      */ 41     @Bean 42     public IRule loadBalance() { 43         return new ServerLoadBalancerRule(); 44     } 45  46     /** 47      * 自定义过滤器 48      * @return 49      */ 50     @Bean 51     public ZuulFilter rateLimiterFilter() { 52         return new RateLimiterFilter(); 53     } 54 }


zuul默认使用的路由是SimpleRouteLocator,不具备动态刷新的效果。DiscoveryClientRouteLocator具备刷新功能,但是需要已有的项目将服务注册到eureka,这不符合已有项目代码零侵入的需求所以排除。那么还有个办法就是自定义路由然后实现RefreshableRouteLocator类。


AbstractDynamicRouter.java

 1 package com.syher.zuul.core.zuul.router;  2   3 import com.syher.zuul.core.zuul.entity.BasicRoute;  4 import org.apache.commons.lang.StringUtils;  5 import org.slf4j.Logger;  6 import org.slf4j.LoggerFactory;  7 import org.springframework.beans.BeanUtils;  8 import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;  9 import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator; 10 import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; 11  12 import java.util.LinkedHashMap; 13 import java.util.List; 14 import java.util.Map; 15  16 /** 17  * @author braska 18  * @date 2018/07/02. 19  **/ 20 public abstract class AbstractDynamicRouter extends SimpleRouteLocator implements RefreshableRouteLocator { 21  22     private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDynamicRouter.class); 23  24     public AbstractDynamicRouter(String servletPath, ZuulProperties properties) { 25         super(servletPath, properties); 26     } 27  28     @Override 29     public void refresh() { 30         doRefresh(); 31     } 32  33     @Override 34     protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() { 35         LinkedHashMap<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<String, ZuulProperties.ZuulRoute>(); 36         routes.putAll(super.locateRoutes()); 37  38         List<BasicRoute> results = readRoutes(); 39  40         for (BasicRoute result : results) { 41             if (StringUtils.isEmpty(result.getPath()) ) { 42                 continue; 43             } 44             ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute(); 45             try { 46                 BeanUtils.copyProperties(result, zuulRoute); 47             } catch (Exception e) { 48                 LOGGER.error("=============load zuul route info from db with error==============", e); 49             } 50             routes.put(zuulRoute.getPath(), zuulRoute); 51         } 52         return routes; 53     } 54  55     /** 56      * 读取路由信息 57      * @return 58      */ 59     protected abstract List<BasicRoute> readRoutes(); 60 }



PropertiesRouter.java

 1 package com.syher.zuul.core.zuul.router;  2   3 import com.google.common.collect.Lists;  4 import com.google.common.util.concurrent.ThreadFactoryBuilder;  5 import com.syher.zuul.common.Context;  6 import com.syher.zuul.core.zuul.entity.BasicRoute;  7 import org.apache.commons.lang.StringUtils;  8 import org.slf4j.Logger;  9 import org.slf4j.LoggerFactory; 10 import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; 11  12 import java.io.File; 13 import java.io.IOException; 14 import java.util.HashMap; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Properties; 18 import java.util.concurrent.Executors; 19 import java.util.concurrent.ScheduledExecutorService; 20 import java.util.stream.Collectors; 21  22 /** 23  * @author braska 24  * @date 2018/07/02. 25  **/ 26 public class PropertiesRouter extends AbstractDynamicRouter { 27  28     private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesRouter.class); 29     public static final String PROPERTIES_FILE = "router.properties"; 30     private static final String ZUUL_ROUTER_PREFIX = "zuul.routes"; 31  32  33     public PropertiesRouter(String servletPath, ZuulProperties properties) { 34         super(servletPath, properties); 35     } 36  37     @Override 38     protected List<BasicRoute> readRoutes() { 39         List<BasicRoute> list = Lists.newArrayListWithExpectedSize(3); 40         try { 41             Properties prop = new Properties(); 42             prop.load( 43                     this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE) 44             ); 45  46             Context context = new Context(new HashMap<>((Map) prop)); 47             Map<String, String> data = context.getSubProperties(ZUUL_ROUTER_PREFIX); 48             List<String> ids = data.keySet().stream().map(s -> s.substring(0, s.indexOf("."))).distinct().collect(Collectors.toList()); 49             ids.stream().forEach(id -> { 50                 Map<String, String> router = context.getSubProperties(String.join(".", ZUUL_ROUTER_PREFIX, id)); 51  52                 String path = router.get("path"); 53                 path = path.startsWith("/") ? path : "/" + path; 54  55                 String serviceId = router.getOrDefault("serviceId", null); 56                 String url = router.getOrDefault("url", null); 57  58                 BasicRoute basicRoute = new BasicRoute(); 59                 basicRoute.setId(id); 60                 basicRoute.setPath(path); 61                 basicRoute.setUrl(router.getOrDefault("url", null)); 62                 basicRoute.setServiceId((StringUtils.isBlank(url) && StringUtils.isBlank(serviceId)) ? id : serviceId); 63                 basicRoute.setRetryable(Boolean.parseBoolean(router.getOrDefault("retry-able", "false"))); 64                 basicRoute.setStripPrefix(Boolean.parseBoolean(router.getOrDefault("strip-prefix", "false"))); 65                 list.add(basicRoute); 66             }); 67         } catch (IOException e) { 68             LOGGER.info("error to read " + PROPERTIES_FILE + " :{}", e); 69         } 70         return list; 71     } 72 }

既然是动态路由实时刷新,那肯定需要一个定时器定时监控properties文件。所以我在启动类SpringBootZuulApplication加了个定时器监控properties是否发生过变更(之前有疑问的现在可以解惑了)。一旦文件被修改过就重新发布一下, 然后会触发routeLocator的refresh方法。

1 public void publish() { 2         if (isPropertiesModified()) { 3             publisher.publishEvent(new RoutesRefreshedEvent(routeLocator)); 4         } 5     }

当然,如果是从数据库或者其他地方比如redis读取就不需要用到定时器,只要在增删改的时候直接publish就好了。

最后,记得PropertiesRouter类交由spring托管(在ZuulConfigure类中配置bean)。

router.properties文件:

1 zuul.routes.dashboard.path=/** 2 zuul.routes.dashboard.strip-prefix=true 3  4 ##不使用动态负载需指定url 5 ##zuul.routes.dashboard.url=http://localhost:9000/ 6 ##zuul服务部署后,动态增加网关映射,无需重启即可实时路由到新的网关 7 ##zuul.routes.baidu.path=/**




2、根据策略结合每台服务器分配的权重选出合适的服务。



ServerLoadBalancerRule.java

 1 package com.syher.zuul.core.ribbon;  2   3 import com.google.common.base.Preconditions;  4 import com.netflix.client.config.IClientConfig;  5 import com.netflix.loadbalancer.AbstractLoadBalancerRule;  6 import com.netflix.loadbalancer.ILoadBalancer;  7 import com.netflix.loadbalancer.Server;  8 import com.syher.zuul.common.util.SystemUtil;  9 import com.syher.zuul.core.ribbon.balancer.LoadBalancer; 10 import com.syher.zuul.core.ribbon.balancer.RandomLoadBalancer; 11 import com.syher.zuul.core.ribbon.balancer.RoundLoadBalancer; 12 import com.syher.zuul.entity.GatewayAddress; 13 import com.syher.zuul.service.GatewayService; 14 import org.apache.commons.lang.StringUtils; 15 import org.slf4j.Logger; 16 import org.slf4j.LoggerFactory; 17 import org.springframework.beans.factory.annotation.Autowired; 18 import org.springframework.beans.factory.annotation.Value; 19  20 /** 21  * @author braska 22  * @date 2018/07/05. 23  **/ 24 public class ServerLoadBalancerRule extends AbstractLoadBalancerRule { 25  26     private static final Logger LOGGER = LoggerFactory.getLogger(ServerLoadBalancerRule.class); 27  28     @Value("${server.host:127.0.0.1}") 29     private String host; 30     @Value("${server.port:8080}") 31     private Integer port; 32  33     @Autowired 34     private GatewayService gatewayService; 35  36     @Override 37     public void initWithNiwsConfig(IClientConfig iClientConfig) { 38     } 39  40     @Override 41     public Server choose(Object key) { 42         return getServer(getLoadBalancer(), key); 43     } 44  45     private Server getServer(ILoadBalancer loadBalancer, Object key) { 46         if (StringUtils.isBlank(host)) { 47             host = SystemUtil.ipList().get(0); 48         } 49         //Preconditions.checkArgument(host != null, "server.host must be specify."); 50         //Preconditions.checkArgument(port != null, "server.port must be specify."); 51  52         GatewayAddress address = gatewayService.getByHostAndPort(host, port); 53         if (address == null) { //这里的逻辑可以改,找不到网关配置信息可以指定默认的负载策略 54             LOGGER.error(String.format("must be config a gateway info for the server[%s:%s].", host, String.valueOf(port))); 55             return null; 56         } 57  58         LoadBalancer balancer = LoadBalancerFactory.build(address.getFkStrategyId()); 59  60         return balancer.chooseServer(loadBalancer); 61     } 62  63     static class LoadBalancerFactory { 64  65         public static LoadBalancer build(String strategy) { 66             GatewayAddress.StrategyType type = GatewayAddress.StrategyType.of(strategy); 67             switch (type) { 68                 case ROUND: 69                     return new RoundLoadBalancer(); 70                 case RANDOM: 71                     return new RandomLoadBalancer(); 72                 default: 73                     return null; 74             } 75         } 76     } 77 }


LoadBalancer.java

 1 package com.syher.zuul.core.ribbon.balancer;  2   3 import com.netflix.loadbalancer.ILoadBalancer;  4 import com.netflix.loadbalancer.Server;  5   6 /**  7  * @author braska  8  * @date 2018/07/06.  9  **/ 10 public interface LoadBalancer { 11  12     /** 13      * choose a loadBalancer 14      * @param loadBalancer 15      * @return 16      */ 17     Server chooseServer(ILoadBalancer loadBalancer); 18 }


AbstractLoadBalancer.java

 1 package com.syher.zuul.core.ribbon.balancer;  2   3 import com.netflix.loadbalancer.ILoadBalancer;  4 import com.netflix.loadbalancer.Server;  5 import com.syher.zuul.core.SpringContext;  6 import com.syher.zuul.service.ServerService;  7 import org.slf4j.Logger;  8 import org.slf4j.LoggerFactory;  9  10 /** 11  * @author braska 12  * @date 2018/07/06. 13  **/ 14 public abstract class AbstractLoadBalancer implements LoadBalancer { 15     private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLoadBalancer.class); 16     protected ServerService serverService; 17  18     @Override 19     public Server chooseServer(ILoadBalancer loadBalancer) { 20         this.serverService = SpringContext.getBean(ServerService.class); 21         Server server = choose(loadBalancer); 22         if (server != null) { 23             LOGGER.info(String.format("the server[%s:%s] has been select.", server.getHost(), server.getPort())); 24         } else { 25             LOGGER.error("could not find any server."); 26         } 27         return server; 28     } 29  30     public abstract Server choose(ILoadBalancer loadBalancer); 31 }


RoundLoadBalancer.java

 1 package com.syher.zuul.core.ribbon.balancer;  2   3 import com.netflix.loadbalancer.ILoadBalancer;  4 import com.netflix.loadbalancer.Server;  5 import com.syher.zuul.common.Constant;  6 import com.syher.zuul.core.GlobalCache;  7 import com.syher.zuul.core.ribbon.LoadBalancerRuleUtil;  8 import com.syher.zuul.entity.ServerAddress;  9  10 import java.util.List; 11  12 /** 13  * 权重轮询 14  * 首次使用取最大权重的服务器。而后通过权重的不断递减,寻找适合的服务器。 15  * @author braska 16  * @date 2018/07/06. 17  **/ 18 public class RoundLoadBalancer extends AbstractLoadBalancer { 19  20     private Integer currentServer; 21     private Integer currentWeight; 22     private Integer maxWeight; 23     private Integer gcdWeight; 24  25     @Override 26     public Server choose(ILoadBalancer loadBalancer) { 27         List<ServerAddress> addressList = serverService.getAvailableServer(); 28         if (addressList != null && !addressList.isEmpty()) { 29             maxWeight = LoadBalancerRuleUtil.getMaxWeightForServers(addressList); 30             gcdWeight = LoadBalancerRuleUtil.getGCDForServers(addressList); 31             currentServer = Integer.parseInt(GlobalCache.instance().getOrDefault(Constant.CURRENT_SERVER_KEY, -1).toString()); 32             currentWeight = Integer.parseInt(GlobalCache.instance().getOrDefault(Constant.CURRENT_WEIGHT_KEY, 0).toString()); 33  34             Integer serverCount = addressList.size(); 35  36             if (1 == serverCount) { 37                 return new Server(addressList.get(0).getHost(), addressList.get(0).getPort()); 38             } else { 39                 while (true) { 40                     currentServer = (currentServer + 1) % serverCount; 41                     if (currentServer == 0) { 42                         currentWeight = currentWeight - gcdWeight; 43                         if (currentWeight <= 0) { 44                             currentWeight = maxWeight; 45                             if (currentWeight == 0) { 46                                 GlobalCache.instance().put(Constant.CURRENT_SERVER_KEY, currentServer); 47                                 GlobalCache.instance().put(Constant.CURRENT_WEIGHT_KEY, currentWeight); 48                                 Thread.yield(); 49                                 return null; 50                             } 51                         } 52                     } 53  54                     ServerAddress address = addressList.get(currentServer); 55                     if (address.getWeight() >= currentWeight) { 56                         GlobalCache.instance().put(Constant.CURRENT_SERVER_KEY, currentServer); 57                         GlobalCache.instance().put(Constant.CURRENT_WEIGHT_KEY, currentWeight); 58                         return new Server(address.getHost(), address.getPort()); 59                     } 60                 } 61             } 62  63         } 64         return null; 65     } 66 }

最后,ServerLoadBalancerRule交由spring托管。


源码:https://gitee.com/syher/spring-boot-project/tree/master/spring-boot-zuul

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!