Java tutorial
/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.distributedlog.impl; import static com.google.common.base.Charsets.UTF_8; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.apache.bookkeeper.versioning.LongVersion; import org.apache.bookkeeper.versioning.Version; import org.apache.bookkeeper.versioning.Versioned; import org.apache.distributedlog.DistributedLogConfiguration; import org.apache.distributedlog.LogSegmentMetadata; import org.apache.distributedlog.ZooKeeperClient; import org.apache.distributedlog.callback.LogSegmentNamesListener; import org.apache.distributedlog.common.concurrent.FutureEventListener; import org.apache.distributedlog.exceptions.LogNotFoundException; import org.apache.distributedlog.exceptions.LogSegmentNotFoundException; import org.apache.distributedlog.exceptions.ZKException; import org.apache.distributedlog.logsegment.LogSegmentMetadataStore; import org.apache.distributedlog.metadata.LogMetadata; import org.apache.distributedlog.metadata.LogMetadataForWriter; import org.apache.distributedlog.util.DLUtils; import org.apache.distributedlog.util.OrderedScheduler; import org.apache.distributedlog.util.Transaction; import org.apache.distributedlog.util.Transaction.OpListener; import org.apache.distributedlog.util.Utils; import org.apache.distributedlog.zk.DefaultZKOp; import org.apache.distributedlog.zk.ZKOp; import org.apache.distributedlog.zk.ZKTransaction; import org.apache.distributedlog.zk.ZKVersionedSetOp; import org.apache.zookeeper.AsyncCallback.Children2Callback; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.Op; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * ZooKeeper based log segment metadata store. */ public class ZKLogSegmentMetadataStore implements LogSegmentMetadataStore, Watcher, Children2Callback { private static final Logger logger = LoggerFactory.getLogger(ZKLogSegmentMetadataStore.class); private static final List<String> EMPTY_LIST = ImmutableList.of(); private static class ReadLogSegmentsTask implements Runnable, FutureEventListener<Versioned<List<String>>> { private final String logSegmentsPath; private final ZKLogSegmentMetadataStore store; private int currentZKBackOffMs; ReadLogSegmentsTask(String logSegmentsPath, ZKLogSegmentMetadataStore metadataStore) { this.logSegmentsPath = logSegmentsPath; this.store = metadataStore; this.currentZKBackOffMs = store.minZKBackoffMs; } @Override public void onSuccess(final Versioned<List<String>> segments) { // reset the back off after a successful operation currentZKBackOffMs = store.minZKBackoffMs; store.notifyLogSegmentsUpdated(logSegmentsPath, store.listeners.get(logSegmentsPath), segments); } @Override public void onFailure(Throwable cause) { int backoffMs; if (cause instanceof LogNotFoundException) { // the log segment has been deleted, remove all the registered listeners store.notifyLogStreamDeleted(logSegmentsPath, store.listeners.remove(logSegmentsPath)); return; } else { backoffMs = currentZKBackOffMs; currentZKBackOffMs = Math.min(2 * currentZKBackOffMs, store.maxZKBackoffMs); } store.scheduleTask(logSegmentsPath, this, backoffMs); } @Override public void run() { if (null != store.listeners.get(logSegmentsPath)) { store.zkGetLogSegmentNames(logSegmentsPath, store).whenComplete(this); } else { logger.debug("Log segments listener for {} has been removed.", logSegmentsPath); } } } /** * A log segment names listener that keeps tracking the version of list of log segments that it has been notified. * It only notify the newer log segments. */ static class VersionedLogSegmentNamesListener { private final LogSegmentNamesListener listener; private Versioned<List<String>> lastNotifiedLogSegments; VersionedLogSegmentNamesListener(LogSegmentNamesListener listener) { this.listener = listener; this.lastNotifiedLogSegments = new Versioned<List<String>>(EMPTY_LIST, Version.NEW); } synchronized void onSegmentsUpdated(Versioned<List<String>> logSegments) { if (lastNotifiedLogSegments.getVersion() == Version.NEW || lastNotifiedLogSegments.getVersion() .compare(logSegments.getVersion()) == Version.Occurred.BEFORE) { lastNotifiedLogSegments = logSegments; listener.onSegmentsUpdated(logSegments); } } @Override public int hashCode() { return listener.hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof VersionedLogSegmentNamesListener)) { return false; } VersionedLogSegmentNamesListener other = (VersionedLogSegmentNamesListener) obj; return listener.equals(other.listener); } @Override public String toString() { return listener.toString(); } } final DistributedLogConfiguration conf; // settings final int minZKBackoffMs; final int maxZKBackoffMs; final boolean skipMinVersionCheck; final ZooKeeperClient zkc; // log segment listeners final ConcurrentMap<String, Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener>> listeners; // scheduler final OrderedScheduler scheduler; final ReentrantReadWriteLock closeLock; boolean closed = false; public ZKLogSegmentMetadataStore(DistributedLogConfiguration conf, ZooKeeperClient zkc, OrderedScheduler scheduler) { this.conf = conf; this.zkc = zkc; this.listeners = new ConcurrentHashMap<String, Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener>>(); this.scheduler = scheduler; this.closeLock = new ReentrantReadWriteLock(); // settings this.minZKBackoffMs = conf.getZKRetryBackoffStartMillis(); this.maxZKBackoffMs = conf.getZKRetryBackoffMaxMillis(); this.skipMinVersionCheck = conf.getDLLedgerMetadataSkipMinVersionCheck(); } protected void scheduleTask(Object key, Runnable r, long delayMs) { closeLock.readLock().lock(); try { if (closed) { return; } scheduler.schedule(key, r, delayMs, TimeUnit.MILLISECONDS); } finally { closeLock.readLock().unlock(); } } protected void submitTask(Object key, Runnable r) { closeLock.readLock().lock(); try { if (closed) { return; } scheduler.submit(key, r); } finally { closeLock.readLock().unlock(); } } // max sequence number and max transaction id @Override public void storeMaxLogSegmentSequenceNumber(Transaction<Object> txn, LogMetadata logMetadata, Versioned<Long> lssn, Transaction.OpListener<Version> listener) { Version version = lssn.getVersion(); assert (version instanceof LongVersion); LongVersion zkVersion = (LongVersion) version; byte[] data = DLUtils.serializeLogSegmentSequenceNumber(lssn.getValue()); Op setDataOp = Op.setData(logMetadata.getLogSegmentsPath(), data, (int) zkVersion.getLongVersion()); ZKOp zkOp = new ZKVersionedSetOp(setDataOp, listener); txn.addOp(zkOp); } @Override public void storeMaxTxnId(Transaction<Object> txn, LogMetadataForWriter logMetadata, Versioned<Long> transactionId, Transaction.OpListener<Version> listener) { Version version = transactionId.getVersion(); assert (version instanceof LongVersion); LongVersion zkVersion = (LongVersion) version; byte[] data = DLUtils.serializeTransactionId(transactionId.getValue()); Op setDataOp = Op.setData(logMetadata.getMaxTxIdPath(), data, (int) zkVersion.getLongVersion()); ZKOp zkOp = new ZKVersionedSetOp(setDataOp, listener); txn.addOp(zkOp); } // updates @Override public Transaction<Object> transaction() { return new ZKTransaction(zkc); } @Override public void createLogSegment(Transaction<Object> txn, LogSegmentMetadata segment, OpListener<Void> listener) { byte[] finalisedData = segment.getFinalisedData().getBytes(UTF_8); Op createOp = Op.create(segment.getZkPath(), finalisedData, zkc.getDefaultACL(), CreateMode.PERSISTENT); txn.addOp(DefaultZKOp.of(createOp, listener)); } @Override public void deleteLogSegment(Transaction<Object> txn, final LogSegmentMetadata segment, final OpListener<Void> listener) { Op deleteOp = Op.delete(segment.getZkPath(), -1); logger.info("Delete segment : {}", segment); txn.addOp(DefaultZKOp.of(deleteOp, new OpListener<Void>() { @Override public void onCommit(Void r) { if (null != listener) { listener.onCommit(r); } } @Override public void onAbort(Throwable t) { logger.info("Aborted transaction on deleting segment {}", segment); KeeperException.Code kc; if (t instanceof KeeperException) { kc = ((KeeperException) t).code(); } else if (t instanceof ZKException) { kc = ((ZKException) t).getKeeperExceptionCode(); } else { abortListener(t); return; } if (KeeperException.Code.NONODE == kc) { abortListener(new LogSegmentNotFoundException(segment.getZkPath())); return; } abortListener(t); } private void abortListener(Throwable t) { if (null != listener) { listener.onAbort(t); } } })); } @Override public void updateLogSegment(Transaction<Object> txn, LogSegmentMetadata segment) { byte[] finalisedData = segment.getFinalisedData().getBytes(UTF_8); Op updateOp = Op.setData(segment.getZkPath(), finalisedData, -1); txn.addOp(DefaultZKOp.of(updateOp, null)); } // reads /** * Process the watched events for registered listeners. */ @Override public void process(WatchedEvent event) { if (Event.EventType.None == event.getType() && Event.KeeperState.Expired == event.getState()) { Set<String> keySet = new HashSet<String>(listeners.keySet()); for (String logSegmentsPath : keySet) { scheduleTask(logSegmentsPath, new ReadLogSegmentsTask(logSegmentsPath, this), 0L); } return; } String path = event.getPath(); if (null == path) { return; } switch (event.getType()) { case NodeDeleted: notifyLogStreamDeleted(path, listeners.remove(path)); break; case NodeChildrenChanged: new ReadLogSegmentsTask(path, this).run(); break; default: break; } } @Override public CompletableFuture<LogSegmentMetadata> getLogSegment(String logSegmentPath) { return LogSegmentMetadata.read(zkc, logSegmentPath, skipMinVersionCheck); } CompletableFuture<Versioned<List<String>>> zkGetLogSegmentNames(String logSegmentsPath, Watcher watcher) { CompletableFuture<Versioned<List<String>>> result = new CompletableFuture<Versioned<List<String>>>(); try { zkc.get().getChildren(logSegmentsPath, watcher, this, result); } catch (ZooKeeperClient.ZooKeeperConnectionException e) { result.completeExceptionally(Utils.zkException(e, logSegmentsPath)); } catch (InterruptedException e) { result.completeExceptionally(Utils.zkException(e, logSegmentsPath)); } return result; } @Override @SuppressWarnings("unchecked") public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) { CompletableFuture<Versioned<List<String>>> result = ((CompletableFuture<Versioned<List<String>>>) ctx); if (KeeperException.Code.OK.intValue() == rc) { /** cversion: the number of changes to the children of this znode **/ LongVersion zkVersion = new LongVersion(stat.getCversion()); result.complete(new Versioned(children, zkVersion)); } else if (KeeperException.Code.NONODE.intValue() == rc) { result.completeExceptionally(new LogNotFoundException("Log " + path + " not found")); } else { result.completeExceptionally( new ZKException("Failed to get log segments from " + path, KeeperException.Code.get(rc))); } } @Override public CompletableFuture<Versioned<List<String>>> getLogSegmentNames(String logSegmentsPath, LogSegmentNamesListener listener) { Watcher zkWatcher; if (null == listener) { zkWatcher = null; } else { closeLock.readLock().lock(); try { if (closed) { zkWatcher = null; } else { Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener> listenerSet = listeners .get(logSegmentsPath); if (null == listenerSet) { Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener> newListenerSet = new HashMap<LogSegmentNamesListener, VersionedLogSegmentNamesListener>(); Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener> oldListenerSet = listeners .putIfAbsent(logSegmentsPath, newListenerSet); if (null != oldListenerSet) { listenerSet = oldListenerSet; } else { listenerSet = newListenerSet; } } synchronized (listenerSet) { listenerSet.put(listener, new VersionedLogSegmentNamesListener(listener)); if (!listeners.containsKey(logSegmentsPath)) { // listener set has been removed, add it back if (null != listeners.putIfAbsent(logSegmentsPath, listenerSet)) { logger.debug("Listener set is already found for log segments path {}", logSegmentsPath); } } } zkWatcher = ZKLogSegmentMetadataStore.this; } } finally { closeLock.readLock().unlock(); } } CompletableFuture<Versioned<List<String>>> getLogSegmentNamesResult = zkGetLogSegmentNames(logSegmentsPath, zkWatcher); if (null != listener) { getLogSegmentNamesResult.whenComplete(new ReadLogSegmentsTask(logSegmentsPath, this)); } return zkGetLogSegmentNames(logSegmentsPath, zkWatcher); } @Override public void unregisterLogSegmentListener(String logSegmentsPath, LogSegmentNamesListener listener) { closeLock.readLock().lock(); try { if (closed) { return; } Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener> listenerSet = listeners .get(logSegmentsPath); if (null == listenerSet) { return; } synchronized (listenerSet) { listenerSet.remove(listener); if (listenerSet.isEmpty()) { listeners.remove(logSegmentsPath, listenerSet); } } } finally { closeLock.readLock().unlock(); } } @Override public void close() throws IOException { closeLock.writeLock().lock(); try { if (closed) { return; } closed = true; } finally { closeLock.writeLock().unlock(); } } // Notifications void notifyLogStreamDeleted(String logSegmentsPath, final Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener> listeners) { if (null == listeners) { return; } this.submitTask(logSegmentsPath, new Runnable() { @Override public void run() { for (LogSegmentNamesListener listener : listeners.keySet()) { listener.onLogStreamDeleted(); } } }); } void notifyLogSegmentsUpdated(String logSegmentsPath, final Map<LogSegmentNamesListener, VersionedLogSegmentNamesListener> listeners, final Versioned<List<String>> segments) { if (null == listeners) { return; } this.submitTask(logSegmentsPath, new Runnable() { @Override public void run() { for (VersionedLogSegmentNamesListener listener : listeners.values()) { listener.onSegmentsUpdated(segments); } } }); } }