专栏名称: 石杉的架构笔记
专注原创、用心雕琢!十余年BAT一线大厂架构经验倾囊相授
目录
相关文章推荐
秦皇岛晚报  ·  【中国好手艺117】铁艺灯笼 ·  昨天  
秦皇岛晚报  ·  【中国好手艺117】铁艺灯笼 ·  昨天  
拾榴询财  ·  AI+大行情来了——直播预告 ·  2 天前  
简七读财  ·  过去100年,这类资产更赚钱 ·  3 天前  
简七读财  ·  爸妈安心养老,多少钱才够用? ·  3 天前  
51好读  ›  专栏  ›  石杉的架构笔记

关于Spring Cloud Zuul 那些“不能说的秘密”!

石杉的架构笔记  · 公众号  ·  · 2019-07-12 08:30

正文


本文摘自于 《Spring Cloud微服务 入门 实战与进阶》 一书,作者尹吉欢


1. /routes 端点

当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。

借助这个端点,可以方便、直观地查看以及管理Zuul的路由。

将所有端点都暴露出来,增加下面的配置:

  1. management.endpoints.web.exposure.include=*

访问 http://localhost:2103/actuator/routes 可以显示所有路由信息:

  1. {

  2. "/cxytiandi/**": "http://cxytiandi.com",

  3. "/hystrix-api/**": "hystrix-feign-demo",

  4. "/api/**": "forward:/local",

  5. "/hystrix-feign-demo/**": "hystrix-feign-demo"

  6. }


2. /filters 端点

/fliters端点会返回Zuul中所有过滤器的信息。可以清楚的了解Zuul中目前有哪些过滤器,哪些被禁用了等详细信息。

访问 http://localhost:2103/actuator/filters 可以显示所有过滤器信息

  1. {

  2. "error": [

  3. {

  4. "class": "com.cxytiandi.zuul_demo.filter.ErrorFilter",

  5. "order": 100,

  6. "disabled": false,

  7. "static": true

  8. }

  9. ],

  10. "post": [

  11. {

  12. "class": "org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter",

  13. "order": 1000,

  14. "disabled": false,

  15. "static": true

  16. }

  17. ],

  18. "pre": [

  19. {

  20. "class": "com.cxytiandi.zuul_demo.filter.IpFilter",

  21. "order": 1,

  22. "disabled": false,

  23. "static": true

  24. }

  25. ],

  26. "route": [

  27. {

  28. "class": "org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter",

  29. "order": 10,

  30. "disabled": false,

  31. "static": true

  32. }

  33. ]

  34. }



3. 文件上传

创建一个新的Maven项目zuul-file-demo,编写一个文件上传的接口,如代码清单7-20所示。

代码清单 7-20 文件上传接口

  1. @RestController

  2. public class FileController {


  3. @PostMapping("/file/upload")

  4. public String fileUpload(@RequestParam(value = "file") MultipartFile file) throws IOException {

  5. byte[] bytes = file.getBytes();

  6. File fileToSave = new File(file.getOriginalFilename());

  7. FileCopyUtils.copy(bytes, fileToSave);

  8. return fileToSave.getAbsolutePath();

  9. }


  10. }


将服务注册到Eureka中,服务名称为zuul-file-demo,通过PostMan来上传文件,如图7-4所示

可以看到接口正常返回了文件上传之后的路径,接下来我们换一个大一点的文件,文件大小为1.7MB。

可以看到报错了(如图7-5所示),通过Zuul上传文件,如果超过1M需要配置上传文件的大小, Zuul和上传的服务都要加上配置:

  1. spring.servlet.multipart.max-file-size=1000Mb

  2. spring.servlet.multipart.max-request-size=1000Mb

配置加完后重新上传就可以成功了,如图7-6所示。


第二种解决办法是在网关的请求地址前面加上/zuul,就可以绕过Spring DispatcherServlet进行上传大文件。

  1. # 正常的地址

  2. http://localhost:2103/zuul-file-demo/file/upload

  3. # 绕过的地址

  4. http://localhost:2103/zuul/zuul-file-demo/file/upload

通过加上/zuul前缀可以让Zuul服务不用配置文件上传大小,但是接收文件的服务还是需要配置文件上传大小,否则文件还是会上传失败。

在上传大文件的时候,时间比较会比较长,这个时候需要设置合理的超时时间来避免超时。

  1. ribbon.ConnectTimeout=3000

  2. ribbon.ReadTimeout=60000

在Hystrix隔离模式为线程下zuul.ribbon-isolation-strategy=thread,需要设 置Hystrix超时时间

  1. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000

4. 请求响应信息输出

系统在生产环境出现问题时,排查问题最好的方式就是查看日志了,日志的记录尽量详细,这样你才能快速定位问题。

下面带大家学习如何在Zuul中输出请求响应的信息来辅助我们解决一些问题。

熟悉Zuul的朋友都知道,Zuul中有4种类型过滤器,每种都有特定的使用场景,要想记录响应数据,那么必须是在请求路由到了具体的服务之后,返回了才有数据,这种需求就适合用post过滤器来实现了。如代码清单7-21所示。

代码清单 7-21 Zull获取请求信息

  1. HttpServletRequest req = (HttpServletRequest)RequestContext.getCurrentContext().getRequest();

  2. System.err.println("REQUEST:: " + req.getScheme() + " " + req.getRemoteAddr() + ":" + req.getRemotePort());

  3. StringBuilder params = new StringBuilder("?");

  4. // 获取URL参数

  5. Enumeration<String> names = req.getParameterNames();

  6. if( req.getMethod().equals("GET") ) {

  7. while (names.hasMoreElements()) {

  8. String name = (String) names.nextElement();

  9. params.append(name);

  10. params.append("=");

  11. params.append(req.getParameter(name));

  12. params.append("&");

  13. }

  14. }


  15. if (params.length() > 0) {

  16. params.delete(params.length()-1, params.length());

  17. }


  18. System.err.println("REQUEST:: > " + req.getMethod() + " " + req.getRequestURI() + params + " " + req.getProtocol());

  19. Enumeration<String> headers = req.getHeaderNames();

  20. while (headers.hasMoreElements()) {

  21. String name = (String) headers.nextElement();

  22. String value = req.getHeader(name);

  23. System.err.println("REQUEST:: > " + name + ":" + value);

  24. }


  25. final RequestContext ctx = RequestContext.getCurrentContext();

  26. // 获取请求体参数

  27. if (!ctx.isChunkedRequestBody()) {

  28. ServletInputStream inp = null;

  29. try {

  30. inp = ctx.getRequest().getInputStream();

  31. String body = null;

  32. if (inp != null) {

  33. body = IOUtils.toString(inp);

  34. System.err.println("REQUEST:: > " + body);

  35. } catch (IOException e) {

  36. e.printStackTrace();

  37. }

  38. }

  39. }

输出效果如下:

获取响应内容第一种方式,如代码清单7-22所示。

代码清单 7-22 获取响应内容 (一)

  1. try {

  2. Object zuulResponse = RequestContext.getCurrentContext().get("zuulResponse");

  3. if (zuulResponse != null) {

  4. RibbonHttpResponse resp = (RibbonHttpResponse) zuulResponse;

  5. String body = IOUtils.toString(resp.getBody());

  6. System.err.println("RESPONSE:: > " + body);

  7. resp.close();

  8. RequestContext.getCurrentContext().setResponseBody(body);

  9. }

  10. } catch (IOException e) {

  11. e.printStackTrace();

  12. }

获取 响应内容第二种方式,如代码清单7-23所示。

代码清单 7-23 获取响应内容 (二)

  1. InputStream stream = RequestContext.getCurrentContext().getResponseDataStream();

  2. try {

  3. if (stream != null) {

  4. String body = IOUtils.toString(stream );

  5. System.err.println("RESPONSE:: > " + body);

  6. RequestContext.getCurrentContext().setResponseBody(body);

  7. }

  8. } catch (IOException e) {

  9. e.printStackTrace();

  10. }

什么上面两种方式可以取到响应内容?

在RibbonRoutingFilter或者SimpleHostRoutingFilter中可以看到下面一段代码,如代码清单7-24所示。

代码清单 7-24 响应内容获取源

  1. public Object run() {

  2. RequestContext context = RequestContext.getCurrentContext();

  3. this.helper.addIgnoredHeaders();

  4. try {

  5. RibbonCommandContext commandContext = buildCommandContext(context);

  6. ClientHttpResponse response = forward(commandContext);

  7. setResponse(response);

  8. return response;

  9. }

  10. catch (ZuulException ex) {

  11. throw new ZuulRuntimeException(ex);

  12. }

  13. catch (Exception ex) {

  14. throw new ZuulRuntimeException(ex);

  15. }

  16. }

forward()方法对服务调用,拿到响应结果,通过setResponse()方法进行响应的设置,如代码清单7-25所示。

代码清单 7-25 setResponse (一)

  1. protected void setResponse(ClientHttpResponse resp) throws ClientException, IOException {

  2. RequestContext.getCurrentContext().set("zuulResponse", resp);

  3. this.helper.setResponse (resp.getStatusCode().value(),

  4. resp.getBody() == null ? null : resp.getBody(), resp.getHeaders());

  5. }

面第一行代码就可以解释我们的第一种获取的方法,这边直接把响应内容加到了RequestContext中。

第二种方式的解释就在helper.setResponse的逻辑里面了,如代码清单7-26所示。

代码清单 7-26 setResponse(二)

  1. public void setResponse(int status, InputStream entity,

  2. MultiValueMap<String, String> headers) throws IOException {

  3. RequestContext context = RequestContext.getCurrentContext();

  4. context.setResponseStatusCode(status);

  5. if (entity != null) {

  6. context.setResponseDataStream(entity);

  7. }


  8. // .....

  9. }


5. Zuul自带的Debug功能

Zuul中自带了一个DebugFilter,一开始我也没明白这个DebugFilter有什么用,看名称很容易理解,用来调试的,可是你看它源码几乎没什么逻辑,就set了两个值而已,如代码清单7-27所示。

代码清单 7-27 DebugFilter run方

  1. @Override

  2. public Object run() {

  3. RequestContext ctx = RequestContext.getCurrentContext();

  4. ctx.setDebugRouting(true);

  5. ctx.setDebugRequest(true);

  6. return null;

  7. }

要想让这个过滤器执行就得研究下它的shouldFilter()方法,如代码清单7-28所示。

代码清单 7-28 DebugFilter shouldFilter 方

  1. @Override

  2. public boolean shouldFilter() {

  3. HttpServletRequest request = RequestContext.getCurrentContext







请到「今天看啥」查看全文