Spring Cloud Zuul 那些你不知道的功能点
本文摘自于 《Spring Cloud微服务 入门 实战与进阶》 一书。
1. /routes 端点
当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。
借助这个端点,可以方便、直观地查看以及管理Zuul的路由。
将所有端点都暴露出来,增加下面的配置:
management.endpoints.web.exposure.include=*
访问 http://localhost:2103/actuator/routes 可以显示所有路由信息:
{
"/cxytiandi/**": "http://cxytiandi.com",
"/hystrix-api/**": "hystrix-feign-demo",
"/api/**": "forward:/local",
"/hystrix-feign-demo/**": "hystrix-feign-demo"
}
2. /filters 端点
/fliters端点会返回Zuul中所有过滤器的信息。可以清楚的了解Zuul中目前有哪些过滤器,哪些被禁用了等详细信息。
访问 http://localhost:2103/actuator/filters 可以显示所有过滤器信息:
{
"error": [
{
"class": "com.cxytiandi.zuul_demo.filter.ErrorFilter",
"order": 100,
"disabled": false,
"static": true
}
],
"post": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",
"order": 1000,
"disabled": false,
"static": true
}
],
"pre": [
{
"class": "com.cxytiandi.zuul_demo.filter.IpFilter",
"order": 1,
"disabled": false,
"static": true
}
],
"route": [
{
"class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",
"order": 10,
"disabled": false,
"static": true
}
]
}
3. 文件上传
创建一个新的Maven项目zuul-file-demo,编写一个文件上传的接口,如代码清单7-20所示。
代码清单 7-20 文件上传接口
@RestController
public class FileController {
@PostMapping("/file/upload")
public String fileUpload(@RequestParam(value = "file") MultipartFile file) throws IOException {
byte[] bytes = file.getBytes();
File fileToSave = new File(file.getOriginalFilename());
FileCopyUtils.copy(bytes, fileToSave);
return fileToSave.getAbsolutePath();
}
}
将服务注册到Eureka中,服务名称为zuul-file-demo,通过PostMan来上传文件,如图7-4所示
可以看到接口正常返回了文件上传之后的路径,接下来我们换一个大一点的文件,文件大小为1.7MB。
可以看到报错了(如图7-5所示),通过Zuul上传文件,如果超过1M需要配置上传文件的大小, Zuul和上传的服务都要加上配置:
spring.servlet.multipart.max-file-size=1000Mb
spring.servlet.multipart.max-request-size=1000Mb
配置加完后重新上传就可以成功了,如图7-6所示。
第二种解决办法是在网关的请求地址前面加上/zuul,就可以绕过Spring DispatcherServlet进行上传大文件。
# 正常的地址
http://localhost:2103/zuul-file-demo/file/upload
# 绕过的地址
http://localhost:2103/zuul/zuul-file-demo/file/upload
通过加上/zuul前缀可以让Zuul服务不用配置文件上传大小,但是接收文件的服务还是需要配置文件上传大小,否则文件还是会上传失败。
在上传大文件的时候,时间比较会比较长,这个时候需要设置合理的超时时间来避免超时。
ribbon.ConnectTimeout=3000
ribbon.ReadTimeout=60000
在Hystrix隔离模式为线程下zuul.ribbon-isolation-strategy=thread,需要设置Hystrix超时时间。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000
4. 请求响应信息输出
系统在生产环境出现问题时,排查问题最好的方式就是查看日志了,日志的记录尽量详细,这样你才能快速定位问题。
下面带大家学习如何在Zuul中输出请求响应的信息来辅助我们解决一些问题。
熟悉Zuul的朋友都知道,Zuul中有4种类型过滤器,每种都有特定的使用场景,要想记录响应数据,那么必须是在请求路由到了具体的服务之后,返回了才有数据,这种需求就适合用post过滤器来实现了。如代码清单7-21所示。
代码清单 7-21 Zull获取请求信息
HttpServletRequest req = (HttpServletRequest)RequestContext.getCurrentContext().getRequest();
System.err.println("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());
StringBuilder params = new StringBuilder("?");
// 获取URL参数
Enumeration<String> names = req.getParameterNames();
if( req.getMethod().equals("GET") ) {
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
params.append(name);
params.append("=");
params.append(req.getParameter(name));
params.append("&");
}
}
if (params.length() > 0) {
params.delete(params.length()-1, params.length());
}
System.err.println("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + params + " " + req.getProtocol());
Enumeration<String> headers = req.getHeaderNames();
while (headers.hasMoreElements()) {
String name = (String) headers.nextElement();
String value = req.getHeader(name);
System.err.println("REQUEST:: > " + name + ":" + value);
}
final RequestContext ctx = RequestContext.getCurrentContext();
// 获取请求体参数
if (!ctx.isChunkedRequestBody()) {
ServletInputStream inp = null;
try {
inp = ctx.getRequest().getInputStream();
String body = null;
if (inp != null) {
body = IOUtils.toString(inp);
System.err.println("REQUEST:: > " + body);
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出效果如下:
获取响应内容第一种方式,如代码清单7-22所示。
代码清单 7-22 获取响应内容(一)
try {
Object zuulResponse = RequestContext.getCurrentContext().get("zuulResponse");
if (zuulResponse != null) {
RibbonHttpResponse resp = (RibbonHttpResponse) zuulResponse;
String body = IOUtils.toString(resp.getBody());
System.err.println("RESPONSE:: > " + body);
resp.close();
RequestContext.getCurrentContext().setResponseBody(body);
}
} catch (IOException e) {
e.printStackTrace();
}
获取响应内容第二种方式,如代码清单7-23所示。
代码清单 7-23 获取响应内容(二)
InputStream stream = RequestContext.getCurrentContext().getResponseDataStream();
try {
if (stream != null) {
String body = IOUtils.toString(stream);
System.err.println("RESPONSE:: > " + body);
RequestContext.getCurrentContext().setResponseBody(body);
}
} catch (IOException e) {
e.printStackTrace();
}
为什么上面两种方式可以取到响应内容?
在RibbonRoutingFilter或者SimpleHostRoutingFilter中可以看到下面一段代码,如代码清单7-24所示。
代码清单 7-24 响应内容获取源码
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
RibbonCommandContext commandContext = buildCommandContext(context);
ClientHttpResponse response = forward(commandContext);
setResponse(response);
return response;
}
catch (ZuulException ex) {
throw new ZuulRuntimeException(ex);
}
catch (Exception ex) {
throw new ZuulRuntimeException(ex);
}
}
forward()方法对服务调用,拿到响应结果,通过setResponse()方法进行响应的设置,如代码清单7-25所示。
代码清单 7-25 setResponse(一)
protected void setResponse(ClientHttpResponse resp) throws ClientException, IOException {
RequestContext.getCurrentContext().set("zuulResponse", resp);
this.helper.setResponse(resp.getStatusCode().value(),
resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());
}
上面第一行代码就可以解释我们的第一种获取的方法,这边直接把响应内容加到了RequestContext中。
第二种方式的解释就在helper.setResponse的逻辑里面了,如代码清单7-26所示。
代码清单 7-26 setResponse(二)
public void setResponse(int status, InputStream entity,
MultiValueMap<String, String> headers) throws IOException {
RequestContext context = RequestContext.getCurrentContext();
context.setResponseStatusCode(status);
if (entity != null) {
context.setResponseDataStream(entity);
}
// .....
}
5. Zuul自带的Debug功能
Zuul中自带了一个DebugFilter,一开始我也没明白这个DebugFilter有什么用,看名称很容易理解,用来调试的,可是你看它源码几乎没什么逻辑,就set了两个值而已,如代码清单7-27所示。
代码清单 7-27 DebugFilter run方法
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.setDebugRouting(true);
ctx.setDebugRequest(true);
return null;
}
要想让这个过滤器执行就得研究下它的shouldFilter()方法,如代码清单7-28所示。
代码清单 7-28 DebugFilter shouldFilter 方法
@Override
public boolean shouldFilter() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
return true;
}
return ROUTING_DEBUG.get();
}
只要满足两个条件中的任何一个就可以开启这个过滤器,第一个条件是请求参数中带了某个参数=true就可以开启,这个参数名是通过下面的代码获取的,如代码清单7-29所示。
代码清单 7-29 DebugFilter启用参数(一)
private static final DynamicStringProperty DEBUG_PARAMETER = DynamicPropertyFactory
.getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
DynamicStringProperty是Netflix的配置管理框架Archaius提供的API,可以从配置中心获取配置,由于Netflix没有开源Archaius的服务端,所以这边用的就是默认值debug,如果大家想动态去获取这个值的话可以用携程开源的Apollo来对接Archaius,这个在后面章节给大家讲解。
可以在请求地址后面追加debug=true来开启这个过滤器,参数名称debug也可以在配置文件中进行覆盖,用zuul.debug.parameter指定,否则就是从Archaius中获取,没有对接Archaius那就是默认值debug。
第二个条件代码,如代码清单7-30所示。
代码清单 7-30 DebugFilter启用参数(二)
private static final DynamicBooleanProperty ROUTING_DEBUG = DynamicPropertyFactory
.getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
是通过配置zuul.debug.request来决定的,可以在配置文件中配置zuul.debug.request=true开启DebugFilter过滤器。
DebugFilter过滤器开启后,并没什么效果,在run方法中只是设置了DebugRouting和DebugRequest两个值为true,于是继续看源码,发现在很多地方有这么一段代码,比如com.netflix.zuul.FilterProcessor.runFilters(String)中,如代码清单7-31所示。
代码清单 7-31 Debug信息添加
if (RequestContext.getCurrentContext().debugRouting()) {
Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
}
当debugRouting为true的时候就会添加一些Debug信息到RequestContext中。现在明白了DebugFilter中为什么要设置DebugRouting和DebugRequest两个值为true。
到这步后发现还是很迷茫,一般我们调试信息的话肯定是用日志输出来的,日志级别就是Debug,但这个Debug信息只是累加起来存储到RequestContext中,没有对使用者展示。
继续看代码吧,功夫不负有心人,在org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter.addResponseHeaders()这段代码中看到了希望。如代码清单7-32所示。
代码清单 7-32 Debug信息设置响应
private void addResponseHeaders() {
RequestContext context = RequestContext.getCurrentContext();
HttpServletResponse servletResponse = context.getResponse();
if (this.zuulProperties.isIncludeDebugHeader()) {
@SuppressWarnings("unchecked")
List<String> rd = (List<String>) context.get(ROUTING_DEBUG_KEY);
if (rd != null) {
StringBuilder debugHeader = new StringBuilder();
for (String it : rd) {
debugHeader.append("[[[" + it + "]]]");
}
servletResponse.addHeader(X_ZUUL_DEBUG_HEADER, debugHeader.toString());
}
}
}
核心代码在于this.zuulProperties.isIncludeDebugHeader(),只有满足这个条件才会把RequestContext中的调试信息作为响应头输出,在配置文件中增加下面的配置即可:
zuul.include-debug-header=true
最后在请求的响应头中可以看到调试内容,如图7-7所示。
本文摘自于 《Spring Cloud微服务 入门 实战与进阶》 一书。
Spring Cloud Zuul 那些你不知道的功能点的更多相关文章
- 笔记:Spring Cloud Zuul 快速入门
Spring Cloud Zuul 实现了路由规则与实例的维护问题,通过 Spring Cloud Eureka 进行整合,将自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获取了 ...
- Spring Cloud Zuul 快速入门
Spring Cloud Zuul 实现了路由规则与实例的维护问题,通过 Spring Cloud Eureka 进行整合,将自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获取了 ...
- Spring Cloud Zuul API服务网关之请求路由
目录 一.Zuul 介绍 二.构建Spring Cloud Zuul网关 构建网关 请求路由 请求过滤 三.路由详解 一.Zuul 介绍 通过前几篇文章的介绍,我们了解了Spring Cloud ...
- Spring Cloud Zuul 概览
什么是API网关 网关这个词其实是一个硬件概念.因为按照定义,网络网关出现在网络的边缘,所以防火墙和代理服务器等相关功能 往往与之集成在一起.在家庭网络 和小型企业中,宽带路由器通常充当网络网关.它将 ...
- Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式。
时间过的很快,写springcloud(十):服务网关zuul初级篇还在半年前,现在已经是2018年了,我们继续探讨Zuul更高级的使用方式. 上篇文章主要介绍了Zuul网关使用模式,以及自动转发机制 ...
- Spring Cloud Zuul 限流详解(附源码)(转)
在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法. ...
- Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务
API 网关的出现的原因是微服务架构的出现,不同的微服务一般会有不同的服务地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会 ...
- 第七章 API网关服务:Spring Cloud Zuul
API网关是一个更为智能的应用服务器, 它的定义类似于面向对象设计模式中的Facade模式, 它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤.它除了要实现 ...
- SpringCloud---API网关服务---Spring Cloud Zuul
1.概述 1.1 微服务架构出现的问题 及 解决: 1.1.1 前言 每个微服务应用都提供对外的Restful API服务,它通过F5.Nginx等网络设备或工具软件实现对各个微服务的路由与负载 ...
随机推荐
- 【Spring Cloud】Spring Cloud Config 实现分布式配置中心
Spring Cloud Config 实现分布式配置中心 一.分布式配置中心 分布式系统中,往往拥有大量的服务应用,而每个应用程序都需要有对应的配置文件来协助完成服务环境初始化.运行.因此生产了大量 ...
- 桶排序(C语言)
#include <stdio.h> int main(void) { int arr[5]={2,5,1,3,3}; //定义需要排序的数组 int res[6]={0}; //初始化& ...
- cordova+vue混合式开发App
应要求第一次使用cordova打包了一下vue写的app项目,期间遇到了不少问题,整理一下流程并记录一下常见问题吧. cordova打包项目需要的环境配置啥的就不具体讲啦,百度一下很多教 ...
- Java死锁演示
Java死锁演示 在线程中嵌套获取锁导致死锁.思路,尽量不要嵌套获取锁. package com.mozq.demo.demo; public class DeadLockDemo { private ...
- Network出现两次相同请求?
出现的状况 Network中出现了两个相同的请求(如图),两个发起了同样的请求,花的时间却不同,一个55ms,一个花了294ms. 两个相同的请求 什么情况啊?研究了一番,我发现有一个地方是不同的 ...
- vuex——action,mutation,getters的调用
一.子模块调用根模块的方法 mutation调用 context.commit('clearloginInfo',{key_root:data},{root:true}); action调用 co ...
- 关于中医的一段对话 [ZZ] -- 思维训练故事
转载自新浪博客 网址: http://blog.sina.cn/dpool/blog/s/blog_9880df4201015khq.html?type=-1 关于中医的一段对话 2013-01-26 ...
- 利用Python进行数据分析-Pandas(第七部分-时间序列)
时间序列(time series)数据是一种重要的结构化数据形式,应用于多个领域,包括金融学.经济学.生态学.神经科学.物理学等.时间序列数据的意义取决于具体的应用场景,主要有以下几种: 时间戳(ti ...
- easyui treegrid数据重复加载问题
在使用easyui的时候,出现了数据重复加载的问题.如下图 关于这个问题有两种说法,第一种说法是 easyui-datagrid 类在html和js中重复定义,数据渲染时会加载两次.另一种是$(&qu ...
- go语言变量作用域
Go 语言变量作用域 作用域为已声明标识符所表示的常量.类型.变量.函数或包在源代码中的作用范围. Go 语言中变量可以在三个地方声明: 函数内定义的变量称为局部变量 函数外定义的变量称为全局变量 函 ...