(五)SpringCloud之服务网关——Netflix Zuul

Zuul提供了服务网关的功能,可以实现

    负载均衡
    反向代理
    动态路由
    请求转发等功能

Zuul大部分功能是通过过滤器实现的,除了标准的四种过滤器类型,还支持自定义过滤器。

使用@EnableZuulProxy注解,Spring容器初始化时,会将Zuul的相关配置初始化,其中包含一个Spring Boot的Bean:ServletRegistrationBean,该类主要用于注册Servlet。

在Servlet的service方法中,执行各种Zuul过滤器。下图为HTTP请求在ZuulServlet中的生命周期。


 这里我们延续前面工程的工程继续往下写,没看到前面的可以在SpringCloud专辑https://blog.csdn.net/suxiexingchen/category_11853792.html中查看前面的工程详情。

1.首先创建一个SpringBoot工程,并建立Eureka客户端,pom文件中加入zuul的依赖

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

2.application.yml中做如下配置,这样就将访问地址转到了设置的URL

eureka:
  client:
    serviceUrl: #注册中心的地址
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8765  #服务端口号
spring:
  application:
    name: Eureka-client-zuul #服务名称

zuul:
  routes:
    a:  #路由的一个名称,可以任意起名
      url: http://localhost:8764 #跳转的URL

 3.最后在启动类上,要加入@EnableZuulProxy注解

package com.studycloud.eurekaclientproducer;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringCloudApplication
@EnableZuulProxy
public class EurekaClientProducerApplication {

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

}

 4.启动前面的所有服务,并在Eureka管理端查看

5.我们这里访问 http://localhost:8765/a/test 就访问到了服务。

6.可是我们的服务都注册到了Eureka啊,所以我们将配置文件再来改造一下,这样我们就可以访问我们注册服务的服务名称了。这才显得Eureka啊

eureka:
  client:
    serviceUrl: #注册中心的地址
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8765  #服务端口号
spring:
  application:
    name: Eureka-client-zuul #服务名称

zuul:
  sensitive-headers:        #敏感header的设置,此处为空,默认Authorization不被转发
  #  ignored-services: '*'    #该配置关闭,则zuul会默认代理所有注册在Eureka上的微服务
  routes:
    admin:              #路由的一个名称,可以任意起名
      path: /admin/** #拦截到的路径
      serviceId: eureka-service-consumerA #模块的服务名
    user:
      path: /user/**
      serviceId: eureka-service-provider

7.因为设置了path所以我们需要将所有请求都加上我们设置的前缀例如

http://localhost:8765/admin/test

 总结:

使用url的话那么你规定的路由名称相当于所有请求的前缀

使用serviceId的话就可以使用注册的模块,但是要设置好path哦,按照path设置的路径来访问

8.其实还没有完,前面我们使用了Hystrix熔断器,好现在将服务B停掉看看熔断器起作用了没,出现了错误,其实是因为Zuul中需要自己实现断路器

504错误代表网关超时 (Gateway timeout),是指服务器作为网关或代理,但是没有及时从上游服务器收到请求    

           

 9.Zuul中实现Hystrix断路器

  • Zuul网关中实现服务降级,只需要在Zuul网关的服务中,编写实现ZuulFallbackProvider接口的java类即可
  • getRoute方法是执行要为哪个服务配置降级处理
  • fallbackResponse方法是进行返回托底信息的,

因为这个问题比较特殊,特殊倒不是代码问题,而是全局观。所以这里我再交代一下前面的结构

9.1首先是这个现有的几个功能和作用

  • Eureka-Server:Eureka的服务端
  • Eureka-client-producerA:Eureka的客户端-提供者A(就是做具体的业务)
  • Eureka-client-producerB:Eureka的客户端-提供者B
  • Eureka-client-consumerA:Eureka的客户端-消费者A(集成Feign进行接口调用,并使用Hystrix熔断器)
  • Eureka-client-zuul:作为网关使用(功能很纯粹就是网关)

 9.2前面的其他代码可以在专辑中查看,这里写Zuul和Hystrix整合使用,交代一下这里使用的SpringBOOT版本

<spring-boot.version>2.3.7.RELEASE</spring-boot.version>

9.3首先看pom文件中需要依赖,因为是Eureka,Zuul所以

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

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

9.4接着是配置文件application.yml

解释一下下面Zuul的配置吧,拦截 /admin的路径到 eureka注册名为eureka-service-consumerA的工程

eureka:
  client:
    serviceUrl: #注册中心的地址
      defaultZone: http://localhost:8761/eureka/
server:
  port: 8765  #服务端口号
spring:
  application:
    name: Eureka-client-zuul #服务名称

zuul:
  sensitive-headers:        #敏感header的设置,此处为空,默认Authorization不被转发
  #  ignored-services: '*'    #该配置关闭,则zuul会默认代理所有注册在Eureka上的微服务
  routes:
    admin:              #路由的一个名称,可以任意起名
      path: /admin/** #拦截到的路径
      serviceId: eureka-service-consumerA #模块的服务名
    user:
      path: /user/**
      serviceId: eureka-service-consumerB
ribbon:
  ReadTimeout: 3000
  ConnectTimeout: 3000

这个路由一定要好好理解,否则总找不到404

 9.5接下来需要实现接口 ProductFallback

解释一下下面的配置getRoute中返回*,代表支持所有的服务都快速失败,都可以走fallbackResponse我们自己设定的错误,是不是像极了以前的全局错误。大包大揽兜底用。

package com.studycloud.eurekaclientproducer;

import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

@Component
public class ProductFallback implements FallbackProvider {

    /**
     * 指定需要托底处理的服务名
     * 推荐 - 为指定的服务定义特性化的fallback逻辑
     * 推荐 - 提供一个处理所有服务的fallback逻辑
     * 好处 - 某个服务发生超时,那么指定的fallback逻辑执行。如果有新服务上线,未提供fallback逻辑还有个通用的
     *
     * 返回fallback处理哪个服务,返回的是服务名称
     * 如果支持所有的服务都快速失败,则返回null或者*即可
     * @return java.lang.String 返回 fallback 处理哪一个服务,返回服务名称
     */
    @Override
    public String getRoute() {
        return "*";
    }

    /**
     * 服务容错处理
     * 服务无法使用时,返回的托底信息
     * @param route 容错服务名称
     * @param cause 服务器异常信息
     */
    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        System.out.println("路由地址为:"+route);
        return new ClientHttpResponse() {
            /**
             * ClientHttpResponse 的 fallback 的状态码 返回HttpStatus
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 int
             */
            @Override
            public int getRawStatusCode() throws IOException {
                return getStatusCode().value();
            }

            /**
             * ClientHttpResponse 的 fallback 的状态码 返回 String
             */
            @Override
            public String getStatusText() throws IOException {
                return getStatusCode().getReasonPhrase();
            }


            /**
             * 设置响应体
             * Zuul会将本方法返回的输入流数据读取,并且通过HttpServletResponse的输出流输出到客户端
             */
            @Override
            public InputStream getBody() throws IOException {
                String msg = "{\"message\":\"服务不可用,请稍后重试\"}";
                return new ByteArrayInputStream(msg.getBytes());
            }
            /**
             * 设置响应的头信息
             */
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders httpHeaders= new HttpHeaders();
                MediaType mediaType = new MediaType("application","json", Charset.forName("utf-8"));
                httpHeaders.setContentType(mediaType);
                return httpHeaders;
            }

            @Override
            public void close() {

            }
        };
    }
}

ProductFallback 中实现方法 getRoute 一定要好好理解,我看了网上很多的说的都不清楚,让人高的糊里糊涂的,其实就一句话,不是他们说的什么消费者,提供者。

返回值就是 【返回 fallback 处理哪一个服务,返回服务名称,如果支持所有的服务都快速失败,则返回null或者*即可】

还有一处容易犯错的错误,这里是JSON因为你已经规定了application为json,如果写普通字符串会报JSON错误,所以注意一下了。

9.6最后在启动类上加上@EnableZuulProxy注解

package com.studycloud.eurekaclientproducer;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringCloudApplication
@EnableZuulProxy
public class EurekaClientProducerApplication {

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

}

 所有启动以后

 

 

9.7,好了梳理一下思绪,启动项目,首先我们调用网关 localhost:8765/admin/test    

成功获取到项目A   

成功获取到项目B,并且是轮询方式

9.8接着我们将Eureka-client-producerA提供者A停掉,猜想一下,服务A停用熔断器生效

 这里一定要强调一下,如果你的熔断器没起作用,我猜想大多数是因为超时没有设置的问题,循着这个方向去吧。

再次运行就只有Eureka-client-producerB还在运行了。

9.9现在再将Eureka-client-consumerA消费者整个停掉,按照道理应该Zuul的容错处理起作用了,事实正如所料,超时后显示出了我们预期的JSON

 10.到这里已经不是一个简单Zuul的使用,而是整体的套用情况,所以心中要有一个全局观才好

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值