赞
踩
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存、Java EE和轻量级容器。它具有堆内内存、堆外内存、磁盘存储,缓存加载器,缓存扩展,缓存异常处理程序等特点。
本文将介绍堆内内存、堆外内存、磁盘存储多种存储介质组合的缓存模式及原理分析。
作为本地缓存框架, Ehcache支持多层缓存模式,常用的有三种数据存储介质:
对于这三种缓存存储介质,Ehcache支持三种组合模式:
典型的堆内+堆外+磁盘的结构图如下:
在Ehcache的多层缓存结构中,最底层被称为Authoritative Tier,其余的缓存层被称为Caching Tier。Authoritative Tier层数据是最全的,其余层的数据都是该层的数据子集,只是临时存储数据。
比如,堆内+堆外模式中,堆外为Authoritative Tier。堆内+堆外+磁盘模式中,磁盘为Authoritative Tier。
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小于offhead小于disk,否则抛出IllegalArgumentException
java.lang.IllegalArgumentException: Tiering Inversion: ‘Pool {4 MB heap}’ is not smaller than ‘Pool {4 MB disk(persistent)}’
程序运行后在/tmp/ehcache-junit/file目录下创建了test_a94a8fe5ccb19ba61c4c0873d391e987982fbbd3/offheap-disk-store目录,该目录下存在两个文件ehcache-disk-store.meta和ehcache-disk-store.data,记录缓存的元数据和缓存数据。
ehcache-disk-store.meta内容如下,明文记录了key和value的类型信息
#Key and value types
#Wed Jul 10 12:52:06 CST 2019
keyType=java.lang.Long
valueType=java.lang.String
ehcache-disk-store.data则记录了缓存的序列化数据。
堆外和磁盘存储时,必须先将对象序列化为java.nio.ByteBuffer,Ehcache允许用户按下面的代码传入自定义的序列化类。
CacheManagerBuilder.newCacheManagerBuilder().withSerializer(Employee.class,
EmployeeSerializer.class).withSerializer(Person.class, PersonSerializer.class)
Ehcache自带的序列化器支持如下的类型:
LongSerializer逻辑如下:
@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逻辑如下:
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(); }
堆外缓存的读写逻辑主要在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); } }
本地缓存是微服务开发中经常使用的功能,开发者通常使用原生的ConcurrentHashmap、Guava、Ehcache等,经常上面的源码分析,我们可以理解Ehcache的分级缓存机制和使用场景。
[https://www.ehcache.org/documentation/2.8/get-started/storage-options.html]
[https://www.ehcache.org/documentation/3.4/tiering.html]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。