Zuul 基于servlet 2.5 (works with 3.x),使用阻塞API。它不支持任何长期的链接,如websocket。html
Gateway创建在Spring Framework 5,Project Reactor 和Spring Boot 2 上,使用非阻塞API。支持Websocket,由于它与Spring紧密集成,因此它是一个更好的开发者体验。java
为何 Spring Cloud 最初选择了使用 Netflix 几年前开源的 Zuul 做为网关,以后又选择了自建 Gateway 呢?有一种说法是,高性能版的 Zuul2 在通过了屡次跳票以后,对于 Spring 这样的整合专家可能也不肯意再继续等待,因此 Spring Cloud Gateway 应运而生。react
本文不对 Spring Cloud Gateway 和 Zuul 的性能做太多赘述,基本能够确定的是 Gateway 做为如今 Spring Cloud 主推的网关方案, Finchley 版本后的 Gateway 比 zuul 1.x 系列的性能和功能总体要好。git
咱们来搭建一个基于 Eureka 注册中心的简单网关,不对 Gateway 的所有功能作过多解读,毕竟<span style="color:blue">官方文档</span>已经写的很详细了,或者能够阅读<span style="color:blue">中文翻译文档</span>。github
SpringBoot 版本号:2.1.6.RELEASEweb
SpringCloud 版本号:Greenwich.RELEASEredis
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis-reactive</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
spring: application: name: cloud-gateway redis: host: 127.0.0.1 timeout: 3000 password: xxxx jedis: pool: max-active: 8 max-idle: 4 cloud: gateway: enabled: true metrics: enabled: true discovery: locator: enabled: true routes: # 普通服务的路由配置 - id: cloud-eureka-client uri: lb://cloud-eureka-client order: 0 predicates: - Path=/client/** filters: # parts 参数指示在将请求发送到下游以前,要从请求中去除的路径中的节数。好比咱们访问 /client/hello,调用的时候变成 http://localhost:2222/hello - StripPrefix=1 # 熔断器 - name: Hystrix args: name: fallbackcmd # 降级处理 fallbackUri: forward:/fallback # 限流器 # 这定义了每一个用户 10 个请求的限制。容许 20 个突发,但下一秒只有 10 个请求可用。 - name: RequestRateLimiter args: # SPEL 表达式获取 Spring 中的 Bean,这个参数表示根据什么来限流 key-resolver: '#{@ipKeyResolver}' # 容许用户每秒执行多少请求(令牌桶的填充速率) redis-rate-limiter.replenishRate: 10 # 容许用户在一秒内执行的最大请求数。(令牌桶能够保存的令牌数)。将此值设置为零将阻止全部请求。 redis-rate-limiter.burstCapacity: 20 # websocket 的路由配置 - id: websocket service uri: lb:ws://serviceid predicates: - Path=/websocket/** management: endpoints: web: exposure: # 开启指定端点 include: gateway,metrics eureka: client: service-url: defaultZone: http://user:password@localhost:1111/eureka/
@SpringBootApplication @EnableDiscoveryClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } /** * 限流的键定义,根据什么来限流 */ @Bean public KeyResolver ipKeyResolver() { return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName()); } }
Spring Cloud Gateway 同 Zuul 相似,有 “pre” 和 “post” 两种方式的 filter。客户端的请求先通过 “pre” 类型的 filter,而后将请求转发到具体的业务服务,收到业务服务的响应以后,再通过“post”类型的filter处理,最后返回响应到客户端。spring
与 Zuul 不一样的是,filter 除了分为 “pre” 和 “post” 两种方式的 filter 外,在 Spring Cloud Gateway 中,filter 从做用范围可分为另外两种,一种是针对于单个路由的 gateway filter,它须要像上面 application.yml 中的 filters 那样在单个路由中配置;另一种是针对于所有路由的global gateway filter,不须要单独配置,对全部路由生效。缓存
咱们一般用全局过滤器实现鉴权、验签、限流、日志输出等。websocket
经过实现 GlobalFilter 接口来自定义 Gateway 的全局过滤器;经过实现 Ordered 接口或者使用 @Order 注解来定义过滤器的执行顺序,执行顺序是从小到大执行,较高的值被解释为较低的优先级。
@Bean @Order(-1) public GlobalFilter a() { return (exchange, chain) -> { log.info("first pre filter"); return chain.filter(exchange).then(Mono.fromRunnable(() -> { log.info("third post filter"); })); }; } @Bean @Order(0) public GlobalFilter b() { return (exchange, chain) -> { log.info("second pre filter"); return chain.filter(exchange).then(Mono.fromRunnable(() -> { log.info("second post filter"); })); }; } @Bean @Order(1) public GlobalFilter c() { return (exchange, chain) -> { log.info("third pre filter"); return chain.filter(exchange).then(Mono.fromRunnable(() -> { log.info("first post filter"); })); }; }
优先级最高的 filter ,它的 “pre” 过滤器最早执行,“post” 过滤器最晚执行。
咱们来定义一个 “pre” 类型的局部过滤器:
@Component public class PreGatewayFilterFactory extends AbstractGatewayFilterFactory<PreGatewayFilterFactory.Config> { public PreGatewayFilterFactory() { super(Config.class); } @Override public GatewayFilter apply(Config config) { // grab configuration from Config object return (exchange, chain) -> { //If you want to build a "pre" filter you need to manipulate the //request before calling chain.filter ServerHttpRequest.Builder builder = exchange.getRequest().mutate(); //use builder to manipulate the request ServerHttpRequest request = builder.build(); return chain.filter(exchange.mutate().request(request).build()); }; } public static class Config { //Put the configuration properties for your filter here } }
其中,须要的过滤器参数配置在 PreGatewayFilterFactory.Config 中。而后,接下来咱们要作的,就是把局部过滤器配置在须要的路由上,根据 SpringBoot 约定大于配置的思想,咱们只须要配置 PreGatewayFilterFactory.java 中,前面的参数就好了,即
spring: cloud: gateway: routes: - id: cloud-eureka-client uri: lb://cloud-eureka-client order: 0 predicates: - Path=/client/** filters: - pre
<b style="color:red">tips:</b>能够去阅读下 Gateway 中默认提供的几种过滤器,好比 StripPrefixGatewayFilterFactory.java 等。
Spring Cloud Gateway 实现动态路由主要利用 RouteDefinitionWriter 这个 Bean:
public interface RouteDefinitionWriter { Mono<Void> save(Mono<RouteDefinition> route); Mono<Void> delete(Mono<String> routeId); }
以前翻阅了网上的一些文章,基本都是经过自定义 controller 和出入参,而后利用 RouteDefinitionWriter 实现动态网关。可是,我在翻阅 Spring Cloud Gateway 文档的时候,发现 Gateway 已经提供了相似的功能:
@RestControllerEndpoint(id = "gateway") public class GatewayControllerEndpoint implements ApplicationEventPublisherAware { /*---省略前面代码---*/ @PostMapping("/routes/{id}") @SuppressWarnings("unchecked") public Mono<ResponseEntity<Void>> save(@PathVariable String id, @RequestBody Mono<RouteDefinition> route) { return this.routeDefinitionWriter.save(route.map(r -> { r.setId(id); log.debug("Saving route: " + route); return r; })).then(Mono.defer(() -> Mono.just(ResponseEntity.created(URI.create("/routes/"+id)).build()) )); } @DeleteMapping("/routes/{id}") public Mono<ResponseEntity<Object>> delete(@PathVariable String id) { return this.routeDefinitionWriter.delete(Mono.just(id)) .then(Mono.defer(() -> Mono.just(ResponseEntity.ok().build()))) .onErrorResume(t -> t instanceof NotFoundException, t -> Mono.just(ResponseEntity.notFound().build())); } /*---省略后面代码---*/ }
要建立一个路由,发送POST请求 <b>/actuator/gateway/routes/{id_route_to_create}</b>,参数为JSON结构,具体参数数据结构:
{ "id": "first_route", "predicates": [{ "name": "Path", "args": {"_genkey_0":"/first"} }], "filters": [], "uri": "http://www.uri-destination.org", "order": 0 }]
要删除一个路由,发送 DELETE请求 <b>/actuator/gateway/routes/{id_route_to_delete}</b>
原文出处:https://www.cnblogs.com/jmcui/p/11259200.html