All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.spi.cluster.zookeeper.impl.ZKAsyncMultiMap Maven / Gradle / Ivy

/*
 *  Copyright (c) 2011-2016 The original author or authors
 *  ------------------------------------------------------
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *       The Eclipse Public License is available at
 *       http://www.eclipse.org/legal/epl-v10.html
 *
 *       The Apache License v2.0 is available at
 *       http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */
package io.vertx.spi.cluster.zookeeper.impl;

import io.vertx.core.*;
import io.vertx.core.impl.logging.Logger;
import io.vertx.core.impl.logging.LoggerFactory;
import io.vertx.core.spi.cluster.AsyncMultiMap;
import io.vertx.core.spi.cluster.ChoosableIterable;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.api.CuratorEventType;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.TreeCache;
import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
import org.apache.curator.framework.recipes.cache.TreeCacheListener;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static org.apache.curator.framework.recipes.cache.TreeCacheEvent.Type.INITIALIZED;
import static org.apache.curator.framework.recipes.cache.TreeCacheEvent.Type.NODE_ADDED;
import static org.apache.curator.framework.recipes.cache.TreeCacheEvent.Type.NODE_REMOVED;

/**
 * Created by Stream.Liu
 */
public class ZKAsyncMultiMap extends ZKMap implements AsyncMultiMap {

  private TreeCache treeCache;
  private CountDownLatch latch = new CountDownLatch(1);
  private ConcurrentMap> cache = new ConcurrentHashMap<>();
  //we should have a snapshot cache which could make event bus information restore to the zk while node get reconnection event.
  //we come across this issue while internal network is unstable.
  private ConcurrentMap> eventBusSnapshotCache = new ConcurrentHashMap<>();

  private static final Logger logger = LoggerFactory.getLogger(ZKAsyncMultiMap.class);

  public ZKAsyncMultiMap(Vertx vertx, CuratorFramework curator, String mapName) {
    super(curator, vertx, ZK_PATH_ASYNC_MULTI_MAP, mapName);
    treeCache = new TreeCache(curator, mapPath);
    treeCache.getListenable().addListener(new Listener());

    try {
      treeCache.start();
      latch.await(10, TimeUnit.SECONDS);
    } catch (Exception e) {
      throw new VertxException(e);
    }
  }

  @Override
  public void add(K k, V v, Handler> completionHandler) {
    String path = valuePath(k, v);
    assertKeyAndValueAreNotNull(k, v)
      .compose(aVoid -> checkExists(path))
      .compose(checkResult -> checkResult ? setData(path, v) : create(path, v))
      .compose(stat -> {
        //add to snapshot cache if path contains eventbus address
        if (path.contains(EVENTBUS_PATH)) {
          ChoosableSet serverIDs = eventBusSnapshotCache.get(path);
          if (serverIDs == null) serverIDs = new ChoosableSet<>(1);
          serverIDs.add(v);
          eventBusSnapshotCache.put(path, serverIDs);
        }
        Promise future = Promise.promise();
        try {
          curator.sync().inBackground((syncClient, syncEvent) -> {
            if (syncEvent.getType() == CuratorEventType.SYNC) {
              curator.getData().inBackground((getClient, getEvent) -> {
                if (stat == null || stat.getMtime() <= getEvent.getStat().getMtime()) {
                  vertx.runOnContext(aVoid -> future.complete());
                } else {
                  vertx.runOnContext(aVoid -> future.fail("can not get correct zxid."));
                }
              }).forPath(path);
            }
          }).forPath(path);
        } catch (Exception ex) {
          vertx.runOnContext(aVoid -> future.fail(ex));
        }
        return future.future();
      })
      .setHandler(completionHandler);
  }

  @Override
  public void get(K k, Handler>> asyncResultHandler) {
    Context ctx = vertx.getOrCreateContext();
    assertKeyIsNotNull(k)
      .compose(aVoid -> {
        final String keyPath = keyPath(k);
        ChoosableSet entries = cache.get(keyPath);
        Promise> future = Promise.promise();
        if (entries != null && !entries.isEmpty()) {
          future.complete(entries);
        } else {
          //sync before get
          try {
            curator.sync().inBackground((clientSync, eventSync) -> {
              if (eventSync.getType() == CuratorEventType.SYNC) {
                Map maps = treeCache.getCurrentChildren(keyPath);
                ChoosableSet newEntries = new ChoosableSet<>(maps != null ? maps.size() : 0);
                if (maps != null) {
                  for (ChildData childData : maps.values()) {
                    try {
                      if (childData != null && childData.getData() != null && childData.getData().length > 0) {
                        newEntries.add(asObject(childData.getData()));
                      }
                    } catch (Exception ex) {
                      ctx.runOnContext(v -> future.fail(ex));
                    }
                  }
                  cache.putIfAbsent(keyPath, newEntries);
                }
                ctx.runOnContext(v -> future.complete(newEntries));
              }
            }).forPath(keyPath);
          } catch (Exception ex) {
            ctx.runOnContext(v -> future.fail(ex));
          }
        }
        return future.future();
      })
      .setHandler(ar -> ctx.runOnContext(v -> asyncResultHandler.handle(ar)));
  }

  @Override
  public void remove(K k, V v, Handler> completionHandler) {
    assertKeyAndValueAreNotNull(k, v)
      .compose(aVoid -> {
        String fullPath = valuePath(k, v);
        return remove(keyPath(k), v, fullPath);
      })
      .setHandler(completionHandler);
  }

  private Future remove(String keyPath, V v, String fullPath) {
    return checkExists(fullPath).compose(checkResult -> {
      Promise future = Promise.promise();
      if (checkResult) {
        Optional.ofNullable(treeCache.getCurrentData(fullPath))
          .ifPresent(childData -> delete(fullPath, null).setHandler(deleteResult -> {
            //delete snapshot cache if keyPath contains event bus address
            if (keyPath.contains(EVENTBUS_PATH)) {
              Optional.ofNullable(eventBusSnapshotCache.get(keyPath)).ifPresent(vs -> {
                vs.remove(v);
                eventBusSnapshotCache.put(keyPath, vs);
              });
            }
            future.complete(true);
          }));
      } else {
        future.complete(false);
      }
      return future.future();
    });
  }

  @Override
  public void removeAllForValue(V v, Handler> completionHandler) {
    removeAllMatching(value -> value.hashCode() == v.hashCode(), completionHandler);
  }

  @Override
  public void removeAllMatching(Predicate p, Handler> completionHandler) {
    List futures = new ArrayList<>();
    Optional.ofNullable(treeCache.getCurrentChildren(mapPath)).ifPresent(childDataMap -> {
      childDataMap.keySet().forEach(partKeyPath -> {
        String keyPath = mapPath + "/" + partKeyPath;
        treeCache.getCurrentChildren(keyPath).keySet().forEach(valuePath -> {
          String fullPath = keyPath + "/" + valuePath;
          Optional.ofNullable(treeCache.getCurrentData(fullPath))
            .filter(childData -> Optional.of(childData.getData()).isPresent())
            .ifPresent(childData -> {
              try {
                V value = asObject(childData.getData());
                if (p.test(value)) {
                  futures.add(remove(keyPath, value, fullPath));
                }
              } catch (Exception e) {
                futures.add(Future.failedFuture(e));
              }
            });
        });
      });
      //
      CompositeFuture.all(futures).compose(compositeFuture -> {
        Promise future = Promise.promise();
        future.complete();
        return future.future();
      }).setHandler(completionHandler);
    });
  }

  private Future restoreSnapshotCache() {
    Promise futureResult = Promise.promise();
    List allFuture = eventBusSnapshotCache.entrySet().stream().map(entry -> {
      String path = entry.getKey().substring(mapPath.length() + 1).split("/", 2)[0];
      ChoosableSet values = entry.getValue();
      List futures = values.getIds().stream().map(value -> {
        Promise future = Promise.promise();
        add((K) path, value, future);
        return future.future();
      }).collect(Collectors.toList());
      return futures;
    }).flatMap(Collection::stream).collect(Collectors.toList());

    CompositeFuture.all(allFuture).setHandler(event -> {
      if (event.failed()) {
        futureResult.fail(event.cause());
      } else {
        futureResult.complete();
      }
    });
    return futureResult.future();
  }

  private class Listener implements TreeCacheListener {
    private AtomicBoolean reconnected = new AtomicBoolean(false);

    private String cachePath(final String key) {
      return mapPath + "/" + key;
    }

    @Override
    public void childEvent(CuratorFramework client, TreeCacheEvent treeCacheEvent) throws Exception {
      if (treeCacheEvent.getType() == INITIALIZED) {
        latch.countDown();
        return;
      }

      final ChildData childData = treeCacheEvent.getData();
      String[] key = null;
      ChoosableSet entries = null;

      // We only care about events with childData: NODE_ADDED, NODE_REMOVED
      if (treeCacheEvent.getType() == NODE_ADDED || treeCacheEvent.getType() == NODE_REMOVED) {
        if (childData == null || mapPath.length() == childData.getPath().length()) {
          return;
        }
        // Strip off the map prefix and leave the multi-map key path: `/`
        key = childData.getPath().substring(mapPath.length() + 1).split("/", 2);
        entries = cache.computeIfAbsent(cachePath(key[0]), k -> new ChoosableSet<>(1));
      }

      // When we only have 1 item in the key[], we're operating on the entire key (e.g. removing it)
      // rather than a child element under the key
      switch (treeCacheEvent.getType()) {
        case NODE_ADDED:
          if (key.length > 1) {
            entries.add(asObject(childData.getData()));
          }
          break;
        case NODE_REMOVED:
          if (key.length == 1) {
            cache.remove(cachePath(key[0]));
          } else {
            // When the child items are serialized into ZK, we use `toString()` to build the path
            // element. When removing, search for the item that has the expected string representation.
            for (V entry : entries)
              if (entry.toString().equals(key[1])) entries.remove(entry);
          }
          //if reconnect status is true, we try to restore eventbus address information to the zookeeper cluster
          if (reconnected.get()) {
            reconnected.set(false);
            restoreSnapshotCache().setHandler(event -> {
              if (event.failed()) {
                logger.error("restore eventbus snapshot cache failed.", event.cause());
              } else {
                logger.info("restore eventbus snapshot cache success.");
              }
            });
          }
          break;
        case CONNECTION_SUSPENDED:
          logger.warn("connection to the zookeeper server have suspended.");
          break;
        case CONNECTION_RECONNECTED:
          reconnected.set(true);
          break;
        case CONNECTION_LOST:
          logger.error("connection to the zookeeper server have lost, all the temporary node will be remove.");
          break;
      }
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy