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

io.journalkeeper.persistence.local.cache.PreloadBufferPool Maven / Gradle / Ivy

/**
 * Licensed 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 io.journalkeeper.persistence.local.cache; import io.journalkeeper.utils.format.Format; import io.journalkeeper.utils.spi.Singleton; import io.journalkeeper.utils.threads.AsyncLoopThread; import io.journalkeeper.utils.threads.ThreadBuilder; import io.journalkeeper.utils.threads.Threads; import io.journalkeeper.utils.threads.ThreadsFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sun.misc.Cleaner; import sun.misc.VM; import sun.nio.ch.DirectBuffer; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; /** * @author LiYue * Date: 2018-12-20 */ @Singleton public class PreloadBufferPool implements MemoryCacheManager { private static final Logger logger = LoggerFactory.getLogger(PreloadBufferPool.class); private static final String PRELOAD_THREAD = "PreloadBuffer-PreloadThread"; private static final String EVICT_THREAD = "PreloadBuffer-EvictThread"; // 缓存比率:如果非堆内存使用率超过这个比率,就不再申请内存,抛出OOM。 // 由于jvm在读写文件的时候会用到少量DirectBuffer作为缓存,必须预留一部分。 private final static double DEFAULT_CACHE_RATIO = 0.9d; /** * 缓存清理比率阈值,超过这个阈值执行缓存清理。 */ private static final float DEFAULT_EVICT_RATIO = 0.9f; /** * 缓存核心利用率,系统会尽量将这个比率以内的内存用满。 */ private static final float DEFAULT_CORE_RATIO = 0.8f; private static final long DEFAULT_WRITE_PAGE_EXTRA_WEIGHT_MS = 60000L; private final static long INTERVAL_MS = 50L; private final static String MAX_MEMORY_KEY = "memory_cache.max_memory"; private final static String EVICT_RATIO_KEY = "memory_cache.evict_ratio"; private final static String CORE_RATIO_KEY = "memory_cache.core_ratio"; private static final String WRITE_PAGE_EXTRA_WEIGHT_MS_KEY="memory_cache.write.weight.ms"; private static final PreloadBufferPool instance = null; private final Threads threads = ThreadsFactory.create(); // 可用的堆外内存上限,这个上限可以用JVM参数"PreloadBufferPool.MaxMemory"指定, // 默认为虚拟机最大内存(VM.maxDirectMemory())的90%。 private final long maxMemorySize; // 核心堆外内存大小,JournalKeeper总是尽量占满coreMemorySize内存用于缓存更多的文件,提升读写性能。 private final long coreMemorySize; // 堆外内存超过evictMemorySize就会启动清理,清理的策略是LRU private final long evictMemorySize; // 正在写入的页在置换时有额外的权重,这个权重用时间Ms体现。 // 默认是60秒。 // 置换权重 = 上次访问时间戳 + 额外权重,优先从内存中驱逐权重小的页。 // 例如:一个只读的页,上次访问时间戳是T,一个读写页,上次访问时间是T - 60秒, // 这两个页在置换时有同样的权重 private final long writePageExtraWeightMs; private final AtomicLong usedSize = new AtomicLong(0L); private final Set directBufferHolders = ConcurrentHashMap.newKeySet(); private final Set mMapBufferHolders = ConcurrentHashMap.newKeySet(); private Map bufferCache = new ConcurrentHashMap<>(); public PreloadBufferPool() { maxMemorySize = calcMaxMemorySize(); float evictRatio = getFloatProperty(EVICT_RATIO_KEY, DEFAULT_EVICT_RATIO); evictMemorySize = Math.round(maxMemorySize * evictRatio); float coreRatio = getFloatProperty(CORE_RATIO_KEY, DEFAULT_CORE_RATIO); coreMemorySize = Math.round(maxMemorySize * coreRatio); writePageExtraWeightMs = Long.parseLong(System.getProperty(WRITE_PAGE_EXTRA_WEIGHT_MS_KEY,String.valueOf(DEFAULT_WRITE_PAGE_EXTRA_WEIGHT_MS))); threads.createThread(buildPreloadThread()); threads.createThread(buildEvictThread()); threads.start(); logger.info("JournalKeeper PreloadBufferPool loaded, max direct memory: {}, core direct memory: {}, evict direct memory: {}.", Format.formatSize(maxMemorySize), Format.formatSize(coreMemorySize), Format.formatSize(evictMemorySize)); } private static float getFloatProperty(String key, float defaultValue) { try { return Float.parseFloat(System.getProperty(key, String.valueOf(defaultValue))); } catch (NumberFormatException e) { return defaultValue; } } private AsyncLoopThread buildPreloadThread() { return ThreadBuilder.builder() .name(PRELOAD_THREAD) .sleepTime(INTERVAL_MS, INTERVAL_MS) .doWork(this::preLoadBuffer) .onException(e -> logger.warn("{} exception:", PRELOAD_THREAD, e)) .daemon(true) .build(); } private AsyncLoopThread buildEvictThread() { return ThreadBuilder.builder() .name(EVICT_THREAD) .sleepTime(INTERVAL_MS, INTERVAL_MS) .condition(() -> usedSize.get() > evictMemorySize) .doWork(this::evict) .onException(e -> logger.warn("{} exception:", EVICT_THREAD, e)) .daemon(true) .build(); } /** * 清除文件缓存页。LRU。 */ private synchronized void evict() { // 清理超过maxCount的缓存页 for (PreLoadCache preLoadCache : bufferCache.values()) { if (!needEviction()) { break; } while (preLoadCache.cache.size() > preLoadCache.maxCount && !needEviction()) { try { destroyOne(preLoadCache.cache.remove()); } catch (NoSuchElementException ignored) { } } } // 清理使用中最旧的页面,直到内存占用率达标 if (needEviction()) { List> sorted; sorted = Stream.concat(directBufferHolders.stream(), mMapBufferHolders.stream()) .filter(BufferHolder::isFree) .map(bufferHolder -> new LruWrapper<>(bufferHolder, bufferHolder.lastAccessTime(), bufferHolder.writable() ? writePageExtraWeightMs : 0L)) .sorted(Comparator.comparing(LruWrapper::getWeight)) .collect(Collectors.toList()); while (needEviction() && !sorted.isEmpty()) { LruWrapper wrapper = sorted.remove(0); BufferHolder holder = wrapper.get(); if (holder.lastAccessTime() == wrapper.getLastAccessTime()) { holder.evict(); } } } } @Override public void printMetric() { long totalUsed = usedSize.get(); long plUsed = bufferCache.values().stream().mapToLong(preLoadCache -> { long cached = preLoadCache.cache.size(); long usedPreLoad = preLoadCache.onFlyCounter.get(); long totalSize = preLoadCache.bufferSize * (cached + usedPreLoad); logger.info("PreloadCache usage: cached: {} * {} = {}, used: {} * {} = {}, total: {}", Format.formatSize(preLoadCache.bufferSize), cached, Format.formatSize(preLoadCache.bufferSize * cached), Format.formatSize(preLoadCache.bufferSize), usedPreLoad, Format.formatSize(preLoadCache.bufferSize * usedPreLoad), Format.formatSize(totalSize)); return totalSize; }).sum(); long mmpUsed = mMapBufferHolders.stream().mapToLong(BufferHolder::size).sum(); long directUsed = directBufferHolders.stream().mapToLong(BufferHolder::size).sum(); logger.info("Direct memory usage: preload/direct/mmp/used/max: {}/{}/{}/{}/{}.", Format.formatSize(plUsed), Format.formatSize(directUsed), Format.formatSize(mmpUsed), Format.formatSize(totalUsed), Format.formatSize(maxMemorySize)); } private boolean needEviction() { return usedSize.get() > evictMemorySize; } private boolean isOutOfMemory() { return usedSize.get() > maxMemorySize; } private boolean isHungry() { return usedSize.get() < coreMemorySize; } @Override public synchronized void addPreLoad(int bufferSize, int coreCount, int maxCount) { PreLoadCache preLoadCache = bufferCache.putIfAbsent(bufferSize, new PreLoadCache(bufferSize, coreCount, maxCount)); if (null != preLoadCache) { preLoadCache.referenceCount.incrementAndGet(); } } @Override public synchronized void removePreLoad(int bufferSize) { PreLoadCache preLoadCache = bufferCache.get(bufferSize); if (null != preLoadCache) { if (preLoadCache.referenceCount.decrementAndGet() <= 0) { bufferCache.remove(bufferSize); preLoadCache.cache.forEach(this::destroyOne); } } } private void destroyOne(ByteBuffer byteBuffer) { usedSize.getAndAdd(-1 * byteBuffer.capacity()); releaseIfDirect(byteBuffer); } private void preLoadBuffer() { for (PreLoadCache preLoadCache : bufferCache.values()) { if (preLoadCache.cache.size() < preLoadCache.coreCount) { if (isHungry()) { try { while (preLoadCache.cache.size() < preLoadCache.coreCount && usedSize.get() + preLoadCache.bufferSize < maxMemorySize) { preLoadCache.cache.add(createOne(preLoadCache.bufferSize)); } } catch (OutOfMemoryError ignored) { return; } } else { List> outdated = directBufferHolders.stream() .filter(b -> b.size() == preLoadCache.bufferSize) .filter(BufferHolder::isFree) .map(bufferHolder -> new LruWrapper<>(bufferHolder, bufferHolder.lastAccessTime(), bufferHolder.writable() ? writePageExtraWeightMs : 0L)) .sorted(Comparator.comparing(LruWrapper::getWeight)) .collect(Collectors.toList()); while (preLoadCache.cache.size() < preLoadCache.coreCount && !outdated.isEmpty()) { LruWrapper wrapper = outdated.remove(0); BufferHolder holder = wrapper.get(); if (holder.lastAccessTime() == wrapper.getLastAccessTime()) { holder.evict(); } } } } } } private ByteBuffer createOne(int size) { reserveMemory(size); return ByteBuffer.allocateDirect(size); } private void reserveMemory(int size) { usedSize.addAndGet(size); try { while (isOutOfMemory()) { PreLoadCache preLoadCache = bufferCache.values().stream() .filter(p -> p.cache.size() > 0) .findAny().orElse(null); if (null != preLoadCache) { destroyOne(preLoadCache.cache.remove()); } else { break; } } if (isOutOfMemory()) { // 如果内存不足,唤醒清理线程立即执行清理 threads.wakeupThread(EVICT_THREAD); // 等待5x10ms,如果还不足抛出异常 for (int i = 0; i < 5 && isOutOfMemory(); i++) { try { Thread.sleep(10); } catch (InterruptedException e) { logger.warn("Interrupted: ", e); } } if (isOutOfMemory()) { throw new OutOfMemoryError(); } } } catch (Throwable t) { usedSize.getAndAdd(-1 * size); } } private void releaseIfDirect(ByteBuffer byteBuffer) { if (byteBuffer instanceof DirectBuffer) { try { Method getCleanerMethod; getCleanerMethod = byteBuffer.getClass().getMethod("cleaner"); getCleanerMethod.setAccessible(true); Cleaner cleaner = (Cleaner) getCleanerMethod.invoke(byteBuffer, new Object[0]); cleaner.clean(); } catch (Exception e) { logger.warn("Exception: ", e); } } } @Override public void allocateMMap(BufferHolder bufferHolder) { reserveMemory(bufferHolder.size()); mMapBufferHolders.add(bufferHolder); } @Override public ByteBuffer allocateDirect(int bufferSize, BufferHolder bufferHolder) { ByteBuffer buffer = allocateDirect(bufferSize); directBufferHolders.add(bufferHolder); return buffer; } private ByteBuffer allocateDirect(int bufferSize) { try { PreLoadCache preLoadCache = bufferCache.get(bufferSize); if (null != preLoadCache) { try { ByteBuffer byteBuffer = preLoadCache.cache.remove(); preLoadCache.onFlyCounter.getAndIncrement(); return byteBuffer; } catch (NoSuchElementException e) { logger.warn("Pool is empty, create ByteBuffer: {}", Format.formatSize(bufferSize)); ByteBuffer byteBuffer = createOne(bufferSize); preLoadCache.onFlyCounter.getAndIncrement(); threads.wakeupThread(PRELOAD_THREAD); return byteBuffer; } } else { logger.warn("No cached buffer in pool, create ByteBuffer: {}", Format.formatSize(bufferSize)); return createOne(bufferSize); } } catch (OutOfMemoryError outOfMemoryError) { logger.debug("OOM: {}/{}.", Format.formatSize(usedSize.get()), Format.formatSize(maxMemorySize)); throw outOfMemoryError; } } @Override public void releaseDirect(ByteBuffer byteBuffer, BufferHolder bufferHolder) { directBufferHolders.remove(bufferHolder); int size = byteBuffer.capacity(); PreLoadCache preLoadCache = bufferCache.get(size); if (null != preLoadCache) { if (needEviction() && preLoadCache.cache.size() >= preLoadCache.maxCount) { destroyOne(byteBuffer); } else { byteBuffer.clear(); preLoadCache.cache.add(byteBuffer); } preLoadCache.onFlyCounter.getAndDecrement(); } else { destroyOne(byteBuffer); } } @Override public void releaseMMap(BufferHolder bufferHolder) { mMapBufferHolders.remove(bufferHolder); usedSize.getAndAdd(-1 * bufferHolder.size()); } @Override public Collection getCaches() { return new ArrayList<>(bufferCache.values()); } @Override public long getMaxMemorySize() { return maxMemorySize; } @Override public long getTotalUsedMemorySize() { return usedSize.get(); } @Override public long getDirectUsedMemorySize() { return directBufferHolders.stream().mapToLong(BufferHolder::size).sum() + bufferCache.values().stream().mapToLong(c -> c.getBufferSize() * c.getCachedCount()).sum(); } /** * 计算可供缓存使用的最大堆外内存。 * * 1. 如果PreloadBufferPool.MaxMemory设置为数值,直接使用设置值。 * 2. 如果PreloadBufferPool.MaxMemory设置为百分比,比如:90%,最大堆外内存 = 物理内存 * 90% - 最大堆内存(由JVM参数-Xmx配置) * 3. 如果PreloadBufferPool.MaxMemory未设置或者设置了非法值,最大堆外内存 = VM.maxDirectMemory() * 90%。 * 其中VM.maxDirectMemory()取值为JVM参数-XX:MaxDirectMemorySize,如果未设置-XX:MaxDirectMemorySize,取值为JVM参数-Xmx。 * * @return 可使用的最大堆外内存大小。 */ private long calcMaxMemorySize() { String mmsString = System.getProperty(MAX_MEMORY_KEY); int pct = Format.getPercentage(mmsString); if (pct > 0 && pct < 100) { long physicalMemorySize = getPhysicalMemorySize(); long reservedHeapMemorySize = Runtime.getRuntime().maxMemory(); if (Long.MAX_VALUE == reservedHeapMemorySize) { logger.warn("Runtime.getRuntime().maxMemory() returns unlimited!"); reservedHeapMemorySize = physicalMemorySize / 2; } // 如果设置了百分比,最大可使用堆外内存= 物理内存 * 百分比 - 最大堆内存 long mms = physicalMemorySize * pct / 100 - reservedHeapMemorySize; if (mms > 0) { return mms; } else { return Math.round(VM.maxDirectMemory() * DEFAULT_CACHE_RATIO); } } return Format.parseSize(System.getProperty(MAX_MEMORY_KEY), Math.round(VM.maxDirectMemory() * DEFAULT_CACHE_RATIO)); } private static long getPhysicalMemorySize() { com.sun.management.OperatingSystemMXBean os = (com.sun.management.OperatingSystemMXBean) java.lang.management.ManagementFactory.getOperatingSystemMXBean(); return os.getTotalPhysicalMemorySize(); } @Override public void close() { if (null != PreloadBufferPool.instance) { PreloadBufferPool.instance.threads.stop(); PreloadBufferPool.instance.bufferCache.values().forEach(p -> { while (!p.cache.isEmpty()) { PreloadBufferPool.instance.destroyOne(p.cache.remove()); } }); PreloadBufferPool.instance.directBufferHolders.parallelStream().forEach(BufferHolder::evict); PreloadBufferPool.instance.mMapBufferHolders.parallelStream().forEach(BufferHolder::evict); PreloadBufferPool.instance.bufferCache.values().forEach(p -> { while (!p.cache.isEmpty()) { PreloadBufferPool.instance.destroyOne(p.cache.remove()); } }); } PreloadBufferPool.logger.info("Preload buffer pool closed."); } @Override public long getMapUsedMemorySize() { return mMapBufferHolders.stream().mapToLong(BufferHolder::size).sum(); } public static class PreLoadCache implements PreloadCacheMetric { private final int bufferSize; private final int coreCount, maxCount; private final Queue cache = new ConcurrentLinkedQueue<>(); private final AtomicInteger onFlyCounter = new AtomicInteger(0); private final AtomicInteger referenceCount; PreLoadCache(int bufferSize, int coreCount, int maxCount) { this.bufferSize = bufferSize; this.coreCount = coreCount; this.maxCount = maxCount; this.referenceCount = new AtomicInteger(1); } public int getBufferSize() { return bufferSize; } public int getCoreCount() { return coreCount; } public int getMaxCount() { return maxCount; } public int getUsedCount() { return onFlyCounter.get(); } public int getCachedCount() { return cache.size(); } } private static class LruWrapper { private final long lastAccessTime; private final long extraWeight; private final V t; LruWrapper(V t, long lastAccessTime, long extraWeight) { this.lastAccessTime = lastAccessTime; this.t = t; this.extraWeight = extraWeight; } private long getLastAccessTime() { return lastAccessTime; } private V get() { return t; } private long getWeight() { return lastAccessTime + extraWeight; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy