Spring Cloud(六)Zuul构建微服务网关

为什么要使用微服务网关

不同的微服务一般会有不同的网络地址,客户端在访问这些微服务时必须记住几十甚至几百个地址,这对于客户端方来说太复杂也难以维护,如下图:
在这里插入图片描述
如果让客户端直接与各个微服务通讯,可能会有很多问题:

  • 客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度
  • 在某些场景下存在跨域请求的问题
  • 加大身份认证的难度,每个微服务需要独立认证

因此,我们需要一个微服务网关,介于客户端与服务器之间的中间层,所有的外部请求都会先经过微服务网关。客户端只需要与网关交互,只知道一个网关地址即可!
在这里插入图片描述

微服务网关封装了应用程序的内部结构,客户端只须跟网关交互,而无须直接调用特定微服务的接口。这样开发就可以得到简化。不仅如此,使用微服务还有以下优点:

  • 易于监控
  • 易于认证
  • 减少了客户端与各个微服务之间的交互次数

Zuul简介

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

  • 动态路由:动态将请求路由到不同后端集群
  • 压力测试:逐渐增加指向集群的流量,以了解性能
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  • 静态响应处理:边缘位置进行响应,避免转发到内部集群
  • 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求。Spring Cloud对Zuul进行了整合和增强

Zuul也整合了 Spring Cloud Ribbon 与 Spring Cloud Hystrix;

使用Zuul

“路由”是指根据请求URL,将请求分配到对应的处理程序。在微服务体系中,Zuul负责接收所有的请求。根据不同的URL匹配规则,将不同的请求转发到不同的微服务处理;

1:新建Spring Boot项目EurekaServer,添加依赖Eureka Server
2:启动类

@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaServerApplication.class, args);
    }
}

3:application.yml

server:
  port: 8761                  
eureka:
  client:
    registerWithEureka: false    #是否将自己注册到Eureka Server,默认为True。由于当前应用就是Eureka Server,故false
    fetchRegistry: false         #是否从Eureka Server获取注册信息,默认True。因为这是一个单节点的Eureka Server,不需要同步其他的Eureka Server节点,故false
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

4:新建Spring Boot项目EurekaClient,添加依赖Eureka Client、Web
5:启动类

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaClientApplication.class, args);
    }
}

6:控制器MyController

@RestController
public class MyController {

    @RequestMapping("/eureka/test")  //等会通过Zuul来访问它,测试Zuul
    public String test() 
    {       
        return "Hello Zuul";
    }
}

7:新建Spring Boot项目ServerZuul,添加依赖Zuul、Eureka Client
8:启动类

@EnableZuulProxy
@SpringBootApplication
public class ServerZuulApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerZuulApplication.class, args);
	}
}

9:application.yml

server:
  port: 8040
spring:
  application:
    name: Zuul
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/  
zuul:
  routes:
    zuul-service:				  # 这里是路由id,随意写
      path: /zuul-service/**      # 这里是映射路径,这里将所有请求前缀为/zuul-service/的请求,转发到http://127.0.0.1:8020处理
      url: http://127.0.0.1:8020  # 映射路径对应的实际url地址
      sensitiveHeaders:           #默认zuul会屏蔽cookie,cookie不会传到下游服务,这里设置为空则取消默认的黑名单,如果设置了具体的头信息则不会传到下游服务

配置解析:

  • zuul-service:配置路由id,可以随意取名
  • url:映射路径对应的实际url地址
  • path:配置映射路径,这里将所有请求前缀为/zuul-service/的请求,转发到http://127.0.0.1:8020处理

10:运行测试
启动项目EurekaServer
启动项目EurekaClient
启动项目ServerZuul
访问:http://localhost:8040/zuul-service/eureka/test
在这里插入图片描述

Zuul的路由端点

当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。借助这个端点,可以方便、直观地查看以及管理Zuul的路由。
spring-cloud-starter-zuul已经包含了spring-cloud-starter-actuator。因此我们编写的ServerZuul已经具备路由管理能力。
运行测试:
访问:http://localhost:8040/actuator/routes
在这里插入图片描述
可以看到从路径到微服务的映射!

如果访问出现404错误:
rug
并且访问http://localhost:8040/actuator,没有发现routes端点:
在这里插入图片描述
则需要在application.yml中新增如下配置即可:

management:
  endpoints:
    web:
      exposure:
        include: '*'

路由配置详解

微服务一般是由几十、上百个服务组成,对于一个URL请求,最终会确认一个服务实例进行处理。如果对每个服务实例手动指定一个唯一访问地址,然后根据URL去手动实现请求匹配,这样做显然就不合理。

Zuul支持与Eureka整合开发,根据ServiceID自动的从注册中心中获取服务地址并转发请求,这样做的好处不仅可以通过单个端点来访问应用的所有服务,而且在添加或移除服务实例的时候不用修改Zuul的路由配置。
1:简化路由配置

(1)添加Eureka客户端依赖(使用Zuul小节第7步已经加了)

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

(2)开启Eureka客户端发现功能

@EnableDiscoveryClient
public class ServerZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServerZuulApplication.class, args);
    }
}

(3)添加Eureka配置,获取服务信息(使用Zuul小节第9步已经有了)
(4)修改映射配置,通过服务名称获取
因为已经有了Eureka客户端,我们可以从Eureka获取服务的地址信息,因此映射时无需指定IP地址,而是通过服务名称来访问,而且Zuul已经集成了Ribbon的负载均衡功能;

zuul:
  routes:
    zuul-service:
      path: /zuul-service/**      # 这里是映射路径
      serviceId: EurekaClinet     #配置转发的微服务名称

访问:http://localhost:8040/zuul-service/eureka/test,结果是一样的!

而大多数情况下,我们的路由名称往往和服务名会写成一样的。因此Zuul就提供了一种简化的配置语法:zuul.routes.serviceId=path

上面的配置可以简化为一条:

zuul:
  routes:
    EurekaClinet: /zuul-service/**

上面已经大大的简化了配置项。但是当服务较多时,配置也是比较繁琐的。因此Zuul就指定了默认的路由规则:

  • 默认情况下,一切服务的映射路径就是服务名本身:例如服务名为eurekaclinet ,则默认的映射路径就是: /eurekaclinet/**

所以,当我们什么都没配置时,访问http://localhost:8040/eurekaclinet/eureka/test结果与上面一样!
在这里插入图片描述
2:忽略指定微服务

zuul:
  ignored-services: eurekaclinet

访问:http://localhost:8040/eurekaclinet/eureka/test
这里写图片描述
因为已经设置忽略了它,所以Zuul不会代理它。自然会报错!
3:忽略所有微服务,只路由指定微服务

zuul:
  ignored-services: '*'   # 使用'*'可忽略所有微服务
  routes:
    eurekaclinet: /client/**  

让Zuul只路由eurekaclinet微服务

4:路由前缀

zuul:
  prefix: /api
  strip-prefix: false
  routes:
    eurekaclinet: /zuul-service/**        #/api/zuul-service/eureka/tests

更改EurekaClient的MyController

@RestController
public class MyController {

    @RequestMapping("/api/eureka/test")  //等会通过Zuul来访问它,测试Zuul
    public String test() 
    {       
        return "Hello Zuul";
    }
}

访问:http://localhost:8040/api/zuul-service/eureka/test会被转发到/eurekaclinet/api/eureka/test
在这里插入图片描述

访问:http://localhost:8040/routes
在这里插入图片描述

使用Zuul上传文件

1:新建Spring Boot项目ServerZuulUpload,添加依赖Web、Eureka Client(2.0.3版本没有包含Web)、Actuator
2:启动类

@EnableEurekaClient
@SpringBootApplication
public class ServerZuulUploadApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerZuulUploadApplication.class, args);
	}
}

3:控制类MyController

@Controller
public class MyController {
	  /**
	   * 上传文件
	   * ps.该示例比较简单,没有做IO异常、文件大小、文件非空等处理
	   * @param file 待上传的文件
	   * @return 文件在服务器上的绝对路径
	   * @throws IOException IO异常
	   */
	  @RequestMapping(value = "/upload", method = RequestMethod.POST)
	  public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException {
	    byte[] bytes = file.getBytes();
	    File fileToSave = new File(file.getOriginalFilename());
	    FileCopyUtils.copy(bytes, fileToSave);
	    return fileToSave.getAbsolutePath();
	  }
}

4:application.yml

server:
  port: 8050
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
  instance:
    prefer-ip-address: true
spring:
  application:
    name: Upload
  servlet:
    multipart:
      max-file-size: 2000Mb
      max-request-size: 2500Mb

将该服务注册到EurekaServer,并配置了文件上传大小的限制!
运行测试(使用的是Postman):
启动项目
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

Zuul的过滤器

Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了4种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期:

  • PRE:这种过滤器在请求被路由之前调用
  • ROUTING:这种过滤器将请求路由到微服务
  • POST:这种过滤器在路由到微服务以后执行
  • ERROR:在其他阶段发生错误时执行该过滤器

除了默认的过滤器类型,Zuul还允许创建自定义的过滤器类型。
1:编写Zuul过滤器,只需继承抽象类ZuulFilter,然后实现几个抽象方法就可以了

public class PreRequestLogFilter extends ZuulFilter {
  private static final Logger LOGGER = LoggerFactory.getLogger(PreRequestLogFilter.class);

  @Override
  public String filterType() {      //返回过滤器类型。有pre、route、post、error等几种取值,对应上文的几种
    return FilterConstants.PRE_TYPE;
  }

  @Override
  public int filterOrder() {//返回一个int值来指定过滤器的执行顺序,不同的过滤器允许返回相同的数字
    return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
  }

  @Override
  public boolean shouldFilter() {
    return true;//返回一个boolen值来判断该过滤器是否被执行,true表示执行,false表示不执行
  }

  @Override
  public Object run() {//过滤器的具体逻辑。这里让它打印请求的HTTP方法以及请求的地址
    RequestContext ctx = RequestContext.getCurrentContext();
    HttpServletRequest request = ctx.getRequest();
    PreRequestLogFilter.LOGGER.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL().toString()));
    return null;
  }
}

2:Zuul启动类:

@EnableZuulProxy
@SpringBootApplication
public class ServerZuulApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerZuulApplication.class, args);
	}
	  @Bean
	  public PreRequestLogFilter preRequestLogFilter() {
	    return new PreRequestLogFilter();
	  }
}

3:运行测试:
启动项目EurekaServer
启动项目EurekaClient
启动项目ServerZuul
多次访问:http://localhost:8040/api/zuul-service/eureka/test
在这里插入图片描述

禁用Zuul过滤器

Zuul的application.yml

zuul:
  PreRequestLogFilter:             #zuul.<ClassNmae>.<filterType>.disable=true即可禁用
    pre:
      disable: true  

启动项目后多次访问:http://localhost:8040/api/zuul-service/eureka/test,控制台并不会打印相关信息;

Zuul的容错与回退

在Spring Cloud中,Zuul默认已经包整合了Hystrix。
1:添加依赖HystrixDashboard
2:启动类:

@EnableHystrixDashboard
@SpringBootApplication
public class ServerHystrixDashboardApplication {

	public static void main(String[] args) {
		SpringApplication.run(ServerHystrixDashboardApplication.class, args);
	}
}

3:运行测试
启动项目EurekaServer
启动项目EurekaClient
启动项目ServerZuul
访问:http://localhost:8040/api/zuul-service/eureka/test可获得正常结果
访问:http://localhost:8040/actuator/hystrix.stream
这里写图片描述
访问:http://localhost:8040/hystrix
这里写图片描述
这里写图片描述
Zuul的Hystrix监控的粒度是微服务,而不是某个API;同时也说明,所有经过Zuul的请求,都会被Hystrix保护起来。
关闭项目EurekaClient
访问:http://localhost:8040/eurekaclinet/eureka/test
这里写图片描述
下面来谈谈如何为Zuul实现回退!

为Zuul实现回退

想要为Zuul实现回退,需要实现FallbackProvider接口。在实现类中,指定为哪个微服务提供回退,并提供一个ClientHttpResponse作为回退响应。

@Component  
public class MyFallbackProvider  implements FallbackProvider{
  @Override
  public String getRoute() {
    // 表明是为哪个微服务提供回退,*表示为所有微服务提供回退
    return "*";
  }

  @Override
  public ClientHttpResponse fallbackResponse(Throwable cause) {
    if (cause instanceof HystrixTimeoutException) {
      return response(HttpStatus.GATEWAY_TIMEOUT);
    } else {
      return this.fallbackResponse();
    }
  }

  @Override
  public ClientHttpResponse fallbackResponse() {
    return this.response(HttpStatus.INTERNAL_SERVER_ERROR);
  }

  private ClientHttpResponse response(final HttpStatus status) {
    return new ClientHttpResponse() {
      @Override
      public HttpStatus getStatusCode() throws IOException {
        return status;
      }

      @Override
      public int getRawStatusCode() throws IOException {
        return status.value();
      }

      @Override
      public String getStatusText() throws IOException {
        return status.getReasonPhrase();
      }

      @Override
      public void close() {
      }

      @Override
      public InputStream getBody() throws IOException {
        return new ByteArrayInputStream("服务不可用,请稍后再试。".getBytes());
      }

      @Override
      public HttpHeaders getHeaders() {
        // headers设定
        HttpHeaders headers = new HttpHeaders();
        MediaType mt = new MediaType("application", "json", Charset.forName("UTF-8"));
        headers.setContentType(mt);
        return headers;
      }
    };
  }
}

添加回退之后,重复上面的测试会发现:
这里写图片描述

参考书籍:Spring Cloud与Docker微服务架构实战
以上只是学习所做的笔记,以供日后参考!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值