之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了。然后运维发现了个问题,服务器的HTTPS端口有大量的CLOSE_WAIT:

     

  我的第一反应是Spring boot有Bug,因为这个项目分为HTTP和HTTPS两种服务以JAR的形式启动的,而HTTP的没有问题,同时,老架构的服务在Tomcat中以HTTPS提供服务也没有问题,我当时认为这大致上可以判断为Socket层面应该是没有问题的,于是我开始分析Spring Boot的代码。

  经过调试和分析(过程如果有机会,再整理一篇),虽然没有找到引起这个现象的原因,但是发现一个规律,所有出现问题的连接org.apache.tomcat.util.net.NioEndpoint的内部类SocketProcessor中doRun方法中,握手状态一直处于handshake == SelectionKey.OP_READ,监听一直不会关闭。

  虽然,到这一步看上去问题应该出现在Socket层面,但是我还是觉得应该是Spring Boot的,因为Spring Boot引用的Tomcat的处理这部分功能的代码虽然是内嵌的(tomcat-embed-core-8.5.4),但是和完整版并没有什么区别,而完整版是没有这个问题的。

  然后,因为两个原因,我决定继续排查,直接去提ISSUE了:一、需要大量时间分析相关代码才能保证解决这个问题不出现其他问题;二、可以肯定这不是我们新架构和开发的问题。于是我去github提了个Issue,问题在:https://github.com/spring-projects/spring-boot/issues/7780,然而第二天果不其然的被建议让我去给Tomcat提Issue:

     

  虽然我依然认为这是在甩锅,但是我并没有什么能证明这不是Tomcat问题的证据。于是我又看了看代码,试图证明一下 ,然而并没有找到。

  终于,我去给Tomcat提了个Bug,https://bz.apache.org/bugzilla/show_bug.cgi?id=60555,回复指向了另外一个BUG,是这个版本确实存在这个问题,原因是:

The problem occurs for TLS connections when the connection is dropped after the socket has been accepted but before the handshake is complete. The socket ended up in a loop:
- timeout -> ERROR event
- process ERROR (this is the new bit from r1746551)
- try to finish handshake
- need more data from client
- register with poller for READ
- wait for timeout
- timeout ... ... and around you go.

  好吧,既然Tomcat接盘了,咱也不多说啥了,但是我对比了一下本地的类包的代码和r1746551的代码,并且调试了一下以后,发现并不是他说的代码造成的,因为我调试了r1746551的代码依然没有解决问题。不过,线上环境的问题倒是有了个勉强可以接受的解决办法,内嵌的Tomcat换成内嵌的Jetty,果然是没有问题了。

  现在gradle.build中排除spring-boot-starter-web对内嵌Tomcat的引用:

compile('org.springframework.boot:spring-boot-starter-web:1.4.0.RELEASE'){
exclude module: "spring-boot-starter-tomcat"
}

  然后换成Jetty

[group: 'org.springframework.boot', name: 'spring-boot-starter-jetty', version: '1.4.0.RELEASE'],

  至于,提给Tomcat的那个问题,我抽空再仔细琢磨琢磨在去接着提,不过刚才测试升级了一下版本果然是没问题了。

  调试了一下,果然感觉解决问题的并不是他写的r1746551,下面是我看代码的时候发现的,直接解决问题的部分,并不包含在r1746551中,原来有问题的部分:

                        if (socket.isHandshakeComplete() || event == SocketEvent.STOP) {
handshake = 0;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}

  现在没问题的代码是:

                        if (socket.isHandshakeComplete()) {
// No TLS handshaking required. Let the handler
// process this socket / event combination.
handshake = 0;
} else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
event == SocketEvent.ERROR) {
// Unable to complete the TLS handshake. Treat it as
// if the handshake failed.
handshake = -1;
} else {
handshake = socket.handshake(key.isReadable(), key.isWritable());
// The handshake process reads/writes from/to the
// socket. status may therefore be OPEN_WRITE once
// the handshake completes. However, the handshake
// happens when the socket is opened so the status
// must always be OPEN_READ after it completes. It
// is OK to always set this as it is only used if
// the handshake completes.
event = SocketEvent.OPEN_READ;
}

  因为问题本就是因为握手正常建立的过程中被关闭造成的,只要判断改成如上,当握手是由于socket建立失败造成的就会走到close方法,而原本的判断方法是无法做到的,于是问题解决了。至于这段代码的位置,我在开始就说了,嘿嘿。。。,如果有我看漏的地方,大家务必告诉我。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

微信公众号:

                      

最新文章

  1. 每天一点 js join 函数
  2. 最新discuz模版制作7堂课让你精通discuz模版制作
  3. jQuery延迟加载(懒加载)插件 – jquery.lazyload.js
  4. Mongo聚合函数
  5. editplus快捷键大全
  6. 枚举 POJ 1753 Flip Game
  7. POJ1068Parencodings
  8. 【IOS】关于CGTransform的几个动画
  9. 小W与网格
  10. ZOJ 1967 POJ 2570 Fiber Network
  11. python中星号的意义(**字典,*列表或元组)
  12. KMP初步
  13. Maven Tomcat7+ 实现自动化部署
  14. 21天打造分布式爬虫-Spider类爬取糗事百科(七)
  15. pacman安装软件包出现损坏
  16. CentOS 7 安装配置zabbix 3.2.8
  17. Zookeeper C++编程实战之配置更新
  18. bzoj 3531 [Sdoi2014]旅行 (树剖+线段树 动态开点)
  19. $watch, $watchCollection, $watchGroup的使用
  20. SyntaxError: Non-UTF-8 code starting with '\xb6' in file XX.py

热门文章

  1. JavaWeb——Filter
  2. (JS+CSS)实现图片放大效果
  3. nodejs进阶(1)—输出hello world
  4. .Net Core MVC 网站开发(Ninesky) 2.3、项目架构调整(续)-使用配置文件动态注入
  5. MySQL加密
  6. OpenGL ES: Array Texture初体验
  7. 二叉树的创建和遍历(C版和java版)
  8. 马哥linux运维初级+中级+高级 视频教程 教学视频 全套下载(近50G)
  9. 如玫瑰一般的PHP与C#混合编程
  10. Linux 桌面美化那点事儿