部分内容摘自 Spring Cloud 官方文档中文版
本文源码地址:
目录
Zuul 简介:
Zuul 基本使用:
Zuul 重定向
通过 Zuul 上传文件
Zuul 断路机制
@EnableZuulProxy 与 @EnableZuulServer
@EnableZuulServer 过滤器
@EnableZuulProxy 过滤器
自定义 Zuul 过滤
路由是微服务体系结构的一部分,是 Netflix 的基于 JVM 的路由器和服务器端负载均衡器。
Zuul 默认结合 Ribbon 实现了负载均衡的功能。
根据 服务发现:Eureka (一) 注册和运行 创建一个服务注册中心(eureka_server)和两个功能相同的 Eureka 客户端(eureka_client_1、eureka_client_2)
两个客户端的配置文件分别改为:
server.port=8762
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.application.name=helloClient1
server.port=8763
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
spring.application.name=helloClient2
两个客户端的 spring.application.name 相同,代表提供了同一种服务,分配不同的端口模拟在不同服务器的场景
在两个客户端模块中分别创建一个相同的 HelloController
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Value("${server.port}")
private String port;
@RequestMapping(value = "/hello")
public String hello(){
return "my port is " + port;
}
}
按照创建 Eureka 客户端的步骤创建一个新的 Module:zuulService
在 pom.xml 中加入以下依赖
org.springframework.cloud
spring-cloud-starter-zuul
1.4.6.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
编写 application.properties
zuul.routes.{}.path= // 设置访问路径
zuul.routes.{}.service-id= (或 zuul.routes.{}.url=) // 设置要路由到的服务
{} 中是该路由的名字
server.port=8766
spring.application.name=zuulService
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
zuul.routes.client1.path=/client1/*
zuul.routes.client1.service-id=helloClient1
zuul.routes.client2.path=/client2/*
zuul.routes.client2.service-id=helloClient2
修改启动类,为其加入 @EnableZuulProxy、@EnableEurekaClient 注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ZuulServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServiceApplication.class, args);
}
}
依次启动 eureka_server、eureka_client_1、eureka_client_2、zuulService
多次访问 http://localhost:8766/client1/hello 则只调用 helloClient1 的 hello 方法
多次访问 http://localhost:8766/client2/hello 则只调用 helloClient2 的 hello 方法
迁移现有应用程序或 API 时的常见模式是用不同的实现慢慢替换它们。Zuul 可以处理来自旧端点的客户端的所有请求并将其重定向到新的客户端。
示例配置
zuul.routes.first.path=/first/**
zuul.routes.first.url=http://first.example.com
zuul.routes.second.path=/second/**
zuul.routes.second.url=forward:/second
zuul.routes.third.path=/third/**
zuul.routes.third.url=forward:/3rd
zuul.routes.legacy.path=/**
zuul.routes.legacy.url=http://legacy.example.com
/first/** 中的路径已被提取到具有外部URL的新服务中。并且 /second/** 中的路径被转发,以便它们可以在本地处理,例如具有正常的Spring @RequestMapping
/third/** 中的路径也被转发,但具有不同的前缀(即 /third/foo 转发到 、3rd/foo)
注意:
被忽略的模式并不完全被忽略,它们只是不被代理处理(因此它们也被有效地转发到本地)
配置文件
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
# 设置ribbon的请求超时时间,大文件上传增加超时时间的大小
ribbon:
ConnectTimeout: 3000
ReadTimeout: 60000
若上传图片的路径为 /pic/upload
则上传大文件时要访问 /zuul/pic/upload 才可以上传成功
zuul 默认整合了断路器
官方文档:
当 Zuul 中给定路由的电路跳闸时,您可以通过创建类型为ZuulFallbackProvider
的bean来提供回退响应。
但是我在相关 jar 包中没有找到 ZuulFallbackProvider ,只有 FallbackProvider,可能是 Spring Cloud 版本的问题,按照官方文档给的示例进行了测试,并没有成功。//TODO
创建一个 ZuulFallbackProvider 的实现类 MyFallBackProvider
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;
@Component
public class MyFallBackProvider implements FallbackProvider {
@Override
public String getRoute() {
// 返回配置中的路由名称
// 若想为所有路由提供默认回退,则返回 "*" 或 null
return "client1";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route: " + route);
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallback: " + cause.getMessage()).getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
@EnableZuulProxy 是 @EnableZuulServer 的超集,它包含着 @EnableZuulServer 安装的所有过滤器。如果你想要一个“空白的 Zuul,你应该使用 @EnableZuulServer
前置过滤器
ServletDetectionFilter
:检测请求是否通过Spring调度程序。使用键FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY
设置布尔值。
FormBodyWrapperFilter
:解析表单数据,并对下游请求进行重新编码。
DebugFilter
:如果设置debug
请求参数,则此过滤器将RequestContext.setDebugRouting()
和RequestContext.setDebugRequest()
设置为true。
路由过滤器
SendForwardFilter
:此过滤器使用Servlet RequestDispatcher
转发请求。转发位置存储在RequestContext
属性FilterConstants.FORWARD_TO_KEY
中。这对于转发到当前应用程序中的端点很有用。
过滤器:
SendResponseFilter
:将代理请求的响应写入当前响应。
错误过滤器:
SendErrorFilter
:如果RequestContext.getThrowable()
不为null,则转发到/错误(默认情况下)。可以通过设置error.path
属性来更改默认转发路径(/error
)。
创建从DiscoveryClient
(如Eureka)以及属性加载路由定义的DiscoveryClientRouteLocator
。每个serviceId
从DiscoveryClient
创建路由。随着新服务的添加,路由将被刷新。
除了上述过滤器之外,还安装了以下过滤器(正常Spring豆类):
前置过滤器
PreDecorationFilter
:此过滤器根据提供的RouteLocator
确定在哪里和如何路由。它还为下游请求设置各种与代理相关的头。
路由过滤器
RibbonRoutingFilter
:此过滤器使用Ribbon,Hystrix和可插拔HTTP客户端发送请求。服务ID位于RequestContext
属性FilterConstants.SERVICE_ID_KEY
中。此过滤器可以使用不同的HTTP客户端。他们是:
Apache HttpClient
。这是默认的客户端。
Squareup OkHttpClient
v3。通过在类路径上设置com.squareup.okhttp3:okhttp
库并设置ribbon.okhttp.enabled=true
来启用此功能。
Netflix Ribbon HTTP客户端。这可以通过设置ribbon.restclient.enabled=true
来启用。这个客户端有限制,比如它不支持PATCH方法,还有内置的重试。
SimpleHostRoutingFilter
:此过滤器通过Apache HttpClient发送请求到预定的URL。URL位于RequestContext.getRouteHost()
。
创建一个 Java 类 MyFilter,继承 ZuulFilter
package com.example.demo.common;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @Auther: wyx
* @Date: 2019-04-18 20:42
* @Description:
*/
@Component
public class MyFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
String name = request.getParameter("name");
if(name == null || name.equals("")){
context.setSendZuulResponse(false);
context.setResponseStatusCode(401);
return null;
}
System.out.println("name: " + name);
return null;
}
}
重启 zuulService,再次访问 http://localhost:8766/client1/hello ,由于没有 name 参数,会报在过滤器中设置的 401 错误
关于过滤器中几个方法:
filterType():return:返回过滤器的类型
pre:请求被路由之前调用
routing:请求被路由的时候被调用
post:routing 或 error 结束之后调用
error:路由之后且发生错误时调用
filterOrder():return:过滤器的执行顺序
shouldFilter():return:是否要执行该过滤器,可以根据内部逻辑检测是否需要执行过滤器
run():return:过滤器的具体逻辑