Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.flink.runtime.state.gemini.engine.vm;
import org.apache.flink.annotation.VisibleForTesting;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.runtime.state.gemini.engine.GRegionID;
import org.apache.flink.runtime.state.gemini.engine.page.PageContext;
import org.apache.flink.runtime.state.gemini.engine.page.PageIndex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static org.apache.flink.util.Preconditions.checkArgument;
import static org.apache.flink.util.Preconditions.checkNotNull;
/**
* LogicPageLRU. when Page load from File, and still not enter into Cache. then this page will be put in this small LRU.
* no thread safe.
*/
public class DataPageLRU {
private static final Logger LOG = LoggerFactory.getLogger(DataPageLRU.class);
private final int lruSize;
private final DataPageLRUFuction function;
private volatile long totalSize = 0;
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final Map entries;
private final List headers;
private final List tailers;
private final List countsPerSlot;
private final int startRegion;
private final int slots;
private int longestSlot;
private final boolean evenEvict;
private CacheStats cacheStats;
/** Whether to update the linked list when access a entry. */
private final boolean accessOrder;
public DataPageLRU(int startRegionId, int endRegionId, int lruSize, DataPageLRUFuction function) {
this(startRegionId, endRegionId, lruSize, function, true, true, null);
}
public DataPageLRU(int startRegionId, int endRegionId, int lruSize, DataPageLRUFuction function, boolean accessOrder, boolean evenEvict, CacheStats cacheStats) {
this.lruSize = lruSize;
this.function = function;
this.accessOrder = accessOrder;
this.startRegion = startRegionId;
this.evenEvict = evenEvict;
this.slots = endRegionId - startRegionId + 1;
this.longestSlot = 0;
this.entries = new HashMap(lruSize);
headers = new ArrayList<>(slots);
tailers = new ArrayList<>(slots);
for (int i = 0; i < slots; ++i) {
headers.add(i, new ListNode(null, null));
tailers.add(i, new ListNode(null, null));
headers.get(i).setNext(tailers.get(i));
headers.get(i).setPrev(tailers.get(i));
tailers.get(i).setNext(headers.get(i));
tailers.get(i).setPrev(headers.get(i));
}
countsPerSlot = new ArrayList<>(slots);
for (int i = 0; i < slots; ++i) {
countsPerSlot.add(i, 0);
}
this.cacheStats = cacheStats;
LOG.info("LRU Cache with size {}, accessMode {}, evenEvict {}, region {} -> {}", lruSize, accessOrder, evenEvict, startRegionId, endRegionId);
}
public long getTotalSize() {
return totalSize;
}
public void put(K key, V value) {
if (cacheStats != null) {
cacheStats.addPageCacheLRUPutCount();
}
lock.writeLock().lock();
ListNode prev = entries.get(key);
try {
if (prev != null) {
int targetSlot = function.getSlotIndex(value, startRegion);
int prevSlot = function.getSlotIndex(prev.value, startRegion);
if (targetSlot != prevSlot) {
deleteFromList(prev);
insertIntoHead(targetSlot, prev);
if (longestSlot == prevSlot && countsPerSlot.get(targetSlot) > countsPerSlot.get(prevSlot)) {
longestSlot = targetSlot;
}
}
if (prev.value != null && !prev.value.equals(value)) {
function.removed(prev.value);
}
prev.setValue(value);
return;
}
if (entries.size() >= lruSize) {
ListNode deleteNode = tailers.get(longestSlot).prev;
deleteFromList(deleteNode);
entries.remove(deleteNode.key);
function.removed(deleteNode.value);
deleteNode.value = null;
deleteNode = null;
}
prev = new ListNode(key, value);
entries.put(key, prev);
int targetSlot = function.getSlotIndex(prev.value, startRegion);
insertIntoHead(targetSlot, prev);
// do not to find the longest chain here for better performance.
// here we make sure that have at least one item to evict next time.
if (countsPerSlot.get(targetSlot) > countsPerSlot.get(longestSlot)) {
longestSlot = targetSlot;
}
if (evenEvict) {
// make the other region can be evictable, when current region only the the new added item.
if (countsPerSlot.get(longestSlot) == 1 && entries.size() > 2) {
longestSlot = ThreadLocalRandom.current().nextInt(slots);
while (countsPerSlot.get(longestSlot) == 0) {
longestSlot = ThreadLocalRandom.current().nextInt(slots);
}
}
}
} finally {
lock.writeLock().unlock();
}
}
public int size() {
return entries.size();
}
private void insertIntoHead(int index, ListNode node) {
ListNode currentHeader = headers.get(index);
node.setPrev(currentHeader);
node.setNext(currentHeader.next);
currentHeader.next.setPrev(node);
currentHeader.setNext(node);
totalSize += function.size(node.value);
countsPerSlot.set(index, countsPerSlot.get(index) + 1);
}
private void deleteFromList(ListNode node) {
node.prev.setNext(node.next);
node.next.setPrev(node.prev);
totalSize -= function.size(node.value);
int slot = function.getSlotIndex(node.value, startRegion);
countsPerSlot.set(slot, countsPerSlot.get(slot) - 1);
}
/**
* Returns the entry associated with the given key in specified region, or Null if there is no entry associated with the given key.
*/
public V get(K key) {
Lock curretLock = accessOrder ? lock.writeLock() : lock.readLock();
curretLock.lock();
try {
ListNode listNode = entries.get(key);
if (listNode != null) {
if (accessOrder) {
deleteFromList(listNode);
insertIntoHead(function.getSlotIndex(listNode.value, startRegion), listNode);
}
return listNode.value;
}
return null;
} finally {
curretLock.unlock();
}
}
public boolean remove(K key) {
lock.writeLock().lock();
try {
ListNode prev = entries.remove(key);
if (prev == null) {
return false;
}
deleteFromList(prev);
function.removed(prev.value);
prev = null;
return true;
} finally {
lock.writeLock().unlock();
}
}
public Tuple2 getHottestPage(GRegionID regionID, PageIndex currentPageIndex) {
int slotId = regionID.getId() - startRegion;
lock.readLock().lock();
try {
ListNode currentNode = headers.get(slotId).next;
ListNode tailNode = tailers.get(slotId);
while (!currentNode.equals(tailNode)) {
if (!function.canAddIntoMainCache(currentNode.value, currentPageIndex, regionID)) {
currentNode = currentNode.next;
continue;
}
return Tuple2.of(currentNode.key, currentNode.value);
}
return null;
} finally {
lock.readLock().unlock();
}
}
@VisibleForTesting
public Map getEntries() {
return Collections.unmodifiableMap(entries);
}
@VisibleForTesting
public int getItemNumInSlot(int slot) {
checkArgument(slot >= 0 && slot < slots);
int count = 0;
ListNode currentTailNode = tailers.get(slot).prev;
while (!headers.get(slot).equals(currentTailNode)) {
count++;
currentTailNode = currentTailNode.prev;
}
return count;
}
public void clear() {
lock.writeLock().lock();
try {
for (ListNode value : entries.values()) {
totalSize -= function.size(value.value);
function.removed(value.value);
}
} finally {
lock.writeLock().unlock();
}
}
/**
* SizeAble.
*/
public interface DataPageLRUFuction {
int size(T value);
void removed(T value);
int getSlotIndex(T value, int offset);
boolean canAddIntoMainCache(T value, PageIndex pageIndex, GRegionID expectedRegionID);
}
/**
* Internal node contains external information for a inner value.
*/
public static class PageWithContext {
// null for invalid page context, the data page will not load from lru into main cache.
private final @Nullable PageContext pageContext;
private final FutureDataPage futureDataPage;
public PageWithContext(@Nullable PageContext pageContext, FutureDataPage futureDataPage) {
this.pageContext = pageContext;
this.futureDataPage = checkNotNull(futureDataPage);
}
public FutureDataPage getFutureDataPage() {
return futureDataPage;
}
@Nullable
public PageContext getPageContext() {
return pageContext;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
PageWithContext other = (PageWithContext) obj;
return Objects.equals(pageContext, other.pageContext) && Objects.equals(futureDataPage, other.futureDataPage);
}
@Override
public int hashCode() {
int hashCode = pageContext == null ? 0 : pageContext.hashCode();
hashCode = hashCode * 31 + futureDataPage.hashCode();
return hashCode;
}
}
private class ListNode {
private ListNode prev;
private ListNode next;
private final K key;
private V value;
public ListNode(final K key, final V value) {
this.key = key;
this.value = value;
this.prev = null;
this.next = null;
}
public void setPrev(ListNode newPrev) {
this.prev = newPrev;
}
public void setNext(ListNode newNext) {
this.next = newNext;
}
private void setValue(V newValue) {
this.value = newValue;
}
@Override
public int hashCode() {
return key.hashCode() * 31 + value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || obj.getClass() != getClass()) {
return false;
}
ListNode other = (ListNode) obj;
return Objects.equals(key, other.key) && Objects.equals(value, other.value);
}
}
}