SpringCloud 使用 Zuul 构建微服务网关

Zuul 是 Netflix 开源的微服务网关,它可以和 Eureka、Ribbon、Hystrix 等组件配合使用。Zuul 的核心就是一系列过滤器,这些过滤器可以完成以下功能:

  • 身份认证和安全
  • 审查和监控
  • 动态路由
  • 压力测试
  • 负载均衡
  • 等待

目前,Zuul 使用默认 HTTP 客户端是 Apache HTTP Client。

下面介绍编写一个 Zuul 微服务网关和基本的配置。

 

准备

Eureka 注册中心,启动在 8761 端口

一个或多个服务,注册在 Eureka 中

比如我目前 Eureka 上已经注册了两个服务

SpringCloud 使用 Zuul 构建微服务网关

分别是用户中心的服务提供者(8080端口)和服务消费者(8081端口)

通过地址栏访问 http://localhost:8080/user/1 和 http://localhost:8081/hello 均能访问到两个服务

其中后者访问效果如下,下面以这个为示例,介绍网关的使用

SpringCloud 使用 Zuul 构建微服务网关

 

下面我们来介绍 zuul,通过网关来访问这两个服务(通常我只暴露服务消费者,即 web 那个服务,服务提供者我们通常不暴露出去)

 

一、编写 Zuul 微服务网关

1.创建一个 SpringBoot 项目

注意:目前我使用都是最新版本, SpringBoot 版本是 2.1.7.RELEASE,SpringCloud 版本是 Greenwich.RELEASE

pom.xml 添加依赖

  1. <!-- Eureka client  -->
  2. <dependency>
  3.     <groupId>org.springframework.cloud</groupId>
  4.     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  5. </dependency>
  6. <!-- zuul -->
  7. <dependency>
  8.     <groupId>org.springframework.cloud</groupId>
  9.     <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  10. </dependency>

 

2.启动类添加 @EnableZuulProxy 注解

  1. @SpringBootApplication
  2. @EnableZuulProxy
  3. public class SensApiGatewayApplication {
  4.     public static void main(String[] args) {
  5.         SpringApplication.run(SensApiGatewayApplication.class, args);
  6.     }
  7. }

 

3.添加配置

application.yml

  1. server:
  2.   port: 9000
  3. spring:
  4.   application:
  5.     name: sens-api-gateway
  6. eureka:
  7.   client:
  8.     serviceUrl:
  9.       defaultZone: http://localhost:8761/eureka/

启动在 9000 端口,注册服务到 Eureka

 

4.运行启动类,启动 Zuul 在 9000 端口

可以看到网关注册到了  Eureka

SpringCloud 使用 Zuul 构建微服务网关

通过地址栏访问

http://localhost:9000/服务名/请求路径,就可以访问到其他服务的路径

如我的用户中心服务消费者服务名为 sens-user-web (上图左侧的 Application 名称是全部大写,实际我的服务名是全小写的)

即下面两个请求结果是一样的

http://localhost:9000/sens-user-web/hello

http://localhost:8081/hello

SpringCloud 使用 Zuul 构建微服务网关

 

可见,网关帮我们转发了这个请求

如果想访问另一个服务,一样的方法

http://localhost:9000/sens-user-core/user/1 也能访问到 http://localhost:8080/user/1

 

可见,目前我们几乎没有任何其他的配置,就能实现通过网关访问到所有的请求

但是这还不够,下面我们会介绍自定义路由和容错回退的一些配置

 

二、自定义路由

目前通过网关访问其他服务的路径,示例如下

http://localhost:9000/sens-user-web/hello

http://localhost:9000/sens-user-core/user/1

 

现在我有两个需求

  • 首先我觉得 sens-user-web 太长,我想改成 user
  • sens-user-core 这个服务我不想暴露,我只想暴露服务消费者。访问生产者的话让网关响应404比较好

解决办法很简单,只需要几行配置

在 application.yml 添加配置

  1. zuul:
  2.   # 前缀,所有服务里的路径前加 /api
  3.   prefix: /api
  4.   routes:
  5.     sens-user-web: /user/**
  6.   # 排除某些路由, 支持正则表达式
  7.   ignored-patterns:
  8.     - /**/abc/efg
  9.   # 排除服务
  10.   ignored-services: sens-user-core

上面几行配置,分别是设置统一的前缀,单条路由重写,排除自定义路由(排除后,访问404),排除服务(该服务所有请求404)

 

这样一来

配置前是 http://localhost:9000/sens-user-web/hello

配置后是 http://localhost:9000/api/user/hello

SpringCloud 使用 Zuul 构建微服务网关

注意:因为我没有排除 sens-user-web,所有之前的 http://localhost:9000/sens-user-web/hello 也可以访问

 

三、设置超时时间

通常我们通过网关访问一个路径,如 http://localhost:9000/sens-user-web/hello,

网关会自己转发到  sens-user-web 服务中的 /hello 路径,

因为 Zuul 内部使用了 Ribbon 做负载均衡,那么对于一些时间比较长的请求,可能会被作为超时处理。

默认时间是 1 秒

 

示例

访问 http://localhost:9000/sens-user-web/hello

我在 sens-user-web 服务 /hello 方法里写一个 sleep,睡眠5s,看会不会超时(也可以通过打断点暂停的方法测试)

  1. @GetMapping("/hello")
  2. public String index() throws InterruptedException {
  3.     System.out.println("进来了");
  4.     //主线程暂停5s
  5.     Thread.sleep(5000);
  6.     return "Hello, this is user web page.";
  7. }

效果如下

SpringCloud 使用 Zuul 构建微服务网关

解决办法

在 pom.xml 中设置超时时间

  1. # 超时时间,ribbon和hystrix是同时生效的,哪个值小哪个生效
  2. hystrix:
  3.   command:
  4.     default:
  5.       execution:
  6.         isolation:
  7.           thread:
  8.             timeoutInMilliseconds: 60000
  9. ribbon:
  10.   ReadTimeout: 60000
  11.   ConnectTimeout: 3000

 

四、Zuul 的容错和回归

当服务不可用的时候,网关会响应 50X 之类的错

比如超时是这样的

SpringCloud 使用 Zuul 构建微服务网关

 

 

这是系统默认的界面,很不友好

我们可以重写回归响应的方法,给用户一个比较好的提示

 

解决办法如下

在网关项目中新建一个类

  1. package com.liuyanzhao.sens.api.gateway.config;
  2. import com.netflix.hystrix.exception.HystrixTimeoutException;
  3. import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
  4. import org.springframework.http.HttpHeaders;
  5. import org.springframework.http.HttpStatus;
  6. import org.springframework.http.MediaType;
  7. import org.springframework.http.client.ClientHttpResponse;
  8. import org.springframework.stereotype.Component;
  9. import java.io.ByteArrayInputStream;
  10. import java.io.IOException;
  11. import java.io.InputStream;
  12. import java.nio.charset.Charset;
  13. /**
  14.  * @author 言曌
  15.  * @date 2019-08-08 10:34
  16.  */
  17. @Component
  18. public class MyFallbackProvider implements FallbackProvider {
  19.     @Override
  20.     public String getRoute() {
  21.         //表明是为哪个微服务提供回退,*表示所有微服务
  22.         return "*";
  23.     }
  24.     @Override
  25.     public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
  26.         if(cause instanceof HystrixTimeoutException) {
  27.             return response(HttpStatus.GATEWAY_TIMEOUT);
  28.         }
  29.         return this.fallbackResponse();
  30.     }
  31.     public ClientHttpResponse fallbackResponse() {
  32.         return this.response(HttpStatus.INTERNAL_SERVER_ERROR);
  33.     }
  34.     private ClientHttpResponse response(final HttpStatus status) {
  35.         return new ClientHttpResponse() {
  36.             @Override
  37.             public HttpStatus getStatusCode() throws IOException {
  38.                 return status;
  39.             }
  40.             @Override
  41.             public int getRawStatusCode() throws IOException {
  42.                 return status.value();
  43.             }
  44.             @Override
  45.             public String getStatusText() throws IOException {
  46.                 return status.getReasonPhrase();
  47.             }
  48.             @Override
  49.             public void close() {
  50.             }
  51.             @Override
  52.             public InputStream getBody() throws IOException {
  53.                 return new ByteArrayInputStream("服务不可用,请稍后再试。Service is not available, please try again later.".getBytes());
  54.             }
  55.             @Override
  56.             public HttpHeaders getHeaders() {
  57.                 //headers设定
  58.                 HttpHeaders headers = new HttpHeaders();
  59.                 MediaType mediaType = new MediaType("application""json", Charset.forName("UTF-8"));
  60.                 headers.setContentType(mediaType);
  61.                 return headers;
  62.             }
  63.         };
  64.     }
  65. }

重启网关服务

SpringCloud 使用 Zuul 构建微服务网关

 

五、管理端点

当 @EnableZuulProxy 和 Spring Boot Actuator配合使用时,Zuul 会暴露两个端点:/actuator/routes, /actuator/filters 。借助这两个端点,我们可以方便地、直观地查看级管理 Zuul。

通常,我们直接访问是会报 404,如下配置可以解决

在 application.yml 添加

  1. # 开启权限访问端点,如 /actuator/routes, /actuator/filters
  2. management:
  3.   endpoints:
  4.     web:
  5.       exposure:
  6.         include: '*'

这个配置在 springboot 1.x 版本是 management.security.enabled=false,端点路径是 /routes和 /filters

 

  • 路由端点

该端点主要是查看路由列表

http://localhost:9000/actuator/routes

SpringCloud 使用 Zuul 构建微服务网关

 

GET 是获取路由列表,POST请求是立即刷新路由列表

 

  • 过滤器路由路径

该端点可以查看所有的过滤器,了解 error、post、pre、route 四种过滤器有哪些,是否启动,执行顺序(order) 是多少。

http://localhost:9000/actuator/filters

SpringCloud 使用 Zuul 构建微服务网关

 

 

源码地址:https://github.com/saysky/sens-web/tree/master/sens-api-gateway

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: