Zuul1.x 高并发下阻塞分析以及解决方案

背景

由于最近博主在压测接口的时候发现我接口出现卡死状态,最开始以为是我自己接口出现问题,单独压测我自己的服务(不经过网关)200/qps/10 次循环 是没问题,但是加上网关(zuul 1.x) 去发现 经过两次循环基本就不能访问,同时其他接口也不能访问,由此问题出现在zuul ,接着开始排查之路。

确认问题

在刚才背景当时只是怀疑zuul 有问题,因为zuul 没有加降级熔断。是否是它需要排查去确认,我当时(测试环境)通过arthas 查看了内存、线程,发现大量waiting 线程,查询具体waiting 线程详细信息后发现有大量http 请求连接没有唤醒,处于watting 根本原因是连接没有关闭。后来又在本地压测通过jconsole 定位如图
在这里插入图片描述
发现和测试环境一样的大量阻塞线程,为啥阻塞就需要看看Zuul 和Ribbon 他们交互逻辑。下面是分析过程。

代码分析

基于之前对zuul 1.x了解执行流程图
在这里插入图片描述
可以看到整个流程也就是route 会建立http连接请求。根据源码之后流程只有两种情况一种是成功执行post 另一种是出现异常执行error。

请求进来代码逻辑

在这里插入图片描述
在这里插入图片描述

异常后执行代码

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
核心出现也就是SendErrorFilter-run在这里插入图片描述
正常不抛异常的话SendResponseFilter 理论是最后一个filter 他会执行关闭操作

private void writeResponse() throws Exception {RequestContext context = RequestContext.getCurrentContext();// there is no body to sendif (context.getResponseBody() == null&& context.getResponseDataStream() == null) {return;}HttpServletResponse servletResponse = context.getResponse();if (servletResponse.getCharacterEncoding() == null) { // only set if not setservletResponse.setCharacterEncoding("UTF-8");}String servletResponseContentEncoding = getResponseContentEncoding(context);OutputStream outStream = servletResponse.getOutputStream();InputStream is = null;try {if (context.getResponseBody() != null) {String body = context.getResponseBody();is = new ByteArrayInputStream(body.getBytes(servletResponse.getCharacterEncoding()));}else {is = context.getResponseDataStream();if (is != null && context.getResponseGZipped()) {// if origin response is gzipped, and client has not requested gzip,// decompress stream before sending to client// else, stream gzip directly to clientif (isGzipRequested(context)) {servletResponseContentEncoding = "gzip";}else {servletResponseContentEncoding = null;is = handleGzipStream(is);}}}if (servletResponseContentEncoding != null) {servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING,servletResponseContentEncoding);}if (is != null) {writeResponse(is, outStream);}}finally {/*** We must ensure that the InputStream provided by our upstream pooling* mechanism is ALWAYS closed even in the case of wrapped streams, which are* supplied by pooled sources such as Apache's* PoolingHttpClientConnectionManager. In that particular case, the underlying* HTTP connection will be returned back to the connection pool iif either* close() is explicitly called, a read error occurs, or the end of the* underlying stream is reached. If, however a write error occurs, we will end* up leaking a connection from the pool without an explicit close()** @author Johannes Edmeier*/if (is != null) {try {//关闭流 同时org.apache.http.conn.EofSensorInputStream 也会清除http 连接is.close();}catch (Exception ex) {log.warn("Error while closing upstream input stream", ex);}}// cleanup ThreadLocal when we are all doneif (buffers != null) {buffers.remove();}try {Object zuulResponse = context.get("zuulResponse");if (zuulResponse instanceof Closeable) {((Closeable) zuulResponse).close();}outStream.flush();// The container will close the stream for us}catch (IOException ex) {log.warn("Error while sending response to client: " + ex.getMessage());}}}

EofSensorInputStream 关闭同时也会归还http连接。
通过上面代码分析,压测的时候发生异常,所以代码执行都会去SendErrorFilter run 方法 他会转发

dispatcher.forward(request, ctx.getResponse());

这个又会重新执行到ZuulServlet 中service 再次请求到之前的微服务接口。因此我们压测那个场景出现阻塞的原因就是:当并发线程高于配置资源后 rabbion http 连接池么有可用连接了,拿不到连接也没有熔断降级配置,抛异常最后执行到SendErrorFilter 这里没有对

public InputStream getResponseDataStream() {return (InputStream) get("responseDataStream");}

执行关闭。导致了连接泄露线程阻塞了,从而页面卡死。
不同情况具体分析

  • 异常发生在route 阶段
    像我们那个场景就是这个阶段,由于线程不够,在获取连接抛出异常,第一次执行到SendErrorFilter 由于没有请求成功 所以getResponseDataStream 是null ,但是由于上面说了会转发会继续走一次ZuulServlet service 这个时候假如有连接释放请求成功后 会对responseDataStream 进行设置赋值 代码如下
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    再次回到SendErrorFilter 的时候 getResponseDataStream 就会有值 这个时候没有对他进行关闭,造成连接泄露。
  • 异常发生在post 阶段
    这个阶段发生异常基本getResponseDataStream 已经有值了,所以说只要你自己定义的post 类型的filter 有异常抛出来没有处理必然发生连接泄露,因为他最后还是执行到SendErrorFilter 。

解决方案

第一步增加熔断降级

@Slf4j
public class CustomFallbackProvider implements FallbackProvider {@Overridepublic String getRoute() {return "*";}@Overridepublic ClientHttpResponse fallbackResponse(String route, Throwable cause) {return new ClientHttpResponse() {/***ClientHttpResponse的fallback的状态码,返回的是HttpStatus* @return*/@Overridepublic HttpStatus getStatusCode() throws IOException {return HttpStatus.INTERNAL_SERVER_ERROR;}/***ClientHttpResponse的fallback的状态码,返回的是int* @return*/@Overridepublic int getRawStatusCode() throws IOException {return this.getStatusCode().value();}/***ClientHttpResponse的fallback的状态码,返回的是String* @return*/@Overridepublic String getStatusText() throws IOException {return this.getStatusCode().getReasonPhrase();}@Overridepublic void close() {}/***设置响应体信息* @return*/@Overridepublic InputStream getBody() {String content = "网络异常,请稍后重试!";return new ByteArrayInputStream(content.getBytes());}/***设置响应的头信息* @return*/@Overridepublic HttpHeaders getHeaders() {HttpHeaders headers = new HttpHeaders();MediaType mediaType = new MediaType("application", "json", Charset.forName("utf-8"));headers.setContentType(mediaType);return headers;}};}
}

为啥增加降级会减少(是大大降低但是不是完全解决)线程阻塞问题?通过代码分析
在这里插入图片描述
在这里插入图片描述
我们有自定义的FallbackProvider 返回ClientHttpResponse 这样不会执行到SendErrorFilter 最后走的还是SendResponseFilter run 方法中关闭流归还连接。

重新写SendErrorFilter

继承ZuulFilter 设置Error 类型 Order 设置-1 保证有异常不去执行SendErrorFilter (context.remove(“throwable”); 之后shouldFilter 返回false 也就不会执行了) 核心代码如下:

@Slf4j
@Component
public class ErrorFilter extends ZuulFilter {@Overridepublic String filterType() {return ERROR_TYPE;}@Overridepublic int filterOrder() {return -1;}protected static final String SEND_ERROR_FILTER_RAN = "sendErrorFilter.ran";@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return ctx.getThrowable() != null && !ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);}@Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext();PrintWriter writer = null;InputStream is = null;try {context.remove("throwable");context.set(SEND_ERROR_FILTER_RAN, true);ZuulException exception = findZuulException(context.getThrowable());HttpServletResponse response = context.getResponse();response.setContentType("application/json; charset=utf8");response.setStatus(exception.nStatusCode);is = context.getResponseDataStream();writer = response.getWriter();Map<String, Object> map = new HashMap<>();map.put("code", exception.nStatusCode);map.put("msg", exception.errorCause);map.put("detail", exception.getMessage());String retStr = JSON.toJSONString(map);writer.print(retStr);writer.flush();} catch (Exception e) {log.error(e.getMessage());} finally {if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (writer != null) {writer.close();}}return null;}protected ZuulException findZuulException(Throwable throwable) {if (Objects.isNull(throwable)) {return null;}if (throwable.getCause() instanceof ZuulRuntimeException) {Throwable cause = null;if (throwable.getCause().getCause() != null) {cause = throwable.getCause().getCause().getCause();}if (cause instanceof ClientException && cause.getCause() != null&& cause.getCause().getCause() instanceof SocketTimeoutException) {ZuulException zuulException = new ZuulException("", 504,ZuulException.class.getName() + ": Hystrix Readed time out");return zuulException;}if (throwable.getCause().getCause() instanceof ZuulException) {return (ZuulException) throwable.getCause().getCause();}}if (throwable.getCause() instanceof ZuulException) {return (ZuulException) throwable.getCause();}if (throwable instanceof ZuulException) {return (ZuulException) throwable;}return new ZuulException(throwable, HttpStatus.INTERNAL_SERVER_ERROR.value(), null);}
}

总结

目前熔断和重新写Error filter 基本可以保证高并发下不发生连接泄露,但是要是性能追求更高 可以使用Nocos、Zuul2.x 等基于Netty 的网关框架。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/637807.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编曲学习:Cubase12导入Cubasis工程的方法!

Steinberg 发布 Cubasis 3 项目导入器&#xff0c;可将 Cubasis 的项目导入到 Cubase 使用https://m.midifan.com/news_body.php?id35635 我偶然看到这个文章&#xff0c;不过发现Cubase12默认好像没有这个选项&#xff0c;心想着要是移动端能和PC端同步&#xff0c;感觉会挺…

【网站项目】基于jsp的199旅游景点管理系统

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

C++中的排序操作:sort与自定义排序(自定义排序函数、匿名函数、运算符重载)

在C编程中&#xff0c;排序是一项常见而又重要的操作。本文将深入介绍C标准库中的sort算法&#xff0c;以及如何利用其强大的自定义排序功能满足各种排序需求。 sort算法简介 C标准库提供了sort算法&#xff0c;能够在O(N log N)的时间内对容器中的元素进行排序。这一高效的排…

快速下载百度网盘的文件——使用motrix

问题描述 下载速度慢 上传速度快 解决方案&#xff1a; Motrix 在该开源程序里面 选windows选择zip 启动之后 &#xff0c;把百度网盘的链接转化成磁力链接。然后输入转化后的连接。转换的网页 每次设置下载认任务是选择高级选项里面的请求头 修改为LogStatistic 然后就能超…

“低绩效”指南

前言 一看这个标题&#xff0c;大家是否有个疑问&#xff0c;为啥是低绩效指南&#xff0c;而不是高绩效指南。可以这么说&#xff0c;能拿到高绩效的人大多有共通之处&#xff0c;然而获得低绩效的同学原因各不相同&#xff0c;针对笔者总结的一些经验或许能帮助大家反向操作…

Qt5编译MySQL数据驱动、部署MySQL服务器、Qt写代码连接MySQL数据库_案例介绍

一、前言 由于Qt 5在高版本中取消了对MySQL数据库的默认支持,要在QT里继续使用mysql需要自己编译库。本篇文章介绍Qt5(我用的Qt5.12.6)里如何编译MySQL的库文件,讲解在Linux下安装配置MySQL数据库,Qt编写代码连接上自己的MySQL数据库完成数据存储。 MySQL是一个开源的关…

Odrive 学习系列四:如何使用脚本自动初始化odrive配置

一、背景: 在学习markbase的教程后,发现odrive的初始化配置命令确实有点多。尽管odrive有自动补全: 且可以通过 ctrl + → 来快速补全: 但是对初学者而言,仍旧有比较大的工作量。 而针对于此,我们可以通过powershell脚本的方式来解决这个问题。 二、设计初始化…

接口测试 03 -- 接口自动化思维 Requests库应用

1. 接口自动化思维梳理 1.1接口自动化的优点 接口测试自动化&#xff0c;简单来讲就是功能测试用例脚本化然后执行脚本&#xff0c;产生一份可视化测试报告。不管什么样的测试方式&#xff0c;都是为了验证功能与发现 BUG。那为什么要做接口测试自动化呢&#xff1f;一句话概括…

【ARM 嵌入式 编译系列 2.1 -- GCC 预处理命令 #error 和 #warning 详细介绍 】

文章目录 #error 和 #warning#error示例 #warning示例 打印行号示例 #error 和 #warning 在C语言中&#xff0c;#error 和 #warning 预处理指令可以用于在编译时生成错误或警告信息&#xff0c;通常用于调试或当代码中某些条件未满足时提醒开发者。当这些指令被编译器处理时&a…

项目解决方案:多地医馆的高清视频监控接入汇聚联网

目 录 一、背景 二、建设目标及需求 1.建设目标 2.现状分析 3.需求分析 三、方案设计 1.设计依据 2.设计原则 3.方案设计 3.1 方案描述 3.2 组网说明 四、产品介绍 1.视频监控综合资源管理平台介绍 2.视频录像服务器和存储 2.1概述 2.2存储设计 …

51单片机流水灯

**led 介绍**LED是“Light Emitting Diode”的缩写&#xff0c;即发光二极管。它是一种半导体器件&#xff0c;能够将电能转化为可见光。LED灯通常由LED芯片、封装材料、铝基板和灯罩等部件组成。 **LED灯具有以下特点&#xff1a;** 节能&#xff1a;LED灯具有较高的光电转换…

oracle篇—19c新特性自动索引介绍

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

Go 知识slice

Go 知识slice 1. 什么是slice2. slice 基础2.1 定义 2.2 实现原理2.2.1 make 创建2.2.2 切片 创建 2.3 操作2.3.1 append 追加2.3.2 表达式切片2.3.3 扩展表达式2.3.4 扩容2.3.5 拷贝 3. 测试一下3.1 len && cap3.2 append && 扩容3.3 切片表达式 1. 什么是sli…

Vue2移动端项目使用$router.go(-1)不生效问题记录

目录 1、this.$router.go(-1) 改成 this.$router.back() 2、存储 from.path&#xff0c;使用 this.$router.push 3、hash模式中使用h5新增的onhashchange事件做hack处理 4、this.$router.go(-1) 之前添加一个 replace 方法 问题背景 &#xff1a; 在 Vue2 的一个移动端开发…

/bin/rm Argument list too long – Linux 删除海量文件报错的解决方法

文章目录 /bin/rm Argument list too long – Linux”配合find与xargs完成删除海量文件使用find的delete选项 /bin/rm Argument list too long – Linux” 这种情况主要在大批量删除居多文件的情况下发生&#xff0c;也是因为删除的命令参数超过了shell参数的个数导致的&#…

Docker安装与启动

Docker概述 Docker是一个快速交付应用、运行应用的技术&#xff1a; 可以将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;可以迁移到任意Linux操作系统运行时利用沙箱机制形成隔离容器&#xff0c;各个应用互不干扰启动、移除都可以通过一行命令完成&#xff0c;方便…

kotlin Pair 和 Triple

在 Kotlin 中&#xff0c;Pair 和 Triple 是两种内建的数据类&#xff0c;用于表示固定数量的元素对和三元组 Pair Pair 类型是用来存储两个相关联值的数据类&#xff0c;它有两个属性&#xff1a;first 和 second。 val pairExample Pair("Apple", "Banana…

AttributeError: module ‘numpy‘ has no attribute ‘float‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

YOLOv5改进系列(27)——添加SCConv注意力卷积(CVPR 2023|即插即用的高效卷积模块)

【YOLOv5改进系列】前期回顾: YOLOv5改进系列(0)——重要性能指标与训练结果评价及分析 YOLOv5改进系列(1)——添加SE注意力机制

【Docker】安装Nginx容器并部署前后端分离项目

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Docker实战》。&#x1f3af;&#x1f3af; &…