运行 非默认类加载器下的本地集群

Run a local cluster under a nondefault classloader

来自网络的本地集群 classloader

我正在尝试从 Web 容器 运行 本地集群(是的,它仅用于开发和测试目的)并且在使用 classloader 时遇到困难。

直接方法

当我以简单且 recommended 的方式做到时,

ILocalCluster localCluster = new LocalCluster();
localCluster.submitTopology(topologyName, stormConf, topology);

我得到奖励

Async loop died!: java.lang.ClassCastException: my.company.storm.bolt.SomeFilteringBolt cannot be cast to org.apache.storm.task.IBolt
    at org.apache.storm.daemon.executor$fn__7953$fn__7966.invoke(executor.clj:787)
    at org.apache.storm.util$async_loop$fn__625.invoke(util.clj:482)
    at clojure.lang.AFn.run(AFn.java:22)
    at java.lang.Thread.run(Thread.java:745)

这是因为用于加载和实例化 StormTopology 的 classloader 是 Jetty WebAppClassLoader 的实例,但是 [=17= 产生的(子)进程] 显然使用系统 classloader。我通过在 SomeFilteringBolt 的静态块中记录 classloader 来确认这一点 - class 确实被加载了两次并且来自 WebAppCL 的螺栓显然不能转换为系统上的螺栓 class加载程序稍后。

预期行为

现在,这让我感到惊讶,因为我认为 Storm 会序列化 StormTopology 实例,"send" 在本地,反序列化它并 运行 它。但是,如果这样做,它肯定会起作用。相反,它似乎直接使用提供的 StormTopology 实例,这在不同的 classloader 下是有问题的。

自从

以来我尝试过的

我尝试将它们设置为 true 以强制 Storm 在本地序列化我的拓扑。没有变化。

我试过运行在系统classloader:

下安装LocalCluster
ClassLoader originalClassloader = Thread.currentThread().getContextClassLoader();
try {
    Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());

    Config topologyConf = createTopologyConfig();
    Map<String, Object> stormConf = createStormConfig(topologyConf);
    StormTopology topology = createTopology(topologyConf);

    ILocalCluster localCluster = new LocalCluster();
    localCluster.submitTopology(topologyName, stormConf, topology);
} finally {
    Thread.currentThread().setContextClassLoader(originalClassloader);
}

这实际上让我更进一步:

Thread  died: java.lang.ExceptionInInitializerError
    at clojure.core__init.__init0(Unknown Source)
    at clojure.core__init.<clinit>(Unknown Source)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at clojure.lang.RT.classForName(RT.java:2154)
    at clojure.lang.RT.classForName(RT.java:2163)
    at clojure.lang.RT.loadClassForName(RT.java:2182)
    at clojure.lang.RT.load(RT.java:436)
    at clojure.lang.RT.load(RT.java:412)
    at clojure.lang.RT.doInit(RT.java:454)
    at clojure.lang.RT.<clinit>(RT.java:330)
    at clojure.lang.Namespace.<init>(Namespace.java:34)
    at clojure.lang.Namespace.findOrCreate(Namespace.java:176)
    at clojure.lang.Var.internPrivate(Var.java:151)
    at org.apache.storm.LocalCluster.<clinit>(Unknown Source)
    at my.company.storm.LocalTopologyRunner.startTopology(LocalTopologyRunner.java:146)
    ... 10 more
Caused by: java.lang.IllegalStateException: Attempting to call unbound fn: #'clojure.core/refer
    at clojure.lang.Var$Unbound.throwArity(Var.java:43)
    at clojure.lang.AFn.invoke(AFn.java:32)
    at clojure.lang.Var.invoke(Var.java:379)
    at clojure.lang.RT.doInit(RT.java:467)
    at clojure.lang.RT.<clinit>(RT.java:330)
    ... 18 more

哇?!

问题

如何从系统 classloader 以外的 classloader 安全地 运行 本地模式下的 Storm 拓扑?

我运行正在使用 Apache Storm 1.0.1,Jetty 8.1,Java 8u112 x64,Windows 7 x64。

根本不是 Storm 专家,但这让我想起了我过去遇到的一个老 "identity crisis" 问题。

尝试两件事:

  • 通过调用 org.eclipse.jetty.webapp.WebAppContext.setParentLoaderPriority(true)

  • 将优先级设置为系统 class 加载程序
  • 如果不起作用,您可以调用方法org.eclipse.jetty.webapp.WebAppContext.setSystemClassesorg.eclipse.jetty.webapp.WebAppContext.addSystemClass来控制哪些class被认为是系统class es 在 webapp 域内。

在加载它们之前(在 webapp 初始化期间)为整个 storm 包执行此操作(它允许像 "org.apache.storm." 这样的通配符)。

值得一试!祝你好运。

Apache Storm 1.0.3 神奇地修复了这个问题。

即使没有 TOPOLOGY_TESTING_ALWAYS_TRY_SERIALIZE,即使发行说明中没有修复的痕迹,所以我无法追踪到代码更改。无论如何,我们很高兴它现在按预期工作。