Zuul 也是Netflix公司做出的系统,主要是Netflix API流量的数量和多样性有时会导致生产问题迅速出现而没有警告。因此Netflix团队做了这么一个统一的入口——网关,可以将所有的API组织起来。
Zuul是所有从设备和web站点到Netflix流媒体应用程序后端请求的前门。作为一个边缘服务应用程序,Zuul被构建来支持动态路由、监视、弹性和安全性。它还可以根据需要将请求路由到多个Amazon自动伸缩组。
Zuul使用了一系列不同类型的过滤器,使我们能够快速灵活地将功能应用到服务中。这些过滤器帮助我们执行以下功能:
从较高的角度来看,Zuul 2.0是一台Netty服务器,它运行前置过滤器(入站过滤器),然后使用Netty客户端代理请求,然后在运行后置过滤器(出站过滤器)后返回响应。How It Works 2.0
过滤器是Zuul业务逻辑的核心所在。它们具有执行大量动作的能力,并且可以在请求-响应生命周期的不同部分运行,如上图所示。
和Zuul 1.*对比
前端用Netty Server代替Servlet,目的是支持前端异步。后端用Netty Client代替Http Client,目的是支持后端异步。
过滤器换了一下名字,用Inbound Filters代替Pre-routing Filters,用Endpoint Filter代替Routing Filter,用Outbound Filters代替Post-routing Filters。
性能提升约20%
这里的Spring Cloud Hoxton.RELEASE支持的Zuul 版本仍然是1.* 系列,因为目前大部分应用用的1.3.1,因为对于Zuul 的使用和介绍仍然以1.3.1 版本为主。(2.0在SpringCloud的后续支持下不会有太大使用上的差异)
Spring Cloud 还提供了一个网关Spring Cloud GateWay,基于NIO的非阻塞协议,我们先实现一个微服务的基本实现,将后面进行各个组件替换。
SpringCloud中的zuul:
<dependency>
<groupId>com.netflix.zuul</groupId>
<artifactId>zuul-core</artifactId>
<version>1.3.1</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>groovy-all</artifactId>
<groupId>org.codehaus.groovy</groupId>
</exclusion>
<exclusion>
<artifactId>mockito-all</artifactId>
<groupId>org.mockito</groupId>
</exclusion>
</exclusions>
</dependency>
新建项目模块cloud-gateways-zuul-simple,引入依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
创建启动类
加上@EnableZuulProxy
注解:
@EnableZuulProxy
@SpringBootApplication
public class ZuulSimpleApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulSimpleApplication.class, args);
}
}
添加应用配置
我们给zuul添加转发配置,当输入ip:port/simple 会转发到http://127.0.0.1:8762
server:
port: 8678
spring:
application:
name: gateways-zuul-simple
zuul:
routes:
simple:
url: http://127.0.0.1:8762 #转发的地址
启动项目
这里需要cloud-client-eureka服务的支持(8762端口),然后启动当前项目
在Postman 中输入127.0.0.1:8678/simple/hi实际上调用的是127.0.0.1:8762/hi
因为我们开启了@EnableZuulProxy
注解,在Spring容器初始化的时候,会将Zuul的相关配置也初始化,Spring Boot提供了ServletRegistrationBean用于注册Servlet,Zuul中有一个类ZuulServlet,在Servlet的service方法中执行各种ZuulFilter。下图是ZuulServlet的生命周期。
zuul把过滤器分为四个阶段,分别是
ZuulServlet的service方法在接收到请求后,会先执行pre阶段的过滤器,再执行routing阶段的过滤器,最后执行post阶段的过滤器,其中routing阶段的过滤器会将请求转发到“源服务”,源服务一般都是第三方服务,也可以是当前集群的其它服务,在实行pre、routing、post阶段发生异常时,会执行error过滤器,整个HTTP请求、响应等数据会被封装到RequestContext对象中。
上面的配置方式对运维人员很不友好,因此Spring Cloud 团队实现Spring Cloud Zuul与Spring Cloud Eureka的整合然后进行路由操作了,并且默认的转发规则就是"Zuul网关地址+访问的服务名称+API URL",所以给定服务名称的时候尽量简短一点,服务名称直接可以从Eureka中获取。
比如:
网关地址: 127.0.0.1:8678
服务名称: auth
接口名称:/hi
通过Zuul网关访问的默认地址就是localhost:8678/auth/hi
server:
port: 8678
spring:
application:
name: gateways-zuul-simple
zuul:
routes:
simple:
url: http://127.0.0.1:8762 #转发的地址
上文的简单实用里面,我们说的配置方式合计就是简单路由。url 的配置会匹配http://或https://,如果直接配置127.0.0.1:8762将转发不成功。
这种配置方式是一种简单路由,由过滤器SimpleHostRoutingFilter使用HttpClient进行转发,该过滤器会将HttpServletRequest的请求数据转化为HttpClient的请求实例HttpRequest,然后把使用CloseableHttpClient转发。
当然Zuul也支持修改HttpClient的配置属性,
zuul:
host:
max-total-connections: 300 #设置目标主机的最大连接数,默认200
max-per-route-connections: 30 #设置每个主机的初始化连接数,默认20
同样我们可以浏览器或者Postman 输入127.0.0.1:8678/simple/hi测试访问。
我们可以通过zuul的prefix配置项配置前缀。
比如我们想给将通过网关的接口统一设置一个url前缀,可以使用如下配置
zuul:
prefix: /api
那之前的接口在想请求simple 的数据,就必须带上/api,127.0.0.1:8678/simple/hi要变成127.0.0.1:8678/api/simple/hi
浏览器或者Postman 输入127.0.0.1:8678/api/simple/hi测试访问。
新建项目模块cloud-gateways-zuul,引入依赖.
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
创建启动类
@EnableZuulProxy
@EnableDiscoveryClient
@SpringBootApplication
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
应用配置:
server:
port: 8679
spring:
application:
name: gateways-zuul
eureka:
client:
service-url:
defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
zuul:
routes:
ribbon-server:
path: /ribbon/** #设置前缀
另外上面的配置,还可以简写为:
zuul:
routes:
ribbon-server:
path: /ribbon/**
启动ribbon-server再启动当前项目。
在浏览器或Postman 输入127.0.01:8679/ribbon/user/1测试
在简单路由里已经介绍了跳转到具体第三方地址的配置,下面我们来演示一下本地跳转的功能,Zuul 里面的本地跳转只要通过forward就可以了。
修改Zuul 部分的配置,添加一个forward的API前缀
server:
port: 8679
spring:
application:
name: gateways-zuul
eureka:
client:
service-url:
defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
zuul:
routes:
ribbon-server:
path: /ribbon/** #设置前缀
url: forward:/local #添加一个forward的API前缀
添加控制层:
在gateways-zuul添加/local控制层
@RestController
public class LocalController {
@GetMapping("/local/user/{id:\\d+}")
public String getId(@PathVariable Long id){
return id.toString()+",我是forward转发来的";
}
}
重启当前项目,访问http://127.0.0.1:8679/ribbon/user/1进行测试.
Zuul作为网关来说,会做很多转发前的校验,而这些校验我们都可以基于Zuul过滤器来实现,比如Token校验、IP黑名单等。
新建过滤器
通过模拟用户必须携带token,来拦截用户的请求。前面我们说过Zuul 的生命周期,我们这里模拟在Header里面需要携带token,并且token 的值必须为123456,否则就会执行失败。
@Slf4j
public class TokenFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre"; // 在请求被路由之前调用
}
@Override
public int filterOrder() {
return 0; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
}
@Override
public boolean shouldFilter() {
return true; // 是否执行该过滤器,此处为true,说明需要过滤
}
@Override
public Object run() {
log.info("我是TokenFilter");
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("token");// 获取请求的参数
// 如果有token参数并且token值为123456,才进行路由
if (StringUtils.isNotBlank(token) && token.equals("123456")) {
ctx.setSendZuulResponse(true); //对请求进行路由
ctx.setResponseStatusCode(200);
ctx.set("code", 1);
} else {
ctx.setSendZuulResponse(false); //不对其进行路由
ctx.setResponseStatusCode(401);
HttpServletResponse response = ctx.getResponse();
response.setHeader("content-type", "text/html;charset=utf8");
ctx.setResponseBody("认证失败");
ctx.set("code", 0);
}
return null;
}
}
自定义过滤器需要继承ZuulFilter , 并且需要实现下面几个方法:
新建配置类,配置过滤器:
@Configuration
public class ZuulConfig {
/**
* 过滤器创建后生效,同理禁用注释掉即可
* @return
*/
@Bean
public TokenFilter tokenFilter(){
return new TokenFilter();
}
}
关闭之前配置的本地跳转配置:
server:
port: 8679
spring:
application:
name: gateways-zuul
eureka:
client:
service-url:
defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
zuul:
routes:
ribbon-server:
path: /ribbon/** #设置前缀
#url: forward:/local #添加一个forward的API前缀
重启应用浏览器或Postman 输入127.0.0.1:8679/ribbon/user/1访问 :
我们在请求中设置上token再次访问:
我们再建一个新的过滤器,这样才能有对比的演示顺序
新建一个过滤器,设置执行顺序为5(在前面的TokenFilter之后),这个过滤器是验证header中是否有name这个key,并且key的值要是zuul才能通过,否则失败。
@Slf4j
public class ZuulFilter extends com.netflix.zuul.ZuulFilter {
@Override
public String filterType() {
return "pre"; // 在请求被路由之前调用
}
@Override
public int filterOrder() {
return 5; // filter执行顺序,通过数字指定 ,优先级为0,数字越大,优先级越低
}
@Override
public boolean shouldFilter() {
return true; // 是否执行该过滤器,此处为true,说明需要过滤
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info("我是ZuulFilter");
String token = request.getHeader("name");// 获取请求的参数
// 如果有name参数并且token值为zuul,才进行路由
if (StringUtils.isNotBlank(token) && token.equals("zuul")) {
ctx.setSendZuulResponse(true); //对请求进行路由
ctx.setResponseStatusCode(200);
ctx.set("code", 1);
} else {
ctx.setSendZuulResponse(false); //不对其进行路由
ctx.setResponseStatusCode(401);
HttpServletResponse response = ctx.getResponse();
response.setHeader("content-type", "text/html;charset=utf8");
ctx.setResponseBody("请携带网关必须参数");
ctx.set("code", 0);
}
return null;
}
}
同样的我们加上这个配置:
@Bean
public ZuulFilter zuulFilter(){
return new ZuulFilter();
}
重启项目
浏览器或Postman输入127.0.0.1:8679/ribbon/user/1
首先全部设置正确的header
和前面一样一切正常.并且看到控制台打印出了执行顺序:
但是当我们去掉token请求时,虽然第一TokenFilter失败了,但是后面ZuulFilter还是执行了.
按理说应该不会执行第二个过滤器,所以这是为什么呢?
Zuul中Filter的执行过程如下:
在ZuulServlet的service方法中执行对应的Filter,比如preRoute()。preRoute()中会通过zuulRunner来执行
过程源码如下:
首先请求经过ZuulServlet执行
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
执行pre类型的过滤器
/**
* executes "pre" filters
*
* @throws ZuulException
*/
void preRoute() throws ZuulException {
zuulRunner.preRoute();
}
调用FilterProcessor的preRoute()
public void preRoute() throws ZuulException {
FilterProcessor.getInstance().preRoute();
}
然后preRoute()调用runFilters()获取所有过滤器并执行
public void preRoute() throws ZuulException {
try {
this.runFilters("pre");
} catch (ZuulException var2) {
throw var2;
} catch (Throwable var3) {
throw new ZuulException(var3, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + var3.getClass().getName());
}
}
public Object runFilters(String sType) throws Throwable {
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
boolean bResult = false;
List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
if (list != null) {
for(int i = 0; i < list.size(); ++i) {
ZuulFilter zuulFilter = (ZuulFilter)list.get(i);
Object result = this.processZuulFilter(zuulFilter);
if (result != null && result instanceof Boolean) {
bResult |= (Boolean)result;
}
}
}
return bResult;
}
由此,可以知道为什么第一个报错了,为什么第二个过滤器也执行。
要解决这个问题需要用到数据传递👇
通过分析Zuul 的这段源码,可以看出,先判断这个过滤器是不是已经禁用了,禁用的话不执行,然后判断shouldFilter(),我们可以通过设置shouldFilter()的true、false 来控制过滤器的执行。
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!this.isFilterDisabled()) {
if (this.shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = this.run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable var7) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(var7);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
因此我们可以在第一个失败的时候,通过RequestContext传递变量,为什么用RequestContext,主要还是因为RequestContext的实现原理是基于ThreadLocal实现的,源码如下
private static RequestContext testContext = null;
protected static final ThreadLocal<? extends RequestContext> threadLocal = new ThreadLocal<RequestContext>() {
protected RequestContext initialValue() {
try {
return (RequestContext)RequestContext.contextClass.newInstance();
} catch (Throwable var2) {
throw new RuntimeException(var2);
}
}
};
在TokenFilter认证失败处添加如下代码,设置isShould为false。
//失败之后通知后续不应该执行了
ctx.set("isShould",false);
将ZuulFilter 中的shouldFilter()修改成如下。
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
Boolean isShould = (Boolean) requestContext.get("isShould");
return null == isShould ? true : isShould; // 是否执行该过滤器,此处为true,说明需要过滤
}
重启项目后再次测试,这一次发现第一个认证失败后后面不在继续执行。
Zuul 禁用过滤器有两种方法,第一种就是我们前面说的通过代码把对应的配置bean注释掉即可.如:我们需要禁用TokenFilter:
// @Bean
// public TokenFilter tokenFilter(){
// return new TokenFilter();
// }
另一种办法是通过修改配置的形式:
zuul:
TokenFilter: #要禁用的过滤器名
route:
disabled: true
重启验证,发现TokenFilter已经失效。
对于过滤器的各阶段在执行时,异常主要发生在run()方法中,Zuul 为我们提供了异常处理的过滤器,方法执行时抛出的异常会被捕获到并且调用RequestContext.setThrowable 方法设置异常,error 阶段的SendErrorFilter会判断RequestContext中是否存在异常,如果存在会执行SendErrorFilter过滤器。
SendErrorFilter过滤器在执行的时候,会将异常信息写到HttpServletRequest中,再调用RequestDispatcher的forward方法,默认会跳转到/error页面,我们这里会实现一个/error 的接口,让错误信息可以被我们自定义成JSON输出。
新建异常过滤器
@Slf4j
public class ErrorFilter extends ZuulFilter {
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 10;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
ctx.setSendZuulResponse(false); //不对其进行路由
ctx.setResponseStatusCode(401);
HttpServletResponse response = ctx.getResponse();
response.setHeader("content-type", "text/html;charset=utf8");
ctx.setResponseBody("认证失败" + throwable.getCause().getMessage());
ctx.set("code", 500);
log.error("异常信息,{}", throwable.getCause().getMessage());
return null;
}
}
添加配置
@Bean
public ErrorFilter errorFilter(){
return new ErrorFilter();
}
在TokenFilter添加一个异常(如果前面禁用了TokenFilter这里需要取消掉或者添加到ZuulFilter中):
@Override
public Object run() {
log.info("我是TokenFilter");
//故意抛出异常
int i = 123 / 0;
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("token");// 获取请求的参数
// 如果有token参数并且token值为123456,才进行路由
if (StringUtils.isNotBlank(token) && token.equals("123456")) {
ctx.setSendZuulResponse(true); //对请求进行路由
ctx.setResponseStatusCode(200);
ctx.set("code", 1);
} else {
ctx.setSendZuulResponse(false); //不对其进行路由
ctx.setResponseStatusCode(401);
HttpServletResponse response = ctx.getResponse();
response.setHeader("content-type", "text/html;charset=utf8");
ctx.setResponseBody("认证失败");
ctx.set("code", 0);
ctx.set("isShould",false);
}
return null;
}
这里提前创建一个信息的统一处理类(封装的返回类):
@Data
public class ApiResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
private static final int SUCCESS = 200;
private static String MSG_SUCCESS = "操作成功";
private static int FAIL = 500;
private static String MSG_FAIL = "操作失败";
private int code;
private String msg;
private T data;
//分页信息
private Object meta;
public static <T> ApiResult<T> ok() {
return restResult(null, SUCCESS, MSG_SUCCESS, null);
}
public static <T> ApiResult<T> okMsg(String msg) {
return restResult(null, SUCCESS, msg, null);
}
public static <T> ApiResult<T> ok(T data) {
return restResult(data, SUCCESS, MSG_SUCCESS, null);
}
public static <T> ApiResult<T> ok(T data, String msg) {
return restResult(data, SUCCESS, msg, null);
}
public static <T> ApiResult<T> okMeta(T data) {
return restResult(data, SUCCESS, null, data);
}
public static <T> ApiResult<T> ok(T data, String msg, Object meta) {
return restResult(data, SUCCESS, msg, meta);
}
public static <T> ApiResult<T> failed() {
return restResult(null, FAIL, MSG_FAIL, null);
}
public static <T> ApiResult<T> failedMsg(String msg) {
return restResult(null, FAIL, msg, null);
}
public static <T> ApiResult<T> failedCodeMsg(int code, String msg) {
return restResult(null, code, msg, null);
}
public static <T> ApiResult<T> failed(T data) {
return restResult(data, FAIL, MSG_FAIL, null);
}
public static <T> ApiResult<T> failedCodeData(int code, T data) {
return restResult(data, code, MSG_FAIL, null);
}
public static <T> ApiResult<T> failed(T data, String msg) {
return restResult(data, FAIL, msg, null);
}
private static <T> ApiResult<T> restResult(T data, int code, String msg, Object meta) {
ApiResult apiResult = new ApiResult();
apiResult.setCode(code);
apiResult.setData(data);
apiResult.setMsg(msg);
apiResult.setMeta(meta);
return apiResult;
}
}
新建异常处理处理类
@RestController
public class ErrorHandlerController implements ErrorController {
@Autowired
private ErrorAttributes errorAttributes;
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public ApiResult error(HttpServletRequest request) {
Map<String, Object> errorAttributes = this.errorAttributes.getErrorAttributes(new ServletWebRequest(request), true);
String message = (String) errorAttributes.get("message");
String trace = (String) errorAttributes.get("trace");
if (StringUtils.isNotBlank(trace)) {
message = message + ",trace is " + trace;
}
return ApiResult.failed(message);
}
}
浏览器或Postman 输入127.0.0.1:8679/ribbon/user/1 进行测试
之前我们在介绍Feign,也讲到的重试机制,现在我们来说一下Zuul的重试机制,希望Zuul 在转发服务的时候,如果失败可以重试几次,可能第一次是网络抖动,或者转发到相同服务名是的其它地址上面。
spring-retry是spring提供的一个基于spring的重试框架,我们直接使用这个框架.
首先我们注释掉前面为了测试错误故意抛出的异常代码。
maven 配置添加
<!--重试包-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
配置中开启重试(默认不开启)
zuul:
retryable: true #开启重试
开启后的配置和Ribbon 配置一样,如下
#重试
ribbon:
#配置首台服务器重试1次
MaxAutoRetries: 1
#配置其他服务器重试两次
MaxAutoRetriesNextServer: 2
#链接超时时间
ConnectTimeout: 500
#请求处理时间
ReadTimeout: 500
#每个操作都开启重试机制
OkToRetryOnAllOperations: true
这里可以直接使用Ribbon 的配置,是因为zuul 里面已经集成了Ribbon的包
修改后完整的配置如下:
server:
port: 8679
spring:
application:
name: gateways-zuul
eureka:
client:
service-url:
defaultZone: http://xunfei:12345@127.0.0.1:8761/eureka/
zuul:
routes:
ribbon-server:
path: /ribbon/** #设置前缀
#url: forward:/local #添加一个forward的API前缀
retryable: true #开启重试
ribbon:
#配置首台服务器重试1次
MaxAutoRetries: 1
#配置其他服务器重试两次
MaxAutoRetriesNextServer: 2
#链接超时时间
ConnectTimeout: 500
#请求处理时间
ReadTimeout: 500
#每个操作都开启重试机制
OkToRetryOnAllOperations: true
然后我们在Ribbon Server添加随机延时
超过500秒就会重试
int millis = new Random().nextInt(3000);
System.out.println("client线程休眠时间:"+millis);
Thread.sleep(millis);
浏览器或Postman 输入127.0.0.1:8679/ribbon/user/1
我们看到控制台的请求次数(休眠)
虽然前面我们配置了重试机制,但是重试次数结束了没成功任然会失败,比如如下错误,因为Spring Cloud Zuul 里面已经集成了Hystrix,我们可以自己定义回退机制。
Caused by: java.net.SocketTimeoutException: Read timed out
新建FallBack类:
实现回退需要实现FallbackProvider接口
@Slf4j
@Component
public class ZuulFallBack implements FallbackProvider {
/**
* 方法中返回*表示对所有服务进行回退操作,
* 如果只想对某个服务进行回退,那么就返回需要回退的服务名称,
* 这个名称是注册到Eureka中的名称
*
* @return
*/
@Override
public String getRoute() {
return "*";
}
/**
* 构造回退的内容
*
* @param route
* @param cause
* @return
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
/**
* 返回响应的状态码
* @return
* @throws IOException
*/
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return this.getStatusCode().value();
}
/**
* 返回响应状态码对应的文本
* @return
* @throws IOException
*/
@Override
public String getStatusText() throws IOException {
return this.getStatusCode().getReasonPhrase();
}
@Override
public void close() {
}
/**
* 返回回退的内容
* @return
* @throws IOException
*/
@Override
public InputStream getBody() throws IOException {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
if (null != throwable) {
log.error("Zuul发生错误,{}", throwable.getCause().getMessage());
ApiResult<String> byteMsg = ApiResult.failed(throwable.getCause().getMessage(), "网络或服务异常");
return new ByteArrayInputStream(JSONUtil.toJsonStr(byteMsg).getBytes());
}
ApiResult<String> byteMsg = ApiResult.failedMsg("网络或服务异常");
return new ByteArrayInputStream(JSONUtil.toJsonStr(byteMsg).getBytes());
}
/**
* 返回响应的请求头信息
* @return
*/
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
MediaType mediaType = new MediaType("application", "json", StandardCharsets.UTF_8);
headers.setContentType(mediaType);
return headers;
}
};
}
}
设置hystrix 超时时间配置
#回退超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 #一秒
重启服务
浏览器或Postman 输入地址127.0.0.1:8679/ribbon/user/1,如果想快速看到,可以直接把ribbon-server关掉肯定失败
本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/springcloud06zuul网关
最后更新:2020-04-23 18:05:35
Update your browser to view this website correctly. Update my browser now