Zuul微服务网关、容错与监控、Zuul路由端点、路由配置、Zuul上传文件、Zuul过滤器、Zuul异常处理、Zuul回退、Zuul聚合微服务

一、为什么要使用微服务网关

二、Zuul

1、编写Zuul微服务网关

2、Zuul的Hystrix容错与监控

3、Zuul的路由端点

4、路由配置

1.自定义指定微服务的访问路径

2.忽略指定微服务

3.忽略所有微服务,只路由指定微服务

4.同时指定微服务的serviceId和对应路径

5.同时指定path和URL

6.使用正则表达式指定Zuul的路由匹配规则

7.路由前缀

8.忽略某些路径

5、Zuul的安全与Header

1.指定敏感Header

2.忽略Header

6、Zuul上传文件

7、Zuul过滤器

1.编写Zuul过滤器

2.Zuul异常处理过滤器

3.Zuul默认过滤器

8、Zuul回退

9、Zuul聚合微服务


一、为什么要使用微服务网关

不同的微服务一般会经过不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求。

如果让客户端直接与各个微服务通信,会有以下的问题:

  • 客户端会多次请求不同的微服务,增加了客户端的复杂性。
  • 存在跨域请求,在一定场景下处理相对复杂。
  • 认证复杂,每个服务都需要独立认证。
  • 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务整个成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
  • 某些微服务可能使用了防火墙/浏览器不友好协议,直接访问会有一定的困难。

以上问题可借助微服务网管解决。微服务网关是介于客户端和服务器之间的中间层,所有外部请求都会先经过微服务网关。使用微服务网关后架构演变为下图。

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

  • 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
  • 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无需再每个微服务中进行认证。
  • 减少了客户端与各个微服务之间的交互次数。

二、Zuul

Zuul是Netflix开源的微服务网关,核心是一系列的过滤器,这些过滤器可以完成以下功能。

  • 身份认证与安全:识别每个资源的验证需求,并拒绝那些与要求不符的请求。
  • 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生产视图。
  • 动态路由:动态地请求路由到不同的后端集群。
  • 压力测试:逐渐增加执行集群的流量,以了解性能。
  • 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求。
  • 静态响应处理:在边缘位置直接建立部分响应,从而避免其转发到内部集群。
  • 多区域弹性:跨越AWS Region进行请求路由,旨在实现ELB(Elastic Load Balancing)使用多样化,以及让系统的边缘更贴近系统的使用者。

1、编写Zuul微服务网关

1.创建项目gateway-zuul

以下是pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springclouddemo</groupId>
    <artifactId>gateway-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway-zuul</name>
    <description>微服务网关</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.编写application配置文件

spring.application.name=gateway-zuul
server.port=7400
eureka.client.service-url.defaultZone=http://localhost:7000/eureka/
eureka.instance.prefer-ip-address=true

3.在main类添加@EnableZuulProxy注解

package com.springclouddemo.gatewayzuul;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

/**
 * @author 何昌杰
 */
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {

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

}

这样一个简单的微服务网关就搭建成功了,并且将这个微服务注册到Eureka Server上。

测试:

  1. 启动项目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
  2. 访问http://localhost:7400/eureka-client-consumer-feign/hello/hcj,请求就会转发到http://localhost:7204/hello/hcj
  3. 访问http://localhost:7400/eureka-client-provider/hello/hcj,请求就会转发到http://localhost:7100/hello/hcj

默认情况下Zuul会代理所有注册到Eureka Server的微服务,并且Zuul的路由规则是:http://ZUUL_HOST:ZUUL_PORT/微服务名称/**会转发到对应的微服务上。

2、Zuul的Hystrix容错与监控

Zuul是默认继承了负载均衡和熔断的,负载均衡无需任何操作,Greenwich版本的Hystrix需要添加@Bean配置路径才可以访问/hystrix.stream

将项目gateway-zuul的main类修改如下:

package com.springclouddemo.gatewayzuul;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;

/**
 * @author 何昌杰
 */
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {

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

    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }
}

测试:

  1. 启动项目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
  2. 分别访问http://localhost:7400/eureka-client-consumer-feign/hello/hcj,http://localhost:7400/eureka-client-provider/hello/hcj
  3. 访问http://localhost:7400/hystrix.stream,可以得到如下内容

说明Zuul已经整合了Hystrix(默认整合)。

3、Zuul的路由端点

当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/actuator/routes(低版本为/routes端点)。借助这个端点,可以方便、直观地查看以及管理Zuul的路由。

/actuator/routes端点的使用非常简单,使用GET方法访问端点,即可以返回Zuul当时映射的路由列表;使用POST方式访问该端点就会强制刷新Zuul当时映射的路由列表(尽管路由会自动刷新,Spring Cloud依然提供了强制立即刷新的方式)。

由于spring-cloud-starter-netflix-zuul已经包含了spring-boot-starter-actuator,因此之前编写的gateway-zuul项目已经具备路由管理的能力,不过需要在application配置文件中添加以下配置。

management.endpoints.web.exposure.include=routes

访问http://localhost:7400/actuator/routes,可以看到以下内容:

4、路由配置

某些场景写我们只想让Zuul代理部分微服务,或者需要对URL进行更加精确的控制。

1.自定义指定微服务的访问路径

配置zuul.routes,指定微服务的serviceId=指定路径 即可:

zuul.routes.eureka-client-consumer-feign=/feign/**

这样配置,eureka-client-consumer-feign微服务就会被映射到/feign/**路径。

2.忽略指定微服务

zuul.ignored-services=eureka-client-provider,eureka-client-consumer-feign

这样配置,Zuul就会忽略eureka-client-provider,eureka-client-consumer-feign微服务,只代理其他微服务。

3.忽略所有微服务,只路由指定微服务

某些场景下我们只想让Zuul代理指定微服务:

zuul.ignored-services='*'
zuul.routes.eureka-client-consumer-feign=/feign/**

这样配置,Zuul就会只路由eureka-client-consumer-feign这个微服务。

4.同时指定微服务的serviceId和对应路径

zuul.routes.feign.service-id=eureka-client-consumer-feign
zuul.routes.feign.path=/feign/**
  • zuul.routes.feign.***的feign只是一个路由名称,可以任意修改名称

5.同时指定path和URL

zuul.routes.feign.service-id=http://localhost:7204/
zuul.routes.feign.path=/feign/**
  • zuul.routes.feign.***的feign只是一个路由名称,可以任意修改名称

6.使用正则表达式指定Zuul的路由匹配规则

借助PatternServiceRouteMapper,实现微服务的映射路由的正则配置:

package com.springclouddemo.gatewayzuul;

import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper;
import org.springframework.context.annotation.Bean;

/**
 * @author 何昌杰
 */
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulApplication {

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

    @Bean
    public ServletRegistrationBean getServlet() {
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
        registrationBean.setLoadOnStartup(1);
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");
        return registrationBean;
    }

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

通过上述配置可以实现eureka-client-consumer-feign这个微服务,映射到/feign/eureka-client-consumer/**这个路径。

7.路由前缀

zuul.routes.eureka-client-consumer-feign.path=/feign/**
zuul.routes.eureka-client-consumer-feign.strip-prefix=false

这样访问Zuul的/hello/**路径,请求就会被转发到eureka-client-consumer-feign的/hello/**上。

8.忽略某些路径

某些场景下我们想让Zuul代理某些微服务,同时又想保护该微服务的某些敏感路径,我们可以使用

zuul.ignored-patterns=/**/user/**

这样配置就是可以忽略微服务中所有包含/admin/的路径

5、Zuul的安全与Header

1.指定敏感Header

一般情况下同一个系统的服务之间共享Header,不过应尽量防止让一些敏感的Header外泄。因此,在很多场景下,需要通过为路由指定一系列敏感Header列表。

zuul.routes.eureka-client-consumer-feign.path=/feign/**
zuul.routes.eureka-client-consumer-feign.sensitive-headers=Cookie,Set-Cookie,Authorization

这样配置就可以为eureka-client-consumer-feign指定微服务访问路径和指定敏感Header

也可以全局指定敏感Header:

zuul.sensitive-headers=Cookie,Set-Cookie,Authorization

2.忽略Header

可以通过zuul.ignored-headers属性指定需要忽略的Header。

zuul.ignored-headers=Authorization

这样配置后Authorization将不会传播到其他的微服务中。

zuul.ignored-headers的默认值为空值,但如果Spring Security在项目的classpath中,那么zuul.ignored-headers的默认值就是Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires。所以当Spring Security在项目的classpath中,同时又需要使用下游微服务的Spriing Security的Header时,可以将zuul.ignore-security-headers设置为false。

6、Zuul上传文件

对于大文件(10M以上)上传,需要为上传路径添加/zuul前缀。也可以使用zuul.servlet-path自定义前缀。

例如假如zuul.routes.eureka-client-consumer-feign-upload=/upload/**,http://localhost/{HOST}:{PORT}/upload是微服务eureka-client-consumer-feign-upload的上传路径,则需要用Zuul的/zuul/upload路径进行上传(添加/zuul前缀)。

如果Zuul使用了Ribbon负载均衡,name对于超大文件,需要扩大超时设置:

(Hystrix与Ribbon的默认请求超时时间都是1秒)

hystrix.command.connect.execution.isolation.thread.timeoutInMilliseconds=60000
ribbon.connectTimeout=3000
ribbon.readTimeout=60000

还需要为提供上传文件的微服务添加以下配置:

(max-file-size默认1MB,max-request-size默认10MB)

spring.servlet.multipart.max-file-size=2000MB
spring.servlet.multipart.max-request-size=2500MB

7、Zuul过滤器

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

  • pre: 这种过滤器在请求被路由之前调用。可利用这种过滤器实现身份验证、在集群中选择请求的微服务,记录调试信息等。
  • routing: 这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用apache httpclient或netflix ribbon请求微服务。
  • post: 这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的http header、收集统计信息和指标、将响应从微服务发送给客户端等。
  • error: 在其他阶段发送错误时执行该过滤器。

除了默认的过滤器类型,zuul还允许创建自定义的过滤器类型。例如,可以定制一种static类型的过滤器,直接在zuul中生成响应,而不将请求转发到后端的微服务。

Zuul请求的生命周期如下图,该图详细描述了各种类型的过滤器的执行顺序。

也可通过查看源码中com.netflix.zuul.http.ZuulServlet类的service了解执行顺序。

1.编写Zuul过滤器

1.复制项目gateway-zuul为gateway-zuul-filter

2.端口修改为7401,微服务名修改为gateway-zuul-filter

3.编写自定义Zuul过滤器filters/PreRequestLogFilter.java

package com.springclouddemo.gatewayzuulfilter.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

/**
 * @author 何昌杰
 */
@Component
public class PreRequestLogFilter extends ZuulFilter {

    private static final Logger log= LoggerFactory.getLogger(PreRequestLogFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        log.info("send {} request to {}",request.getMethod(),request.getRequestURL());
        return null;
    }
}
  • filterType 指定过滤器类型,对应上文几种过滤器
  • filterOrder 指定过滤器执行顺序(默认越小越先执行)
  • shouldFilter 是否启用该过滤器(true为启用,false为禁用)
  • run 过滤器的具体业务逻辑

测试:

  1. 启动项目gateway-zuul-filter、eureka-server、eureka-client-provider
  2. 访问http://localhost:7401/eureka-client-provider/hello/hcj,正常响应,gateway-zuul-filter控制台输出以下内容
2019-07-11 21:47:54.622  INFO 5128 --- [nio-7401-exec-4] c.s.g.filters.PreRequestLogFilter        : send GET request to http://localhost:7401/eureka-client-provider/hello/hcj

说明我们的自定义Zuul过滤器正常运行。

2.Zuul异常处理过滤器

当zuul通过eureka调用一个不可用、不存在、宕机了的服务时,可能就会直接返回类似于这样的不友好的画面:

我们可以通过编写一个异常过滤器来处理这种情况:

package com.springclouddemo.gatewayzuulfilter.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author 何昌杰
 */
@Component
public class ErrorFilter extends ZuulFilter {
    private static final Logger log = LoggerFactory.getLogger(ErrorFilter.class);
    @Override
    public String filterType() {
        return "error";
    }

    @Override
    public int filterOrder() {
        return -1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletResponse response = ctx.getResponse();
        response.setStatus(HttpStatus.SC_NOT_FOUND);
        response.setContentType("application/json;charset=UTF-8");
        Throwable throwable = ctx.getThrowable();
        try (PrintWriter writer = response.getWriter()) {
            writer.print("{\"resultCode\":404,\"data\":null,\"cause\":\"" + throwable.getCause() + "\",\"message\":\"路由转发错误\"}");
        } catch (IOException e) {
            log.error("系统异常{}", e.getMessage());
        }
        return null;
    }
}

 

测试:

  1. 启动项目gateway-zuul-filter、eureka-server、eureka-client-provider
  2. 访问http://localhost:7401/eureka-client-provider/hello/hcj,正常响应
  3. 停止项目eureka-client-provider后再次访问http://localhost:7401/eureka-client-provider/hello/hcj,响应如下:

说明我们的异常处理过滤器正常运行。

3.Zuul默认过滤器

类型顺序过滤器功能
pre-3ServletDetectionFilter标记处理Servlet的类型
pre-2Servlet30WrapperFilter包装HttpServletRequest请求
pre-1FormBodyWrapperFilter包装请求体
route1DebugFilter标记调试标志
route5PreDecorationFilter处理请求上下文供后续使用
route10RibbonRoutingFilterserviceId请求转发
route100SimpleHostRoutingFilterurl请求转发
route500SendForwardFilterforward请求转发
post0SendErrorFilter处理有错误的请求响应
post1000SendResponseFilter处理正常的请求响应

8、Zuul回退

想为Zuul添加回退需要实现FallbakcProvider接口,指定为哪些微服务提供回退并且提供一个ClientHTTPResponse作为回退响应。

1.复制项目gateway-zuul为gateway-zuul-fallback

2.端口修改为7402,微服务名修改为gateway-zuul-fallback

3.编写Zuul的回退类

package com.springclouddemo.gatewayzuulfallback.fallbacks;

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;

/**
 * @author 何昌杰
 */
@Component
public class ProviderFallback implements FallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse() {
            @Override
            public HttpStatus getStatusCode() throws IOException {
                //fallback时的状态码
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException{
                //数字类型的状态码
                return 200;
            }

            @Override
            public String getStatusText() throws IOException{
                //状态文本
                return this.getStatusCode().getReasonPhrase();
            }

            @Override
            public void close() {

            }

            @Override
            public InputStream getBody() throws IOException{
                //响应体
                return new ByteArrayInputStream("此微服务不可用,请稍后重试!".getBytes());
            }

            @Override
            public HttpHeaders getHeaders() {
                //响应头部
                HttpHeaders httpHeaders = new HttpHeaders();
                MediaType mediaType = new MediaType("application", "json", Charset.forName("UTF-8"));
                httpHeaders.setContentType(mediaType);
                return httpHeaders;
            }
        };
    }
}
  • getRoute() 返回值指定微服务的serviceId,也可以是*代表所有微服务。

测试:

  1. 启动项目gateway-zuul-fallback、eureka-server、eureka-client-provider
  2. 访问http://localhost:7402/eureka-client-provider/hello/hcj,正常响应
  3. 停止项目eureka-client-provider后再次访问http://localhost:7402/eureka-client-provider/hello/hcj,响应如下:

说明我们为Zuul添加回退成功。

9、Zuul聚合微服务

在很多次场景下,外部请求需要查询Zuul后端的多个微服务。举个例子,一个电影售票手机APP,在购票订单页上,既需要查询“电影微服务”获得电影相关信息,又需要查询“用户微服务”获得当前用户的信息。如果让手机端直接请求各个微服务(即使使用Zuul进行转发),那么网络开销、流量耗费、耗费时长可能都无法令人满意。那么对于这种场景,可使用Zuul聚合微服务请求——手机APP只需发送一个请求给Zuul,由于Zuul请求用户微服务以及电影微服务,并组织好数据给手机APP。

使用这种方式,手机端只须发送一次请求即可,简化了客户端侧的开发;不仅如此,由于Zuul、用户微服务、电影微服务一般都在同一局域网,因此速度非常快,效率会非常高。

下面围绕以上这个场景,来编写代码。

1.复制项目gateway-zuul为gateway-zuul-aggregation

2.将端口修改为7403,微服务名修改为gateway-zuul-aggregation

3.修改启动类

package com.springclouddemo.gatewayzuulaggregation;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author 何昌杰
 */
@SpringBootApplication
@EnableZuulProxy
public class GatewayZuulAggregationApplication {

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

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

}

4.创建业务类services/AggregationService.java

package com.springclouddemo.gatewayzuulaggregation.services;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import rx.Observable;

/**
 * @author 何昌杰
 */
@Service
public class AggregationService {
    @Autowired
    private RestTemplate restTemplate;

    @HystrixCommand(fallbackMethod = "fallback")
    public Observable<String> helloDemo1(String name) {
        // 创建一个被观察者
        return Observable.create(observer -> {
            // 请求微服务1的/hello/{name}端点
            String res = restTemplate.getForObject("http://eureka-client-consumer-feign/hello/{name}", String.class, name);
            observer.onNext(res);
            observer.onCompleted();
        });
    }

    @HystrixCommand(fallbackMethod = "fallback")
    public Observable<String> helloDemo2(String name) {
        return Observable.create(syncOnSubscribe -> {
            // 请求微服务2的/hello/{name}端点
            String res = restTemplate.getForObject("http://eureka-client-consumer-hystrix/hello/{name}", String.class, name);
            syncOnSubscribe.onNext(res);
            syncOnSubscribe.onCompleted();
        });
    }

    public String fallback(String name) {
        return "默认值:"+name;
    }

}

5.创建controllers/AggregationController.java

在Controller中聚合多个请求

package com.springclouddemo.gatewayzuulaggregation.controllers;

import com.google.common.collect.Maps;
import com.springclouddemo.gatewayzuulaggregation.services.AggregationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import rx.Observable;
import rx.Observer;

import java.util.HashMap;

/**
 * @author 何昌杰
 */

@RestController
public class AggregationController {
    public static final Logger LOGGER = LoggerFactory.getLogger(AggregationController.class);

    @Autowired
    private AggregationService aggregationService;

    @GetMapping("/aggregate/{name}")
    public DeferredResult<HashMap<String, String>> aggregate(@PathVariable String name) {
        Observable<HashMap<String, String>> result = this.aggregateObservable(name);
        return this.toDeferredResult(result);
    }

    public Observable<HashMap<String, String>> aggregateObservable(String name) {
        // 合并两个或者多个Observables发射出的数据项,根据指定的函数变换它们
        return Observable.zip(
                this.aggregationService.helloDemo1(name),
                this.aggregationService.helloDemo1(name),
                (res1, res2) -> {
                    HashMap<String, String> map = Maps.newHashMap();
                    map.put("microservice1", res1);
                    map.put("microservice2", res2);
                    return map;
                }
        );
    }

    public DeferredResult<HashMap<String, String>> toDeferredResult(Observable<HashMap<String, String>> details) {
        DeferredResult<HashMap<String, String>> result = new DeferredResult<>();
        // 订阅
        details.subscribe(new Observer<HashMap<String, String>>() {
            @Override
            public void onCompleted() {
                LOGGER.info("完成...");
            }

            @Override
            public void onError(Throwable throwable) {
                LOGGER.error("发生错误...", throwable);
            }

            @Override
            public void onNext(HashMap<String, String> movieDetails) {
                result.setResult(movieDetails);
            }
        });
        return result;
    }
}

 

测试:

  1. 启动项目gateway-zuul-aggregation、eureka-server、eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix
  2. 访问http://localhost:7403/aggregate/hcj,响应如下

    控制台输出:

    2019-07-11 22:56:53.639  INFO 5448 --- [nio-7403-exec-1] c.s.g.controllers.AggregationController  : 完成...

     

  3. 停止项目eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix后再次访问http://localhost:7403/aggregate/hcj,响应如下:

    控制台输出:

    2019-07-11 23:03:58.003  INFO 5448 --- [io-7403-exec-10] c.s.g.controllers.AggregationController  : 完成...

    说明我们已经成功用Zuul聚合了两个微服务的接口。

课程介绍 【完善体系+精品资料】本课程总计115课时,打造全网最全的微服务体系课程;从微服务是什么、能够做什么开始讲起,绝对零基础入门到精通类型。课程整体脉络十分清晰,每个章节一个知识点,画图+源码+运行讲解,不信你学不会。1、课程先讲解了什么是单体架构、什么是微服务架构、他们之间有什么区别和联系,各自有什么优缺点。2、从本质入手,使用最简单的Spring Boot搭建微服务,让你认清微服务是一种思想和解决问题的手段,而不是新兴技术。3、讲解Spring Boot 与 Spring Cloud 微服务架构之间的联系,原生的RestTemplate工具,以及Actuator监控端点的使用。4、带着微服务所带来的各种优缺点,为大家引入服务发现与注册的概念和原理,从而引入我们的第一个注册中心服务Eureka。5、引入负载均衡的理念,区分什么是服务端负载均衡,什么是客户端负载均衡,进而引入Ribbon负载均衡组件的详细使用。6、为了解决微服务之间复杂的调用,降低代码的复杂度,我们引入了Feign声明式客户端,让你几行代码学习服务的远程调用。7、为了解决服务之间的稳定性,避免发生雪崩问题,我们引入了Hystrix断路器,服务降级和熔断机制。8、微服务集群十分庞大,监控起来是十分困难的,尤其是对每一个接口的熔断情况进行监控,因此我们引入了Turbine微服务监控。9、微服务的调用是杂乱无章的,可以网状调用,怎么做到统一的入口出口,统一的授权、加密、解密、日志过滤,我们引入了第一代网关Zuul。10、微服务配置分散,每次要修改配置都要重启服务,因此我们引入了Config配置中心。11、跟上主流,Consul是当前主流的服务注册与发现、配置中心一体化的解决方案。12、阿里的Nacos服务注册与发现、配置中心在国内炙手可热,Nacos 经历过双十一的微服务中间件。13、Turbin做微服务监控还是太弱,我们需要更强大,可视化,操作性更强的监控系统,因此我引入了Spring Boot Admin体系。14、Zuul已经停止更新支持,Spring Cloud官方推荐的二代网关Spring Cloud Gateway更加强大。15、微服务的安全架构体系虽然复杂,但是是有学习条例的,什么是认证授权、什么是OAuth2.0的原理、 JWT、怎么样去开发实现。 课程资料 【独家资料】1、课程附带全部63个项目源码,其中Hoxton版本项目源码37个,Edgware版本项目26个,2、230页高清PDF正版课件。3、附带nacos、consul、cmder等视频配套软件。学习方法1、每一节课程均有代码,较好的方式为一边听我的讲解,一边使用我提供的项目代码进行观察和运行。2、课程体系庞大,但是并不杂乱,每个章节只针对一个知识点,减轻学习压力。3、坚持每天学习1~2个章节,可以在地铁、公交上用手机学习。【完善知识体系图】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值