Ehcache multi-level caching mechanism

1 Introduction

Ehcache is open source Java distributed cache a widely used. Mainly for common caching, Java EE and lightweight containers. It has an internal memory heap, the heap external memory, disk storage, the cache loader, the cache extensions, cache and so the exception handler.

This article will introduce within the heap memory, external memory heap analysis, cache mode, and a variety of disk storage principle combination of storage media.

2. hierarchical cache design

As a local cache frame, Ehcache supports multiple cache mode, there are three commonly used data storage medium:

  • The stack
    directly in the JAVA object JVM heap memory, the advantage of speed; the disadvantage of increased frequency of GC and GC time.
  • Outside the stack
    in the stack stored in the external memory serialized JAVA object, the advantage of not increasing the GC; disadvantage is slow access speed, requires additional processing time serialization and deserialization.
  • Disk
    cache data to disk, the advantage of power-down without losing data, a larger free space; disadvantage is much slower access speed than the outer stack.

For the three cache storage media, Ehcache combination supports three modes:

  • + Stack outside the reactor
  • Within the disk stack +
  • + Heap + stack within the outer disk

A typical stack structure of FIG. + + Outer disk stack is as follows:
Pictures .png

2.1 Timing Diagram

In the multilayer structure Ehcache the cache, referred to as the bottom Authoritative Tier, the remaining buffer layer is referred Caching Tier. Authoritative Tier layer data is the most complete, the remaining layers of the layer data are subsets of the data, data is stored only temporarily.

For example, the outer pile + mode stack, the stack of outer Authoritative Tier. The incore + + outer disk stack mode, the disk is Authoritative Tier.

2.1.1 put

Pictures .png

2.1.2 get

Pictures .png

3. Code Analysis

3.1 Cache use

        CacheManagerConfiguration<PersistentCacheManager> persistentManagerConfig = CacheManagerBuilder
            .persistence(new File("/tmp", "ehcache-junit"));

        PersistentCacheManager persistentCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
            .with(persistentManagerConfig).build();
        persistentCacheManager.init();

        ResourcePoolsBuilder resource = ResourcePoolsBuilder.newResourcePoolsBuilder()
            .heap(4, MemoryUnit.MB)
            .offheap(16, MemoryUnit.MB)
            .disk(256, MemoryUnit.MB, true);

        CacheConfiguration<Long, String> config = CacheConfigurationBuilder
            .newCacheConfigurationBuilder(Long.class, String.class, resource).build();
        Cache<Long, String> cache = persistentCacheManager.createCache("test",
            CacheConfigurationBuilder.newCacheConfigurationBuilder(config));

        cache.put(100L, "abc");
        System.out.println(cache.get(100L));
        System.out.println(cache.get(101L));

Heap space must be less than less than offhead disk, or throw an IllegalArgumentException
a java.lang.IllegalArgumentException: Tiering is Inversion: 'Pool. 4 MB heap {}' IS Not Smaller Within last '. 4 MB Disk Pool {(persistent)}'

Running the program created in the / tmp / ehcache-junit / file directory of test_a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 / offheap-disk-store directory, there are two files ehcache-disk-store.meta and ehcache-disk-store.data this directory, record cache metadata and data caches.

ehcache-disk-store.meta follows, the record type information plaintext key value and

#Key and value types
#Wed Jul 10 12:52:06 CST 2019
keyType=java.lang.Long
valueType=java.lang.String

ehcache-disk-store.data records the sequence of the data cache.

3.2 Serialization

When the stack and external disk storage, must first target sequence into java.nio.ByteBuffer, Ehcache following code allows users to customize incoming sequence classes.

CacheManagerBuilder.newCacheManagerBuilder().withSerializer(Employee.class,
  EmployeeSerializer.class).withSerializer(Person.class, PersonSerializer.class)

Ehcache own serializer supports the following types:

  • java.io.Serializable
  • java.lang.Long
  • java.lang.Integer
  • java.lang.Float
  • java.lang.Double
  • java.lang.Character
  • java.lang.String
  • byte[]

LongSerializer logic is as follows:

  @Override
  public ByteBuffer serialize(Long object) {
    ByteBuffer byteBuffer = ByteBuffer.allocate(8);
    byteBuffer.putLong(object).flip();
    return byteBuffer;
  }

  @Override
  public Long read(ByteBuffer binary) throws ClassNotFoundException {
    return binary.getLong();
  }

StringSerializer logic is as follows:

public ByteBuffer serialize(String object) {
    int length = object.length();

    try(ByteArrayOutputStream bout = new ByteArrayOutputStream(length)) {
      int i = 0;

      for (; i < length; i++) {
        char c = object.charAt(i);
        if (c == 0x0000 || c > 0x007f) {
          break;
        }
        bout.write(c);
      }

      for (; i < length; i++) {
        char c = object.charAt(i);
        if (c == 0x0000) {
          bout.write(0xc0);
          bout.write(0x80);
        } else if (c < 0x0080) {
          bout.write(c);
        } else if (c < 0x800) {
          bout.write(0xc0 | ((c >>> 6) & 0x1f));
          bout.write(0x80 | (c & 0x3f));
        } else {
          bout.write(0xe0 | ((c >>> 12) & 0x1f));
          bout.write(0x80 | ((c >>> 6) & 0x3f));
          bout.write(0x80 | (c & 0x3f));
        }
      }

      return ByteBuffer.wrap(bout.toByteArray());

    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  @Override
  public String read(ByteBuffer binary) throws ClassNotFoundException {
    StringBuilder sb = new StringBuilder(binary.remaining());
    int i = binary.position();
    int end = binary.limit();
    for (; i < end; i++) {
      byte a = binary.get(i);
      if (((a & 0x80) != 0)) break;
      sb.append((char) a);
    }

    for (; i < end; i++) {
      byte a = binary.get(i);
      if ((a & 0x80) == 0) {
        sb.append((char) a);
      } else if ((a & 0xe0) == 0xc0) {
        sb.append((char) (((a & 0x1f) << 6) | ((binary.get(++i) & 0x3f))));
      } else if ((a & 0xf0) == 0xe0) {
        sb.append((char) (((a & 0x0f) << 12) | ((binary.get(++i) & 0x3f) << 6) | (binary.get(++i) & 0x3f)));
      } else {
        //these remaining stanzas are for compatibility with the previous regular UTF-8 codec
        int codepoint;
        if ((a & 0xf8) == 0xf0) {
          codepoint = ((a & 0x7) << 18) | ((binary.get(++i) & 0x3f) << 12) | ((binary.get(++i) & 0x3f) << 6) | ((binary.get(++i) & 0x3f));
        } else if ((a & 0xfc) == 0xf8) {
          codepoint = ((a & 0x3) << 24) | ((binary.get(++i) & 0x3f) << 18) | ((binary.get(++i) & 0x3f) << 12) | ((binary.get(++i) & 0x3f) << 6) | ((binary.get(++i) & 0x3f));
        } else if ((a & 0xfe) == 0xfc) {
          codepoint = ((a & 0x1) << 30) | ((binary.get(++i) & 0x3f) << 24) | ((binary.get(++i) & 0x3f) << 18) | ((binary.get(++i) & 0x3f) << 12) | ((binary.get(++i) & 0x3f) << 6) | ((binary.get(++i) & 0x3f));
        } else {
          throw new SerializerException("Unexpected encoding");
        }
        sb.appendCodePoint(codepoint);
      }
    }

    return sb.toString();
  }

3.3 offheap get and put

3.3.1 get

Outer stack cache read and write logic, mainly in the org.ehcache.impl.internal.store.offheap.AbstractOffHeapStore

  private Store.ValueHolder<V> internalGet(K key, final boolean updateAccess, final boolean touchValue) throws StoreAccessException {

    final StoreEventSink<K, V> eventSink = eventDispatcher.eventSink();
    final AtomicReference<OffHeapValueHolder<V>> heldValue = new AtomicReference<>();
    try {
      OffHeapValueHolder<V> result = backingMap().computeIfPresent(key, (mappedKey, mappedValue) -> {
        long now = timeSource.getTimeMillis();

        if (mappedValue.isExpired(now)) {
          onExpiration(mappedKey, mappedValue, eventSink);
          return null;
        }

        // 更新访问时间
        if (updateAccess) {
          mappedValue.forceDeserialization();
          OffHeapValueHolder<V> valueHolder = setAccessTimeAndExpiryThenReturnMapping(mappedKey, mappedValue, now, eventSink);
          if (valueHolder == null) {
            heldValue.set(mappedValue);
          }
          return valueHolder;
        } else if (touchValue) {
          mappedValue.forceDeserialization();
        }
        return mappedValue;
      });
      if (result == null && heldValue.get() != null) {
        result = heldValue.get();
      }
      eventDispatcher.releaseEventSink(eventSink);
      return result;
    } catch (RuntimeException re) {
      eventDispatcher.releaseEventSinkAfterFailure(eventSink, re);
      throw handleException(re);
    }
  }

4. Summary

Local caching service development is micro frequently used functions, developers often use native ConcurrentHashmap, Guava, Ehcache, etc., often on top of the source code analysis, we can understand hierarchical caching mechanism Ehcache and usage scenarios.

5. Reference

[https://www.ehcache.org/documentation/2.8/get-started/storage-options.html]
[https://www.ehcache.org/documentation/3.4/tiering.html]

Guess you like

Origin blog.csdn.net/a860MHz/article/details/95328906