Hazelcast JET在Spring Boot上執行

banq發表於2019-01-10

Hazelcast JET目前是分散式計算框架領域的新成員。根據Hazelcast團隊的說法,他們甚至比Apache SparkApache Flink更快。檢視基準。讓我們看看如何使用Hazelcast JET和Spring Boot,當然我會像往常一樣構建一個簡單的演示。

Hazelcast JET和Spring Boot依賴項設定:

<dependency>
    <groupId>com.hazelcast.jet</groupId>
    <artifactId>hazelcast-jet</artifactId>
    <version>0.3.2-SNAPSHOT</version>
</dependency>


並且讓我們嘗試建立一個Hazelcast JET例項並最終將其關閉,以便驗證我們是否能夠正確啟動Hazelcast JET:

try {
        JetInstance instance = Jet.newJetInstance();
 } finally {
        Jet.shutdownAll();
 }


不幸的是,Spring Boot應用程式內部的Hazelcast JET例項建立將崩潰:

java.lang.NoSuchFieldError: JET
    at com.hazelcast.jet.impl.config.XmlJetConfigBuilder.getXmlType(XmlJetConfigBuilder.java:128) ~[hazelcast-jet-0.3.2-SNAPSHOT.jar!/:0.3.2-SNAPSHOT]
    at com.hazelcast.config.AbstractXmlConfigHelper.getNamespaceType(AbstractXmlConfigHelper.java:136) ~[hazelcast-3.7.6.jar!/:3.7.6]
    at com.hazelcast.config.AbstractXmlConfigHelper.<init>(AbstractXmlConfigHelper.java:72) ~[hazelcast-3.7.6.jar!/:3.7.6]
    at com.hazelcast.config.AbstractConfigBuilder.<init>(AbstractConfigBuilder.java:59) ~[hazelcast-3.7.6.jar!/:3.7.6]
    at com.hazelcast.jet.impl.config.XmlJetConfigBuilder.<init>(XmlJetConfigBuilder.java:67) ~[hazelcast-jet-0.3.2-SNAPSHOT.jar!/:0.3.2-SNAPSHOT]
    at com.hazelcast.jet.impl.config.XmlJetConfigBuilder.getConfig(XmlJetConfigBuilder.java:80) ~[hazelcast-jet-0.3.2-SNAPSHOT.jar!/:0.3.2-SNAPSHOT]


好吧,不要責怪Hazelcast JET團隊,只是Spring Boot已經內建了Hazelcast IMDG(Spring Boot 1.5.3版本為3.7.6),其中這個版本包含了hazelcast ConfigType列舉,但沒有包含JET欄位。就這樣。解決此問題的方法是在您的pom中升級Hazelcast IMDG。對我有用的是將hazelcast.version屬性設定為:

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    <hazelcast.version>3.8</hazelcast.version>
</properties>


在這個解決方法之後,一切都應該工作正常,你應該能夠看到他們漂亮的ascii藝術:-)

Hazelcast JET架構
在進入JET演示之前,讓我們描述一下它的架構。Hazelcast JET基於將計算工作組合到DAG圖中。這不是革命性的東西,因為Apache Spark也基於DAG工作。但是讓我們更深入地瞭解Hazelcast JET DAG術語::

  • Vertex 是JET作業的一個工作單元。把它作為計算工作的一步。一個Vertex 包含一個或多個您實現的處理器(類AbstractProcessor)來執行您想要的邏輯。每個Vertex 可以有更多的處理器。Number基於本地和全域性並行設定。你可以有SINK Vertex (只有輸入,不發射任何東西),INTERNAL Vertex (有輸入並向其序數發射輸出)和SOURCE Vertex (僅發射)。
  • DAG是Vertex 的邊緣聯結器。每次建立JET作業時,都必須將處理器實現與Edges連線。

為了向您展示一個示例,我們可以構建一個簡單的JET Job,它將RabbitMQ代理中的資料注入到分散式Hazelcast Map中。我們的工作將包含兩個Vertex ,第一個Vertex 將發出來自RabbitMQ的專案,第二個Vertex 將是簡單的Sink進入Map。

private static Job createJetJob(JetInstance instance) {
        DAG dag = new DAG();
        Properties props = props(
                "server", "localhost",
                "user", "guest",
                "password", "guest");
        Vertex source = dag.newVertex("source", readRabbitMQ(props, "jetInputQueue"));
        Vertex sink = dag.newVertex("sink", writeMap("sink"));
        dag.edge(between(source, sink));
        return instance.newJob(dag);
    }


詳細程式碼
方法readRabbitMQ返回一個JET Processor實現,用於從RabbitMQ讀取訊息。該實現的靈感來自官方的ReadKafkaP處理器。我剛剛以輪詢方式重寫了聯結器以與RabbitMQ代理進行通訊。讀取部分的訊息在AbstractProcessor.complete方法中。要了解這種方法,您需要了解兩件事:

  • 重複呼叫方法,直到返回true。由於我的作業中的第一個Vertex 是Source,因此我的ReadRabbitMQP.complete方法始終返回false。
  • 來自RabbitMQ代理的輪詢訊息需要透過Traverser介面返回。另請參閱Traversers util類以獲得方便的方法。

輪詢RabbitMQ訊息的完整方法:

@Override
    public boolean complete() {
        System.out.println("....Invoking RabbitMQ vertex processor complete...");
        if (emitCooperatively(traverser)) {
            final Message message =
                    this.rabbitTemplate.receive(this.queueNames[0]);
            if (message != null) {
                System.out.println("Message payload: " + new String(message.getBody()));
                final List<Message> list = new ArrayList<>();
                list.add(message);
                Random rn = new Random();
                traverser = traverseStream(list.stream()).map(r ->
                        entry(String.valueOf(rn.nextInt()), new String(r.getBody()))
                );
            }
        }
        return false;
    }


Hazelcast JET和工作分配到叢集
將來自RabbitMQ代理的流式傳輸寫入Hazelcast JET時,接收器Vertex 中的每個處理器都將成為消費者,從而完成相同的工作。
來自AMPQ佇列的有效負載將在JET處理器之間進行負載平衡。無論如何,為特定Vertex 建立處理器的方式是ProcessorMetaSupplierProcessorSupplier實現的工作。
簡而言之,JET使用ProcessorMetaSupplier實現為DAG圖中的每個Vertex 獲取ProcessorSupplier。然後將ProcessorSupplier傳送到Vertex ,根據並行度設定建立處理器。我強烈建議您閱讀JET文件中的NumberGenerator示例,這有助於瞭解JET如何建立處理器。
在RabbitMQ流媒體的情況下,我再說一遍,每個處理器都將做同樣的工作,因此:

private static final class MetaSupplier<K, V> implements ProcessorMetaSupplier {

        static final long serialVersionUID = 1L;
        private final String[] queueNames;
        private Properties properties;

        private MetaSupplier(String[] queueNames, Properties properties) {
            this.queueNames = queueNames;
            this.properties = properties;
        }

        @Override
        public Function<Address, ProcessorSupplier> get(List<Address> addresses) {
            return address -> new Supplier<>(queueNames, properties);
        }
    }

    private static class Supplier<K, V> implements ProcessorSupplier {

        static final long serialVersionUID = 1L;

        private final String[] queueNames;
        private final Properties properties;
        private transient List<Processor> processors;

        Supplier(String[] topicIds, Properties properties) {
            this.properties = properties;
            this.queueNames = topicIds;
        }

        @Override
        public List<Processor> get(int count) {
            return processors = range(0, count)
                    .mapToObj(i -> new ReadRabbitMQP<>(queueNames, properties))
                    .collect(toList());
        }

        @Override
        public void complete(Throwable error) {
            processors.stream()
                    .filter(p -> p instanceof ReadRabbitMQP)
                    .map(p -> (ReadRabbitMQP) p)
                    .forEach(p -> Util.uncheckRun(p::close));
        }
    }


測試此演示
  • git clone https://bitbucket.org/tomask79/spring-hazelcast-jet-streaming.git
  • mvn clean install
  • 在RabbitMQ中建立一個名為“jetInputQueue”的佇列
  • java -jar target / demo-0.0.1-SNAPSHOT.jar

現在將一些訊息傳送到“jetInputQueue”輸入4條以後輸出:

Received 4 entries in 78080 milliseconds.
....Invoking RabbitMQ vertex processor complete...
....Invoking RabbitMQ vertex processor complete...
Received 4 entries in 78182 milliseconds.
....Invoking RabbitMQ vertex processor complete...
....Invoking RabbitMQ vertex processor complete...
Received 4 entries in 78283 milliseconds.
....Invoking RabbitMQ vertex processor complete...
....Invoking RabbitMQ vertex processor complete...
Received 4 entries in 78385 milliseconds.
....Invoking RabbitMQ vertex processor complete...
....Invoking RabbitMQ vertex processor complete...
Received 4 entries in 78486 milliseconds.
....Invoking RabbitMQ vertex processor complete..


我對Hazelcast JET的看法
優點:

  • 令人難以置信的效能。與Apache Spark相比,甚至更好一點。
  • 非常編碼友好的DAG API。僅僅兩天後,我就能夠編寫JET DAG作業。

缺點:
  • 更好的容錯能力。目前,如果我的RabbitMQ JET消費者死亡,那麼整個工作將因資料丟失而中止.. :(
  • 我想看看Apache Hive對Hazelcast JET的支援。就像編寫SQL樣式語句一樣,Hive會為我生成JET DAG,就像它與Apache Spark一樣。

無論如何JET引起了我的注意,我期待著下一個版本。

點選標題看原文

相關文章