Package com.cetsoft.imcache.cache.offheap

Source Code of com.cetsoft.imcache.cache.offheap.OffHeapCache

/*
* Copyright (C) 2014 Cetsoft, http://www.cetsoft.com
*
* 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.
*
* Author : Yusuf Aytas
* Date   : Sep 20, 2013
*/
package com.cetsoft.imcache.cache.offheap;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import com.cetsoft.imcache.cache.AbstractCache;
import com.cetsoft.imcache.cache.CacheLoader;
import com.cetsoft.imcache.cache.EvictionListener;
import com.cetsoft.imcache.cache.offheap.bytebuffer.OffHeapByteBuffer;
import com.cetsoft.imcache.cache.offheap.bytebuffer.OffHeapByteBufferStore;
import com.cetsoft.imcache.cache.offheap.bytebuffer.Pointer;
import com.cetsoft.imcache.cache.search.IndexHandler;
import com.cetsoft.imcache.concurrent.lock.StripedReadWriteLock;
import com.cetsoft.imcache.serialization.Serializer;

/**
* The Class OffHeapCache is a cache that uses offheap byte buffers
* to store or retrieve data by serializing items into bytes. To do so,
* OffHeapCache uses pointers to point array location of an item.
* OffHeapCache clears the buffers periodically to gain free space if
* buffers are dirty(unused memory). It also does eviction depending on
* access time to the objects.
*
* @param <K> the key type
* @param <V> the value type
*/
public class OffHeapCache<K, V> extends AbstractCache<K, V> {

  /** The Constant DELTA. */
  private static final float DELTA = 0.00001f;
 
  /** The default buffer cleaner period which is 10 minutes. */
  public static final long DEFAULT_BUFFER_CLEANER_PERIOD = 10 * 60 * 1000;
 
  /** The default eviction period which is 10 minutes. */
  public static final long DEFAULT_EVICTION_PERIOD = 10 * 60 * 1000;

  /** The default buffer cleaner threshold. */
  public static final float DEFAULT_BUFFER_CLEANER_THRESHOLD = 0.5f;
 
  /** The Constant DEFAULT_CONCURRENCY_LEVEL. */
  public static final int DEFAULT_CONCURRENCY_LEVEL = 4;

  /** The hit. */
  protected AtomicLong hit = new AtomicLong();

  /** The miss. */
  protected AtomicLong miss = new AtomicLong();

  /** The pointer map. */
  protected ConcurrentMap<K, Pointer> pointerMap = new ConcurrentHashMap<K, Pointer>();

  /** The serializer. */
  private Serializer<V> serializer;

  /** The buffer store. */
  private OffHeapByteBufferStore bufferStore;

  /** The read write lock. */
  private StripedReadWriteLock readWriteLock;

  /** The Constant NO_OF_CLEANERS. */
  private static final AtomicInteger NO_OF_CLEANERS = new AtomicInteger();
 
  /** The Constant NO_OF_EVICTORS. */
  private static final AtomicInteger NO_OF_EVICTORS = new AtomicInteger();
 
  /**
   * Instantiates a new offheap cache.
   *
   * @param cacheLoader the cache loader
   * @param evictionListener the eviction listener
   * @param indexHandler the query executer
   * @param byteBufferStore the byte buffer store
   * @param serializer the serializer
   * @param bufferCleanerPeriod the buffer cleaner period
   * @param bufferCleanerThreshold the buffer cleaner threshold
   * @param concurrencyLevel the concurrency level
   * @param evictionPeriod the eviction period
   */
  public OffHeapCache(CacheLoader<K, V> cacheLoader, EvictionListener<K, V> evictionListener,
      IndexHandler<K, V> indexHandler,OffHeapByteBufferStore byteBufferStore, Serializer<V> serializer,
      long bufferCleanerPeriod, final float bufferCleanerThreshold, int concurrencyLevel,
      final long evictionPeriod) {
    super(cacheLoader,evictionListener,indexHandler);
    initCache(byteBufferStore, serializer, bufferCleanerPeriod, bufferCleanerThreshold, concurrencyLevel,
        evictionPeriod);
  }
 
  /**
   * Inits the cache.
   *
   * @param byteBufferStore the byte buffer store
   * @param serializer the serializer
   * @param bufferCleanerPeriod the buffer cleaner period
   * @param bufferCleanerThreshold the buffer cleaner threshol
   * @param concurrencyLevel the concurrency level
   * @param evictionPeriod the eviction period
   */
  protected void initCache(OffHeapByteBufferStore byteBufferStore, Serializer<V> serializer, long bufferCleanerPeriod,
      final float bufferCleanerThreshold, int concurrencyLevel, final long evictionPeriod) {
    if(concurrencyLevel>11||concurrencyLevel<0){
      throw new IllegalArgumentException("ConcurrencyLevel must be between 0 and 11 inclusive!");
    }
    this.serializer = serializer;
    this.bufferStore = byteBufferStore;
    this.readWriteLock = new StripedReadWriteLock(concurrencyLevel);
    ScheduledExecutorService cleanerService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
      public Thread newThread(Runnable runnable) {
        return new Thread(runnable, "imcache:bufferCleaner(name="+getName()+",thread="+ NO_OF_CLEANERS.incrementAndGet() + ")");
      }
    });
    cleanerService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        cleanBuffers(bufferCleanerThreshold);
      }
    }, bufferCleanerPeriod, bufferCleanerPeriod, TimeUnit.MILLISECONDS);
    ScheduledExecutorService evictionService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
      public Thread newThread(Runnable runnable) {
        return new Thread(runnable, "imcache:evictionService(name="+getName()+",thread="+ NO_OF_EVICTORS.incrementAndGet() + ")");
      }
    });
    evictionService.scheduleAtFixedRate(new Runnable() {
      public void run() {
        doEviction(evictionPeriod);
      }
    }, bufferCleanerPeriod, evictionPeriod, TimeUnit.MILLISECONDS);
  }

  /*
   * (non-Javadoc)
   *
   * @see com.cetsoft.imcache.cache.Cache#put(java.lang.Object, java.lang.Object)
   */
  public void put(K key, V value) {
    writeLock(key);
    Pointer pointer = pointerMap.get(key);
    try {
      byte[] bytes = serializer.serialize(value);
      if (pointer == null) {
        pointer = bufferStore.store(bytes);
      } else {
        synchronized (pointer) {
          pointer = bufferStore.update(pointer, bytes);
        }
      }
      pointerMap.put(key, pointer);
    } finally {
      writeUnlock(key);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.cetsoft.imcache.cache.Cache#get(java.lang.Object)
   */
  public V get(K key) {
    Pointer pointer = pointerMap.get(key);
    if (pointer != null) {
      readLock(key);
      try {
        hit.incrementAndGet();
        synchronized (pointer) {
          byte[] payload = bufferStore.retrieve(pointer);
          return serializer.deserialize(payload);
        }
      } finally {
        readUnlock(key);
      }
    } else {
      miss.incrementAndGet();
      V value = cacheLoader.load(key);
      if (value != null) {
        put(key, value);
      }
      return value;
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see com.cetsoft.imcache.cache.Cache#invalidate(java.lang.Object)
   */
  public V invalidate(K key) {
    writeLock(key);
    try{
      Pointer pointer = pointerMap.get(key);
      if (pointer != null) {
        synchronized (pointer) {
          byte[] payload = bufferStore.remove(pointer);
          pointerMap.remove(key);
          return serializer.deserialize(payload);
        }
      }
    }finally{
      writeUnlock(key);
    }
    return null;
  }

  /*
   * (non-Javadoc)
   *
   * @see com.cetsoft.imcache.cache.Cache#contains(java.lang.Object)
   */
  public boolean contains(K key) {
    return pointerMap.containsKey(key);
  }

  /*
   * (non-Javadoc)
   *
   * @see com.cetsoft.imcache.cache.Cache#clear()
   */
  public void clear() {
    pointerMap.clear();
    bufferStore.free();
  }

  /*
   * (non-Javadoc)
   *
   * @see com.cetsoft.imcache.cache.Cache#hitRatio()
   */
  public double hitRatio() {
    return hit.get() / (hit.get() + miss.get());
  }

  /**
   * Read Lock for key is locked.
   *
   * @param key the key
   */
  protected void readLock(K key) {
    readWriteLock.readLock(Math.abs(key.hashCode()));
  }

  /**
   * Read Lock for key is unlocked.
   *
   * @param key the key
   */
  protected void readUnlock(K key) {
    readWriteLock.readUnlock(Math.abs(key.hashCode()));
  }

  /**
   * Write Lock for key is locked..
   *
   * @param key the key
   */
  protected void writeLock(K key) {
    readWriteLock.writeLock(Math.abs(key.hashCode()));
  }

  /**
   * Write Lock for key is unlocked.
   *
   * @param key the key
   */
  protected void writeUnlock(K key) {
    readWriteLock.writeUnlock(Math.abs(key.hashCode()));
  }
 
  /**
   * Clean buffers.
   *
   * @param bufferCleanerThreshold the buffer cleaner threshold
   */
  protected void cleanBuffers(final float bufferCleanerThreshold) {
    // Buffers can be fully dirty and we may not find any pointer to resolve that.
    // This case is so unlikely that we did not consider.
    Collection<Pointer> pointers = pointerMap.values();
    List<Pointer> pointersToBeRedistributed = new ArrayList<Pointer>();
    Map<OffHeapByteBuffer, Float> buffers = new HashMap<OffHeapByteBuffer, Float>();
    Set<Integer> buffersToBeCleaned = new HashSet<Integer>();
    // For all pointers we try to understand if there is a need for redistribution.
    for (Pointer pointer : pointers) {
      Float ratio = buffers.get(pointer.getOffHeapByteBuffer());
      if (ratio == null) {
        // calculate the ratio of the dirty
        ratio = getDirtyRatio(pointer);
        buffers.put(pointer.getOffHeapByteBuffer(), ratio);
      }
      if (ratio - bufferCleanerThreshold > DELTA) {
        pointersToBeRedistributed.add(pointer);
        buffersToBeCleaned.add(pointer.getOffHeapByteBuffer().getIndex());
      }
    }
    bufferStore.redistribute(pointersToBeRedistributed);
    for (Integer bufferIndex : buffersToBeCleaned) {
      bufferStore.free(bufferIndex);
    }
  }

  /**
   * Gets the dirty ratio.
   *
   * @param pointer the pointer
   * @return the dirty ratio
   */
  protected float getDirtyRatio(Pointer pointer) {
    return (float) ((double)pointer.getOffHeapByteBuffer().dirtyMemory() / (pointer.getOffHeapByteBuffer().freeMemory()
        + pointer.getOffHeapByteBuffer().usedMemory() + pointer.getOffHeapByteBuffer().dirtyMemory()));
  }
 
  /**
   * Do eviction.
   *
   * @param evictionPeriod the eviction period
   */
  protected void doEviction(final long evictionPeriod) {
    Set<Entry<K,Pointer>> entries = pointerMap.entrySet();
    for (Entry<K,Pointer> entry : entries) {
      synchronized (entry.getValue()) {
        if(entry.getValue().getAccessTime()+evictionPeriod<System.currentTimeMillis()){
          invalidate(entry.getKey());
        }
      }
    }
  }

}
TOP

Related Classes of com.cetsoft.imcache.cache.offheap.OffHeapCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.