SpringCloud微服务架构开发——06网关服务Zuul

目录

一、Zuul简介

什么是Zuul

为什么用Hystrix

客户端直接与微服务通信

客户通过Zuul网关与微服务通信

二、Zuul快速入门 

①创建eureka-server项目作为注册中心

②创建服务提供者eureka-provider项目

③创建服务消费者eureka-consumer项目

④创建网关服务gateway-zuul项目

⑤项目测试

 三、Zuul的路由映射规则配置

 1、服务路由配置

 2、服务路由默认规则

关闭所有默认的路由配置规则:

3、自定义路由映射规则

4、路径匹配

5、路由前缀

六、Zuul与Hystrix结合实现熔断 

基本流程:

(1)实现网关处理类

 (2)启动测试

七、 Zuul中的Eager Load配置

八、Zuul的过滤器

Zuul过滤器的介绍

Zuul请求的生命周期

自定义Zuul过滤器 

九、禁用Zuul过滤器


一、Zuul简介

什么是Zuul

概述: Zuul是Netflix的一个开源组件,它是通过Servlet实现的。

作用:通过把网关和服务治理整合到一起,Spring Cloud Zuul可以获取到服务注册信息,结合Ribbon,Hystrix等更好地实现路由转发、负载均衡等功能。

为什么用Hystrix

我们可以搭建简单的微服务架构系统并实现各服务之间的调用,但是不同的微服务一般会有不同的网络地址,而外部客户端(例如手机APP)可能需要调用多个服务的接口才能完成一个业务需求。而客户端直接与各个微服务通信,这样会有许多问题出现。

客户端直接与微服务通信

一个电商的手机APP,可能会调用多个微服务的接口,才能完成一次购票的业务流程。

SpringCloud微服务架构开发——06网关服务Zuul_第1张图片

 客户端与微服务直接通信,就会产生许多问题,具体如下

1.客户端会多次请求不同的微服务,使客户端变得更为复杂。

2. 存在跨域请求,在一定场景下处理相对复杂。例如,在重定向或js发起的   ajax请求时,会因为域名不同、二级域名不同、子域名不同或端口号不同等因素,处理变得相对复杂。

3. 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个,这种情况下,如果是客户端与微服务直接通信的架构,那么用户访问每一个接口的请求路径都需要改变,这就使得重构变得难以实现。

4. 某些微服务可能会设置防火墙等不友好的协议,难以做到直接访问。

客户通过Zuul网关与微服务通信

针对客户端与服务直接通信产生的问题,可以使用服务网关解决。服务网关相当于介于客户端和服务端之间的中间层,所有的外部请求都会先经过服务网关进行调度和过滤。

服务网关除了要实现请求路由、负载均衡、过滤等功能之外,还要实现更多功能,例如与服务相关框架整合、服务请求的熔断等。下图描述了加入服务网关的微服务调度过程。

SpringCloud微服务架构开发——06网关服务Zuul_第2张图片

二、Zuul快速入门 

①创建eureka-server项目作为注册中心

spring:
  application:
    name: eureka-server
server:
  port: 7000
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
    service-url:
      defaultZone:
        http://${eureka.instance.hostname}:${server.port}/eureka/
  instance:
    hostname: localhost
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

}

②创建服务提供者eureka-provider项目

使用Spring Initializr方式创建一个提供者项目,将Artifact命名为eureka-provider,添加Test、Eureka Client、Web依赖。

spring:
  application:
    name: eureka-provider
server:
  port: 7006
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka
  instance:
    hostname: localhost
@SpringBootApplication
@EnableEurekaClient
public class EurekaProviderApplication {

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

}

创建controller包,并在controller包下创建HystrixController类,在HystrixController类中创建一个hi()方法。

@RestController
public class HystrixController{
    @RequestMapping("/hi")
        public String hi(String id){
            return "hi,访问成功!"+id;
    }
}

③创建服务消费者eureka-consumer项目

使用Spring Initializr方式创建一个消费者项目,将Artifact命名为eureka-consumer,添加Web、Test和Eureka Client依赖。

spring:
  application:
    name: eureka-consumer
server:
  port: 8764
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka
@SpringBootApplication
@EnableEurekaClient
public class EurekaConsumerApplication {

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

}

创建一个config包,在config包下新建配置类RestConfig,并添加@Configuration注解。在RestConfig类中创建一个Bean实例方法RestTemplate(),并在restTemplate()方法上添加@LoadBalanced注解,使 RestTemplate实例对象处理请求时拥有客户端负载均衡的能力,具体如下所示。

@Configuration
public class RestConfig{
   @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

创建一个service包,在service包下新建LocalItemService类并添加@Service注解。在LocalItemService类中注入RestTemplate实例对象,并添加一个hi()方法,在hi()方法中对eureka-provider提供者服务进行调用。

@Service
public class LocalItemService {
    @Autowired
    RestTemplate restTemplate;
    public String hi(@RequestParam(value = "id")String id){
        return restTemplate.
        getForobject("http://eureka-provider/hi?id="+id,String.class);
    }
}

创建一个controller包,在controller包下新建LocalItemController类并添加@RestController注解。在LocalItemController类中注入LocalItemService实例对象,并创建一个hi()方法

@RestController
public class LocalItemController {
    @Autowired
    LocalItemService localItemService;
    @GetMapping("/hi")
    public String hi(String id) {
        return localItemService.hi(id);
    }
}

④创建网关服务gateway-zuul项目

使用Spring Initializr方式构建一个名称为gateway-zuul的网关服务项目,将Artifact命名为gateway-zuul,添加Zuul、Test、Eureka Client、Web依赖,其中,Zuul依赖具体如下。


    org.springframework.cloud
    spring-cloud-starter-netflix-zuul

 引入依赖后,在全局配置文件application.yml进行相关配置,包括配置程序名称、端口号,服务注册地址等,配置后的application.yml代码如下

spring:
  application:
    name: gateway-zuul
server:
  port: 8835
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7000/eureka
zuul:
  routes:
    eureka-consumer:
      path: /eureka-consumer/**  #zuul路由前缀

在项目gateway-zuul的启动类GatewayZuulApplication添加@EnableZuulProxy注解开启服务网关Zuul功能

@SpringBootApplication
@EnableZuulProxy   //开启Zuul网关服务
public class GatewayZuulApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayZuulApplication.class, args);
    }
}

⑤项目测试

依次启动项目eureka-server,eureka-provider,eureka-consumer,gateway-zuul。启动成功后,使用浏览器访问http://loaclhost:7000。

SpringCloud微服务架构开发——06网关服务Zuul_第3张图片

从注册中心地址中可以看出,这里我们访问消费者服务跟之前有所不同,我们需要在访问地址前加上我们在application.yml文件中配置的Zuul路由的前缀/eureka-consumer,这里我们访问http://localhost:8835/eureka-consumer/hi?id=12,效果如图所示。

SpringCloud微服务架构开发——06网关服务Zuul_第4张图片 

 三、Zuul的路由映射规则配置

 1、服务路由配置

服务路由是Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,我们在使用服务路由配置的时候,无需通过serviceId指定具体服务实例地址,只需要通过zuul.routes.<路由名>.path与zuul.routes.<路由名>. serviceId的方式成对配置即可。

SpringCloud微服务架构开发——06网关服务Zuul_第5张图片

 代码的作用是将符合/eureka-consumer/**规则的请求路径转发到名为eureka-consumer的服务实例上。

 对于面向服务的路由配置,除了使用path与serviceId映射的配置方式之外,还有一种更简洁的配置方式,即zuul.routes.=,其中用来指定路由的具体服务名,用来配置匹配的请求映射地址

SpringCloud微服务架构开发——06网关服务Zuul_第6张图片

 2、服务路由默认规则

默认情况下,Zuul会自动为Eureka服务注册中心的所有服务创建映射关系进行路由,这会使得一些我们不希望对外开放的服务也可能被外部访问到。当使用服务名称作为前缀路径时,实际上会匹配类似下面的默认路由配置

SpringCloud微服务架构开发——06网关服务Zuul_第7张图片

 使用关闭默认的路由配置后,此时我们需要在配置文件中逐个为需要路由的服务添加映射规则。当然可以使用path与serviceId 组合的配置方式,也可以使用更简洁的zuul.routes.=配置方式,只有在配置文件中出现的映射规则会被创建路由,而从Eureka中获取的其他服务,Zuul将不会再为它们创建路由规则。

关闭所有默认的路由配置规则:

关闭所有默认的路由配置规则。
zuul: 
  ignored-services: '*'

3、自定义路由映射规则

 如果我们的各个微服务应用都遵循了类似userprovider-v1这样的命名规则,通过“-”分隔的规范来定义服务名和服务版本标识的话,那么,我们可以使用Zuul中自定义服务与路由映射关系的功能,实现符合上述规则的微服务自动化地创建类似/v1/userprovider/**的路由匹配规则。实现步骤非常简单,引用官方文档,只需在网关项目中,增加Bean实例。

@Bean 
public PatternServiceRouteMapper serviceRouteMapper() {
     return new PatternServiceRouteMapper (
         "(?^.+)-(?v.+$)",
         "${version}/${name}");
}

4、路径匹配

 在使用服务路由的配置方式时,我们需要为每个路由规则定义路由表达式,也就是path参数。在Zuul中,路由表达式采用了Ant风格定义。Ant风格的路由表达式共有三种通配符:

通配符

说明

举例

 ?

匹配任意单个字符

/eureka-consumer/?

*

匹配任意数量的字符

/eureka-consumer/*

**

匹配任意数量的字符,支持多级目录

/eureka-consumer/**

为了更灵活的使用路由配置规则,Zuul还提供了一个忽略表达式参数zuul.ignored-patterns,该参数用来设置不被网关进行路由的URL表达式。例如,不希望/hi接口被路由,配置信息如下。

zuul : 
  ignored-patterns : /**/hi/**      
  routes :
    eureka-consumer :                    
      path : /eureka-consumer/**
      serviceId : eureka-consumer

 当配置了忽略表达式参数时,通过网关访问eureka-consumer的/hi接口时,会提示该接口不存在,在控制台可以看到没有匹配路由的输出信息。

o.s.c.n.z.f.pre.PreDecorationFilter : No route found for uri: /eureka-consumer /hi

5、路由前缀

Zuul提供了zuul.prefix参数设置路由前缀。SpringCloud微服务架构开发——06网关服务Zuul_第8张图片

prefix属性将路由前缀设置为/api,访问eureka-consumer服务的/api/eureka-consumer/1路径,请求将会被转发到eureka-consumer的/api/1。说明设置prefix参数后,Zuul会把代理前缀从默认路径中移除掉,为避免这种情况,可以使用zuul.stripPrefix=false来关闭移除代理前缀的动作,也可以通过zuul.routes.<路由名>.strip-prefix=false来指定服务关闭移除代理前缀的动作。

六、Zuul与Hystrix结合实现熔断 

基本流程:

1.实现网关处理类

2.启动测试

(1)实现网关处理类

Zuul和Hystrix结合使用实现熔断功能时,需要实现FallbackProvider接口,在网关项目gateway-zuul中创建网关处理类MyFallbackProvider类,用于处理回退逻辑。并实现 FallbackProvider中的两个抽象方法,包括getRoute ()、 fallbackResponse ()。

getRoute ()方法:用于指定回退功能的服务

fallbackResponse ()方法:用于执行回退操作的具体逻辑

1.Zuul和Hystrix结合使用实现熔断功能时,实现FallbackProvider接口,实现getRoute()方法

2.实现fallbackResponse()方法,重写getStatusCode()、getRawStatusCode() getStatusText()方法。

3.重写fallbackResponse()中close()、getHeaders()、getBody()方法。

package com.chen.gatewayzuul.handle;

import com.netflix.appinfo.RefreshableAmazonInfoProvider;
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 sun.util.locale.provider.FallbackLocaleProviderAdapter;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;

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

@Component
public class MyFallbackProvider implements FallbackProvider{


    @Override
    public String getRoute() {
        return "eureka-consumer";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route,
                                               Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                return HttpStatus.OK;
            }
            @Override
            public int getRawStatusCode() throws IOException {
                return this.getStatusCode().value();
            }
            @Override
            public String getStatusText() throws IOException {
                return this.getStatusCode().getReasonPhrase();
            }
            @Override
            public void close() {
            }
            @Override
            public InputStream getBody() throws IOException {
                return new ByteArrayInputStream("连接异常,I'm the fallback".getBytes());
            }
            @Override
            public HttpHeaders getHeaders() {
                HttpHeaders headers = new HttpHeaders();
                MediaType mt = new MediaType("application", "json",
                        Charset.forName("UTF-8"));
                headers.setContentType(mt);
                return headers;
            }
        };
    }
}

 (2)启动测试

启动eureka-server和gateway-zuul项目,使用浏览器访问http://localhost:8835/eureka-consumer/hi?id=12,浏览器会显示MyFallbackProvider类中执行回退逻辑的方法中返回的字符串。SpringCloud微服务架构开发——06网关服务Zuul_第9张图片

 注:如果需要所有的路由服务都能执行回退逻辑,就只需要在getRoute()方法中使用return语句返回 * 通配符即可 

    @Override
    public String getRoute() {
        return "*";
    }

七、 Zuul中的Eager Load配置

如果我们使用默认路由,而没有通过配置的方式指定具体路由规则,那么zuul.ribbon.eager-load.enabled=true的配置就没有什么作用了。 因此,在真正使用的时候,我们可以通过zuul.ignored-services=*来忽略所有的默认路由,让所有路由配置均在配置文件中维护,以达到网关zuul启动时就默认初始化好了各个路由所要转发的负载均衡对象。

八、Zuul的过滤器

Zuul过滤器的介绍

Spring Cloud Zuul作为网关组件将客户端请求路由到业务处理过中,大部分功能都是通过过滤器实现的。Zuul定义了四种标准的过滤器类型。

(1)pre:该过滤器会在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。

(2)route:负责请求转发到服务。原始请求在此构建,并使用ApacheHttpClient或Netflix Ribbon发送原始请求。

(3)post:在route和error过滤器之后被调用,可以在响应消息中添加标准HTTP Header、收集统计信息和指标,以及将响应发送给客户端等。

(4)error:处理请求发送错误时被调用。

Zuul请求的生命周期

Zuul请求的生命周期描述了各种类型过滤器的执行顺序。

SpringCloud微服务架构开发——06网关服务Zuul_第10张图片

HTTP请求到达Zuul,最先来到pre过滤器,在这里会去映射url patern到目标地址上,然后将请求与找到的地址交给route类型(routing在filterType方法返回的类型是“route”,不是routing)的过滤器进行求转发,请求服务实例获取响应,通过post类型过滤器对处理结果进行加工、转换等操作并返回。error类型的过滤器比较特殊,它在整个请求过程中,只在有异常的情况下才会触发,将异常结果交给post类型过滤器加工并返回。

自定义Zuul过滤器 

手编写一个Zuul过滤器,来实现打印用户请求的Http方法以及请求地址。 编写Zuul过滤器只需要继承ZuulFilter,并实现 ZuulFilter中的四个抽象方法,包括filterType()、 filterOrder()、shouldFilter()和run()。

在gateway-zuul项目中定义表示Zuul过滤器的PreRequestLogFilter类,继承ZuulFilter。实现ZuulFilter的filterType抽象方法。

public class PreRequestLogFilter extends ZuulFilter {
    private static final Logger logger =    
            LoggerFactory.getLogger(
		PreRequestLogFilter.class);
    @Override
    public String filterType(){
		return "pre";
	}
}

在gateway-zuul项目中定义表示Zuul过滤器的PreRequestLogFilter类,继承ZuulFilter。实现ZuulFilter的抽象方法filter0rder。

public class PreRequestLogFilter extends ZuulFilter {
    private static final Logger logger =  
            LoggerFactory.getLogger(
		PreRequestLogFilter.class);
    @0verride
	public int filter0rder(){
		return 1;
	}
}

在gateway-zuul项目中定义表示Zuul过滤器的PreRequestLogFilter类,继承ZuulFilter。实现ZuulFilter的抽象方法shouldFilter。

public class PreRequestLogFilter extends ZuulFilter {
    private static final Logger logger =  
            LoggerFactory.getLogger(
		PreRequestLogFilter.class);
    @Override
	public boolean shouldFilter(){
		return true;
	}
}

在gateway-zuul项目中定义表示Zuul过滤器的PreRequestLogFilter类,继承ZuulFilter。实现ZuulFilter的抽象方法run()。

@Override
	public Object run(){
         RequestContext ctx = RequestContext.getCurrentContext();
         HttpServletRequest request = ctx.getRequest();
         logger.info("进入访问过滤器,访问的url:{},访问的方法:  
         {}",request.getRequestURL(),request.getMethod());
         String accessToken = request.getHeader("accessToken"); 
         if(StringUtils.isEmpty(accessToken)) {
                logger.info("当前请求没有accessToken");
                ctx.setSendZuulResponse(false);
                ctx.setResponseStatusCode(401);
                return null;
            }
            logger.info("请求通过过滤器");
            return null;}
	}

启动测试。依次启动eureka-server,eureka-consumer和gateway-zuul。使用浏览器访问http://localhost:8835/eureka-consumer/hi,会在在控制台看到下列信息,如图所示。

SpringCloud微服务架构开发——06网关服务Zuul_第11张图片

九、禁用Zuul过滤器

Spring Cloud默认为Zuul编写并启动了一些过滤器,例如DebugFilter、       FormBodyWrapperilter、PreDecorationFilter等,这些过滤器都会存放在spring-  cloud-netflix-core这个Jar包的org.springframework.cloud.netflix.zuul.         filters包中。 在一些情况下,我们需要禁用掉部分过滤器,只需设置zuul...disable=true,就可以禁用SimpleClassName所对应的过滤器。以过滤器org.springframework.cloud.netflix.zuul.filters.post.SentResponseFilter为例,只需要设置zuul.SendResponseFilter.pre.disable=true就可以了。

你可能感兴趣的