表现
生产环境一百多台机器,某接口全面返回http错误码590。
接口相关的业务功能不可用,且不能自己恢复,后紧急下线相关功能配置,重启后恢复。
Bug引入
微服务使用netty的reactive模式进行开发,请求返回体为completableFuture<XXResponse>。
接口处理函数中,新建一个completableFucture,作为返回值。
completableFuture中有代码实现判空不到位,导致抛空指针异常,导致无法走到complete(XXResponse)执行。
从外部看,比如使用postman调用的时候,表现为接口一直阻塞,不响应。
定位过程
尝试在本地环境复现:
1)使用postman runner模拟高频请求。
postman runner本身是不支持并发请求的。
有个小技巧,可以通过修改postman setting里的超时时间,不用上一个等返回就发起下一个。
2)jvisualVM查看内存使用,对内存和线程进行dump。
发现内存并没有明显泄漏。看各个线程池也没发现有特别异常的。
相关知识点:
1)Netty的reactive请求处理模型
https://servicecomb.apache.org/references/java-chassis/zh_CN/general-development/reactive.htmlJava CompletableFuture 详解https://servicecomb.apache.org/references/java-chassis/zh_CN/general-development/reactive.html
请求网络数据到达后,请求会写到channel中,这个过程很快。
进入eventloop1的处理:
vertex线程池对eventloop select到的已经就绪的请求进行处理。这个过程包括:走比如熔断、限流等过滤器,再走到业务代码。
进入异步线程池的处理:
业务代码创建comletableFuture,在线程池中异步执行,在异步线程中通过complete带回结果,主线程直接返回future。
此时注册到另一个eventloop中,当响应完成时会在这里就绪,另一个线程会select到就绪的返回结果,再写回连接数据。
vert.x线程不能阻塞。业务异步线程要保证complete效率,否则会导致被调堆积。
如果在业务线程中涉及IO,调用其他的微服务,也使用响应式编程,返回一个future,就释放当前线程到下一个任务。通过eventloop去获取调用返回。
nio的核心思想就是去除因为io阻塞的线程,转换为多路复用,这个有点像操作系统的io中断、中断响应,搞过嵌入式开发的同学应该很熟悉。
实际上当接口中会调用其他接口的时候,使用响应式编程可以将这些一断一断串起来,最大化效率
2)接口熔断
底层还是hystrix,hystrix隔离模式目前有两种方式:信号量模式和线程池模式。
reactive模型下,hystrix通常使用信号量模型。信号量并不支持超时, 早先的一批请求可能长时间无法得到响应。
hystrix两种隔离模式分析 - 指针怒草内存栈 - 博客园
Hystrix 信号量机制实现资源隔离_littleAusna的博客-CSDN博客_hystrix信号量隔离
原因分析
1)线程中抛异常后,未在线程内catch,而是在线程外catch,导致没有给future返回值。
2)Netty的reactive请求处理模型下,无返回值的future会维持在NioEventLoop中。
3)hystrix的信号量不是否,触发了hystrix的隔离机制,导致快速失败返回590。
待确认:
是否future不返回会导致内存泄漏。