网关就可以对外暴露聚合API,屏蔽内部微服务的微小变动,保持整个系统的稳定性。它还可以做负载均衡,统一鉴权,协议转换,监控监测等一系列功能。
Zuul是Spring Cloud全家桶中的微服务API网关。
所有从设备或网站来的请求都会经过Zuul到达后端的Netflix应用程序。作为一个边界性质的应用程序,Zuul提供了动态路由、监控、弹性负载和安全功能。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
认证和安全:识别每一个资源的验证要求,并拒绝那些不符的请求
性能监测:在服务边界追踪并统计数据,提供精确的生产视图
动态路由:动态将请求路由到不同后端集群
压力测试:逐渐增加指向集群的流量,以了解性能
负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
静态响应处理:边缘位置进行响应,避免转发到内部集群
多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化
Spring Cloud对Zuul进行了整合和增强。目前,Zuul使用的默认是Apache的HTTP Client,也可以使用Rest Client,可以设置ribbon.restclient.enabled=true.
对于网关来说所面对的请求大部分来自于外部(客户端未注册到Eureka Server),要实现网关的高可用可以在在网关前面再架一个前置代理(如Nginx)。如果客户端注册在Eureka Server上那么客户端可以从Eureka Server处获取Zuul网关地址实现客户端负载均衡。
客户端未注册到Eureka Server上(比如手机App端等):
客户端注册在Eureka Server上(比如:MVC App, SPA 等):
一个外部请求,可能会通过Zuul查询多个微服务,如果让其一个个请求,就算Zuul转发,网络开销、流量耗费、时长都不是很理想。
我们可以使用Zuul聚合微服务请求,即应用系统只发送一个请求给Zuul,由Zuul请求不同的微服务,并把数据返给应用系统。
eureka注册中心、eureka服务端、zuul
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.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-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</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>
@SpringBootApplication
@EnableZuulProxy
public class CloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(CloudZuulApplication.class, args);
}
}
spring.application.name=cloud-zuul
server.port=8040
# logging 配置
logging.config=classpath:log4j2.xml
#根据ip注册实例
eureka.instance.prefer-ip-address=true
#指定注册实例ID(默认是主机名:应用名:应用端口)
#eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}
#指定注册实例主机名
#eureka.instance.hostname=127.0.0.1
#eureka.instance.hostname= ${spring.cloud.client.ip-address}
#注册地址 eureka服务端的地址 多节点用,分隔
eureka.client.service-url.defaultZone=http://127.0.0.1:8025/eureka/
先启动eureka注册中心、eureka服务端(cloud-client)然后启动zuul。其中eureka服务端启动两个,一个端口为8030一个为8031
1.zuul默认代理所有微服务。在浏览器输入http://localhost:8040/cloud-client/client/api/findById?id=5
结果是:
<JSONObject>
<id>5</id>
</JSONObject>
说明配置生效
2.zuul默认代理所有微服务。要忽略指定微服务用下的配置。多个服务用,隔开,要忽略所有微服务用 *。这里忽略cloud-client服务。
zuul.ignored-services=cloud-client
在浏览器输入http://localhost:8040/cloud-client/client/api/findById?id=5
结果是是404,说明配置生效。
3.指定微服务(cloud-client)的访问路径。
zuul.routes.cloud-client=/user/**
表示以/user开头,就会请求到服务id为cloud-client的服务上面,请求地址是/**。
在浏览器输入http://localhost:8040/user/client/api/findById?id=5
结果是:
<JSONObject>
<id>5</id>
</JSONObject>
说明配置生效
4.为cloud-client服务配置别名(client)和访问路径。
zuul.routes.client.service-id=cloud-client
zuul.routes.client.path=/user/**
表示访问路径是/user开头的会请求到cloud-client的服务上面,请求地址是/**。
在浏览器输入http://localhost:8040/user/client/api/findById?id=5
结果是:
<JSONObject>
<id>5</id>
</JSONObject>
说明配置生效
5.指定path和URL。
zuul.routes.client.service-id=cloud-client
zuul.routes.client.url=http://localhost:8030
zuul.routes.client.path=/user/**
表示请求是/user开头,就会请求到http://localhost:8030 上面,请求地址是http://localhost:8030/** 该配置无法使用负载均衡功能。
在浏览器输入http://localhost:8040/user/client/api/findById?id=5
结果是:
<JSONObject>
<id>5</id>
</JSONObject>
其中端口为8030的client有日志INFO findById:5
,而端口为8031的client没有日志。说明配置生效
6.指定path和URL,并保留Zuul的Hystrix、Ribbon特性
zuul.routes.client.service-id=cloud-client
zuul.routes.client.path=/user/**
#为Ribbon禁用Eureka
ribbon.eureka.enabled=false
cloud-client.ribbon.listOfServers=localhost:8030,localhost:8031
表示请求是/user开头,就会请求到http://localhost:8030、localhost:8031上面,请求地址是http://localhost:8030/** 或者http://localhost:8031/** 该配置可以使用zuul负载均衡功能。
在浏览器输入http://localhost:8040/user/client/api/findById?id=5
,多访问几次,结果都是:
<JSONObject>
<id>5</id>
</JSONObject>
其中端口为8030及8031的client都有日志INFO findById:5
。说明配置生效
7.设置全局路由前缀,并重写指定微服务(这里是cloud-client)的访问路径。
#设置一个全局的前缀,即所有请求必须添加client
zuul.prefix=/client
#是否将这个代理前缀去掉
zuul.strip-prefix=true
zuul.routes.cloud-client=/user/**
其中zuul.strip-prefix=false则访问路径以/client/user开头时,就会请求到服务id为cloud-client的服务上面,请求地址是/client/**
zuul.strip-prefix=true则访问路径以/client/user开头时,就会请求到服务id为cloud-client的服务上面,请求地址是/**。
zuul.strip-prefix是true还是false,浏览器输入http://localhost:8040/user/client/api/findById?id=5
则结果都为404
当zuul.strip-prefix=false时,浏览器输入http://localhost:8040/client/user/api/findById?id=5
则结果为:
<JSONObject>
<id>5</id>
</JSONObject>
当zuul.strip-prefix=false时,浏览器输入http://localhost:8040/client/user/client/api/findById?id=5
则结果为404
当zuul.strip-prefix=true时,浏览器输入http://localhost:8040/client/user/api/findById?id=5
则结果为404
当zuul.strip-prefix=true时,浏览器输入http://localhost:8040/client/user/client/api/findById?id=5
则结果为:
<JSONObject>
<id>5</id>
</JSONObject>
证明配置生效
8.设置局部路由前缀,并重写指定微服务(这里是cloud-client)的访问路径。
zuul.prefix=/client
zuul.routes.client.service-id=cloud-client
#是否将这个代理前缀去掉
zuul.routes.client.strip-prefix=true
zuul.routes.client.path=/user/**
其中zuul.routes.client.strip-prefix=false 不能生效
zuul.routes.client.strip-prefix=true生效,以/client/user开头比如是/client/user/…,就会请求到服务id为cloud-client的服务上面,请求地址是/…
当zuul.routes.client.strip-prefix=true时浏览器输入http://localhost:8040/client/user/client/api/findById?id=5
则结果为:
<JSONObject>
<id>5</id>
</JSONObject>
当zuul.routes.client.strip-prefix=true时浏览器输入http://localhost:8040/client/user/api/findById?id=5
则结果为404
证明配置生效
9.忽略某些微服务中的某些路径
zuul.ignoredPatterns=/**/findById
zuul.routes.cloud-client=/user/**
浏览器输入http://localhost:8040/user/client/api/findById?id=5
则结果为404
浏览器输入http://localhost:8040/user/client/api/=5
则结果为:
<JSONObject>
<findByPathId>5</findByPathId>
</JSONObject>
10.本地转发
新建一个类
@RestController
@RequestMapping("api")
@Log4j2
public class Controller {
@GetMapping("findById")
public boolean findById(@RequestParam(name = "id") Long id) {
log.info("Zuul-->findById:"+id);
return true;
}
}
配置如下:
zuul.routes.forward.path=/user/**
zuul.routes.forward.url=forward:/api
浏览器输入http://localhost:8040/user/findById?id=5
会被本地转发成http://localhost:8040/api/findById?id=5
结果为:
<Boolean>true</Boolean>
如果要通过Zuul来上传文件则要加路径前面加/zuul,比如:curl -F "[email protected]" http://localhost:8040/zuul/user/client/api/upload
如果不加就会报错
对于有大文件上传则还要设置超时时间:
hystrix.command.default.execution.isolation.thread.timeoutInMillseconds=60000
ribbon.ConnectTimeout=90000
ribbon.ReadTimeout=90000
如果设置路由前缀则zuul在前缀前面如: curl -F "[email protected]" http://localhost:8040/zuul/client/user/client/api/upload
1.过滤客户端请求中的headers
#全局
zuul.sensitive-headers=Cookie,Set-Cookie,Authorization
#局部
zuul.routes.client.custom-sensitive-headers=true
zuul.routes.client.sensitive-headers=Cookie,Set-Cookie,Authorization
如果客户端在发请求是带了Cookie,那么Cookie不会传递给下游服务。
2.过滤服务之间通信附带的headers
zuul.ignored-headers=Cookie,Set-Cookie,Authorization
如果客户端在发请求是带了Cookie,那么Cookie依然会传递给下游服务。但是如果下游服务再转发就会被过滤。
zuul.ignored-headers的作用与上面zuul.sensitive-headers差不多,事实上sensitive-headers会被添加到ignored-headers中。
还有一种情况就是客户端带了Cookie,在ZUUL的Filter中又addZuulRequestHeader(“Cookie”, “new”),
那么客户端的Cookie将会被覆盖,此时不需要sensitive-headers。
如果设置了sensitiveHeaders: Cookie,那么Filter中设置的Cookie依然不会被过滤。
Type:用以表示路由过程中的阶段(包含pre、routing、post、error)
Execution Order:表示相同Type的Filter的执行顺序
Criteria:执行条件
Action:执行体
Zuul提供了动态读取、编译和执行Filter的框架。各个Filter间没有直接联系,但是都通过RequestContext共享一些状态数据。
Zuul支持任何基于JVM的语言,但是过滤器目前是用Groovy编写的。
pre:在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录
routing:将请求路由到微服务,用于构建发送给微服务的请求,并使用Apache Http Client或者Netflix Ribbon请求微服务。指定serviceId时使用Netflix Ribbon否则使用Apache Http Client
post:在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。
error:出错时执行
1.修改启动类
@SpringBootApplication
@EnableZuulProxy
public class CloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(CloudZuulApplication.class, args);
}
@Bean
public PreRequestLogFilter preRequestLogFilter(){
return new PreRequestLogFilter();
}
}
2.添加过滤器类
@Log4j2
public class PreRequestLogFilter extends ZuulFilter {
// 过滤器类型
@Override
public String filterType() {
return "pre";//在请求路由到目标之前执行。一般用于请求认证、负载均衡和日志记录
// return "routing";//将请求路由到微服务,用于构建发送给微服务的请求,并使用Apache Http Client或者Netflix Ribbon请求微服务
// return "post";//在目标请求返回后执行。一般会在此步骤添加响应头、收集统计和性能数据等。
// return "error";//出错时执行
}
// 优先级,越大越靠后执行
@Override
public int filterOrder() {
return 0;
}
// 是否需要过滤
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
log.info(String.format("send %s request to %s", request.getMethod(), request.getRequestURL())+System.currentTimeMillis());
return null;
}
}
3.自定义的Filter可以通过shouldFilter方法返回false来禁用,第三方的可以通过下面方式来禁用
#PreRequestLogFilter是过滤器名,pre是类型
zuul.PreRequestLogFilter.pre.disable=true
@Component
public class DefaultFallbackProvider 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 this.getStatusCode().value();
}
@Override
public String getStatusText() throws IOException {
//状态码的文本形式
return getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
//响应体
return new ByteArrayInputStream("cloud-client Service is not avaiable, please try again later."
.getBytes());
}
@Override
public HttpHeaders getHeaders() {
//设定headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_HTML);
return headers;
}
};
}
}
关闭client浏览器输入http://localhost:8040/cloud-client/client/api/findById?id=5
页面会输出cloud-client Service is not available, please try again later.
参考文章:Spring Cloud 微服务架构学习笔记与示例、ZUUL-API网关、使用Zuul构建API Gateway