一、为什么要使用微服务网关
不同的微服务一般会经过不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求。
如果让客户端直接与各个微服务通信,会有以下的问题:
- 客户端会多次请求不同的微服务,增加了客户端的复杂性。
- 存在跨域请求,在一定场景下处理相对复杂。
- 认证复杂,每个服务都需要独立认证。
- 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务整个成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施。
- 某些微服务可能使用了防火墙/浏览器不友好协议,直接访问会有一定的困难。
以上问题可借助微服务网管解决。微服务网关是介于客户端和服务器之间的中间层,所有外部请求都会先经过微服务网关。使用微服务网关后架构演变为下图。
如图,微服务网关封装了应用程序的内部结构,客户端只需跟网关交互,而无需直接调用特定微服务的接口。这样,开发就可以简化。不仅如此,使用微服务网关还有以下优点:
- 易于监控。可在微服务网关收集监控数据并将其推送到外部系统进行分析。
- 易于认证。可在微服务网关上进行认证,然后再将请求转发到后端的微服务,而无需再每个微服务中进行认证。
- 减少了客户端与各个微服务之间的交互次数。
二、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上。
测试:
- 启动项目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
- 访问http://localhost:7400/eureka-client-consumer-feign/hello/hcj,请求就会转发到http://localhost:7204/hello/hcj上
- 访问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;
}
}
测试:
- 启动项目eureka-server、gateway-zuul、eureka-client-provider、eureka-client-consumer-feign
- 分别访问http://localhost:7400/eureka-client-consumer-feign/hello/hcj,http://localhost:7400/eureka-client-provider/hello/hcj
- 访问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 过滤器的具体业务逻辑
测试:
- 启动项目gateway-zuul-filter、eureka-server、eureka-client-provider
- 访问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;
}
}
测试:
- 启动项目gateway-zuul-filter、eureka-server、eureka-client-provider
- 访问http://localhost:7401/eureka-client-provider/hello/hcj,正常响应
- 停止项目eureka-client-provider后再次访问http://localhost:7401/eureka-client-provider/hello/hcj,响应如下:
说明我们的异常处理过滤器正常运行。
3.Zuul默认过滤器
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
route | 1 | DebugFilter | 标记调试标志 |
route | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
post | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
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,也可以是*代表所有微服务。
测试:
- 启动项目gateway-zuul-fallback、eureka-server、eureka-client-provider
- 访问http://localhost:7402/eureka-client-provider/hello/hcj,正常响应
- 停止项目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;
}
}
测试:
- 启动项目gateway-zuul-aggregation、eureka-server、eureka-client-provider、eureka-client-consumer-feign、eureka-client-consumer-hystrix
- 访问http://localhost:7403/aggregate/hcj,响应如下
控制台输出:
2019-07-11 22:56:53.639 INFO 5448 --- [nio-7403-exec-1] c.s.g.controllers.AggregationController : 完成...
- 停止项目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聚合了两个微服务的接口。