Ehcache3 入门

本文是基于Ehcache 3.7官方文档,总结的自己比较在意的内容。详细内容可以参看官方文档:Ehcache 3.7 Documentation Overview.

一、介绍

1、什么是Ehcache

Ehcache 是一个开源的、基于标准的,健壮、可靠、快速、简单、轻量级的java分布式缓存,支持与其他框架的集成,是Hibernate默认的CacheProvider。同时也实现了JSR107的规范,是Jcache的一种实现。
Ehcache 目前提供四层模型,支持四种级别:

  • heap
  • off-heap
  • disk
  • clustered

同时提供了java内配置和xml配置,提供线程池、事务、管理器等,并支持扩展和监听事件,提供了disk持久化以及clustered持久化。

2、依赖引入

Ehcache非常轻量级,唯一需要的依赖是slf4j。
maven使用:

<dependency>
		<groupId>org.ehcache</groupId>
		<artifactId>ehcache</artifactId>
		<version>3.7.1</version>
</dependency>

如果与其他框架集成,请相应的引入对应的依赖。
如果需要使用事务,从3.1版本,应该就单独剥离出去了,所以需要引入:

<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache-transactions</artifactId>
    <version>3.7.1</version>
</dependency>

3、分层架构

在这里插入图片描述 Ehcache3.7 支持堆、堆外、磁盘以及集群缓存;但是除了堆之外外的三种缓存,缓存的键值对必须支持序列化和反序列化。
我们在使用的时候,可以单独使用任意一个,比如:

CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, Integer.class,
				ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(1, MemoryUnit.GB));
  • heap:堆缓存,受到jvm的管控,可以不序列化,速度最快;默认使用的是引用传递,也可以使用复制器来进行值传递。可以设置缓存条数或者缓存的大小来进行堆缓存大小的设置。尽量不要设置的过大,否则容易引起GC.(如果设置缓存的大小,则计算比较麻烦,同时又多了一个计算缓存大小的过程)。
  • off-heap:堆外缓存,不受jvm的管控,受到RAM的限制,在ehcache中配置,至少1M。通过-XX:MaxDirectMemorySize限制ehcache可分配的最大堆外内存,但是实际使用发现,不配置也能使用。如果不配置,使用堆外缓存时,ehcache将会使用jvm的内存,最大值为堆内存,但实际比-Xmx要小,可以通过Runtime,getRuntime().maxMemory()获取。因此,对于堆内对垃圾收集的影响过于严重的大量数据,应该选择堆外。
  • disk:磁盘存储,尽量使用高性能的SSD。这一层的存储,不能在不同的CacheManager之间共享!
  • clustered:集群

以上只有 heap 支持 运行时改变大小!

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
Cache<Long, String> cache = cacheManager.createCache("runforupdate", CacheConfigurationBuilder
				.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(2)).build());
//do something
//change the heap 
cache.getRuntimeConfiguration()
	.updateResourcePools(ResourcePoolsBuilder.heap(10).build());
4、层组合

如果需要使用多个层,则需要遵循一定的规则。
规则:

  • 必须有heap层
  • disk和clusterd不能同时存在
  • 层的大小应该采用金字塔的方式,即金字塔上的层比下的层使用更少的内存
    在这里插入图片描述

根据规则进行以下有效配置:

  • heap + offheap
  • heap + offheap + disk
  • heap + offheap + clustered
  • heap + disk
  • heap + clustered

对于多层,put、get的顺序:

  • 当将一个值放入缓存时,它直接最低层。比如heap + offheap + disk,直接会存储在disk层。
  • 当获取一个值,从最高层获取,如果没有继续向下一层获取,一旦获取到,会向上层推送,同时上层存储该值。
5、访问模式

Ehcache支持以下几种模式:

  • Cache-aside
  • Cache-as-SoR
    • Read-through
    • Write-through
    • Write-behind
六、缓存配置

配置方式有两种:

  • java配置
  • XML配置

二、使用

1、缓存过期和淘汰策略
(1) 过期策略

在缓存级别配置,有三种策略:

  • no expiry : 永不过期
  • time-to-live :创建后一段时间过期
  • time-to-idle : 访问后一段时间过期

示例:

  • java
		CacheConfiguration<String, String> configuration = CacheConfigurationBuilder
				.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
				.withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(Duration.ofSeconds(300))).build();
  • xml
<cache alias="test">
  <expiry>
    <ttl unit="seconds">300</ttl> 
  </expiry>
  <heap>10</heap>
</cache>

支持实现ExpiryPolicy接口来自定义过期策略。

(2) 淘汰策略
  • FIFO
  • LRU 默认策略
  • LFU
2、自定义Serializer

除了heap层,可以存储对象;其他所有层都只能通过二进制形式表示,所以需要对对象进行序列化和反序列化。Ehcache提供了Serializer来,所以我们除了可以直接实现Serializable,也可以通过Ehcache的Serializer来自定义。这里使用Kryo(比java的Serializable更快更小)结合Ehcache提供的Serizlizer来进行序列化和反序列化。

Ehcache默认给基本的数据类型都提供了优化的序列化器,如下:

  • java.io.Serializable
  • java.lang.Long
  • java.lang.Integer
  • java.lang.Float
  • java.lang.Double
  • java.lang.Character
  • java.lang.String
  • byte[]
(1) Serializer接口
package org.ehcache.spi.serialization;

import java.nio.ByteBuffer;

/**
 * Defines the contract used to transform type instances to and from a serial form.
 * <p>
 * Implementations must be thread-safe.
 * <p>
 * When used within the default serialization provider, there is an additional requirement.
 * The implementations must define a constructor that takes in a {@code ClassLoader}.
 * The {@code ClassLoader} value may be {@code null}.  If not {@code null}, the class loader
 * instance provided should be used during deserialization to load classes needed by the deserialized objects.
 * <p>
 * The serialized object's class must be preserved; deserialization of the serial form of an object must
 * return an object of the same class. The following contract must always be true:
 * <p>
 * {@code object.getClass().equals( mySerializer.read(mySerializer.serialize(object)).getClass())}
 * @param <T> the type of the instances to serialize
 * @see SerializationProvider
 */
public interface Serializer<T> {

  /**
   * Transforms the given instance into its serial form.
   * @param object the instance to serialize
   * @return the binary representation of the serial form
   * @throws SerializerException if serialization fails
   */
  ByteBuffer serialize(T object) throws SerializerException;

  /**
   * Reconstructs an instance from the given serial form.
   * @param binary the binary representation of the serial form
   * @return the de-serialized instance
   * @throws SerializerException if reading the byte buffer fails
   * @throws ClassNotFoundException if the type to de-serialize to cannot be found
   */
  T read(ByteBuffer binary) throws ClassNotFoundException, SerializerException;

  /**
   * Checks if the given instance and serial form {@link Object#equals(Object) represent} the same instance.
   * @param object the instance to check
   * @param binary the serial form to check
   * @return {@code true} if both parameters represent equal instances, {@code false} otherwise
   * @throws SerializerException if reading the byte buffer fails
   * @throws ClassNotFoundException if the type to de-serialize to cannot be found
   */
  boolean equals(T object, ByteBuffer binary) throws ClassNotFoundException, SerializerException;

}

该接口只有三个方法,但是需要注意以下几点:

  • A. 实现类必须提供一个带有ClassLoader参数的构造器,比如:
public MySerializer(ClassLoader classLoader) {
}
  • B. 实现类必须线程安全
  • C. 被序列化对象的class必须被保存;被序列化对象的class与反序列化后的对象的class必须相等。(如果在构造CacheManager时没有指定classLoader,则使用ehcache的默认classLoader)

同时如果实现 java.io.Closeable 接口,当关闭缓存管理器时,将调用该序列化器的close方法。

(2) 通用序列化器

Ehcache不建议使用通用序列化器,建议将序列化器注册限制在具体的类中,可以根据实际来进行改变。
使用Kryo作为序列化/反序列化工具,

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.objenesis.strategy.StdInstantiatorStrategy;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import de.javakaffee.kryoserializers.ArraysAsListSerializer;

public class KryoUtil {

	private static ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
		@Override
		protected Kryo initialValue() {
			Kryo kryo = new Kryo();
			// 支持对象循环引用,默认 true
			kryo.setReferences(true);

			// 是否需要强制注册,默认 false
			kryo.setRegistrationRequired(false);

			// 支持无参构造器等(推荐每个类都有自己的无参构造器,可以为private)
			((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy())
					.setFallbackInstantiatorStrategy(new StdInstantiatorStrategy());
			// 支持Arrays.asList()
			kryo.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
			return kryo;
		}
	};

	private static ThreadLocal<Output> outs = ThreadLocal.withInitial(() -> new Output(1024, 1024 * 1024));
	private static ThreadLocal<Input> ins = ThreadLocal.withInitial(() -> {
		return new Input();
	});

	/**
	 * 
	 * @return
	 */
	public static Kryo get() {
		return kryos.get();
	}

	/**
	 * Serializer
	 * 
	 * @return
	 */
	public static byte[] serializer(Object object) {
		try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); Output out = outs.get();) {
			out.setOutputStream(bos);
			kryos.get().writeClassAndObject(out, object);
			out.flush();
			return bos.toByteArray();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 序列化
	 * 
	 * @return
	 */
	public static ByteBuffer serializerByteBuffer(Object object) {
		return ByteBuffer.wrap(serializer(object));
	}

	/**
	 * 反序列化
	 * 
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static <T> T deserializer(byte[] b) {
		try (Input in = ins.get();) {
			in.setBuffer(b, 0, b.length);
			return (T) KryoUtil.get().readClassAndObject(in);
		}
	}

	/**
	 * 反序列化
	 * 
	 * @return
	 */
	public static <T> T deserializerByteBuffer(ByteBuffer binary) {
		byte[] bytes = new byte[binary.capacity()];
		binary.get(bytes);
		return deserializer(bytes);
	}
}

序列化器:

import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;

import org.ehcache.spi.serialization.Serializer;
import org.ehcache.spi.serialization.SerializerException;

import com.wzy.map.util.serializer.KryoUtil;

public class EhcacheSerializer<T> implements Serializer<T>, Closeable {

	public EhcacheSerializer() {
	}

	public EhcacheSerializer(ClassLoader loader) {
	}

	@Override
	public ByteBuffer serialize(T object) throws SerializerException {
		return KryoUtil.serializerByteBuffer(object);
	}

	@Override
	public T read(ByteBuffer binary) throws ClassNotFoundException, SerializerException {
		return KryoUtil.deserializerByteBuffer(binary);
	}

	@Override
	public boolean equals(T object, ByteBuffer binary) throws ClassNotFoundException, SerializerException {
		return object.equals(read(binary));
	}

	@Override
	public void close() throws IOException {
		System.out.println("close...");
	}

}
3、自定义Copier

官网定义,Copier is the Ehcache abstraction solving this: it is specific to the on-heap store.
默认情况下,堆上存储,保存的是对象的引用,可以通过key和value定义对应的Copier来自定义是存储引用还是重新创建一个对象。

public interface Copier<T> {

  /**
   * Creates a copy of the instance passed in.
   * <p>
   * This method is invoked as a value is read from the cache.
   *
   * @param obj the instance to copy
   * @return the copy of the {@code obj} instance
   */
  T copyForRead(T obj);

  /**
   * Creates a copy of the instance passed in.
   * <p>
   * This method is invoked as a value is written to the cache.
   *
   * @param obj the instance to copy
   * @return the copy of the {@code obj} instance
   */
  T copyForWrite(T obj);
}

可以使用Kryo来进行复制。

4、Cache as SOR

Ehcache 支持Read Through、Write Through、Write Behind;在3.7中,提供了CacheLoaderWriter接口。
需要注意的是,实现类必须是线程安全的,同时write behind级别不支持重试失败的写操作,需要我们自己实现。

这里业务代码不再同时维护cache和sor,业务代码只操作cache,把cache当作sor;而cache再对真实的数据库等进行读写操作!
这里以Write Behind为例子来简单模拟一下场景:

import java.util.concurrent.ConcurrentMap;

import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
import org.ehcache.spi.loaderwriter.CacheLoaderWriter;

public class SampleLoaderWriter<K, V> implements CacheLoaderWriter<K, V> {
	// sor
	private ConcurrentMap<K, V> map;

	public SampleLoaderWriter() {
		map = new ConcurrentHashMap<K, V>();
		// do something
	}

	@Override
	public V load(K key) throws Exception {
		return (V) map.get(key);
	}

	@Override
	public void write(K key, V value) throws Exception {
		map.putIfAbsent(key, value);
	}

	@Override
	public void delete(K key) throws Exception {
		map.remove(key);
	}

}

配置CacheLoaderWriter:

CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder().build(true);
		Cache<Long, String> cache = cacheManager.createCache("cache",
				CacheConfigurationBuilder
						.newCacheConfigurationBuilder(Long.class, String.class, ResourcePoolsBuilder.heap(10).offhea)
						.withLoaderWriter(new SampleLoaderWriter<Long, String>())
						.add(WriteBehindConfigurationBuilder
								.newBatchedWriteBehindConfiguration(10, TimeUnit.SECONDS, 3).queueSize(10)
								.concurrencyLevel(1).enableCoalescing())
						.build());

业务代码只需要处理cache就可以!

5、监听器

Ehcache 提供 CacheEventListerner 来监听缓存事件。

/**
 * Definition of the contract for implementing listeners to receive {@link CacheEvent}s from a
 * {@link org.ehcache.Cache Cache}.
 *
 * @param <K> the key type for the observed cache
 * @param <V> the value type for the observed cache
 */
public interface CacheEventListener<K, V> {

  /**
   * Invoked on {@link org.ehcache.event.CacheEvent CacheEvent} firing.
   * <p>
   * This method is invoked according to the {@link EventOrdering}, {@link EventFiring} and
   * {@link EventType} requirements provided at listener registration time.
   * <p>
   * Any exception thrown from this listener will be swallowed and logged but will not prevent other listeners to run.
   *
   * @param event the actual {@code CacheEvent}
   */
  void onEvent(CacheEvent<? extends K, ? extends V> event);

}

实现 onEvent() 方法即可。因为监听器是在缓存级别注册的,因此只接收已注册的缓存的事件。可以在运行时注册监听器。
注册示例:

CacheEventListenerConfigurationBuilder cacheEventListenerConfiguration = CacheEventListenerConfigurationBuilder
				.newEventListenerConfiguration(new ListenerObject(), EventType.CREATED, EventType.UPDATED,
						EventType.REMOVED)
				.unordered().asynchronous();
		final CacheManager manager = CacheManagerBuilder.newCacheManagerBuilder()
				.withCache("foo",
						CacheConfigurationBuilder
								.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.heap(10))
								.add(cacheEventListenerConfiguration))
				.build(true);
6、Eviction Advisor

Ehcache 提供EvictionAdvisor 接口,当ehcache试图从缓存中删除数据时,将调用该接口以确定给定的数据是否该清除。

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值