Scala + SBT - 如何为着色的 Akka 库配置 reference.conf

Scala + SBT - How to configure reference.conf for a shaded Akka library

TL;DR

我正在尝试隐藏 akka 库的一个版本并将其与我的应用程序捆绑在一起(以便能够 运行 CDH 5.7 上的 spray-can 服务器Spark 1.6 的版本)。着色过程 messes up akka's default configuration,并在为着色 akka 手动提供 akkareference.conf 的单独版本后,看起来这两个版本仍然以某种方式混淆了。

是否已知阴影 akka 版本会导致问题?我做错了什么?

背景

我有一个 Scala/Spark 应用程序目前 运行 正在 Spark 1.6.1 独立运行。应用程序 运行 是一个使用 spray 1.3.3spray-can http 服务器,它需要 akka 2.3.9Spark 1.6.1 独立版包括一个兼容的 akka 2.3.11)。

我正在尝试将应用程序迁移到新的基于 ClouderaSpark 集群 运行 宁 CDH 5.7 版本的 Spark 1.6。问题是 CDH 5.7 中的 Spark 1.6akka 2.2.3 捆绑在一起,这不足以使 spray 1.3.3 正常运行。

尝试的解决方案

根据 sbt-assembly 的着色功能中 this post, I decided to shade akka 2.3.9 and bundle it along with my application. Although this time I stumbled upon a new problem - akka has it's default configuration defined in a reference.conf file, which should be located on the application's classpath. Due to a known issue 的建议,着色 akka 库似乎需要单独配置。

所以,我最终使用以下着色规则对 akka 进行着色:

ShadeRule.rename("akka.**" -> "akka_2_3_9_shade.@1")
    .inLibrary("com.typesafe.akka" % "akka-actor_2.10" % "2.3.9")
    .inAll

并在我的项目中包含一个额外的 reference.conf 文件,它与 akka 的原始 reference.conf 相同,但所有出现的“akka”都替换为“akka_2_3_9_shade".

不过,现在 Spark 提供的 akka 似乎与阴影 akka 以某种方式混淆了,因为我收到以下错误:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate MailboxType [akka.dispatch.UnboundedMailbox], defined in [akka.actor.default-mailbox], make sure it has a public constructor with [akka.actor.ActorSystem.Settings, com.typesafe.config.Config] parameters
    at akka_2_3_9_shade.dispatch.Mailboxes$$anonfun.applyOrElse(Mailboxes.scala:197)
    at akka_2_3_9_shade.dispatch.Mailboxes$$anonfun.applyOrElse(Mailboxes.scala:195)
    at scala.runtime.AbstractPartialFunction.apply(AbstractPartialFunction.scala:33)
    at scala.util.Failure$$anonfun$recover.apply(Try.scala:185)
    at scala.util.Try$.apply(Try.scala:161)
    at scala.util.Failure.recover(Try.scala:185)
    at akka_2_3_9_shade.dispatch.Mailboxes.lookupConfiguration(Mailboxes.scala:195)
    at akka_2_3_9_shade.dispatch.Mailboxes.lookup(Mailboxes.scala:78)
    at akka_2_3_9_shade.actor.LocalActorRefProvider.akka$actor$LocalActorRefProvider$$defaultMailbox$lzycompute(ActorRefProvider.scala:561)
    at akka_2_3_9_shade.actor.LocalActorRefProvider.akka$actor$LocalActorRefProvider$$defaultMailbox(ActorRefProvider.scala:561)
    at akka_2_3_9_shade.actor.LocalActorRefProvider$$anon.<init>(ActorRefProvider.scala:568)
    at akka_2_3_9_shade.actor.LocalActorRefProvider.rootGuardian$lzycompute(ActorRefProvider.scala:564)
    at akka_2_3_9_shade.actor.LocalActorRefProvider.rootGuardian(ActorRefProvider.scala:563)
    at akka_2_3_9_shade.actor.LocalActorRefProvider.init(ActorRefProvider.scala:618)
    at akka_2_3_9_shade.actor.ActorSystemImpl.liftedTree2(ActorSystem.scala:619)
    at akka_2_3_9_shade.actor.ActorSystemImpl._start$lzycompute(ActorSystem.scala:616)
    at akka_2_3_9_shade.actor.ActorSystemImpl._start(ActorSystem.scala:616)
    at akka_2_3_9_shade.actor.ActorSystemImpl.start(ActorSystem.scala:633)
    at akka_2_3_9_shade.actor.ActorSystem$.apply(ActorSystem.scala:142)
    at akka_2_3_9_shade.actor.ActorSystem$.apply(ActorSystem.scala:109)
    at akka_2_3_9_shade.actor.ActorSystem$.apply(ActorSystem.scala:100)
    at MyApp.api.Boot$delayedInit$body.apply(Boot.scala:45)
    at scala.Function0$class.apply$mcV$sp(Function0.scala:40)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
    at scala.App$$anonfun$main.apply(App.scala:71)
    at scala.App$$anonfun$main.apply(App.scala:71)
    at scala.collection.immutable.List.foreach(List.scala:318)
    at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:32)
    at scala.App$class.main(App.scala:71)
    at MyApp.api.Boot$.main(Boot.scala:28)
    at MyApp.api.Boot.main(Boot.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at org.apache.spark.deploy.SparkSubmit$.org$apache$spark$deploy$SparkSubmit$$runMain(SparkSubmit.scala:731)
    at org.apache.spark.deploy.SparkSubmit$.doRunMain(SparkSubmit.scala:181)
    at org.apache.spark.deploy.SparkSubmit$.submit(SparkSubmit.scala:206)
    at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:121)
    at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)
Caused by: java.lang.ClassCastException: interface akka_2_3_9_shade.dispatch.MailboxType is not assignable from class akka.dispatch.UnboundedMailbox
    at akka_2_3_9_shade.actor.ReflectiveDynamicAccess$$anonfun$getClassFor.apply(DynamicAccess.scala:69)
    at akka_2_3_9_shade.actor.ReflectiveDynamicAccess$$anonfun$getClassFor.apply(DynamicAccess.scala:66)
    at scala.util.Try$.apply(Try.scala:161)
    at akka_2_3_9_shade.actor.ReflectiveDynamicAccess.getClassFor(DynamicAccess.scala:66)
    at akka_2_3_9_shade.actor.ReflectiveDynamicAccess.CreateInstanceFor(DynamicAccess.scala:84)
    ... 34 more

我的应用程序 Boot.scala 文件中的相关代码如下:

[45]    implicit val system = ActorSystem()
...
[48]    val service = system.actorOf(Props[MyAppApiActor], "MyApp.Api")
...
[52]    val port = config.getInt("MyApp.server.port")
[53]    IO(Http) ? Http.Bind(service, interface = "0.0.0.0", port = port)

好的,所以最终我设法解决了这个问题。

原来 akka 使用定义为字符串文字的键从配置文件加载(部分)配置设置。例如,您可以在 akka/actor/ActorSystem.scala 中找到很多这样的内容。

并且 sbt-assembly 似乎没有 更改对字符串文字中带阴影的 library/package 名称的引用。

另外,一些配置键 sbt-assembly 的阴影改变了。我还没有真正花时间去查找它们在 akka 的源代码中的定义位置和定义方式,但是在 ActorSystem 初始化代码期间抛出的以下异常证明了这是确实如此:

ConfigException$Missing: No configuration setting found for key 'akka_2_3_9_shade'

因此,解决方案是包含一个自定义配置文件(例如 akka_spray_shade.conf),并在其中复制以下配置部分:

  • akka 的原始内容 reference.conf,但配置值中的 akka 前缀更改为 akka_2_3_9_shade。 (这是硬编码字符串文字配置键所必需的)
  • akka 的原始内容 reference.conf,但配置值中的 akka 前缀更改为 akka_2_3_9_shade 并具有根配置键从 akka 更改为 akka_2_3_9_shade。 (这是被 sbt-assembly 修改的配置键所必需的)
  • spray 的原始内容 reference.conf,但配置值中的 akka 前缀更改为 akka_2_3_9_shade。 (这是确保 spray 始终指代阴影 akka 所必需的)

现在,必须在应用程序 Boot.scala 代码 ActorSystem 的初始化期间 显式 提供此自定义配置文件:

val akkaShadeConfig = ConfigFactory.load("akka_spray_shade")
implicit val system = ActorSystem("custom-actor-system-name", akkaShadeConfig)

已接受答案的一个小补充。

没有必要将此配置放在自定义命名的文件中,如 akka_spray_shade.conf。配置可以放在 application.conf 中,当没有明确指定自定义配置时,在 ActorSystem 创建期间默认加载:ActorSystem("custom-actor-system-name") 实际上意味着 ActorSystem("custom-actor-system-name", ConfigFactory.load("application")).

我也为此苦苦挣扎了很长时间。事实证明,sbt-assembly 中的默认合并策略排除了所有 reference.conf 文件。将此添加到 build.sbt 为我解决了它:

assemblyMergeStrategy in assembly := {
  case PathList("reference.conf") => MergeStrategy.concat
}