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…

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

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

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

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

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

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

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

目 录 一、背景 二、建设目标及需求 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 的一个移动端开发…

Docker安装与启动

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

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; &…

爬虫requests+综合练习

Day2 - 1.requests第一血_哔哩哔哩_bilibili requests作用&#xff1a;模拟浏览器发请求 requests流程&#xff1a;指定url -> 发起请求 -> 获取响应数据 -> 持续化存储 爬取搜狗首页的页面数据 import requests# 指定url url https://sogou.com # 发起请求 resp…

Three.JS教程1 环境搭建、场景与相机

Three.JS教程1 环境搭建、场景与相机 一、Three.JS简介二、环境搭建1. 开发准备2. 安装 three.js3. 新建文件index.htmlmain.js 4. 关于附加组件5. 启动 三、创建场景1. 场景的概念2. 相机的概念3. 相机的几个相关概念&#xff08;1&#xff09;视点&#xff08;Position&#…

【redis13】集群前奏:sentinel模式

1.哨兵sentinel引入背景 我们现在来思考一个问题&#xff1a;如何实现服务的高可用。我们首先想到至少要满足两个要求&#xff1a;1.服务端能够实现主从自动切换&#xff1b;2.对于客户端来说&#xff0c;如果发生了主从切换&#xff0c;则能够自动连接到最新的master节点。 我…

S/MIME电子邮件证书申请指南

近年来&#xff0c;邮件安全问题日益突出&#xff0c;电子邮件成为诈骗、勒索软件攻击的重灾区。恶意邮件的占比屡创新高&#xff0c;邮件泄密事件更是比比皆是。在如此严峻的网络安全形势下&#xff0c;使用S/MIME电子邮件证书进行邮件收发是当今最佳的邮件安全解决方案之一。…

【PICO】【Unity】【VR】如何对打包后的PICO项目有效Debug

【背景】 PICO项目打包后再运行就看不到Console了。当然,会有各类专业的Debug工具。 有一类Debug的工具是Preview形式下展示Debug信息,但是发现Preview成功不见得打包也成功。 打包后也会有一些Debug工具,不过这里我给出自己的简单解决办法。 【解决方案】 Unity Console…

Java毕业设计-基于jsp+servlet的大学生学业规划咨询服务平台管理系统-第84期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于jspservlet的大学生学业规划咨询服务平台管理系统&#xff1a;前端 jsp、jquery、ajax&#xff0c;后端 servlet、jdbc&#xff0c;角色分为管理员、学生&#xff1b…