信息:您可能希望在介绍后直接跳到编辑4。
最近我写了一个简单的scala服务器应用程序。该应用程序主要将传入的数据写入数据库或检索它。这是我的第一个scala-akka应用程序。
当我将应用程序部署到我的服务器后,它在大约一天后失败了。我从digitalocean提供的简单统计数据中了解到,如果我从服务器请求数据,那么CPU的使用率会以线性方式上升。如果我没有从服务器请求任何东西,那么CPU使用率几乎是恒定的,但并没有从之前的状态下降。
我将应用程序连接到visualvm并看到如果我不对app(I)做任何事情,线程数是常数,或者如果我以类似锯子的方式将内容发送到服务器(II),则增长。
在线程数和CPU使用率之间有一个明显的颜色,这使得sens。
当我检查线程选项卡时,我看到大多数线程都是default-akka.actor.default-dispatcher
个线程
他们似乎也做不了多少。
什么可能导致这类问题?我该如何解决? 广告。编辑4:我认为我找到了问题的根源,但我仍然不明白为什么会发生这种情况,以及我应该如何解决它。
PS:我必须承认屏幕截图不是来自失败的应用程序。我没有原来的失败。然而,这个程序和失败的程序之间的唯一区别是application.conf
我添加了:
actor {
default-dispatcher {
fork-join-executor {
# Settings this to 1 instead of 3 seems to improve performance.
parallelism-factor = 2.0
parallelism-max = 24
task-peeking-mode = FIFO
}
}
}
这似乎减缓了线程数量上升的速度,但没有解决问题。
编辑:WriterActor用法的片段(RestApi)
trait RestApi extends CassandraCluster {
import models._
import cassandraDB.{WriterActor, ReaderActor}
implicit def system: ActorSystem
implicit def materializer: ActorMaterializer
implicit def ec: ExecutionContext
implicit val timeout = Timeout(20 seconds)
val cassandraWriterWorker = system.actorOf(Props(new WriterActor(cluster)), "cassandra-writer-actor")
val cassandraReaderWorker = system.actorOf(Props(new ReaderActor(cluster)), "cassandra-reader-actor")
...
def cassandraReaderCall(message: Any): ToResponseMarshallable = message match {
//...
case message: GetActiveContactsByPhoneNumber => (cassandraReaderWorker ? message)(2 seconds).mapTo[Vector[String]].map(result => Json.obj("active_contacts" -> result))
case _ => StatusCodes.BadRequest
}
def confirmedWriterCall(message: Any) = {
(cassandraWriterWorker ? message).mapTo[Boolean].map(result => result)
}
val apiKeyStringV1 = "test123"
val route =
...
path("contacts") {
parameter('apikey ! apiKeyStringV1) {
post {
entity(as[Contacts]){ contact: Contacts =>
cassandraWriterWorker ! contact
complete(StatusCodes.OK)
}
} ~
get {
parameter('phonenumber) { phoneNumber: String =>
complete(cassandraReaderCall(GetActiveContactsByPhoneNumber(phoneNumber)))
}
}
}
} ~
} ~
path("log"/ "gps") {
parameter('apikey ! apiKeyStringV1) {
(post & entity(as[GpsLog])) { gpsLog =>
cassandraWriterWorker ! gpsLog
complete(StatusCodes.OK)
}
}
}
}
}
}
编辑2:Writer Worker相关代码。 我没有发布所有方法,因为它们基本相同。但是here你可以找到整个文件
import java.util.UUID
import akka.actor.Actor
import com.datastax.driver.core.Cluster
import models._
class WriterActor(cluster: Cluster) extends Actor{
import scala.collection.JavaConversions._
val session = cluster.connect(Keyspaces.akkaCassandra)
// ... other inserts
val insertGpsLog = session.prepare("INSERT INTO gps_logs(id, phone_number, lat, long, time) VALUES (?,?,?,?,?);")
// ...
def insertGpsLog(phoneNumber: String, locWithTime: LocationWithTime): Unit =
session.executeAsync(insertGpsLog.bind(UUID.randomUUID().toString, phoneNumber, new java.lang.Double(locWithTime.location.lat),
new java.lang.Double(locWithTime.location.long), new java.lang.Long(locWithTime.time)))
def receive: Receive = {
// ...
case gpsLog: GpsLog => gpsLog.locationWithTimeLog.foreach(locWithTime => insertGpsLog(gpsLog.phoneNumber, locWithTime))
}
}
编辑3.错过过多线程使用的诊断。
我害怕错过诊断问题的根源。接下来,我在写完后添加了数据请求并忘了它。当我删除它时,线程数量停止增长。所以这是犯错的最可能的地方。我更新了使用ReaderActor的特征,并在下面添加了ReaderActor的相关代码。
object ReaderActor {
// ...
case class GetActiveContactsByPhoneNumber(phoneNumber: String)
}
class ReaderActor(cluster: Cluster) extends Actor {
import models._
import ReaderActor._
import akka.pattern.pipe
import scala.collection.JavaConversions._
import cassandra.resultset._
import context.dispatcher
val session = cluster.connect(Keyspaces.akkaCassandra)
def buildActiveContactByPhoneNumberResponse(r: Row): String = {
val phoneNumber = r.getString(ContactsKeys.phoneNumber)
return phoneNumber
}
def buildSubSelectContactsList(r: Row): java.util.List[String] = {
val phoneNumber = r.getSet(ContactsKeys.contacts, classOf[String])
return phoneNumber.toList
}
def receive: Receive = {
//...
case GetActiveContactsByPhoneNumber(phoneNumber: String) =>
val subQuery = QueryBuilder.select(ContactsKeys.contacts).
from(Keyspaces.akkaCassandra, ColumnFamilies.contact).
where(QueryBuilder.eq(ContactsKeys.phoneNumber, phoneNumber))
def queryActiveUsers(phoneNumbers: java.util.List[String]) = QueryBuilder.select(ContactsKeys.phoneNumber).
from(Keyspaces.akkaCassandra, ColumnFamilies.contact).
where(QueryBuilder.in(ContactsKeys.phoneNumber, phoneNumbers))
session.execute(subQuery) map((row: Row) => session.executeAsync(queryActiveUsers(buildSubSelectContactsList(row))) map(_.all().map(buildActiveContactByPhoneNumberResponse).toVector) pipeTo sender)
//...
}
}
编辑4 我在本地运行代码,控制所有请求。当没有请求时,正在运行的线程的数量在一定数量附近交替,但是没有上升或下降的趋势。 我提出了各种要求,看看会发生什么变化。 下面张贴的图片显示了几个州。
所以每次我们从数据库中读取时(无论使用多少次executeAsync调用)都会发生2个额外的线程。读取和写入调用之间的唯一区别是,一个使用ask模式而另一个不使用。我通过改变路线来检查它:
get {
parameter('phonenumber) { phoneNumber: String =>
complete(cassandraReaderCall(GetActiveContactsByPhoneNumber(phoneNumber)))
}
}
到
get {
parameter('phonenumber) { phoneNumber: String =>
cassandraReaderWorker ! GetActiveContactsByPhoneNumber(phoneNumber)
complete(StatusCodes.OK)
}
}
现在显然没有得到任何结果,但也没有产生这些线程。 所以答案似乎在于问题模式。
我希望有人可以提供一个答案,说明为什么会发生这种情况以及如何解决这个问题?