Akka Actor 消息延迟
Akka Actor Messaging Delay
我在使用多个请求扩展我的应用程序时遇到问题。
每个请求都会向一个 actor 发送一个请求,然后该 actor 会产生其他 actor。这很好,但是,在负载下(一次请求超过 5 个),ask
需要大量时间才能将消息传递给目标参与者。最初的设计是均匀地隔开请求,但这造成了瓶颈。示例:
在此图中,ask
是在查询计划解析器之后发送的。但是,当 Actor 收到此消息时,存在 multi-second 间隔。这仅在负载 (5+ requests/sec) 下出现。我一开始以为这是一个饥饿问题。
设计:
每个 planner-executor 都是每个请求的单独实例。它每次都会生成一个新的 'Request Acceptor' actor(它会在收到消息时记录 'requesting score')。
- 我给了 actorsystem 一个自定义的全局执行器(大的)。我注意到即使在这个巨大的延迟期间,线程也没有被超出核心线程池大小
- 我确保 child 演员中的所有执行上下文都使用了正确的执行上下文
- 确保 actors 内部的所有阻塞调用都使用了 future
- 我给 parent 参与者(和所有 child)一个自定义调度程序,核心大小为 50,最大大小为 100。它 没有 请求更多(它保持在 50)即使在这些延迟期间
- 最后,我尝试为每个请求(在 planner-executor 内)创建一个 完全 新的 Actorsystem。这也没有明显的效果!
我对此有点困惑。从这些测试来看,它看起来不像是线程饥饿问题。回到第一个问题,我不知道为什么消息需要越来越长的时间来传递我发出的更多并发请求。到达此点之前的 Zipkin 跟踪不会随着更多请求而降级,直到它到达此处的 ask
。在此之前,服务器能够处理多个步骤,例如验证请求、与数据库对话,最后进入 planner-executor。所以我怀疑应用程序本身是 运行 超出 cpu 的时间。
我们在 Akka 中遇到了这个非常相似的问题。我们观察到请求模式在 peek 负载时将消息传递给目标参与者的巨大延迟。
大多数这些问题都与堆内存消耗有关,而不是因为调度程序的使用。
最后,我们通过调整以下一些配置和更改解决了这些问题。
1) 确保停止不再需要的 entities/actors。如果它是一个坚持不懈的演员,那么您可以随时在需要时将它带回来。
参考:https://doc.akka.io/docs/akka/current/cluster-sharding.html#passivation
2) 如果您使用的是集群分片,请检查 akka.cluster.sharding.state-store-mode。通过将其更改为持久性,我们获得了 50% 以上的 TPS。
3) 最小化您的日志条目(将其设置为信息级别)。
4) 调整您的日志以频繁地将消息发布到您的日志系统。相应地更新批量大小、批量计数和间隔。这样内存就被释放了。在我们的例子中,巨大的堆内存用于缓冲日志消息并批量发送。如果间隔更长,那么您可能会填满堆内存,这会影响性能(需要更多 GC activity)。
5) 运行 在单独的调度程序上阻止操作。
6) 使用自定义序列化程序 (protobuf) 并避免使用 JavaSerializer。
7) 添加以下 JAVA_OPTS 到你的 jar
export JAVA_OPTS="$JAVA_OPTS -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -Djava.security.egd=file:/dev/. /urandom
最主要的是XX:MaxRAMFraction=2,它将使用超过 60% 的可用内存。默认情况下,它的 4 意味着您的应用程序将仅使用可用内存的四分之一,这可能不够。
参考:https://blog.csanchez.org/2017/05/31/running-a-jvm-in-a-container-without-getting-killed/
此致,
维诺斯
我在使用多个请求扩展我的应用程序时遇到问题。
每个请求都会向一个 actor 发送一个请求,然后该 actor 会产生其他 actor。这很好,但是,在负载下(一次请求超过 5 个),ask
需要大量时间才能将消息传递给目标参与者。最初的设计是均匀地隔开请求,但这造成了瓶颈。示例:
在此图中,ask
是在查询计划解析器之后发送的。但是,当 Actor 收到此消息时,存在 multi-second 间隔。这仅在负载 (5+ requests/sec) 下出现。我一开始以为这是一个饥饿问题。
设计: 每个 planner-executor 都是每个请求的单独实例。它每次都会生成一个新的 'Request Acceptor' actor(它会在收到消息时记录 'requesting score')。
- 我给了 actorsystem 一个自定义的全局执行器(大的)。我注意到即使在这个巨大的延迟期间,线程也没有被超出核心线程池大小
- 我确保 child 演员中的所有执行上下文都使用了正确的执行上下文
- 确保 actors 内部的所有阻塞调用都使用了 future
- 我给 parent 参与者(和所有 child)一个自定义调度程序,核心大小为 50,最大大小为 100。它 没有 请求更多(它保持在 50)即使在这些延迟期间
- 最后,我尝试为每个请求(在 planner-executor 内)创建一个 完全 新的 Actorsystem。这也没有明显的效果!
我对此有点困惑。从这些测试来看,它看起来不像是线程饥饿问题。回到第一个问题,我不知道为什么消息需要越来越长的时间来传递我发出的更多并发请求。到达此点之前的 Zipkin 跟踪不会随着更多请求而降级,直到它到达此处的 ask
。在此之前,服务器能够处理多个步骤,例如验证请求、与数据库对话,最后进入 planner-executor。所以我怀疑应用程序本身是 运行 超出 cpu 的时间。
我们在 Akka 中遇到了这个非常相似的问题。我们观察到请求模式在 peek 负载时将消息传递给目标参与者的巨大延迟。
大多数这些问题都与堆内存消耗有关,而不是因为调度程序的使用。
最后,我们通过调整以下一些配置和更改解决了这些问题。
1) 确保停止不再需要的 entities/actors。如果它是一个坚持不懈的演员,那么您可以随时在需要时将它带回来。 参考:https://doc.akka.io/docs/akka/current/cluster-sharding.html#passivation
2) 如果您使用的是集群分片,请检查 akka.cluster.sharding.state-store-mode。通过将其更改为持久性,我们获得了 50% 以上的 TPS。
3) 最小化您的日志条目(将其设置为信息级别)。
4) 调整您的日志以频繁地将消息发布到您的日志系统。相应地更新批量大小、批量计数和间隔。这样内存就被释放了。在我们的例子中,巨大的堆内存用于缓冲日志消息并批量发送。如果间隔更长,那么您可能会填满堆内存,这会影响性能(需要更多 GC activity)。
5) 运行 在单独的调度程序上阻止操作。
6) 使用自定义序列化程序 (protobuf) 并避免使用 JavaSerializer。
7) 添加以下 JAVA_OPTS 到你的 jar
export JAVA_OPTS="$JAVA_OPTS -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -Djava.security.egd=file:/dev/. /urandom
最主要的是XX:MaxRAMFraction=2,它将使用超过 60% 的可用内存。默认情况下,它的 4 意味着您的应用程序将仅使用可用内存的四分之一,这可能不够。
参考:https://blog.csanchez.org/2017/05/31/running-a-jvm-in-a-container-without-getting-killed/
此致,
维诺斯