是否可以嵌套Hazelcast JET管道,以便内部管道可以计算外部管道的结果?

时间:2019-03-13 16:29:02

标签: java deadlock distributed-computing hazelcast hazelcast-jet

请考虑以下情形:

我们要获取一个大型的分布式对象集合,对于该集合中的每个对象,我们都希望使用当前对象和另一个大型分布式集合来进行另一个计算,以计算转换当前对象的结果。

例如

集合A:1、2、3、4、5、6、7、8 ......

集合B:1,2,3,4,5,6,7,8 ......

对于A中的每个值, 我们迭代B中的所有值,将每个值乘以2并将这些值相加, 我们将A中的每个值映射到该总和乘以当前A值。

下面是我的尝试,使用以下命令会导致死锁:

c2.newJob(p2).join()

使用以下命令时没有死锁:

c2.newJob(p2)

,但是我们希望p2完成以确保获得正确的总和。

对于特定的用例,这似乎是使用Jet的非惯用方式,但是我想使用该模式来解决其他问题,因此,非常感谢您的帮助。

JetInstance jet =  Jet.newJetInstance();
JetInstance c1 =  Jet.newJetClient();

Pipeline p1 = Pipeline.create();

List<Integer> aIn =  jet.getList("a-in");
aIn.add(1);
aIn.add(2);
aIn.add(3);

p1.drawFrom(Sources.list("a-in"))
        .map(e -> {
          Pipeline p2 = Pipeline.create();
          JetInstance c2 =  Jet.newJetClient();

          List<Integer> bIn = c2.getList("b-in");
          bIn.add(1);
          bIn.add(2);
          bIn.add(3);

          p2.drawFrom(Sources.list("b-in"))
                  .map(i->((Integer)i)*2)
                  .drainTo(Sinks.list("b-out"));

          List<Integer> bOut = c2.getList("b-out");

          // I would have thought it should just wait for the computation to complete,
          // instead the join here causes jet to block itself,
          c2.newJob(p2).join();

          int sum = 0;
          for (Integer i : bOut){
            sum+=i;
          }

          return ((Integer)e)*sum;
        }).drainTo(Sinks.list("a-out"));
c1.newJob(p1).join();

2 个答案:

答案 0 :(得分:1)

@newlogic,请尝试以下方法:

  1. 创建一个从b-in读取并写入b-out地图而不是列表的作业。您可以使用已知密钥,也可以仅使用时间戳等作为密钥并在该表上定义TTL以删除旧结果。
  2. b-out表上创建一个侦听器(本地侦听器,因此将仅通知拥有更新键的节点)以侦听entryAdd / Updated事件,具体取决于您在第一步中选择的内容并提交新作业通过该侦听器方法来处理a-in

这样,您无需等待,一旦完成第一项工作,它将自动触发第二项工作。

答案 1 :(得分:1)

您的代码中存在多个问题:

  1. map函数不应阻塞。在即将发布的版本中,我们将添加mapUsingContextAsync,您可以在其中使用客户端连接作为上下文,提交作业并返回job.getFuture()

  2. map操作将并行运行。您需要确保他们不共享临时列表。在您的示例中,所有子作业都使用b-out,它们会覆盖彼此的数据。

  3. 造成僵局的原因是:join()中的map()阻止了合作社工作人员,正在等待子作业完成,但是由于以下原因该子作业无法完成:被阻止的协作工作者线程。

此外,Jet并没有针对非常小的批处理作业进行优化,但是我想您的实际作业更大。部署这项工作有很多开销。如果作业本身仅运行几毫秒,那么开销会很大。在这种情况下,最好只使用list.stream().map(i->i*2).sum()而不是子职位。

JetInstance jet = Jet.newJetInstance();
JetInstance c1 = Jet.newJetClient();

Pipeline p1 = Pipeline.create();

List<Integer> aIn = jet.getList("a-in");
aIn.add(1);
aIn.add(2);
aIn.add(3);

List<Integer> bIn = jet.getList("b-in");
bIn.add(1);
bIn.add(2);
bIn.add(3);

p1.drawFrom(Sources.list("a-in"))
  .mapUsingContextAsync(
          ContextFactory
                  .withCreateFn(inst -> tuple2(inst, inst.<UUID, Long>getMap("tmpResults")))
                  // mark as non-cooperative, job submission does some blocking
                  .toNonCooperative()
                  .withLocalSharing()
                  .withMaxPendingCallsPerProcessor(2)
                  .withDestroyFn(ctx -> ctx.f1().destroy()),
          (ctx, item) -> {
              Pipeline p2 = Pipeline.create();
              JetInstance instance = ctx.f0();
              UUID key = UUID.randomUUID();
              IMapJet<UUID, Long> tmpResultsMap = ctx.f1();

              p2.drawFrom(Sources.list("b-in"))
                .map(i -> ((Integer) i) * 2L)
                .aggregate(summingLong(Long::longValue))
                .map(sum -> entry(key, sum))
                .drainTo(Sinks.map(tmpResultsMap));

              return instance.newJob(p2).getFuture()
                             .thenApply(r -> entry(item, tmpResultsMap.remove(key)));
          })
  .drainTo(Sinks.list("a-out"));

c1.newJob(p1).join();
jet.getList("a-out").forEach(System.out::println);

这将输出以下输出:

1=12
2=12
3=12

以上代码可在当前快照中使用,并且应在数周内到期的Jet 3.0中使用。