转自:https://blog.csdn.net/pengjunlee/article/details/87285673

为路由提供HystrixFallback

当Zuul中某一个路由的断路器被断开时,你可以通过创建一个FallbackProvider类型的Bean来为它提供一个Fallback响应。在这个Bean中你需要指定Fallback响应所对应的路由的ID并提供一个ClientHttpResponse作为返回的Fallback响应,下面是一个FallbackProvider实现的实例。

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream; 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 com.netflix.hystrix.exception.HystrixTimeoutException; @Component
public class MessageFallbackProvider implements FallbackProvider { @Override
public String getRoute() {
return "zuul-msg";
} @Override
public ClientHttpResponse fallbackResponse(String route, final Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return response(HttpStatus.GATEWAY_TIMEOUT);
} else {
return response(HttpStatus.INTERNAL_SERVER_ERROR);
}
} private ClientHttpResponse response(final HttpStatus status) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
} @Override
public int getRawStatusCode() throws IOException {
return status.value();
} @Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
} @Override
public void close() {
} @Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("消息服务暂时不可用,请稍后重试!".getBytes());
} @Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}

路由的配置信息如下:

zuul:
routes:
message-service: /zuul-msg/**

当message-service服务调用失败时,返回结果见下图。

如果你想为所有的路由提供一个默认的Fallback,可以创建一个FallbackProvider Bean并将它的getRoute() 方法的返回值设置为 “*”或者 null。

class MyFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
return "*";
} @Override
public ClientHttpResponse fallbackResponse(String route, Throwable throwable) {
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".getBytes());
} @Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}

重写Location头信息

如果Zuul面向的是一个Web应用,那么你可能会碰到需要重写Location请求头的情况,你只需要创建一个LocationRewriteFilter类型Bean即可。

import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
@EnableZuulProxy
public class ZuulConfig {
@Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}

注意:此过滤器会作用于所有响应状态码为 3XX 的Location头信息,这并不一定适用于所有的场景,比如重定向到外部URL。

启用跨域请求

默认情况下,Zuul会把所有的Cross Origin requests (CORS,跨域请求)路由给相应的服务,如果你想代替Zuul自己来处理这些请求,你可以提供一个自定义的 WebMvcConfigurer Bean:

	@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/path-1/**").allowedOrigins("http://allowed-origin.com").allowedMethods("GET",
"POST");
}
};
}

在这个例子中,我们允许从 http://allowed-origin.com 向Zuul中以 path-1 开头的终端发送 GET和POST类型的跨域请求。你既可以将 CORS 配置作用于指定的路径,也可以使用 /** 让它作用于整个应用。你可以在其中配置以下属性:allowedOrigins、allowedMethods、allowedHeaders、exposedHeaders、allowCredentials、maxAge。

创建前置过滤器

前置过滤器对RequestContext中的数据进行设置,并提供给下游的过滤器使用,它的最主要的用途就是对路由过滤器所需的信息进行设置。

import javax.servlet.http.HttpServletRequest;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; public class QueryParamPreFilter extends ZuulFilter {
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration
} @Override
public String filterType() {
return FilterConstants.PRE_TYPE;
} @Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return !ctx.containsKey(FilterConstants.FORWARD_TO_KEY) // a filter has already forwarded
&& !ctx.containsKey(FilterConstants.SERVICE_ID_KEY); // a filter has already determined serviceId
} @Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
if (request.getParameter("sample") != null) {
// put the serviceId in `RequestContext`
ctx.put(FilterConstants.SERVICE_ID_KEY, request.getParameter("foo"));
}
return null;
}
}

创建路由过滤器

路由过滤器在前置过滤器之后运行,并负责构造需要发送给其他服务的请求。它在这的主要工作是:把请求数据传送至客户端所需要的 model 中,并把客户端 model 中的内容转换成响应数据返回。

import java.io.InputStream;
import java.util.Enumeration;
import java.util.List;
import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.StreamUtils; import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; import okhttp3.Headers;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.internal.http.HttpMethod; public class OkHttpRoutingFilter extends ZuulFilter {
@Autowired
private ProxyRequestHelper helper; @Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
} @Override
public int filterOrder() {
return FilterConstants.SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;
} @Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext().getRouteHost() != null
&& RequestContext.getCurrentContext().sendZuulResponse();
} @Override
public Object run() {
OkHttpClient httpClient = new OkHttpClient.Builder().build(); // customize RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest(); String method = request.getMethod(); String uri = this.helper.buildZuulRequestURI(request); Headers.Builder headers = new Headers.Builder();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
Enumeration<String> values = request.getHeaders(name); while (values.hasMoreElements()) {
String value = values.nextElement();
headers.add(name, value);
}
}
try {
InputStream inputStream = request.getInputStream(); RequestBody requestBody = null;
if (inputStream != null && HttpMethod.permitsRequestBody(method)) {
MediaType mediaType = null;
if (headers.get("Content-Type") != null) {
mediaType = MediaType.parse(headers.get("Content-Type"));
}
requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));
} Request.Builder builder = new Request.Builder().headers(headers.build()).url(uri).method(method,
requestBody); Response response = httpClient.newCall(builder.build()).execute(); LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>(); for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {
responseHeaders.put(entry.getKey(), entry.getValue());
} this.helper.setResponse(response.code(), response.body().byteStream(), responseHeaders);
context.setRouteHost(null); // prevent SimpleHostRoutingFilter from running
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

创建后置过滤器

后置过滤器通常是用来对响应进行处理,例如下面这个过滤器添加了一个UUID作为 X-Sample 的头内容。

import java.util.UUID;

import javax.servlet.http.HttpServletResponse;

import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext; public class AddResponseHeaderFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.POST_TYPE;
} @Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER - 1;
} @Override
public boolean shouldFilter() {
return true;
} @Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());
return null;
}
}

Zuul错误处理

在Zuul过滤器生命周期的任一阶段一旦出现异常,错误过滤器就会执行。SendErrorFilter 只会在RequestContext.getThrowable() 不是 null 的时候运行。它会在请求中设置一些特定的 javax.servlet.error.* 属性并将请求转向Springboot的错误页面。

应用上下文饿加载

Zuul 在内部使用的是Ribbon 对远程url进行调用。默认情况下,Ribbon客户端会在Spring Cloud第一次调用时进行懒加载。如果需要在应用启动时就进行加载,可以做如下配置。

zuul:
ribbon:
eager-load:
enabled: true

请求失败重试

Spring Cloud Netflix 提供了很多种方法用来创建 HTTP 请求,你可以使用负载均衡的RestTemplate、Ribbon 或者 Feign ,但无论你选择了哪一种,都有可能会出现请求失败的情况。当一个请求失败时,你可能会希望它自动地进行重试,要实现这一点的话,你需要把 Spring Retry 添加到你的classpath ,然后,负载均衡的RestTemplate、Ribbon 、 Feign 以及 Zuul 就会对所有失败的请求自动进行重试(前提是你的配置允许它这样做的话)。

补偿策略
默认情况下,重试请求是没有补偿策略的,如果你想要配置一个补偿策略,你需要为指定的服务创建一个 LoadBalancedRetryFactory 类型的Bean,并重写它的 createBackOffPolicy 方法。

@Configuration
public class MyConfiguration {
@Bean
LoadBalancedRetryFactory retryFactory() {
return new LoadBalancedRetryFactory() {
@Override
public BackOffPolicy createBackOffPolicy(String service) {
return new ExponentialBackOffPolicy();
}
};
}
}

相关配置

在使用Ribbon进行重试时,你可以使用下面前三个个Ribbon配置项对重试的功能进行控制。

# Max number of retries on the same server (excluding the first try)
sample-client.ribbon.MaxAutoRetries=1 # Max number of next servers to retry (excluding the first server)
sample-client.ribbon.MaxAutoRetriesNextServer=1 # Whether all operations can be retried for this client
sample-client.ribbon.OkToRetryOnAllOperations=true # Interval to refresh the server list from the source
sample-client.ribbon.ServerListRefreshInterval=2000 # Connect timeout used by Apache HttpClient
sample-client.ribbon.ConnectTimeout=3000 # Read timeout used by Apache HttpClient
sample-client.ribbon.ReadTimeout=3000 # Initial list of servers, can be changed via Archaius dynamic property at runtime
sample-client.ribbon.listOfServers=www.microsoft.com:80,www.yahoo.com:80,www.google.com:80

另外,如果你想当返回的响应的状态码为某些值的时候才进行重试,你可以使用 clientName.ribbon.retryableStatusCodes 把那些需要Ribbon客户端进行重试的状态码列举出来。

clientName:
ribbon:
retryableStatusCodes: 404,502

或者创建一个 LoadBalancedRetryPolicy 类型的Bean ,并实现它的retryableStatusCode方法。

禁用重试

# 全部禁用
zuul.retryable=false
# 禁用指定路由
zuul.routes.routename.retryable=false

参考文章
https://github.com/spring-projects/spring-retry

https://cloud.spring.io/spring-cloud-netflix/single/spring-cloud-netflix.html#netflix-zuul-starter

【springcloud】Zuul高级配置(zuul--3)的更多相关文章

  1. 【springcloud】Zuul高级配置(zuul--2)

    转自:https://blog.csdn.net/pengjunlee/article/details/87162192 自定义路由规则 在<API Gateway 的路由和过滤(Zuul)&g ...

  2. 跟我学SpringCloud | 第十篇:服务网关Zuul高级篇

    SpringCloud系列教程 | 第十篇:服务网关Zuul高级篇 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如无特殊说明,本系列教程全 ...

  3. SpringCloud:Zuul路由配置超时问题

    测试访问时长 修改下业务类,增加sleep休眠时长,以此查看Zuul的熔断 @GetMapping("/test1") public Object test1() { try { ...

  4. SpringCloud:路由ZUUL的配置详解

    以下是两种配置文件的配置方式,可以根据需要选取对自己项目有利的配置. 自定义访问路径(path) 配置application.yml文件 #provider-user:是你的微服务模块的名称,及spr ...

  5. ⑤SpringCloud 实战:引入Zuul组件,开启网关路由

    这是SpringCloud实战系列中第4篇文章,了解前面第两篇文章更有助于更好理解本文内容: ①SpringCloud 实战:引入Eureka组件,完善服务治理 ②SpringCloud 实战:引入F ...

  6. spring cloud 配置zuul实用

    在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡 ...

  7. SpringCloud学习系列之七 ----- Zuul路由网关的过滤器和异常处理

    前言 在上篇中介绍了SpringCloud Zuul路由网关的基本使用版本,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloud Finchley版)中的路由 ...

  8. springCloud学习4(Zuul服务路由)

    镇博图 springcloud 总集:https://www.tapme.top/blog/detail/2019-02-28-11-33 本篇中 Zuul 版本为 1.x,目前最新的是 2.x,二者 ...

  9. SpringCloud笔记七:Zuul

    目录 什么是Zull 为什么需要Zuul 新建Zuul项目 运行Zuul Zuul的基本配置 忽略微服务的真实名称 设置统一公共前缀 总结 什么是Zull Zuul就是一个网关,实现的功能:代理.路由 ...

随机推荐

  1. vivo x9i ADB 模拟点击

    手机连接电脑无反应,安装360驱动大师 更多设置--关于---多次点击软件版本号--开启开发者选项 USB调试--USB模拟点击(需要密码开启)

  2. python 最大公约数 最小公倍数

    def gongyueshu(m,n): if m<n: m,n=n,m elif m==n: return m if m/n==int(m/n): return n else: for i i ...

  3. HTML5-CSS(三)

    一.CSS  盒模型 1.元素尺寸:CSS 盒模型中最基础的就是设置一个元素的尺寸大小.有三组样式来配置一个元素的尺寸大小,样式表如下 //设置元素尺寸 div { width: 200px;heig ...

  4. P2491 消防/P1099 树网的核

    P2491 消防/P1099 树网的核 双倍经验,双倍快乐. 题意 在一个树上选择一段总长度不超过\(s\)的链使所有点到该链距离的最大值最小. 输出这个最小的值. 做法 Define:以下\(s\) ...

  5. selenium定位,操作元素

    1.定位方式 1.id driver.find_element_by_id('username').send_keys('byhy') 2.name driver.find_element_by_na ...

  6. Power Apps 画布应用中非可委派函数查询数量限制

    本文介绍: Power Apps 中的委派函数和非可委派函数. 现象: 在Power Apps中显示某些数据源时,如果数据量超过 500,则可能遇到现实不全的情况,实际上大部分是由于 委派函数 和 非 ...

  7. SpringBoot: 后台接口文档 - 基于Swagger3

    目录 前言:什么是Swagger 起步:(只需简单的3步) 加载依赖 添加注解@EnableOpenApi 启动SpringBoot,访问Swagger后台界面 配置:基于Java的配置 注解:Swa ...

  8. noip模拟27[妹子图·腿·腰](fengwu半仙的妹子们)

    \(noip模拟27\;solutions\) 这次吧,我本来以为我能切掉两个题,结果呢??只切掉了一个 不过,隔壁Varuxn也以为能切两个,可惜了,他一个都没切...... 确实他分比我高一点,但 ...

  9. DataGridView 显示行号与背景颜色

    实现的方式有好几种.之前使用的是下面这种在RowPostPaint事件中实现,效率不高.每次改变控件尺寸时都会执行 private void MsgGridView_RowPostPaint(obje ...

  10. LinkedHashMap 的实现原理

    LinkedHashMap 概述 HashMap 是无序的,HashMap 在 put 的时候是根据 key 的 hashcode 进行 hash 然后放入对应的地方.所以在按照一定顺序 put 进 ...