Scala Akka http内存泄漏与web套接字

Scala Akka http内存泄漏与web套接字,scala,akka,akka-stream,akka-http,Scala,Akka,Akka Stream,Akka Http,我有一个web服务器,它接受传入的websocket连接,在Scala中使用akka http实现。然而,我一直在观察我的应用程序内存使用的单调增加。经过长时间的挖掘,我发现每个连接都会创建一些内部Akka对象,但在客户端断开连接后不会得到清理。特别是,这个类:akka.stream.impl.fusing.ActorGraphExplorer。每个连接将创建一个新的此类对象。我使用jmap来计算对象的数量,命令如下所示。我不确定我是不是做错了什么。任何建议都将不胜感激 我有一个超级简单的ech

我有一个web服务器,它接受传入的websocket连接,在Scala中使用akka http实现。然而,我一直在观察我的应用程序内存使用的单调增加。经过长时间的挖掘,我发现每个连接都会创建一些内部Akka对象,但在客户端断开连接后不会得到清理。特别是,这个类:
akka.stream.impl.fusing.ActorGraphExplorer
。每个连接将创建一个新的此类对象。我使用
jmap
来计算对象的数量,命令如下所示。我不确定我是不是做错了什么。任何建议都将不胜感激

我有一个超级简单的echo websocket服务器来复制这一观察结果:

package samples

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{Message, TextMessage}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{Flow, Source}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.io.StdIn

object AkkaWsExample {
  implicit val system = ActorSystem()
  implicit val materializer = ActorMaterializer()

  private val greeterWebSocketService = {
    Flow[Message]
      .collect {
        case tm: TextMessage =>
          println(s"Received $tm")
          TextMessage(Source.single("Hello ") ++ tm.textStream)
      }
  }

  def main(args: Array[String]): Unit = {

    //#websocket-routing
    val route =
      path("greeter") {
        get {
          handleWebSocketMessages(greeterWebSocketService)
        }
      }

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)

    println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    StdIn.readLine() // for the future transformations
    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done
  }
}
然后,我使用任何方法连接到该服务器并断开连接,然后运行jmap来计算对象数,并注意到每个连接有一个严格意义上的新对象。我也尝试了数千个连接,同样的事情也发生了

我使用此命令计算对象的数量:

jmap-histo:live[pid]| grep ActorGraphTranslator

以下是启动时以及打开和关闭1000个连接后的结果

ip-192-168-30-10:~liuh$jps | grep Akka | awk{print$1}| xargs jmap-histori:live | grep ActorGraphInt | head-n1

701:156 akka.stream.impl.fusing.ActorGraphTranslator

ip-192-168-30-10:~liuh$jps | grep Akka | awk{print$1}| xargs jmap-histori:live | grep ActorGraphInt | head-n1

119:100156056 akka.stream.impl.fusing.ActorGraphTranslator


您可以看到,对象计数严格地随着连接数的增加而增加。我确保客户端已断开连接-我关闭了进程,并使用
netstat
验证连接是否已关闭。

您可能没有考虑到Scala基于JVM,JVM使用垃圾收集器,而垃圾收集器又不是确定性的。特别是如果没有产生足够的内存压力(与允许的内存限制相比),GC可能根本不会运行。您可以通过强制使用GC(这在生产中可能很糟糕,但在调试时可以)来轻松验证这一理论。尝试在
main
方法的开头添加以下代码:

new Thread() {
  override def run(): Unit = {
    println("Start GC-thread")
    val start = System.currentTimeMillis()
    while (true) {
      Thread.sleep(1000)
      System.gc()
    }
  }
}.start()

这段代码启动一个独立的线程,要求VM每秒执行GC。我敢打赌,使用这样的代码,您的测试将不会显示超过几个活动的
ActorGraphExplorer
对象。至少这是我在你的例子中看到的。如果您在实际代码中仍然看到许多
ActorGraphTranslator
,那么您的示例可能不够充分,您应该发布一个更好的示例。

谢谢您的建议。jmap-histo:live实际上强制执行一个完整的GC。我解决了这个问题。这是akka http中的一个bug。我使用的版本只是其中的一个版本,它已经被引入,但尚未修复。github.com/akka/akka-http/issues/2067Hi。您使用的是哪个版本?我有同样的problem@user1861088,最后一个问题可能是向您提出的