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.bookkeeper.mledger.impl; import static org.apache.bookkeeper.mledger.impl.ManagedLedgerImpl.createManagedLedgerException; import static org.apache.bookkeeper.mledger.util.SafeRun.safeRun; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.primitives.Longs; import io.netty.buffer.ByteBuf; import java.util.Enumeration; import java.util.List; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import org.apache.bookkeeper.client.AsyncCallback.ReadCallback; import org.apache.bookkeeper.client.BKException; import org.apache.bookkeeper.client.api.LedgerEntry; import org.apache.bookkeeper.client.api.ReadHandle; import org.apache.bookkeeper.mledger.AsyncCallbacks; import org.apache.bookkeeper.mledger.AsyncCallbacks.ReadEntriesCallback; import org.apache.bookkeeper.mledger.Entry; import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @SuppressWarnings("checkstyle:javadoctype") public class EntryCacheManager { private final long maxSize; private final long evictionTriggerThreshold; private final double cacheEvictionWatermak; private final AtomicLong currentSize = new AtomicLong(0); private final ConcurrentMap<String, EntryCache> caches = Maps.newConcurrentMap(); private final EntryCacheEvictionPolicy evictionPolicy; private final AtomicBoolean evictionInProgress = new AtomicBoolean(false); private final ManagedLedgerFactoryImpl mlFactory; protected final ManagedLedgerFactoryMBeanImpl mlFactoryMBean; protected static final double MB = 1024 * 1024; private static final double evictionTriggerThresholdPercent = 0.98; /** * */ public EntryCacheManager(ManagedLedgerFactoryImpl factory) { this.maxSize = factory.getConfig().getMaxCacheSize(); this.evictionTriggerThreshold = (long) (maxSize * evictionTriggerThresholdPercent); this.cacheEvictionWatermak = factory.getConfig().getCacheEvictionWatermark(); this.evictionPolicy = new EntryCacheDefaultEvictionPolicy(); this.mlFactory = factory; this.mlFactoryMBean = factory.mbean; log.info("Initialized managed-ledger entry cache of {} Mb", maxSize / MB); } public EntryCache getEntryCache(ManagedLedgerImpl ml) { if (maxSize == 0) { // Cache is disabled return new EntryCacheDisabled(ml); } EntryCache newEntryCache = new EntryCacheImpl(this, ml); EntryCache currentEntryCache = caches.putIfAbsent(ml.getName(), newEntryCache); if (currentEntryCache != null) { return currentEntryCache; } else { return newEntryCache; } } void removeEntryCache(String name) { EntryCache entryCache = caches.remove(name); if (entryCache == null) { return; } long size = entryCache.getSize(); entryCache.clear(); if (log.isDebugEnabled()) { log.debug("Removed cache for {} - Size: {} -- Current Size: {}", name, size / MB, currentSize.get() / MB); } } boolean hasSpaceInCache() { long currentSize = this.currentSize.get(); // Trigger a single eviction in background. While the eviction is running we stop inserting entries in the cache if (currentSize > evictionTriggerThreshold && evictionInProgress.compareAndSet(false, true)) { mlFactory.scheduledExecutor.execute(safeRun(() -> { // Trigger a new cache eviction cycle to bring the used memory below the cacheEvictionWatermark // percentage limit long sizeToEvict = currentSize - (long) (maxSize * cacheEvictionWatermak); long startTime = System.nanoTime(); log.info("Triggering cache eviction. total size: {} Mb -- Need to discard: {} Mb", currentSize / MB, sizeToEvict / MB); try { evictionPolicy.doEviction(Lists.newArrayList(caches.values()), sizeToEvict); long endTime = System.nanoTime(); double durationMs = TimeUnit.NANOSECONDS.toMicros(endTime - startTime) / 1000.0; log.info("Eviction completed. Removed {} Mb in {} ms", (currentSize - this.currentSize.get()) / MB, durationMs); } finally { mlFactoryMBean.recordCacheEviction(); evictionInProgress.set(false); } })); } return currentSize < maxSize; } void entryAdded(long size) { currentSize.addAndGet(size); } void entriesRemoved(long size) { currentSize.addAndGet(-size); } public long getSize() { return currentSize.get(); } public long getMaxSize() { return maxSize; } public void clear() { caches.values().forEach(cache -> cache.clear()); } protected class EntryCacheDisabled implements EntryCache { private final ManagedLedgerImpl ml; public EntryCacheDisabled(ManagedLedgerImpl ml) { this.ml = ml; } @Override public String getName() { return ml.getName(); } @Override public boolean insert(EntryImpl entry) { return false; } @Override public void invalidateEntries(PositionImpl lastPosition) { } @Override public void invalidateAllEntries(long ledgerId) { } @Override public void clear() { } @Override public Pair<Integer, Long> evictEntries(long sizeToFree) { return Pair.of(0, (long) 0); } @Override public void asyncReadEntry(ReadHandle lh, long firstEntry, long lastEntry, boolean isSlowestReader, final ReadEntriesCallback callback, Object ctx) { lh.readAsync(firstEntry, lastEntry).whenComplete((ledgerEntries, exception) -> { if (exception != null) { callback.readEntriesFailed(createManagedLedgerException(exception), ctx); return; } List<Entry> entries = Lists.newArrayList(); long totalSize = 0; try { for (LedgerEntry e : ledgerEntries) { // Insert the entries at the end of the list (they will be unsorted for now) EntryImpl entry = EntryImpl.create(e); entries.add(entry); totalSize += entry.getLength(); } } finally { ledgerEntries.close(); } mlFactoryMBean.recordCacheMiss(entries.size(), totalSize); ml.mbean.addReadEntriesSample(entries.size(), totalSize); callback.readEntriesComplete(entries, null); }); } @Override public void asyncReadEntry(ReadHandle lh, PositionImpl position, AsyncCallbacks.ReadEntryCallback callback, Object ctx) { } @Override public long getSize() { return 0; } @Override public int compareTo(EntryCache other) { return Longs.compare(getSize(), other.getSize()); } } public static Entry create(long ledgerId, long entryId, ByteBuf data) { return EntryImpl.create(ledgerId, entryId, data); } private static final Logger log = LoggerFactory.getLogger(EntryCacheManager.class); }