Java 使用-cluster选项启动时,Vert.x性能会下降

Java 使用-cluster选项启动时,Vert.x性能会下降,java,hazelcast,vert.x,Java,Hazelcast,Vert.x,我想知道是否有人遇到过同样的问题 我们有一个Vert.x应用程序,它的最终目的是向Cassandra集群中插入6亿行。我们正在测试Vert.x的速度,结合Cassandra进行少量测试 如果我们在不使用-cluster选项的情况下运行带Shade插件的胖jar构建,我们可以在大约一分钟内插入1000万条记录。当我们最终添加-cluster选项时,我们将在集群中运行Vert.x应用程序,插入1000万条记录大约需要5分钟 有人知道为什么吗 我们知道Hazelcast配置会产生一些开销,但没想到会慢

我想知道是否有人遇到过同样的问题

我们有一个Vert.x应用程序,它的最终目的是向Cassandra集群中插入6亿行。我们正在测试Vert.x的速度,结合Cassandra进行少量测试

如果我们在不使用-cluster选项的情况下运行带Shade插件的胖jar构建,我们可以在大约一分钟内插入1000万条记录。当我们最终添加-cluster选项时,我们将在集群中运行Vert.x应用程序,插入1000万条记录大约需要5分钟

有人知道为什么吗

我们知道Hazelcast配置会产生一些开销,但没想到会慢5倍。这意味着在不使用集群选项的情况下使用1个EC2时,集群中需要5个EC2实例才能获得相同的结果

如前所述,一切都在EC2实例上运行:

t2.small上有2台Cassandra服务器 t2.2xlarge上有1台Vert.x服务器
只需添加项目的代码。我想那会有帮助的

发送器垂直线:

public class ProviderVerticle extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        IntStream.range(1, 30000001).parallel().forEach(i -> {
        vertx.eventBus().send("clustertest1", Json.encode(new TestCluster1(i, "abc", LocalDateTime.now())));
        });
    }

    @Override
    public void stop() throws Exception {
        super.stop();
   }
}
还有插入者的眩晕

public class ReceiverVerticle extends AbstractVerticle {

    private int messagesReceived = 1;

    private Session cassandraSession;

    @Override
    public void start() throws Exception {

        PoolingOptions poolingOptions = new PoolingOptions()
                .setCoreConnectionsPerHost(HostDistance.LOCAL, 2)
                .setMaxConnectionsPerHost(HostDistance.LOCAL, 3)
                .setCoreConnectionsPerHost(HostDistance.REMOTE, 1)
                .setMaxConnectionsPerHost(HostDistance.REMOTE, 3)
                .setMaxRequestsPerConnection(HostDistance.LOCAL, 20)
                .setMaxQueueSize(32768)
                .setMaxRequestsPerConnection(HostDistance.REMOTE, 20);

        Cluster cluster = Cluster.builder()
                .withPoolingOptions(poolingOptions)
                .addContactPoints(ClusterSetup.SEEDS)
                .build();

        System.out.println("Connecting session");
        cassandraSession = cluster.connect("kiespees");
        System.out.println("Session connected:\n\tcluster [" + cassandraSession.getCluster().getClusterName() + "]");
        System.out.println("Connected hosts: ");

        cassandraSession.getState().getConnectedHosts().forEach(host -> System.out.println(host.getAddress()));

        PreparedStatement prepared = cassandraSession.prepare(
                "insert into clustertest1 (id, value, created) " +
                        "values (:id, :value, :created)");

        PreparedStatement preparedTimer = cassandraSession.prepare(
                "insert into timer (name, created_on, amount) " +
                        "values (:name, :createdOn, :amount)");

        BoundStatement timerStart = preparedTimer.bind()
                .setString("name", "clusterteststart")
                .setInt("amount", 0)
                .setTimestamp("createdOn", new Timestamp(new Date().getTime()));
        cassandraSession.executeAsync(timerStart);

        EventBus bus = vertx.eventBus();

        System.out.println("Bus info: " + bus.toString());
        MessageConsumer<String> cons = bus.consumer("clustertest1");
        System.out.println("Consumer info: " + cons.address());

        System.out.println("Waiting for messages");

        cons.handler(message -> {
            TestCluster1 tc = Json.decodeValue(message.body(), TestCluster1.class);

            if (messagesReceived % 100000 == 0)
                System.out.println("Message received: " + messagesReceived);

            BoundStatement boundRecord = prepared.bind()
                    .setInt("id", tc.getId())
                    .setString("value", tc.getValue())
                    .setTimestamp("created", new Timestamp(new Date().getTime()));
            cassandraSession.executeAsync(boundRecord);

            if (messagesReceived % 100000 == 0) {
                BoundStatement timerStop = preparedTimer.bind()
                        .setString("name", "clusterteststop")
                        .setInt("amount", messagesReceived)
                        .setTimestamp("createdOn", new Timestamp(new Date().getTime()));
                cassandraSession.executeAsync(timerStop);
            }

            messagesReceived++;
            //message.reply("OK");
        });
    }

    @Override
    public void stop() throws Exception {
        super.stop();
        cassandraSession.close();
    }
}

只需添加项目的代码。我想那会有帮助的

发送器垂直线:

public class ProviderVerticle extends AbstractVerticle {

    @Override
    public void start() throws Exception {
        IntStream.range(1, 30000001).parallel().forEach(i -> {
        vertx.eventBus().send("clustertest1", Json.encode(new TestCluster1(i, "abc", LocalDateTime.now())));
        });
    }

    @Override
    public void stop() throws Exception {
        super.stop();
   }
}
还有插入者的眩晕

public class ReceiverVerticle extends AbstractVerticle {

    private int messagesReceived = 1;

    private Session cassandraSession;

    @Override
    public void start() throws Exception {

        PoolingOptions poolingOptions = new PoolingOptions()
                .setCoreConnectionsPerHost(HostDistance.LOCAL, 2)
                .setMaxConnectionsPerHost(HostDistance.LOCAL, 3)
                .setCoreConnectionsPerHost(HostDistance.REMOTE, 1)
                .setMaxConnectionsPerHost(HostDistance.REMOTE, 3)
                .setMaxRequestsPerConnection(HostDistance.LOCAL, 20)
                .setMaxQueueSize(32768)
                .setMaxRequestsPerConnection(HostDistance.REMOTE, 20);

        Cluster cluster = Cluster.builder()
                .withPoolingOptions(poolingOptions)
                .addContactPoints(ClusterSetup.SEEDS)
                .build();

        System.out.println("Connecting session");
        cassandraSession = cluster.connect("kiespees");
        System.out.println("Session connected:\n\tcluster [" + cassandraSession.getCluster().getClusterName() + "]");
        System.out.println("Connected hosts: ");

        cassandraSession.getState().getConnectedHosts().forEach(host -> System.out.println(host.getAddress()));

        PreparedStatement prepared = cassandraSession.prepare(
                "insert into clustertest1 (id, value, created) " +
                        "values (:id, :value, :created)");

        PreparedStatement preparedTimer = cassandraSession.prepare(
                "insert into timer (name, created_on, amount) " +
                        "values (:name, :createdOn, :amount)");

        BoundStatement timerStart = preparedTimer.bind()
                .setString("name", "clusterteststart")
                .setInt("amount", 0)
                .setTimestamp("createdOn", new Timestamp(new Date().getTime()));
        cassandraSession.executeAsync(timerStart);

        EventBus bus = vertx.eventBus();

        System.out.println("Bus info: " + bus.toString());
        MessageConsumer<String> cons = bus.consumer("clustertest1");
        System.out.println("Consumer info: " + cons.address());

        System.out.println("Waiting for messages");

        cons.handler(message -> {
            TestCluster1 tc = Json.decodeValue(message.body(), TestCluster1.class);

            if (messagesReceived % 100000 == 0)
                System.out.println("Message received: " + messagesReceived);

            BoundStatement boundRecord = prepared.bind()
                    .setInt("id", tc.getId())
                    .setString("value", tc.getValue())
                    .setTimestamp("created", new Timestamp(new Date().getTime()));
            cassandraSession.executeAsync(boundRecord);

            if (messagesReceived % 100000 == 0) {
                BoundStatement timerStop = preparedTimer.bind()
                        .setString("name", "clusterteststop")
                        .setInt("amount", messagesReceived)
                        .setTimestamp("createdOn", new Timestamp(new Date().getTime()));
                cassandraSession.executeAsync(timerStop);
            }

            messagesReceived++;
            //message.reply("OK");
        });
    }

    @Override
    public void stop() throws Exception {
        super.stop();
        cassandraSession.close();
    }
}

当您为应用程序启用任何类型的集群时,您将使应用程序对故障具有更强的恢复能力,但同时也会增加性能损失

例如,没有集群的当前流类似于:

client -> 
  vert.x app -> 
    in memory same process eventbus (negletible) ->
    handler -> cassandra
  <- vert.x app
<- client
启用群集后:

client ->
  vert.x app ->
    serialize request ->
      network request cluster member ->
        deserialize request ->
          handler -> cassandra
        <- serialize response
      <- network reply
    <- deserialize response
  <- vert.x app
<- client
正如您所看到的,需要进行许多编码-解码操作,再加上几个网络调用,这些都会添加到您的总请求时间中


为了获得最佳性能,您需要利用位置优势—您离数据存储越近—通常最快。

当您为应用程序启用任何类型的群集时,您使应用程序对故障具有更强的恢复能力,但同时也会增加性能损失

例如,没有集群的当前流类似于:

client -> 
  vert.x app -> 
    in memory same process eventbus (negletible) ->
    handler -> cassandra
  <- vert.x app
<- client
启用群集后:

client ->
  vert.x app ->
    serialize request ->
      network request cluster member ->
        deserialize request ->
          handler -> cassandra
        <- serialize response
      <- network reply
    <- deserialize response
  <- vert.x app
<- client
正如您所看到的,需要进行许多编码-解码操作,再加上几个网络调用,这些都会添加到您的总请求时间中


为了获得最佳性能,您需要利用位置优势,离数据存储越近通常速度最快。

您实际上遇到了Vert.x Hazelcast群集管理器的困境

首先,您正在使用worker Verticle发送消息3000001。在引擎盖下,Hazelcast处于阻塞状态,因此,当您发送来自工作人员的消息时,版本3.3.3不会考虑这一点。最近,我们添加了3.4.0.Beta1中没有的修复程序,但3.4.0-SNAPSHOTS中有此修复程序,这将改进这种情况

第二,当您同时发送所有消息时,它会遇到另一种情况,阻止Hazelcast群集管理器使用群集拓扑的缓存。此拓扑缓存通常在发送第一条消息后更新,一次发送所有消息可防止使用ache简短说明HazelcastAsyncMultiMapgetInProgressCount将大于0,并阻止使用缓存,因此需要支付昂贵的查找代价,因此需要缓存

如果我将Bertjan的复制机与3.4.0-SNAPSHOT+Hazelcast一起使用,并进行以下更改:将消息发送到目的地,等待回复。回复发送所有消息后,我得到了很多改进

无聚类:5852毫秒 使用HZ 3.3.3进行聚类:16745 ms 使用HZ 3.4.0群集-快照+初始消息:8609 ms


我还认为,您不应该使用worker verticle发送那么多消息,而应该通过批处理使用事件循环verticle发送它们。也许您应该解释一下您的用例,我们可以考虑解决它的最佳方法。

您实际上遇到了Vert.x Hazelcast群集管理器的一些极端情况

首先,您正在使用worker Verticle发送消息3000001。在引擎盖下,Hazelcast处于阻塞状态,因此,当您发送来自工作人员的消息时,版本3.3.3不会考虑这一点。最近,我们添加了3.4.0.Beta1中没有的修复程序,但3.4.0-SNAPSHOTS中有此修复程序,这将改进这种情况

第二,当您同时发送所有消息时,它会遇到另一种情况,阻止Hazelcast群集管理器使用群集拓扑的缓存。此拓扑缓存通常在发送第一条消息后更新,一次发送所有消息可防止使用ache简短说明HazelcastAsyncMultiMapgetInProgressCount将大于0,并阻止使用缓存,因此需要支付昂贵的查找代价,因此需要缓存

如果我将Bertjan的复制机与3.4.0-SNAPSHOT+Hazelcast一起使用,并进行以下更改:将消息发送到目的地,等待回复。回复后发送 所有的消息,然后我得到了很多改进

无聚类:5852毫秒 使用HZ 3.3.3进行聚类:16745 ms 使用HZ 3.4.0群集-快照+初始消息:8609 ms


我还认为,您不应该使用worker verticle发送那么多消息,而应该通过批处理使用事件循环verticle发送它们。也许你应该解释一下你的用例,我们可以考虑解决它的最佳方法。

你能给出一些关于你的应用架构的细节吗?例如,它如何使用事件总线?hi@tsegismont,我们有一个sender verticle 1实例,它使用循环发送将Json序列化的POJO对象3个基本成员放在事件总线上。此循环是一个Intstream循环,使用parallel选项运行。另一方面,receiver verticle 8实例处理来自eventbus的传入消息。我们将序列化的Json解码回POJO,并使用准备好的语句将对象插入cassandra集群。我将更新该问题,以包含我们在此测试项目中使用的代码。您能否提供一些有关应用程序架构的详细信息?例如,它如何使用事件总线?hi@tsegismont,我们有一个sender verticle 1实例,它使用循环发送将Json序列化的POJO对象3个基本成员放在事件总线上。此循环是一个Intstream循环,使用parallel选项运行。另一方面,receiver verticle 8实例处理来自eventbus的传入消息。我们将序列化的Json解码回POJO,并使用准备好的语句将对象插入cassandra集群。我将更新这个问题,以包含我们在这个测试项目中使用的代码。我今天在Bjorn现场,不能完全理解集群模式和非集群模式之间吞吐量的差异。希望我们遗漏了一些明显的东西;-我创建了一个可运行的复制程序:有关详细信息,请参阅自述文件。JGroupsClusterManager似乎比默认的Hazelcast群集管理器更快。IgniteClusterManager也是,但速度不比JGroups快。请参阅自述文件中的“发现”。讨论转移到谷歌小组:我今天在Bjorn现场,无法完全理解集群模式和非集群模式之间吞吐量的差异。希望我们遗漏了一些明显的东西;-我创建了一个可运行的复制程序:有关详细信息,请参阅自述文件。JGroupsClusterManager似乎比默认的Hazelcast群集管理器更快。IgniteClusterManager也是,但速度不比JGroups快。请参阅自述文件中的“发现”。讨论转移到谷歌小组:感谢朱利安的详细解释!同意这是一个特例。用例是从数亿条记录中读取一大块信息,并将每条记录放在eventbus上,由垂直体进行处理。分批发送是一个好主意,因为这将允许计划程序在接收和处理消息的分批之间处理其他事件,例如,另一个选项可以是压缩消息的数量,例如,将10条消息的信息放在一条消息中。3.4.0-SNAPSHOT中的修复看起来是一个很好的改进,我将把这一点传递给团队。要尝试的事情:-成批发送以允许在发送消息之间进行一些处理-尝试3.4.0-SNAPSHOT-通过将信息捆绑到消息中使消息粒度更粗-从eventloop垂直发送而不是从worker垂直发送-尝试使用不同的群集管理器JGroup而不是谢谢朱利安的详细解释!同意这是一个特例。用例是从数亿条记录中读取一大块信息,并将每条记录放在eventbus上,由垂直体进行处理。分批发送是一个好主意,因为这将允许计划程序在接收和处理消息的分批之间处理其他事件,例如,另一个选项可以是压缩消息的数量,例如,将10条消息的信息放在一条消息中。3.4.0-SNAPSHOT中的修复看起来是一个很好的改进,我将把这一点传递给团队。要尝试的事情:-成批发送以允许在发送消息之间进行一些处理-尝试3.4.0-SNAPSHOT-通过将信息捆绑到消息中使消息粒度更粗-从eventloop垂直发送而不是从worker垂直发送-尝试使用不同的群集管理器JGroup而不是黑兹卡斯特