在JetCache中不仅可以通过在类和接口的函数上使用注解@Cached、@CacheUpdate和@CacheInvalidate等实现缓存加载、更新和删除操作,也支持通过调用API接口的形式来实现缓存的加载、更新和删除操作。
缓存接口
缓存接口的定义如下:
/**
* 缓存接口,支持空值。
*/
public interface Cache<K, V> extends Closeable {
/**
* 从缓存中获取一个条目。
* <p>如果缓存的构造器指定了一个{@link CacheLoader},并且缓存中没有关联,
* 它会尝试加载该条目。</p>
* <p>如果在缓存访问过程中发生错误,方法会返回null而不是抛出异常。</p>
* @param key 要返回其关联值的键
* @return 与指定键关联的值。null可能表示:<ul>
* <li>条目不存在或已过期</li>
* <li>条目的值为null</li>
* <li>在缓存访问过程中发生错误(不抛出异常)</li>
* </ul>
* @throws CacheInvokeException 仅当加载器抛出异常时
* @see CacheLoader
* @see #GET(Object)
*/
default V get(K key) throws CacheInvokeException {
CacheGetResult<V> result = GET(key);
if (result.isSuccess()) {
return result.getValue();
} else {
return null;
}
}
/**
* 从缓存中获取一组条目,将它们作为与请求的键集相关联的值的Map返回。
* <p>如果缓存的构造器指定了一个{@link CacheLoader},并且缓存中没有关联,
* 它会尝试加载条目。</p>
* <p>如果在缓存访问过程中发生错误,方法不会抛出异常。</p>
* @param keys 要返回其关联值的键集合。
* @return 为给定键找到的条目的映射。在缓存中未找到的键不包含在返回的映射中。
* @throws CacheInvokeException 仅当加载器抛出异常时
* @see CacheLoader
* @see #GET_ALL(Set)
*/
default Map<K, V> getAll(Set<? extends K> keys) throws CacheInvokeException {
MultiGetResult<K, V> cacheGetResults = GET_ALL(keys);
return cacheGetResults.unwrapValues();
}
/**
* 将指定的值与指定的键在缓存中关联起来。
* <p>如果在缓存访问过程中发生错误,方法不会抛出异常。</p>
* <p>如果实现支持异步操作,此方法的缓存操作为异步。</p>
* @param key 与指定值关联的键
* @param value 要与指定键关联的值
* @see #PUT(Object, Object)
*/
default void put(K key, V value) {
PUT(key, value);
}
/**
* 将指定映射中的所有条目复制到缓存中。
* <p>如果在缓存访问过程中发生错误,方法不会抛出异常。</p>
* <p>如果实现支持异步操作,此方法的缓存操作为异步。</p>
* @param map 要存储在此缓存中的映射。
* @see #PUT_ALL(Map)
*/
default void putAll(Map<? extends K, ? extends V> map) {
PUT_ALL(map);
}
/**
* 将指定映射中的所有条目复制到缓存中。
* <p>如果在访问缓存时发生错误,该方法不会抛出异常。</p>
* @param map 要存储在缓存中的映射。
* @param expireAfterWrite KV关联的TTL(生存时间)
* @param timeUnit expireAfterWrite的时间单位
* @see #PUT_ALL(Map, long, TimeUnit)
*/
default void putAll(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {
PUT_ALL(map, expireAfterWrite, timeUnit);
}
/**
* 原子地将指定的键与给定的值关联,如果它还没有与一个值关联的话。
* <p>如果在缓存访问过程中发生错误,方法不会抛出异常。</p>
* <p>{@link MultiLevelCache} 不支持此方法。</p>
* @param key 要与指定的值关联的键
* @param value 要与指定的键关联的值
* @return 如果设置了值,则为true;如果KV关联在缓存中不存在,或在缓存访问过程中发生错误,则为false。
* @see #PUT_IF_ABSENT(Object, Object, long, TimeUnit)
*/
default boolean putIfAbsent(K key, V value) {
CacheResult result = PUT_IF_ABSENT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
return result.getResultCode() == CacheResultCode.SUCCESS;
}
/**
* 如果存在,则从缓存中移除指定键的映射。
* <p>如果在缓存访问过程中发生错误,方法不会抛出异常。</p>
* @param key 要从缓存中移除映射的键
* @return 如果键成功移除,则为true;如果KV关联在缓存中不存在,或在缓存访问过程中发生错误,则为false。
* @see #REMOVE(Object)
*/
default boolean remove(K key) {
return REMOVE(key).isSuccess();
}
/**
* 移除指定键的条目。
* <p>如果在缓存访问过程中发生错误,方法不会抛出异常。</p>
* <p>如果实现支持异步操作,此方法的缓存操作是异步的。</p>
* @param keys 要移除的键
* @see #REMOVE_ALL(Set)
*/
default void removeAll(Set<? extends K> keys) {
REMOVE_ALL(keys);
}
/**
* 如果与给定键关联的有值,则返回该值;否则使用加载器加载值并返回,然后更新缓存。
* @param key 键
* @param loader 值加载器
* @return 与键关联的值
* @see CacheConfig#isCacheNullValue()
*/
default V computeIfAbsent(K key, Function<K, V> loader) {
return computeIfAbsent(key, loader, config().isCacheNullValue());
}
/**
* 如果与给定键关联的有值,则返回该值;否则使用加载器加载值并返回,然后根据参数决定是否更新缓存。
* @param key 键
* @param loader 值加载器
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否将null值放入缓存
* @return 与键关联的值
*/
V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull);
/**
* 如果与给定键关联的有值,则返回该值;否则使用加载器加载值并返回,然后根据参数决定是否更新缓存,并设置过期时间。
* @param key 键
* @param loader 值加载器
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否将null值放入缓存
* @param expireAfterWrite 缓存项的TTL(生存时间)
* @param timeUnit expireAfterWrite的时间单位
* @return 与键关联的值
*/
V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull, long expireAfterWrite, TimeUnit timeUnit);
}
上面的方法是对缓存的增删改查操作,代码逻辑相对比较简单,默认是直接调用Cache接口中的GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法来实现的。其中computeIfAbsent方法是用于解决缓存穿透的问题,即当缓存中没有对应的数据时,需要调用指定的loader获取相应的数据并写入到缓存中,跟Map中computeIfAbsent方法基本上原理是相似的,即如果缓存中key不存在会调用loader获取缓存的值,并写入到缓存中。
在上面的接口定义中,我们注意到JetCache缺少批量加载缓存的功能,无论是JetCache的注解亦或是API接口都不支持,我们后续会专门增加一个章节用于介绍如何实现缓存的批量加载的功能。当然,JetCache也不支持根据缓存的键或值自定义缓存有效期的能力,我们在后面都会介绍如何进行扩展。
只需要实现GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法就能够实现缓存的增删改查操作。对应的源码如下:
/**
* 将指定映射中的所有条目复制到缓存中。
* <p>如果实现支持异步操作,调用此方法后缓存访问可能并未完成。
* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。
* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。</p>
* @param map 要存储在缓存中的映射。
* @return 操作结果
*/
default CacheResult PUT_ALL(Map<? extends K, ? extends V> map) {
if (map == null) {
return CacheResult.FAIL_ILLEGAL_ARGUMENT;
}
return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
}
/**
* 将指定映射中的所有条目复制到缓存中。
* <p>如果实现支持异步操作,调用此方法后缓存访问可能并未完成。
* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。
* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。</p>
* @param map 要存储在缓存中的映射。
* @param expireAfterWrite KV关联的TTL(生存时间)
* @param timeUnit expireAfterWrite的时间单位
* @return 操作结果
*/
CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);
/**
* 如果缓存中存在指定键的映射,则从缓存中移除该映射。
* <p>如果实现支持异步操作,调用此方法后缓存访问可能并未完成。
* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。
* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。</p>
* @param key 要从缓存中移除映射的键
* @return 操作结果
*/
CacheResult REMOVE(K key);
/**
* 移除指定键的映射。
* <p>如果实现支持异步操作,调用此方法后缓存访问可能并未完成。
* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。
* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。</p>
* @param keys 要移除的键集合
* @return 操作结果
*/
CacheResult REMOVE_ALL(Set<? extends K> keys);
/**
* 如果指定键尚未与值关联,则将其与给定值关联。
* <p>如果实现支持异步操作,调用此方法后缓存访问可能并未完成。
* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。
* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。</p>
* @param key 与指定值关联的键
* @param value 要与指定键关联的值
* @param expireAfterWrite KV关联的TTL(生存时间)
* @param timeUnit expireAfterWrite的时间单位
* @return 如果指定键尚未与值关联,则返回SUCCESS;如果指定键已与值关联,则返回EXISTS;如果发生错误,则返回FAIL。
*/
CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
我们知道缓存分为进程内缓存和远程缓存,进程内的缓存如LinkedHashMap和caffeine,远程缓存如通过lettuce、redisson和spring-data-redis操作的redis,还有就是现在使用越来越少的MemeryCache。这些只需要实现上面的现GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法就可以了。
但是细节上不会那么简单,我会在下面继续进行介绍,但是介绍之前还是需要介绍一下锁,因为实现使用缓存的场景肯定会涉及多线程,这样就需要使用锁来避免不同线程同时去对相同的键进行缓存的增删改操作了。在Cache接口中默认实现了锁的方法,源码如下:
/**
* 尝试使用缓存获取指定键的独占锁,此方法不会阻塞。
* 用法示例:
* <pre>
* try(AutoReleaseLock lock = cache.tryLock("MyKey",100, TimeUnit.SECONDS)){
* if(lock != null){
* // 执行某些操作
* }
* }
* </pre>
* <p>{@link MultiLevelCache} 将使用最后一级缓存来支持此操作。</p>
* @param key 锁键
* @param expire 锁的过期时间
* @param timeUnit 锁的过期时间单位
* @return 如果成功获取锁,则返回一个 AutoReleaseLock 实例(实现了 java.lang.AutoCloseable 接口)。
* 如果尝试失败(表示另一个线程/进程/服务器已持有锁),或在访问缓存时发生错误,则返回 null。
* @see #tryLockAndRun(Object, long, TimeUnit, Runnable)
*/
@SuppressWarnings("unchecked")
default AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit) {
if (key == null) {
return null;
}
// 生成唯一的UUID作为锁标识
final String uuid = UUID.randomUUID().toString();
// 计算锁的过期时间戳
final long expireTimestamp = System.currentTimeMillis() + timeUnit.toMillis(expire);
// 获取缓存配置
final CacheConfig config = config();
// 定义一个AutoReleaseLock,它包含解锁逻辑
AutoReleaseLock lock = () -> {
int unlockCount = 0;
// 尝试解锁次数
while (unlockCount++ < config.getTryLockUnlockCount()) {
// 如果锁未过期,则尝试解锁
if(System.currentTimeMillis() < expireTimestamp) {
CacheResult unlockResult = REMOVE(key);
// 解锁结果处理
if (unlockResult.getResultCode() == CacheResultCode.FAIL
|| unlockResult.getResultCode() == CacheResultCode.PART_SUCCESS) {
logger.info("[tryLock] [{} of {}] [{}] unlock failed. Key={}, msg = {}",
unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getMessage());
// 重试解锁
} else if (unlockResult.isSuccess()) {
logger.debug("[tryLock] [{} of {}] [{}] successfully release the lock. Key={}",
unlockCount, config.getTryLockUnlockCount(), uuid, key);
return;
} else {
logger.warn("[tryLock] [{} of {}] [{}] unexpected unlock result: Key={}, result={}",
unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getResultCode());
return;
}
} else {
// 锁已过期
logger.info("[tryLock] [{} of {}] [{}] lock already expired: Key={}",
unlockCount, config.getTryLockUnlockCount(), uuid, key);
return;
}
}
};
int lockCount = 0;
Cache cache = this;
// 尝试加锁次数
while (lockCount++ < config.getTryLockLockCount()) {
// 尝试添加锁
CacheResult lockResult = cache.PUT_IF_ABSENT(key, uuid, expire, timeUnit);
// 加锁结果处理
if (lockResult.isSuccess()) {
logger.debug("[tryLock] [{} of {}] [{}] successfully get a lock. Key={}",
lockCount, config.getTryLockLockCount(), uuid, key);
return lock;
} else if (lockResult.getResultCode() == CacheResultCode.FAIL || lockResult.getResultCode() == CacheResultCode.PART_SUCCESS) {
// 缓存访问失败时的处理逻辑
logger.info("[tryLock] [{} of {}] [{}] cache access failed during get lock, will inquiry {} times. Key={}, msg={}",
lockCount, config.getTryLockLockCount(), uuid,
config.getTryLockInquiryCount(), key, lockResult.getMessage());
int inquiryCount = 0;
// 尝试查询次数
while (inquiryCount++ < config.getTryLockInquiryCount()) {
// 尝试查询锁状态
CacheGetResult inquiryResult = cache.GET(key);
// 查询结果处理
if (inquiryResult.isSuccess()) {
if (uuid.equals(inquiryResult.getValue())) {
// 成功获得锁
logger.debug("[tryLock] [{} of {}] [{}] successfully get a lock after inquiry. Key={}",
inquiryCount, config.getTryLockInquiryCount(), uuid, key);
return lock;
} else {
// 不是锁的所有者
logger.debug("[tryLock] [{} of {}] [{}] not the owner of the lock, return null. Key={}",
inquiryCount, config.getTryLockInquiryCount(), uuid, key);
return null;
}
} else {
logger.info("[tryLock] [{} of {}] [{}] inquiry failed. Key={}, msg={}",
inquiryCount, config.getTryLockInquiryCount(), uuid, key, inquiryResult.getMessage());
// 重试查询
}
}
} else {
// 其他持有锁
logger.debug("[tryLock] [{} of {}] [{}] others holds the lock, return null. Key={}",
lockCount, config.getTryLockLockCount(), uuid, key);
return null;
}
}
// 所有尝试均未成功获得锁
logger.debug("[tryLock] [{}] return null after {} attempts. Key={}", uuid, config.getTryLockLockCount(), key);
return null;
}
/**
* 尝试以独占方式执行一个操作。
* <p>{@link MultiLevelCache} 将使用最后一级缓存来支持此操作。</p>
* 用法示例:
* <pre>
* cache.tryLock("MyKey",100, TimeUnit.SECONDS),() -> {
* // 执行某些操作
* });
* </pre>
* @param key 锁键
* @param expire 锁的过期时间
* @param timeUnit 锁的过期时间单位
* @param action 需要执行的操作
* @return 如果成功获取锁并执行了操作,则返回 true;否则返回 false。
*/
default boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action){
try (AutoReleaseLock lock = tryLock(key, expire, timeUnit)) {
if (lock != null) {
action.run();
return true;
} else {
return false;
}
}
}
tryLock方法是尝试获取缓存键的锁,其中key对应的是缓存的键,这样可以保证即使在多线程条件下,同一个键在同一时间内只会获取到一个锁。
- 定义释放锁的方法,先判断尝试解锁次数小于配置的最大解锁次数,如果超过就不在尝试解锁。然后判断锁是否到期,如果锁已到期就直接返回。最后调用REMOVE方法删除锁信息,如果删除不成功就会进行重试,否则直接返回。
- 首先调用PUT_IF_ABSENT试图在缓存中添加锁标识(唯一的GUID),以及锁的到期时间,该方法仅在键不存在时才会添加成功,否则会添加失败。PUT_IF_ABSENT添加锁不成功有两种可能,即已经存在该键,或者是缓存中没有该键,但是因为特殊原因(如网络原因导致写入Redis失败)导致写入键值失败。如果缓存中存在该键,则获取锁失败,返回null。否则会继续尝试获取锁信息。
tryLockAndRun方法实现了获取锁,执行指定的操作并释放锁,如果获取锁失败就直接返回false。
AbstractCache
在JetCache中AbstractCache是一个抽象类,继承自Cache,实现了GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法,源码如下:
/**
* 抽象缓存类,提供了缓存的基本实现,支持键值对的存取操作。该类是线程安全的。
*
* @param <K> 键的类型
* @param <V> 值的类型
*/
public abstract class AbstractCache<K, V> implements Cache<K, V> {
/**
* 通知缓存事件监听器。
*
* @param e 缓存事件。
*/
public void notify(CacheEvent e) {
List<CacheMonitor> monitors = config().getMonitors();
for (CacheMonitor m : monitors) {
m.afterOperation(e);
}
}
/**
* 获取缓存中指定键的值。
*
* @param key 键。
* @return CacheGetResult<V> 获取结果,包含值和操作状态。
*/
@Override
public final CacheGetResult<V> GET(K key) {
long t = System.currentTimeMillis();
CacheGetResult<V> result;
// 对于null键,直接返回错误结果。
if (key == null) {
result = new CacheGetResult<V>(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null);
} else {
result = do_GET(key);
}
// 异步触发获取事件的通知。
result.future().thenRun(() -> {
CacheGetEvent event = new CacheGetEvent(this, System.currentTimeMillis() - t, key, result);
notify(event);
});
return result;
}
/**
* 实际获取缓存值的逻辑。
*
* @param key 键。
* @return CacheGetResult<V> 获取结果,包含值和操作状态。
*/
protected abstract CacheGetResult<V> do_GET(K key);
/**
* 批量获取缓存中多个键对应的值。
*
* @param keys 键的集合。
* @return MultiGetResult<K, V> 批量获取结果,包含值的映射和操作状态。
*/
@Override
public final MultiGetResult<K, V> GET_ALL(Set<? extends K> keys) {
long t = System.currentTimeMillis();
MultiGetResult<K, V> result;
// 对于null键集合,直接返回错误结果。
if (keys == null) {
result = new MultiGetResult<>(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null);
} else {
result = do_GET_ALL(keys);
}
// 异步触发批量获取事件的通知。
result.future().thenRun(() -> {
CacheGetAllEvent event = new CacheGetAllEvent(this, System.currentTimeMillis() - t, keys, result);
notify(event);
});
return result;
}
/**
* 实际批量获取缓存值的逻辑。
*
* @param keys 键的集合。
* @return MultiGetResult<K, V> 批量获取结果,包含值的映射和操作状态。
*/
protected abstract MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys);
/**
* 将键值对存储到缓存中。
* @param key 键,不能为null。
* @param value 值。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 操作结果,包含操作是否成功等信息。
*/
@Override
public final CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
long t = System.currentTimeMillis();
CacheResult result;
if (key == null) {
result = CacheResult.FAIL_ILLEGAL_ARGUMENT;
} else {
result = do_PUT(key, value, expireAfterWrite, timeUnit);
}
// 在异步操作完成后触发事件通知
result.future().thenRun(() -> {
CachePutEvent event = new CachePutEvent(this, System.currentTimeMillis() - t, key, value, result);
notify(event);
});
return result;
}
/**
* 实际执行PUT操作的抽象方法。
* @param key 键。
* @param value 值。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 操作结果,包含操作是否成功等信息。
*/
protected abstract CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
/**
* 批量将键值对存储到缓存中。
* @param map 要存储的键值对集合。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 操作结果,包含操作是否成功等信息。
*/
@Override
public final CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {
long t = System.currentTimeMillis();
CacheResult result;
if (map == null) {
result = CacheResult.FAIL_ILLEGAL_ARGUMENT;
} else {
result = do_PUT_ALL(map, expireAfterWrite, timeUnit);
}
// 在异步操作完成后触发事件通知
result.future().thenRun(() -> {
CachePutAllEvent event = new CachePutAllEvent(this, System.currentTimeMillis() - t, map, result);
notify(event);
});
return result;
}
/**
* 实际执行批量PUT操作的抽象方法。
* @param map 要存储的键值对集合。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 操作结果,包含操作是否成功等信息。
*/
protected abstract CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit);
/**
* 从缓存中移除指定键的项。
* @param key 要移除的键,不能为null。
* @return 操作结果,包含操作是否成功等信息。
*/
@Override
public final CacheResult REMOVE(K key) {
long t = System.currentTimeMillis();
CacheResult result;
if (key == null) {
result = CacheResult.FAIL_ILLEGAL_ARGUMENT;
} else {
result = do_REMOVE(key);
}
// 在异步操作完成后触发事件通知
result.future().thenRun(() -> {
CacheRemoveEvent event = new CacheRemoveEvent(this, System.currentTimeMillis() - t, key, result);
notify(event);
});
return result;
}
/**
* 实际执行移除操作的抽象方法。
* @param key 要移除的键。
* @return 操作结果,包含操作是否成功等信息。
*/
protected abstract CacheResult do_REMOVE(K key);
/**
* 从缓存中移除指定键集合对应的项。
* @param keys 要移除的键的集合,不能为null。
* @return 操作结果,包含操作是否成功等信息。
*/
@Override
public final CacheResult REMOVE_ALL(Set<? extends K> keys) {
long t = System.currentTimeMillis();
CacheResult result;
if (keys == null) {
result = CacheResult.FAIL_ILLEGAL_ARGUMENT;
} else {
result = do_REMOVE_ALL(keys);
}
// 在异步操作完成后触发事件通知
result.future().thenRun(() -> {
CacheRemoveAllEvent event = new CacheRemoveAllEvent(this, System.currentTimeMillis() - t, keys, result);
notify(event);
});
return result;
}
/**
* 实际执行批量移除操作的抽象方法。
* @param keys 要移除的键的集合。
* @return 操作结果,包含操作是否成功等信息。
*/
protected abstract CacheResult do_REMOVE_ALL(Set<? extends K> keys);
/**
* 如果指定的键在缓存中不存在,则将其添加。
* @param key 键,不能为null。
* @param value 值。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 操作结果,包含操作是否成功等信息。
*/
@Override
public final CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
long t = System.currentTimeMillis();
CacheResult result;
if (key == null) {
result = CacheResult.FAIL_ILLEGAL_ARGUMENT;
} else {
result = do_PUT_IF_ABSENT(key, value, expireAfterWrite, timeUnit);
}
// 在异步操作完成后触发事件通知
result.future().thenRun(() -> {
CachePutEvent event = new CachePutEvent(this, System.currentTimeMillis() - t, key, value, result);
notify(event);
});
return result;
}
/**
* 实际执行PUT_IF_ABSENT操作的抽象方法。
* @param key 键。
* @param value 值。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 操作结果,包含操作是否成功等信息。
*/
protected abstract CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);
在AbstractCache类中,GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法主要是先判断键是否有效,然后调用do_GET、do_GET_ALL、do_PUT、do_PUT_ALL、do_REMOVE、do_REMOVE_ALL等方法对缓存进行增删改查,最后使用观察者模式发布缓存的操作事件。
上面的代码逻辑还是相对比较简单的就不做太多的讲解,接下来,我们着重介绍AbstractCache类中缓存加载的代码逻辑,源码如下:
// 使用ConcurrentHashMap来存储加载器锁,以支持并发控制。
private volatile ConcurrentHashMap<Object, LoaderLock> loaderMap;
// 标记缓存是否已关闭。
protected volatile boolean closed;
// 用于初始化loaderMap的互斥锁,确保线程安全。
private static final ReentrantLock reentrantLock = new ReentrantLock();
/**
* 初始化或获取loaderMap。
*
* @return ConcurrentHashMap<Object, LoaderLock> 返回loaderMap实例。
*/
ConcurrentHashMap<Object, LoaderLock> initOrGetLoaderMap() {
if (loaderMap == null) {
reentrantLock.lock();
try {
if (loaderMap == null) {
loaderMap = new ConcurrentHashMap<>();
}
}finally {
reentrantLock.unlock();
}
}
return loaderMap;
}
/**
* 如果给定的键在缓存中不存在,则使用给定的加载器函数来计算值,并将结果放入缓存。
*
* @param key 键,用于在缓存中查找值。
* @param loader 加载器函数,用于在缓存中找不到键对应的值时计算值。
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否将null缓存起来。
* @return 缓存中键对应的值,或者是通过加载器计算出的值。
*/
@Override
public final V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull) {
return computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,
0, null, this);
}
/**
* 如果给定的键在缓存中不存在,并且希望设置过期时间,则使用给定的加载器函数来计算值,并将结果放入缓存。
*
* @param key 键,用于在缓存中查找值。
* @param loader 加载器函数,用于在缓存中找不到键对应的值时计算值。
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否将null缓存起来。
* @param expireAfterWrite 缓存条目的写入后过期时间。
* @param timeUnit 缓存条目的过期时间单位。
* @return 缓存中键对应的值,或者是通过加载器计算出的值。
*/
@Override
public final V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,
long expireAfterWrite, TimeUnit timeUnit) {
return computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,
expireAfterWrite, timeUnit, this);
}
/**
* 判断是否需要更新缓存。
*
* @param loadedValue 加载器计算出的值。
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否将null缓存起来。
* @param loader 加载器函数。
* @return 如果需要更新缓存,则返回true;否则返回false。
*/
private static <K, V> boolean needUpdate(V loadedValue, boolean cacheNullWhenLoaderReturnNull, Function<K, V> loader) {
if (loadedValue == null && !cacheNullWhenLoaderReturnNull) {
return false;
}
if (loader instanceof CacheLoader && ((CacheLoader<K, V>) loader).vetoCacheUpdate()) {
return false;
}
return true;
}
/**
* 实际执行computeIfAbsent逻辑的方法。
*
* @param key 键,用于在缓存中查找值。
* @param loader 加载器函数,用于在缓存中找不到键对应的值时计算值。
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否将null缓存起来。
* @param expireAfterWrite 缓存条目的写入后过期时间。
* @param timeUnit 缓存条目的过期时间单位。
* @param cache 缓存实例。
* @return 缓存中键对应的值,或者是通过加载器计算出的值。
*/
static <K, V> V computeIfAbsentImpl(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,
long expireAfterWrite, TimeUnit timeUnit, Cache<K, V> cache) {
AbstractCache<K, V> abstractCache = CacheUtil.getAbstractCache(cache);
CacheLoader<K, V> newLoader = CacheUtil.createProxyLoader(cache, loader, abstractCache::notify);
CacheGetResult<V> r;
if (cache instanceof RefreshCache) {
RefreshCache<K, V> refreshCache = ((RefreshCache<K, V>) cache);
r = refreshCache.GET(key);
refreshCache.addOrUpdateRefreshTask(key, newLoader);
} else {
r = cache.GET(key);
}
if (r.isSuccess()) {
return r.getValue();
} else {
Consumer<V> cacheUpdater = (loadedValue) -> {
if(needUpdate(loadedValue, cacheNullWhenLoaderReturnNull, newLoader)) {
if (timeUnit != null) {
cache.PUT(key, loadedValue, expireAfterWrite, timeUnit).waitForResult();
} else {
cache.PUT(key, loadedValue).waitForResult();
}
}
};
V loadedValue;
if (cache.config().isCachePenetrationProtect()) {
loadedValue = synchronizedLoad(cache.config(), abstractCache, key, newLoader, cacheUpdater);
} else {
loadedValue = newLoader.apply(key);
cacheUpdater.accept(loadedValue);
}
return loadedValue;
}
}
/**
* 使用同步方式从缓存中加载值。
*
* @param config 缓存配置。
* @param abstractCache 缓存的抽象实现。
* @param key 键。
* @param newLoader 加载器函数。
* @param cacheUpdater 缓存更新的消费者接口。
* @return 加载的值。
*/
static <K, V> V synchronizedLoad(CacheConfig config, AbstractCache<K,V> abstractCache,
K key, Function<K, V> newLoader, Consumer<V> cacheUpdater) {
ConcurrentHashMap<Object, LoaderLock> loaderMap = abstractCache.initOrGetLoaderMap();
Object lockKey = buildLoaderLockKey(abstractCache, key);
while (true) {
boolean create[] = new boolean[1];
LoaderLock ll = loaderMap.computeIfAbsent(lockKey, (unusedKey) -> {
create[0] = true;
LoaderLock loaderLock = new LoaderLock();
loaderLock.signal = new CountDownLatch(1);
loaderLock.loaderThread = Thread.currentThread();
return loaderLock;
});
if (create[0] || ll.loaderThread == Thread.currentThread()) {
try {
CacheGetResult<V> getResult = abstractCache.GET(key);
if (getResult.isSuccess()) {
ll.success = true;
ll.value = getResult.getValue();
return getResult.getValue();
} else {
V loadedValue = newLoader.apply(key);
ll.success = true;
ll.value = loadedValue;
cacheUpdater.accept(loadedValue);
return loadedValue;
}
} finally {
if (create[0]) {
ll.signal.countDown();
loaderMap.remove(lockKey);
}
}
} else {
try {
Duration timeout = config.getPenetrationProtectTimeout();
if (timeout == null) {
ll.signal.await();
} else {
boolean ok = ll.signal.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
if(!ok) {
logger.info("loader wait timeout:" + timeout);
return newLoader.apply(key);
}
}
} catch (InterruptedException e) {
logger.warn("loader wait interrupted");
return newLoader.apply(key);
}
if (ll.success) {
return (V) ll.value;
} else {
continue;
}
}
}
}
/**
* 构建加载锁的键。
*
* @param c 缓存实例。
* @param key 键。
* @return 用于加载锁的键。
*/
private static Object buildLoaderLockKey(Cache c, Object key) {
if (c instanceof AbstractEmbeddedCache) {
return ((AbstractEmbeddedCache) c).buildKey(key);
} else if (c instanceof AbstractExternalCache) {
byte bytes[] = ((AbstractExternalCache) c).buildKey(key);
return ByteBuffer.wrap(bytes);
} else if (c instanceof MultiLevelCache) {
c = ((MultiLevelCache) c).caches()[0];
return buildLoaderLockKey(c, key);
} else if(c instanceof ProxyCache) {
c = ((ProxyCache) c).getTargetCache();
return buildLoaderLockKey(c, key);
} else {
throw new CacheException("impossible");
}
}
这段Java代码定义了一个名为computeIfAbsentImpl的静态方法,该方法用于在给定的缓存中根据键获取值,如果缓存中不存在该键对应的值,则使用提供的加载器函数加载值,并将加载的值放入缓存中。方法有多个参数,包括键、加载器函数、是否缓存null值、过期时间、时间单位和缓存对象。
该方法首先通过CacheUtil.getAbstractCache(cache)获取缓存的抽象类对象,然后通过CacheUtil.createProxyLoader()创建一个代理加载器。接下来,根据缓存类型分别调用RefreshCache.GET()或cache.GET()方法获取缓存值。如果获取成功,则返回该值;否则,根据是否需要更新缓存以及是否设置了过期时间,调用cache.PUT()方法更新缓存。
如果缓存配置了穿透保护,则调用synchronizedLoad()方法进行加锁加载。该方法使用ConcurrentHashMap维护一个加载器映射表,通过构建锁键来实现细粒度锁。在加锁过程中,如果当前线程是加载器线程,则直接进行加载并更新缓存;否则,等待加载完成或超时后返回加载的值。
总之,这段代码实现了根据键加载或获取缓存值的功能,并提供了缓存穿透保护的机制。
内存缓存
进程内的缓存框架支持LinkedHashMap和caffeine,在实际开发过程中一般推荐使用caffeine,好处网上一大堆,这里不做赘述。
AbstractEmbeddedCache
抽象类AbstractEmbeddedCache继承自AbstractCache,作为内存缓存的父类,其实现了常见内存缓存类的共通实现,由于是在内存缓存,不需要对缓存的数据进行序列化处理,相对处理速度自然相对会更快,序列化缓存键的操作已经在AbstractCache中实现,AbstractEmbeddedCache中就不会重复造轮子了。AbstractEmbeddedCache源码如下:
/**
* 抽象嵌入式缓存类,扩展自AbstractCache,提供缓存的内部映射和配置管理。
* 需要由具体缓存实现类继承并实现createAreaCache方法。
*
* @param <K> 键的类型
* @param <V> 值的类型
*/
public abstract class AbstractEmbeddedCache<K, V> extends AbstractCache<K, V> {
protected EmbeddedCacheConfig<K, V> config; // 缓存配置
protected InnerMap innerMap; // 内部映射,用于缓存数据
/**
* 创建区域缓存,由子类具体实现。
*
* @return InnerMap 实例,表示特定缓存区域。
*/
protected abstract InnerMap createAreaCache();
private final ReentrantLock lock = new ReentrantLock(); // 用于并发控制的锁
/**
* 构造函数,初始化嵌入式缓存。
*
* @param config 缓存配置,包含缓存的各种设置项。
*/
public AbstractEmbeddedCache(EmbeddedCacheConfig<K, V> config) {
this.config = config;
innerMap = createAreaCache();
}
/**
* 获取缓存配置。
*
* @return CacheConfig 缓存配置。
*/
@Override
public CacheConfig<K, V> config() {
return config;
}
/**
* 基于提供的键构建缓存键,如果配置了键转换器,则会应用转换。
*
* @param key 原始键。
* @return 转换后的键,用于缓存存储。
*/
public Object buildKey(K key) {
Object newKey = key;
Function<K, Object> keyConvertor = config.getKeyConvertor();
if (keyConvertor != null) {
newKey = keyConvertor.apply(key);
}
return newKey;
}
/**
* 从缓存中获取指定键的值。
*
* @param key 键。
* @return CacheGetResult 包含获取结果的对象,可能为不存在、已过期或其他状态。
*/
@Override
protected CacheGetResult<V> do_GET(K key) {
Object newKey = buildKey(key);
CacheValueHolder<V> holder = (CacheValueHolder<V>) innerMap.getValue(newKey);
return parseHolderResult(holder);
}
/**
* 解析缓存值持有者结果,确定缓存状态(存在、过期等)。
*
* @param holder 缓存值持有者。
* @return CacheGetResult 包含获取结果的对象。
*/
protected CacheGetResult<V> parseHolderResult(CacheValueHolder<V> holder) {
long now = System.currentTimeMillis();
if (holder == null) {
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
} else if (now >= holder.getExpireTime()) {
return CacheGetResult.EXPIRED_WITHOUT_MSG;
} else {
lock.lock();
try{
long accessTime = holder.getAccessTime();
if (config.isExpireAfterAccess()) {
long expireAfterAccess = config.getExpireAfterAccessInMillis();
if (now >= accessTime + expireAfterAccess) {
return CacheGetResult.EXPIRED_WITHOUT_MSG;
}
}
holder.setAccessTime(now);
}finally {
lock.unlock();
}
return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);
}
}
/**
* 批量获取缓存值。
*
* @param keys 键的集合。
* @return MultiGetResult 包含批量获取结果的对象。
*/
@Override
protected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {
// 将原始键转换为缓存键,并批量获取值
ArrayList<K> keyList = new ArrayList<K>(keys.size());
ArrayList<Object> newKeyList = new ArrayList<Object>(keys.size());
keys.stream().forEach((k) -> {
Object newKey = buildKey(k);
keyList.add(k);
newKeyList.add(newKey);
});
Map<Object, CacheValueHolder<V>> innerResultMap = innerMap.getAllValues(newKeyList);
Map<K, CacheGetResult<V>> resultMap = new HashMap<>();
for (int i = 0; i < keyList.size(); i++) {
K key = keyList.get(i);
Object newKey = newKeyList.get(i);
CacheValueHolder<V> holder = innerResultMap.get(newKey);
resultMap.put(key, parseHolderResult(holder));
}
MultiGetResult<K, V> result = new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);
return result;
}
/**
* 向缓存中放入一个键值对,设置过期时间。
*
* @param key 键。
* @param value 值。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 时间单位。
* @return CacheResult 描述操作结果的对象。
*/
@Override
protected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
CacheValueHolder<V> cacheObject = new CacheValueHolder(value ,timeUnit.toMillis(expireAfterWrite));
innerMap.putValue(buildKey(key), cacheObject);
return CacheResult.SUCCESS_WITHOUT_MSG;
}
/**
* 批量放入缓存项。
*
* @param map 键值对的映射。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 时间单位。
* @return CacheResult 描述操作结果的对象。
*/
@Override
protected CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {
HashMap newKeyMap = new HashMap();
for (Map.Entry<? extends K, ? extends V> en : map.entrySet()) {
CacheValueHolder<V> cacheObject = new CacheValueHolder(en.getValue(), timeUnit.toMillis(expireAfterWrite));
newKeyMap.put(buildKey(en.getKey()), cacheObject);
}
innerMap.putAllValues(newKeyMap);
final HashMap resultMap = new HashMap();
map.keySet().forEach((k) -> resultMap.put(k, CacheResultCode.SUCCESS));
return CacheResult.SUCCESS_WITHOUT_MSG;
}
/**
* 从缓存中移除指定键的项。
*
* @param key 键。
* @return CacheResult 描述操作结果的对象。
*/
@Override
protected CacheResult do_REMOVE(K key) {
innerMap.removeValue(buildKey(key));
return CacheResult.SUCCESS_WITHOUT_MSG;
}
/**
* 批量移除缓存项。
*
* @param keys 键的集合。
* @return CacheResult 描述操作结果的对象。
*/
@Override
protected CacheResult do_REMOVE_ALL(Set<? extends K> keys) {
Set newKeys = keys.stream().map((key) -> buildKey(key)).collect(Collectors.toSet());
innerMap.removeAllValues(newKeys);
return CacheResult.SUCCESS_WITHOUT_MSG;
}
// 内部方法,用于彻底清除指定键的缓存项
public void __removeAll(Set<? extends K> keys) {
innerMap.removeAllValues(keys);
}
/**
* 如果指定键的缓存项不存在,则放入该键值对,并设置过期时间。
*
* @param key 键。
* @param value 值。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 时间单位。
* @return CacheResult 描述操作结果的对象。
*/
@Override
protected CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
CacheValueHolder<V> cacheObject = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));
if (innerMap.putIfAbsentValue(buildKey(key), cacheObject)) {
return CacheResult.SUCCESS_WITHOUT_MSG;
} else {
return CacheResult.EXISTS_WITHOUT_MSG;
}
}
}
在上面的函数中,抽象函数createAreaCache()创建内部映射实例innerMap用于缓存数据,do_GET、do_GET_ALL、do_PUT、do_PUT_ALL、do_REMOVE、do_REMOVE_ALL等方法都是基于innerMap实例来实现缓存数据的增删改查的。缓存数据时,会构建CacheValueHolder实例,用于保存缓存的数值、缓存创建时间和到期时间。之所以要缓存数据的过期时间,是便于框架在缓存数据过期后可以及时的将缓存数据从内存中清除出去。
InnerMap是JetCache专为内存缓存定义的通用缓存处理接口,源码如下:
/**
* InnerMap 接口定义了一套操作映射数据的方法,包括获取值、存储值、移除值等操作。
*/
public interface InnerMap {
/**
* 通过键获取对应的值。
*
* @param key 用于获取值的键。
* @return 键对应的值,如果不存在则返回 null。
*/
Object getValue(Object key);
/**
* 通过键的集合获取所有对应的值。
*
* @param keys 键的集合。
* @return 一个映射,包含给定键集合中每个键对应的值。
*/
Map getAllValues(Collection keys);
/**
* 为指定的键存储一个值。
*
* @param key 要存储值的键。
* @param value 键对应的值。
*/
void putValue(Object key, Object value);
/**
* 从映射中批量添加键值对。
*
* @param map 包含要添加的键值对的映射。
*/
void putAllValues(Map map);
/**
* 移除指定键对应的值。
*
* @param key 要移除的键。
* @return 如果成功移除,返回 true;如果键不存在,返回 false。
*/
boolean removeValue(Object key);
/**
* 如果指定键不存在,则添加该键对应的值。
*
* @param key 要添加值的键。
* @param value 键对应的值。
* @return 如果成功添加,返回 true;如果键已存在,返回 false。
*/
boolean putIfAbsentValue(Object key, Object value);
/**
* 移除指定键集合中所有键对应的值。
*
* @param keys 要移除的键的集合。
*/
void removeAllValues(Collection keys);
}
LinkedHashMapCache和CaffeineCache类继承自AbstractEmbeddedCache,主要是实现抽象函数createAreaCache()创建内部映射InnerMap的实例。
LinkedHashMapCache
LinkedHashMapCache是一个基于 LinkedHashMap 的缓存实现类,继承自 AbstractEmbeddedCache。
它通过 LRUMap (最近最少使用)算法来管理缓存项,支持缓存过期清理。LinkedHashMapCache的源码如下:
/**
* LinkedHashMapCache是一个基于 LinkedHashMap 的缓存实现类,继承自 AbstractEmbeddedCache。
* 它通过 LRUMap (最近最少使用)算法来管理缓存项,支持缓存过期清理。
*
* @param <K> 缓存键的类型
* @param <V> 缓存值的类型
*/
public class LinkedHashMapCache<K, V> extends AbstractEmbeddedCache<K, V> {
private static Logger logger = LoggerFactory.getLogger(LinkedHashMapCache.class);
/**
* 构造函数,初始化缓存。
*
* @param config 缓存配置,包含缓存大小等设置。
*/
public LinkedHashMapCache(EmbeddedCacheConfig<K, V> config) {
super(config);
addToCleaner(); // 将当前缓存实例添加到缓存清理器中,以便定期清理。
}
/**
* 将当前缓存实例添加到缓存清理器。
*/
protected void addToCleaner() {
Cleaner.add(this);
}
/**
* 创建一个新的缓存区域,实际是一个 LRUMap 实例。
*
* @return InnerMap 实例,用于存储缓存项。
*/
@Override
protected InnerMap createAreaCache() {
return new LRUMap(config.getLimit());
}
/**
* 将当前缓存实例转换为指定类型的对象。
*
* @param clazz 需要转换成的目标类类型。
* @return 转换后的对象实例,如果无法转换则抛出异常。
* @throws IllegalArgumentException 如果指定的类不是 LinkedHashMap 类型,则抛出此异常。
*/
@Override
public <T> T unwrap(Class<T> clazz) {
if (clazz.equals(LinkedHashMap.class)) {
return (T) innerMap;
}
throw new IllegalArgumentException(clazz.getName());
}
/**
* 清理已经过期的缓存项。
*/
public void cleanExpiredEntry() {
((LRUMap) innerMap).cleanExpiredEntry();
}
/**
* LRUMap 是一个基于 LinkedHashMap 实现的缓存映射,支持 LRU 算法和缓存过期机制。
*/
final class LRUMap extends LinkedHashMap implements InnerMap {
private final int max; // 缓存最大容量
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁,用于并发控制
/**
* 构造函数,初始化 LRUMap。
*
* @param max 缓存的最大容量。
*/
public LRUMap(int max) {
super((int) (max * 1.4f), 0.75f, true); // 调整 LinkedHashMap 的构造参数,以适应 LRU 策略
this.max = max;
}
/**
* 当缓存项数量超过最大容量时,移除最老的缓存项。
*
* @param eldest 被考虑移除的最老缓存项。
* @return 返回 true 如果最老缓存项被移除,否则返回 false。
*/
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > max;
}
/**
* 获取指定键的缓存值,支持并发控制。
*
* @param key 缓存项的键。
* @return 返回对应键的缓存值,如果不存在则返回 null。
*/
@Override
public Object getValue(Object key) {
Lock lock = readWriteLock.readLock();
lock.lock();
try{
return get(key);
}finally {
lock.unlock();
}
}
/**
* 获取多个键对应的缓存值,支持并发控制。
*
* @param keys 缓存项的键集合。
* @return 返回一个映射,包含所有指定键的缓存值,如果键不存在则对应值为 null。
*/
@Override
public Map getAllValues(Collection keys) {
Lock lock = readWriteLock.readLock();
lock.lock();
Map values = new HashMap();
try{
for (Object key : keys) {
Object v = get(key);
if (v != null) {
values.put(key, v);
}
}
}finally {
lock.unlock();
}
return values;
}
/**
* 添加或更新一个缓存项,支持并发控制。
*
* @param key 缓存项的键。
* @param value 缓存项的值。
*/
@Override
public void putValue(Object key, Object value) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try{
put(key, value);
}finally {
lock.unlock();
}
}
/**
* 批量添加或更新多个缓存项,支持并发控制。
*
* @param map 包含要添加或更新的缓存项的键值对集合。
*/
@Override
public void putAllValues(Map map) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try{
Set<Map.Entry> set = map.entrySet();
for (Map.Entry en : set) {
put(en.getKey(), en.getValue());
}
}finally {
lock.unlock();
}
}
/**
* 移除指定键的缓存项,支持并发控制。
*
* @param key 缓存项的键。
* @return 如果缓存项被成功移除,返回 true,否则返回 false。
*/
@Override
public boolean removeValue(Object key) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try{
return remove(key) != null;
}finally {
lock.unlock();
}
}
/**
* 移除多个键对应的缓存项,支持并发控制。
*
* @param keys 缓存项的键集合。
*/
@Override
public void removeAllValues(Collection keys) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try{
for (Object k : keys) {
remove(k);
}
}finally {
lock.unlock();
}
}
/**
* 如果指定键的缓存项不存在或已过期,则添加新的缓存项,支持并发控制。
*
* @param key 缓存项的键。
* @param value 缓存项的值。
* @return 如果新的缓存项被成功添加,则返回 true,否则返回 false。
*/
@Override
@SuppressWarnings("unchecked")
public boolean putIfAbsentValue(Object key, Object value) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try{
CacheValueHolder h = (CacheValueHolder) get(key);
if (h == null || parseHolderResult(h).getResultCode() == CacheResultCode.EXPIRED) {
put(key, value);
return true;
} else {
return false;
}
}finally {
lock.unlock();
}
}
}
}
LinkedHashMapCache创建的InnerMap实例类型是LRUMap,LRUMap继承自LinkedHashMap类并实现了InnerMap接口,由于LinkedHashMap可以维护插入顺序或访问顺序两种有序性,其中访问顺序指put和get操作已存在的Entry时,会将Entry移动到双向链表的表尾。
LRUMap重写了父类LinkedHashMap的removeEldestEntry函数,当缓存项数量超过最大容量时,移除最老的缓存项。可以看一下父类LinkedHashMap的afterNodeInsertion函数,该函数在removeEldestEntry返回true时,会删除链表的表头。源码如下:
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
由于LinkedHashMap的方法put和get操作已存在的Entry时,会将Entry移动到双向链表的表尾。则最近最少使用的缓存项自然而然的就被移动到双向链表的表头了。从字面理解,afterNodeInsertion是指LinkedHashMap中节点插入的后置函数,在HashMap的源码中,put、merge、compute和computeIfAbsent函数的最后会调用afterNodeInsertion函数。
现在看到的是,当向缓存LinkedHashMap中写入缓存数据,且缓存项大于LinkedHashMap的最大容量时才会删除最近最少使用的缓存数据。对于已经到期的缓存数据是在什么时候删除的呢?我们注意到addToCleaner函数会将当前的LinkedHashMapCache添加到缓存清理器中,缓存清理器Cleaner的功能就是定时的将已经过期的缓存数据从内存中清除掉。Cleaner源码如下:
class Cleaner {
static LinkedList<WeakReference<LinkedHashMapCache>> linkedHashMapCaches = new LinkedList<>();
private static final ReentrantLock reentrantLock = new ReentrantLock();
static {
ScheduledExecutorService executorService = JetCacheExecutor.defaultExecutor();
executorService.scheduleWithFixedDelay(() -> run(), 60, 60, TimeUnit.SECONDS);
}
static void add(LinkedHashMapCache cache) {
reentrantLock.lock();
try{
linkedHashMapCaches.add(new WeakReference<>(cache));
}finally {
reentrantLock.unlock();
}
}
static void run() {
reentrantLock.lock();
try{
Iterator<WeakReference<LinkedHashMapCache>> it = linkedHashMapCaches.iterator();
while (it.hasNext()) {
WeakReference<LinkedHashMapCache> ref = it.next();
LinkedHashMapCache c = ref.get();
if (c == null) {
it.remove();
} else {
c.cleanExpiredEntry();
}
}
}finally {
reentrantLock.unlock();
}
}
}
Cleaner会每隔60秒钟,调用所有LinkedHashMapCache实例的cleanExpiredEntry函数来清空已经到期的缓存数据。函数cleanExpiredEntry的源码如下:
public void cleanExpiredEntry() {
((LRUMap) innerMap).cleanExpiredEntry();
}
上面的函数会调用LRUMap类的cleanExpiredEntry函数来清空已经到期的缓存数据,cleanExpiredEntry源码如下:
/**
* 清理已经过期的缓存项。
*/
void cleanExpiredEntry() {
Lock lock = readWriteLock.writeLock();
lock.lock();
try{
// 遍历缓存项,移除所有过期的缓存。
for (Iterator it = entrySet().iterator(); it.hasNext();) {
Map.Entry en = (Map.Entry) it.next();
Object value = en.getValue();
if (value != null && value instanceof CacheValueHolder) {
CacheValueHolder h = (CacheValueHolder) value;
if (System.currentTimeMillis() >= h.getExpireTime()) {
it.remove();
}
} else {
// 如果缓存项为空或不是 CacheValueHolder 类型,则记录错误。
if (value == null) {
logger.error("key " + en.getKey() + " is null");
} else {
logger.error("value of key " + en.getKey() + " is not a CacheValueHolder. type=" + value.getClass());
}
}
}
}finally {
lock.unlock();
}
}
cleanExpiredEntry函数会轮询当前LinkedHashMap实例中的所有缓存数据,然后将已经过期的数据移除。
关于LinkedHashMapCache的设计还存在问题,在发现缓存数据已到期时,并没有及时的将其移除。由于间隔时间是60秒,在短时间内当有大量的缓存数据进行读写时,可能会存在部分读频繁的数据已经过期,而部分未到期,且读写不频繁的数据就会被移到双向列表的表头,一旦缓存数据量超过最大容量就会把未到期的数据给清了。
CaffeineCache
基于Caffeine实现的缓存类,扩展自AbstractEmbeddedCache,提供了缓存管理的功能。源码如下:
/**
* 基于Caffeine实现的缓存类,扩展自AbstractEmbeddedCache,提供了缓存管理的功能。
*/
public class CaffeineCache<K, V> extends AbstractEmbeddedCache<K, V> {
// Caffeine缓存实例
private com.github.benmanes.caffeine.cache.Cache cache;
/**
* 构造函数,初始化Caffeine缓存。
*
* @param config 缓存配置信息,包含缓存大小、过期时间等配置。
*/
public CaffeineCache(EmbeddedCacheConfig<K, V> config) {
super(config);
}
/**
* 将当前缓存实例转换为指定类型的缓存接口。
*
* @param clazz 需要转换的目标缓存接口的Class对象。
* @return 转换后的缓存实例,如果无法转换则抛出IllegalArgumentException。
* @throws IllegalArgumentException 当指定的类不是com.github.benmanes.caffeine.cache.Cache时抛出。
*/
@Override
public <T> T unwrap(Class<T> clazz) {
if (clazz.equals(com.github.benmanes.caffeine.cache.Cache.class)) {
return (T) cache;
}
throw new IllegalArgumentException(clazz.getName());
}
/**
* 创建缓存区域,配置缓存的大小、过期策略等。
*
* @return 内部映射接口,提供了对缓存操作的方法。
*/
@Override
@SuppressWarnings("unchecked")
protected InnerMap createAreaCache() {
// 基于配置初始化Caffeine缓存构建器
Caffeine<Object, Object> builder = Caffeine.newBuilder();
builder.maximumSize(config.getLimit()); // 设置缓存最大大小
// 配置缓存过期策略
final boolean isExpireAfterAccess = config.isExpireAfterAccess(); // 是否按访问时间过期
final long expireAfterAccess = config.getExpireAfterAccessInMillis(); // 访问过期时间
builder.expireAfter(new Expiry<Object, CacheValueHolder>() {
// 计算过期时间的方法
private long getRestTimeInNanos(CacheValueHolder value) {
long now = System.currentTimeMillis();
long ttl = value.getExpireTime() - now;
if(isExpireAfterAccess){
ttl = Math.min(ttl, expireAfterAccess);
}
return TimeUnit.MILLISECONDS.toNanos(ttl);
}
@Override
public long expireAfterCreate(Object key, CacheValueHolder value, long currentTime) {
return getRestTimeInNanos(value);
}
@Override
public long expireAfterUpdate(Object key, CacheValueHolder value,
long currentTime, long currentDuration) {
return currentDuration;
}
@Override
public long expireAfterRead(Object key, CacheValueHolder value,
long currentTime, long currentDuration) {
return getRestTimeInNanos(value);
}
});
// 构建并初始化缓存
cache = builder.build();
// 返回一个内部映射,提供了基本的缓存操作接口
return new InnerMap() {
@Override
public Object getValue(Object key) {
return cache.getIfPresent(key);
}
@Override
public Map getAllValues(Collection keys) {
return cache.getAllPresent(keys);
}
@Override
public void putValue(Object key, Object value) {
cache.put(key, value);
}
@Override
public void putAllValues(Map map) {
cache.putAll(map);
}
@Override
public boolean removeValue(Object key) {
return cache.asMap().remove(key) != null;
}
@Override
public void removeAllValues(Collection keys) {
cache.invalidateAll(keys);
}
@Override
public boolean putIfAbsentValue(Object key, Object value) {
return cache.asMap().putIfAbsent(key, value) == null;
}
};
}
}
CaffeineCache的代码相对比较简单,在JetCache中内存缓存推荐使用Caffeine,性能相对LinkedHashMap更快,官方的Caffeine性能测试结果如下:
上面的结果仅仅是Caffeine官方提供的参考,详细可以到Caffeine官方上查看。
远程缓存
由于MemoryCache逐渐被Redis所取代,且Redis相对更加通用,所以JetCache目前支持的远程缓存是Redis。鉴于信创的需求,已经有多家国产公司开发出可以替代Redis的内存缓存系统,如阿里巴巴的Tair,东方通的TongRDS等,不确定JetCache后续会不会支持其他内存缓存系统。
JetCache通过引入lettuce、redisson和spring-data-redis中间件来实现Redis缓存的操作,如果要支持阿里巴巴的Tair,东方通的TongRDS等相对也比较简单。
AbstractExternalCache
抽象外部缓存类,扩展自AbstractCache,提供了缓存配置、键的转换和构建、以及配置检查等功能。源码如下:
/**
* 抽象外部缓存类,扩展自AbstractCache,提供了缓存配置、键的转换和构建、以及配置检查等功能。
* @param <K> 键的类型
* @param <V> 值的类型
*/
public abstract class AbstractExternalCache<K, V> extends AbstractCache<K, V> {
private ExternalCacheConfig<K, V> config; // 缓存配置对象
/**
* 构造函数,初始化外部缓存配置。
* @param config 外部缓存的配置,不可为null。
*/
public AbstractExternalCache(ExternalCacheConfig<K, V> config) {
this.config = config;
checkConfig(); // 检查配置的合法性
}
/**
* 配置检查方法,确保必要的配置项已经设置。
*/
protected void checkConfig() {
// 检查值编码器、解码器和键前缀是否已设置,未设置则抛出异常
if (config.getValueEncoder() == null) {
throw new CacheConfigException("no value encoder");
}
if (config.getValueDecoder() == null) {
throw new CacheConfigException("no value decoder");
}
if (config.getKeyPrefix() == null) {
throw new CacheConfigException("keyPrefix is required");
}
}
/**
* 构建缓存键。
* @param key 用户提供的键,可能需要转换。
* @return 经过转换和组合前缀后的缓存键字节数组。
* @throws CacheException 如果构建过程发生异常。
*/
public byte[] buildKey(K key) {
try {
Object newKey = key; // 初始化为原始键
if (config.getKeyConvertor() != null) {
// 根据版本适配不同的键转换逻辑
if (config.getKeyConvertor() instanceof KeyConvertor) {
if (!isPreservedKey(key)) {
newKey = config.getKeyConvertor().apply(key);
}
} else {
// 旧版本的键转换处理
if (key instanceof byte[]) {
newKey = key;
} else if (key instanceof String) {
newKey = key;
} else {
newKey = config.getKeyConvertor().apply(key);
}
}
}
// 构建最终的键
return ExternalKeyUtil.buildKeyAfterConvert(newKey, config.getKeyPrefix());
} catch (IOException e) {
throw new CacheException(e);
}
}
/**
* 判断键是否被保留不进行转换。
* @param key 原始键。
* @return 如果键是保留键(如锁键或时间戳键),则返回true,否则返回false。
*/
private boolean isPreservedKey(Object key) {
if (key instanceof byte[]) {
byte[] keyBytes = (byte[]) key;
// 判断键是否以特定后缀结尾,决定是否转换
return endWith(keyBytes, RefreshCache.LOCK_KEY_SUFFIX)
|| endWith(keyBytes, RefreshCache.TIMESTAMP_KEY_SUFFIX);
}
return false;
}
/**
* 判断字节数组是否以指定的后缀结尾。
* @param key 待检查的字节数组。
* @param suffix 指定的后缀字节数组。
* @return 如果key以suffix结尾,返回true,否则返回false。
*/
private boolean endWith(byte[] key, byte[] suffix) {
int len = suffix.length;
if (key.length < len) {
return false;
}
int startPos = key.length - len;
// 比较key的后缀与suffix是否相同
for (int i = 0; i < len; i++) {
if (key[startPos + i] != suffix[i]) {
return false;
}
}
return true;
}
}
在内存缓存 AbstractEmbeddedCache和远程缓存AbstractExternalCache中都有构建缓存键的实现,且实现过程也不一样,会什么会这样呢?这是因为内存缓存的键可以是对象或基本类型,但是在redis中字符数组。且内存缓存中Cache对象是根据缓存名称进行创建的,InnerMap中的键是不包含缓存名称的,这样即使键一样,但是缓存名称不一样,相互之间也不会有影响。而在Redis中,在同一个DB中,键往往是一个字符数组,需要同时包含缓存名称和键,所以在AbstractExternalCache中需要将缓存名称加上键,构成一个唯一的Redis中的键。
ExternalKeyUtil.buildKeyAfterConvert源码如下:
/**
* 根据提供的键和前缀构建一个键值数组。
* 该方法支持多种类型的键,包括字符串、字节数组、数字、日期、布尔值和可序列化对象。
* 键会被转换为字节数组,并与前缀合并,最终返回一个包含前缀和键的字节数组。
*
* @param newKey 新键,可以是字符串、字节数组、数字、日期、布尔值或可序列化对象。
* 不能为空,否则会抛出 NullPointerException。
* @param prefix 键的前缀,会转换为字节数组并与键的字节数组合并。
* 为空字符串时,不影响结果。
* @return 返回包含前缀和键的字节数组。
* @throws NullPointerException 如果新键为 null。
* @throws CacheException 如果新键的类型不支持转换。
* @throws IOException 如果序列化新键时发生错误。
*/
public static byte[] buildKeyAfterConvert(Object newKey, String prefix) throws IOException {
// 检查新键是否为 null
if (newKey == null) {
throw new NullPointerException("key can't be null");
}
byte[] keyBytesWithOutPrefix;
// 根据键的类型转换为字节数组
if (newKey instanceof String) {
keyBytesWithOutPrefix = newKey.toString().getBytes(StandardCharsets.UTF_8);
} else if (newKey instanceof byte[]) {
keyBytesWithOutPrefix = (byte[]) newKey;
} else if (newKey instanceof Number) {
keyBytesWithOutPrefix = (newKey.getClass().getSimpleName() + newKey).getBytes(StandardCharsets.UTF_8);
} else if (newKey instanceof Date) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss,SSS");
keyBytesWithOutPrefix = (newKey.getClass().getSimpleName() + sdf.format(newKey)).getBytes();
} else if (newKey instanceof Boolean) {
keyBytesWithOutPrefix = newKey.toString().getBytes(StandardCharsets.UTF_8);
} else if (newKey instanceof Serializable) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(newKey);
os.close();
bos.close();
keyBytesWithOutPrefix = bos.toByteArray();
} else {
// 如果键的类型不支持,抛出异常
throw new CacheException("can't convert key of class: " + newKey.getClass());
}
// 将前缀转换为字节数组
byte[] prefixBytes = prefix.getBytes(StandardCharsets.UTF_8);
// 创建一个字节数组,用于存放最终的结果
byte[] rt = new byte[prefixBytes.length + keyBytesWithOutPrefix.length];
// 将前缀和键的字节数组合并到结果数组中
System.arraycopy(prefixBytes, 0, rt, 0, prefixBytes.length);
System.arraycopy(keyBytesWithOutPrefix, 0, rt, prefixBytes.length, keyBytesWithOutPrefix.length);
return rt;
}
RedissonCache
RedissonCache是基于Redisson中间件实现了对Redis的增删改查的,源码如下:
/**
* 基于Redisson客户端实现的缓存类,继承自AbstractExternalCache,提供了缓存的基本操作。
*
* @param <K> 键的类型
* @param <V> 值的类型
*/
public class RedissonCache<K, V> extends AbstractExternalCache<K, V> {
private final RedissonClient client; // Redisson客户端实例
private final RedissonCacheConfig<K, V> config; // 缓存配置
private final Function<Object, byte[]> valueEncoder; // 值编码器
private final Function<byte[], Object> valueDecoder; // 值解码器
/**
* 构造函数,初始化Redisson缓存。
*
* @param config 缓存配置,包含Redisson客户端、值编码器和值解码器等配置项。
*/
public RedissonCache(final RedissonCacheConfig<K, V> config) {
super(config);
this.config = config;
this.client = config.getRedissonClient();
this.valueEncoder = config.getValueEncoder();
this.valueDecoder = config.getValueDecoder();
}
/**
* 获取缓存键的字符串形式。
*
* @param key 缓存的键。
* @return 键的字符串形式。
*/
protected String getCacheKey(final K key) {
final byte[] newKey = buildKey(key);
return new String(newKey, StandardCharsets.UTF_8);
}
/**
* 获取缓存配置。
*
* @return 缓存配置。
*/
@Override
public CacheConfig<K, V> config() {
return this.config;
}
/**
* 不支持unwrap操作。
*
* @param clazz 需要转换成的类类型。
* @throws UnsupportedOperationException 永远抛出此异常,表示不支持unwrap操作。
*/
@Override
public <T> T unwrap(final Class<T> clazz) {
throw new UnsupportedOperationException("RedissonCache does not support unwrap");
}
/**
* 获取Redisson使用的Codec。
*
* @return 返回ByteArrayCodec实例。
*/
private Codec getCodec() {
return ByteArrayCodec.INSTANCE;
}
/**
* 编码缓存值。
*
* @param holder 缓存值持有者。
* @return 编码后的值。
*/
private byte[] encoder(final CacheValueHolder<V> holder) {
if (Objects.nonNull(holder)) {
return valueEncoder.apply(holder);
}
return null;
}
/**
* 解码缓存值。
*
* @param key 缓存的键。
* @param data 缓存的数据。
* @param counter 解码尝试的次数。
* @return 解码后的缓存值持有者。
*/
@SuppressWarnings({"unchecked"})
private CacheValueHolder<V> decoder(final K key, final byte[] data, final int counter) {
CacheValueHolder<V> holder = null;
if (Objects.nonNull(data) && data.length > 0) {
try {
holder = (CacheValueHolder<V>) valueDecoder.apply(data);
} catch (CacheEncodeException e) {
holder = compatibleOldVal(key, data, counter + 1);
if(Objects.isNull(holder)){
logError("decoder", key, e);
}
} catch (Throwable e) {
logError("decoder", key, e);
}
}
return holder;
}
/**
* 简化版的解码缓存值,不传入尝试次数。
*
* @param key 缓存的键。
* @param data 缓存的数据。
* @return 解码后的缓存值持有者。
*/
private CacheValueHolder<V> decoder(final K key, final byte[] data) {
return decoder(key, data, 0);
}
/**
* 兼容旧版本值的解码。
*
* @param key 缓存的键。
* @param data 缓存的数据。
* @param counter 解码尝试的次数。
* @return 兼容旧版本后的缓存值持有者。
*/
private CacheValueHolder<V> compatibleOldVal(final K key, final byte[] data, final int counter) {
if (Objects.nonNull(key) && Objects.nonNull(data) && data.length > 0 && counter <= 1) {
try {
final Codec codec = this.client.getConfig().getCodec();
if (Objects.nonNull(codec)) {
final Class<?> cls = ByteArrayCodec.class;
if (codec.getClass() != cls) {
final ByteBuf in = ByteBufAllocator.DEFAULT.buffer().writeBytes(data);
final byte[] out = (byte[]) codec.getValueDecoder().decode(in, null);
return decoder(key, out, counter);
}
}
} catch (Throwable e) {
logError("compatibleOldVal", key, e);
}
}
return null;
}
/**
* 获取缓存项。
*
* @param key 缓存的键。
* @return 缓存获取结果,包含缓存状态和值(如果存在)。
*/
@Override
@SuppressWarnings({"unchecked"})
protected CacheGetResult<V> do_GET(final K key) {
try {
final RBucket<byte[]> rb = this.client.getBucket(getCacheKey(key), getCodec());
final CacheValueHolder<V> holder = decoder(key, rb.get());
if (Objects.nonNull(holder)) {
final long now = System.currentTimeMillis(), expire = holder.getExpireTime();
if (expire > 0 && now >= expire) {
return CacheGetResult.EXPIRED_WITHOUT_MSG;
}
return new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);
}
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
} catch (Throwable e) {
logError("GET", key, e);
return new CacheGetResult<>(e);
}
}
/**
* 批量获取缓存项。
*
* @param keys 缓存的键集合。
* @return 缓存批量获取结果,包含每个键的获取状态和值(如果存在)。
*/
@Override
@SuppressWarnings({"unchecked"})
protected MultiGetResult<K, V> do_GET_ALL(final Set<? extends K> keys) {
try {
final Map<K, CacheGetResult<V>> retMap = new HashMap<>(1 << 4);
if (Objects.nonNull(keys) && !keys.isEmpty()) {
final Map<K, String> keyMap = new HashMap<>(keys.size());
keys.stream().filter(Objects::nonNull).forEach(k -> {
final String key = getCacheKey(k);
if (Objects.nonNull(key)) {
keyMap.put(k, key);
}
});
if (!keyMap.isEmpty()) {
final Map<String, byte[]> kvMap = this.client.getBuckets(getCodec()).get(keyMap.values().toArray(new String[0]));
final long now = System.currentTimeMillis();
for (K k : keys) {
final String key = keyMap.get(k);
if (Objects.nonNull(key) && Objects.nonNull(kvMap)) {
final CacheValueHolder<V> holder = decoder(k, kvMap.get(key));
if (Objects.nonNull(holder)) {
final long expire = holder.getExpireTime();
final CacheGetResult<V> ret = (expire > 0 && now >= expire) ? CacheGetResult.EXPIRED_WITHOUT_MSG :
new CacheGetResult<>(CacheResultCode.SUCCESS, null, holder);
retMap.put(k, ret);
continue;
}
}
retMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
}
}
}
return new MultiGetResult<>(CacheResultCode.SUCCESS, null, retMap);
} catch (Throwable e) {
logError("GET_ALL", "keys(" + (Objects.nonNull(keys) ? keys.size() : 0) + ")", e);
return new MultiGetResult<>(e);
}
}
/**
* 添加缓存项。
*
* @param key 缓存的键。
* @param value 缓存的值。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 缓存操作结果。
*/
@Override
protected CacheResult do_PUT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {
try {
final CacheValueHolder<V> holder = new CacheValueHolder<>(value, timeUnit.toMillis(expireAfterWrite));
this.client.getBucket(getCacheKey(key), getCodec()).set(encoder(holder), expireAfterWrite, timeUnit);
return CacheGetResult.SUCCESS_WITHOUT_MSG;
} catch (Throwable e) {
logError("PUT", key, e);
return new CacheResult(e);
}
}
/**
* 批量添加缓存项。
*
* @param map 键值对映射。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 过期时间的单位。
* @return 缓存操作结果。
*/
@Override
protected CacheResult do_PUT_ALL(final Map<? extends K, ? extends V> map, final long expireAfterWrite, final TimeUnit timeUnit) {
try {
if (Objects.nonNull(map) && !map.isEmpty()) {
final long expire = timeUnit.toMillis(expireAfterWrite);
final RBatch batch = this.client.createBatch();
map.forEach((k, v) -> {
final CacheValueHolder<V> holder = new CacheValueHolder<>(v, expire);
batch.getBucket(getCacheKey(k), getCodec()).setAsync(encoder(holder), expireAfterWrite, timeUnit);
});
batch.execute();
}
return CacheResult.SUCCESS_WITHOUT_MSG;
} catch (Throwable e) {
logError("PUT_ALL", "map(" + map.size() + ")", e);
return new CacheResult(e);
}
}
/**
* 删除缓存项。
*
* @param key 缓存的键。
* @return 缓存操作结果。
*/
@Override
protected CacheResult do_REMOVE(final K key) {
try {
final boolean ret = this.client.getBucket(getCacheKey(key), getCodec()).delete();
return ret ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.FAIL_WITHOUT_MSG;
} catch (Throwable e) {
logError("REMOVE", key, e);
return new CacheResult(e);
}
}
/**
* 批量删除缓存项。
*
* @param keys 缓存的键集合。
* @return 缓存操作结果。
*/
@Override
protected CacheResult do_REMOVE_ALL(final Set<? extends K> keys) {
try {
if (Objects.nonNull(keys) && !keys.isEmpty()) {
final RBatch batch = this.client.createBatch();
keys.forEach(key -> batch.getBucket(getCacheKey(key), getCodec()).deleteAsync());
batch.execute();
}
return CacheResult.SUCCESS_WITHOUT_MSG;
} catch (Throwable e) {
logError("REMOVE_ALL", "keys(" + keys.size() + ")", e);
return new CacheResult(e);
}
}
/**
* 在缓存中添加一个键值对,如果该键不存在。此操作等同于 PUT 操作,但仅当指定的键不存在时才执行。
* 如果键已存在,则不进行任何操作,并返回表示操作失败的 CacheResult。
*
* @param key 缓存中的键,不可为 null。
* @param value 缓存中的值,不可为 null。
* @param expireAfterWrite 缓存项的过期时间。
* @param timeUnit 缓存项的过期时间单位。
* @return 返回一个 CacheResult 对象,表示操作的结果。成功且键不存在时返回 SUCCESS_WITHOUT_MSG,键已存在时返回 EXISTS_WITHOUT_MSG,操作失败时返回包含错误信息的 CacheResult。
*/
@Override
protected CacheResult do_PUT_IF_ABSENT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {
try {
// 将过期时间转换为毫秒,并创建 CacheValueHolder 对象
final Duration expire = Duration.ofMillis(timeUnit.toMillis(expireAfterWrite));
final CacheValueHolder<V> holder = new CacheValueHolder<>(value, expire.toMillis());
// 尝试在缓存中设置键值对,如果键不存在,则设置成功
final boolean success = this.client.getBucket(getCacheKey(key), getCodec()).setIfAbsent(encoder(holder), expire);
// 根据操作结果返回相应的 CacheResult
return success ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.EXISTS_WITHOUT_MSG;
} catch (Throwable e) {
// 记录操作失败的日志
logError("PUT_IF_ABSENT", key, e);
// 返回包含错误信息的 CacheResult
return new CacheResult(e);
}
}
RedisLettuceCache
RedisLettuceCache是基于Lettuce中间件实现了对Redis的增删改查的,源码如下:
/**
* 基于Lettuce客户端实现的Redis缓存类,扩展自AbstractExternalCache。
* 提供缓存的基本操作,包括PUT、GET、REMOVE等。
*
* @param <K> 键的类型
* @param <V> 值的类型
*/
public class RedisLettuceCache<K, V> extends AbstractExternalCache<K, V> {
/**
* RedisLettuce缓存配置
*/
private final RedisLettuceCacheConfig<K, V> config;
/**
* 值的编码器,将对象转换为字节数组
*/
private final Function<Object, byte[]> valueEncoder;
/**
* 值的解码器,将字节数组转换为对象
*/
private final Function<byte[], Object> valueDecoder;
/**
* Redis客户端
*/
private final AbstractRedisClient client;
/**
* Lettuce连接管理器
*/
private final LettuceConnectionManager lettuceConnectionManager;
/**
* Redis同步字符串命令
*/
private final RedisStringCommands<byte[], byte[]> stringCommands;
/**
* Redis异步字符串命令
*/
private final RedisStringAsyncCommands<byte[], byte[]> stringAsyncCommands;
/**
* Redis异步键命令
*/
private final RedisKeyAsyncCommands<byte[], byte[]> keyAsyncCommands;
/**
* 构造函数,初始化RedisLettuce缓存。
*
* @param config RedisLettuce缓存配置
*/
public RedisLettuceCache(RedisLettuceCacheConfig<K, V> config) {
super(config);
this.config = config;
this.valueEncoder = config.getValueEncoder();
this.valueDecoder = config.getValueDecoder();
// 校验必要的配置项
if (config.getRedisClient() == null) {
throw new CacheConfigException("RedisClient is required");
}
if (config.isExpireAfterAccess()) {
throw new CacheConfigException("expireAfterAccess is not supported");
}
client = config.getRedisClient();
lettuceConnectionManager = config.getConnectionManager();
lettuceConnectionManager.init(client, config.getConnection());
stringCommands = (RedisStringCommands<byte[], byte[]>) lettuceConnectionManager.commands(client);
stringAsyncCommands = (RedisStringAsyncCommands<byte[], byte[]>) lettuceConnectionManager.asyncCommands(client);
keyAsyncCommands = (RedisKeyAsyncCommands<byte[], byte[]>) stringAsyncCommands;
}
/**
* 将当前缓存实例包装为指定类型的缓存实例。
*
* @param clazz 需要转换成的缓存接口类型
* @param <T> 转换后的类型
* @return 转换后的缓存实例
* @throws IllegalArgumentException 当指定的类型不被支持时抛出
*/
@Override
public <T> T unwrap(Class<T> clazz) {
Objects.requireNonNull(clazz);
if (AbstractRedisClient.class.isAssignableFrom(clazz)) {
return (T) client;
} else if (RedisClusterCommands.class.isAssignableFrom(clazz)) {
return (T) stringCommands;
} else if (RedisClusterAsyncCommands.class.isAssignableFrom(clazz)) {
return (T) stringAsyncCommands;
} else if (RedisClusterReactiveCommands.class.isAssignableFrom(clazz)) {
return (T) lettuceConnectionManager.reactiveCommands(client);
}
throw new IllegalArgumentException(clazz.getName());
}
/**
* 获取缓存配置。
*
* @return 缓存配置
*/
@Override
public CacheConfig<K, V> config() {
return config;
}
/**
* 设置异步操作超时时间。
*
* @param cr 缓存结果
*/
private void setTimeout(CacheResult cr) {
Duration d = Duration.ofMillis(config.getAsyncResultTimeoutInMillis());
cr.setTimeout(d);
}
/**
* 插入键值对到缓存。
*
* @param key 键
* @param value 值
* @param expireAfterWrite 缓存过期时间
* @param timeUnit 时间单位
* @return 缓存操作结果
*/
@Override
protected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
try {
// 构造带过期时间的缓存值持有者
CacheValueHolder<V> holder = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));
byte[] newKey = buildKey(key);
RedisFuture<String> future = stringAsyncCommands.psetex(newKey, timeUnit.toMillis(expireAfterWrite), valueEncoder.apply(holder));
CacheResult result = new CacheResult(future.handle((rt, ex) -> {
// 处理操作结果
if (ex != null) {
JetCacheExecutor.defaultExecutor().execute(() -> logError("PUT", key, ex));
return new ResultData(ex);
} else {
if ("OK".equals(rt)) {
return new ResultData(CacheResultCode.SUCCESS, null, null);
} else {
return new ResultData(CacheResultCode.FAIL, rt, null);
}
}
}));
setTimeout(result);
return result;
} catch (Exception ex) {
logError("PUT", key, ex);
return new CacheResult(ex);
}
}
/**
* 批量插入键值对到缓存。
*
* @param map 键值对映射
* @param expireAfterWrite 缓存过期时间
* @param timeUnit 时间单位
* @return 缓存操作结果
*/
@Override
protected CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {
try {
CompletionStage<Integer> future = CompletableFuture.completedFuture(0);
// 遍历并设置所有键值对
for (Map.Entry<? extends K, ? extends V> en : map.entrySet()) {
CacheValueHolder<V> holder = new CacheValueHolder(en.getValue(), timeUnit.toMillis(expireAfterWrite));
RedisFuture<String> resp = stringAsyncCommands.psetex(buildKey(en.getKey()), timeUnit.toMillis(expireAfterWrite), valueEncoder.apply(holder));
future = future.thenCombine(resp, (failCount, respStr) -> "OK".equals(respStr) ? failCount : failCount + 1);
}
CacheResult result = new CacheResult(future.handle((failCount, ex) -> {
// 处理操作结果
if (ex != null) {
JetCacheExecutor.defaultExecutor().execute(() -> logError("PUT_ALL", "map(" + map.size() + ")", ex));
return new ResultData(ex);
} else {
if (failCount == 0) {
return new ResultData(CacheResultCode.SUCCESS, null, null);
} else if (failCount == map.size()) {
return new ResultData(CacheResultCode.FAIL, null, null);
} else {
return new ResultData(CacheResultCode.PART_SUCCESS, null, null);
}
}
}));
setTimeout(result);
return result;
} catch (Exception ex) {
logError("PUT_ALL", "map(" + map.size() + ")", ex);
return new CacheResult(ex);
}
}
/**
* 从缓存中获取值。
*
* @param key 键
* @return 缓存获取结果,包含获取到的值和操作结果状态
*/
@Override
protected CacheGetResult<V> do_GET(K key) {
try {
byte[] newKey = buildKey(key);
RedisFuture<byte[]> future = stringAsyncCommands.get(newKey);
CacheGetResult<V> result = new CacheGetResult<>(future.handle((valueBytes, ex) -> {
// 处理获取结果
if (ex != null) {
JetCacheExecutor.defaultExecutor().execute(() -> logError("GET", key, ex));
return new ResultData(ex);
} else {
try {
if (valueBytes != null) {
CacheValueHolder<V> holder = (CacheValueHolder<V>) valueDecoder.apply(valueBytes);
if (System.currentTimeMillis() >= holder.getExpireTime()) {
return new ResultData(CacheResultCode.EXPIRED, null, null);
} else {
return new ResultData(CacheResultCode.SUCCESS, null, holder);
}
} else {
return new ResultData(CacheResultCode.NOT_EXISTS, null, null);
}
} catch (Exception exception) {
logError("GET", key, exception);
return new ResultData(exception);
}
}
}));
setTimeout(result);
return result;
} catch (Exception ex) {
logError("GET", key, ex);
return new CacheGetResult(ex);
}
}
/**
* 批量从缓存中获取值。
*
* @param keys 键集合
* @return 批量获取结果,包含每个键的获取结果
*/
@Override
protected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {
try {
ArrayList<K> keyList = new ArrayList<K>(keys);
byte[][] newKeys = keyList.stream().map((k) -> buildKey(k)).toArray(byte[][]::new);
Map<K, CacheGetResult<V>> resultMap = new HashMap<>();
if (newKeys.length == 0) {
return new MultiGetResult<K, V>(CacheResultCode.SUCCESS, null, resultMap);
}
// 执行批量获取操作
RedisFuture<List<KeyValue<byte[],byte[]>>> mgetResults = stringAsyncCommands.mget(newKeys);
MultiGetResult<K, V> result = new MultiGetResult<>(mgetResults.handle((list, ex) -> {
if (ex != null) {
JetCacheExecutor.defaultExecutor().execute(() -> logError("GET_ALL", "keys(" + keys.size() + ")", ex));
return new ResultData(ex);
} else {
try {
// 处理每个键的获取结果
for (int i = 0; i < list.size(); i++) {
KeyValue kv = list.get(i);
K key = keyList.get(i);
if (kv != null && kv.hasValue()) {
CacheValueHolder<V> holder = (CacheValueHolder<V>) valueDecoder.apply((byte[]) kv.getValue());
if (System.currentTimeMillis() >= holder.getExpireTime()) {
resultMap.put(key, CacheGetResult.EXPIRED_WITHOUT_MSG);
} else {
CacheGetResult<V> r = new CacheGetResult<V>(CacheResultCode.SUCCESS, null, holder);
resultMap.put(key, r);
}
} else {
resultMap.put(key, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
}
}
return new ResultData(CacheResultCode.SUCCESS, null, resultMap);
} catch (Exception exception) {
logError("GET_ALL", "keys(" + keys.size() + ")", exception);
return new ResultData(exception);
}
}
}));
setTimeout(result);
return result;
} catch (Exception ex) {
logError("GET_ALL", "keys(" + keys.size() + ")", ex);
return new MultiGetResult<K, V>(ex);
}
}
/**
* 从缓存中移除指定键。
*
/**
* 从缓存中移除指定的键。
*
* @param key 要移除的键
* @return CacheResult 包含操作结果的结果对象,其中包含成功、失败、不存在等状态
*/
@Override
protected CacheResult do_REMOVE(K key) {
try {
// 异步删除指定的键
RedisFuture<Long> future = keyAsyncCommands.del(buildKey(key));
CacheResult result = new CacheResult(future.handle((rt, ex) -> {
// 处理异步操作的结果或异常
if (ex != null) {
// 记录错误信息
JetCacheExecutor.defaultExecutor().execute(() -> logError("REMOVE", key, ex));
return new ResultData(ex);
} else {
// 根据删除的结果判断操作状态
if (rt == null) {
return new ResultData(CacheResultCode.FAIL, null, null);
} else if (rt == 1) {
return new ResultData(CacheResultCode.SUCCESS, null, null);
} else if (rt == 0) {
return new ResultData(CacheResultCode.NOT_EXISTS, null, null);
} else {
return new ResultData(CacheResultCode.FAIL, null, null);
}
}
}));
// 设置超时时间
setTimeout(result);
return result;
} catch (Exception ex) {
// 捕获并处理异常
logError("REMOVE", key, ex);
return new CacheResult(ex);
}
}
/**
* 从缓存中移除指定的一组键。
*
* @param keys 要移除的键的集合
* @return CacheResult 包含操作结果的结果对象,其中包含成功、失败等状态
*/
@Override
protected CacheResult do_REMOVE_ALL(Set<? extends K> keys) {
try {
// 将键集合转换为字节数组集合并异步删除
byte[][] newKeys = keys.stream().map((k) -> buildKey(k)).toArray((len) -> new byte[keys.size()][]);
RedisFuture<Long> future = keyAsyncCommands.del(newKeys);
CacheResult result = new CacheResult(future.handle((v, ex) -> {
// 处理异步操作的结果或异常
if (ex != null) {
// 记录错误信息
JetCacheExecutor.defaultExecutor().execute(() -> logError("REMOVE_ALL", "keys(" + keys.size() + ")", ex));
return new ResultData(ex);
} else {
return new ResultData(CacheResultCode.SUCCESS, null, null);
}
}));
// 设置超时时间
setTimeout(result);
return result;
} catch (Exception ex) {
// 捕获并处理异常
logError("REMOVE_ALL", "keys(" + keys.size() + ")", ex);
return new CacheResult(ex);
}
}
/**
* 如果指定的键不存在,则将其添加到缓存中。
*
* @param key 键
* @param value 值
* @param expireAfterWrite 缓存过期时间
* @param timeUnit 时间单位
* @return CacheResult 包含操作结果的结果对象,其中包含成功、已存在、失败等状态
*/
@Override
protected CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
try {
// 创建值持有者并设置过期时间
CacheValueHolder<V> holder = new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));
byte[] newKey = buildKey(key);
// 异步且仅当键不存在时设置键值对
RedisFuture<String> future = stringAsyncCommands.set(newKey, valueEncoder.apply(holder), SetArgs.Builder.nx().px(timeUnit.toMillis(expireAfterWrite)));
CacheResult result = new CacheResult(future.handle((rt, ex) -> {
// 处理异步操作的结果或异常
if (ex != null) {
// 记录错误信息
JetCacheExecutor.defaultExecutor().execute(() -> logError("PUT_IF_ABSENT", key, ex));
return new ResultData(ex);
} else {
// 根据设置的结果判断操作状态
if ("OK".equals(rt)) {
return new ResultData(CacheResultCode.SUCCESS, null, null);
} else if (rt == null) {
return new ResultData(CacheResultCode.EXISTS, null, null);
} else {
return new ResultData(CacheResultCode.FAIL, rt , null);
}
}
}));
// 设置超时时间
setTimeout(result);
return result;
} catch (Exception ex) {
// 捕获并处理异常
logError("PUT_IF_ABSENT", key, ex);
return new CacheResult(ex);
}
}
}
多级缓存
多级缓存是指内存缓存+远程缓存,操作时先操作内存缓存,然后再操作远程缓存。例如查询缓存数据时,先从内存缓存中获取数据,如果不存在才会从远程缓存中获取数据。增删改缓存数据时,会同时修改内存缓存和远程缓存的数据。
MultiLevelCache
MultiLevelCache类的属性private Cache[] caches的第一个Cache实例是内存缓存实例,第二个Cache实例是远程缓存实例。在缓存的增删改查操作时,会分别对caches中的Cache实例进行增删改查处理。
先看一下构建多级缓存的源码,便于理解;SimpleCacheManager的getOrCreateCache函数用于获取缓存实例,如果不存在就会创建一个新的。源码如下:
/**
* 根据提供的配置获取或创建一个缓存实例。
* 如果指定名称的缓存已存在,则直接返回该缓存实例;如果不存在,则根据配置创建一个新的缓存实例。
*
* @param config 缓存的配置信息,包括区域名称和缓存名称,不能为空。
* @return 缓存实例,类型为 {@link Cache}。
* @throws IllegalStateException 如果缓存构建器模板未设置。
* @throws NullPointerException 如果配置中的区域名称或名称为空。
*/
@Override
public <K, V> Cache<K, V> getOrCreateCache(QuickConfig config) {
// 检查缓存构建器模板是否已设置
if (cacheBuilderTemplate == null) {
throw new IllegalStateException("cacheBuilderTemplate not set");
}
// 确保配置中的区域名称和名称不为空
Objects.requireNonNull(config.getArea());
Objects.requireNonNull(config.getName());
// 根据区域获取缓存映射
ConcurrentHashMap<String, Cache> m = getCachesByArea(config.getArea());
// 尝试从映射中获取缓存实例
Cache c = m.get(config.getName());
if (c != null) {
// 如果缓存已存在,直接返回
return c;
}
// 如果缓存不存在,则通过配置创建新的缓存实例并添加到映射中
return m.computeIfAbsent(config.getName(), n -> create(config));
}
在getOrCreateCache函数函数的最后会调用create函数来创建缓存实例,对应的源码如下:
/**
* 根据配置创建缓存实例。
* 本函数根据配置决定创建哪种类型的缓存。支持的缓存类型包括本地缓存、远程缓存以及多级缓存。
* 如果配置指定为本地缓存,则只创建本地缓存实例。
* 如果配置指定为远程缓存,则只创建远程缓存实例。
* 如果配置没有指定缓存类型或指定为多级缓存,则同时创建本地和远程缓存实例,并构建一个多级缓存。
* 此外,根据配置还可以对缓存实例进行刷新策略和加载器的设置。
*
* @param config 缓存配置,包含缓存的各种配置信息,如缓存类型、本地缓存过期时间等。
* @return 创建好的缓存实例。
*/
private Cache create(QuickConfig config) {
Cache cache;
// 根据缓存类型决定创建哪种缓存
if (config.getCacheType() == null || config.getCacheType() == CacheType.REMOTE) {
cache = buildRemote(config);
} else if (config.getCacheType() == CacheType.LOCAL) {
cache = buildLocal(config);
} else {
// 创建多级缓存
Cache local = buildLocal(config);
Cache remote = buildRemote(config);
boolean useExpireOfSubCache = config.getLocalExpire() != null;
cache = MultiLevelCacheBuilder.createMultiLevelCacheBuilder()
.expireAfterWrite(remote.config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS)
.addCache(local, remote)
.useExpireOfSubCache(useExpireOfSubCache)
.cacheNullValue(config.getCacheNullValue() != null ?
config.getCacheNullValue() : DEFAULT_CACHE_NULL_VALUE)
.buildCache();
}
// 根据配置应用刷新策略和加载器
if (config.getRefreshPolicy() != null) {
cache = new RefreshCache(cache);
} else if (config.getLoader() != null) {
cache = new LoadingCache(cache);
}
// 设置缓存的刷新策略和加载器
cache.config().setRefreshPolicy(config.getRefreshPolicy());
cache.config().setLoader(config.getLoader());
// 应用穿透防护设置
boolean protect = config.getPenetrationProtect() != null ? config.getPenetrationProtect()
: cacheBuilderTemplate.isPenetrationProtect();
cache.config().setCachePenetrationProtect(protect);
cache.config().setPenetrationProtectTimeout(config.getPenetrationProtectTimeout());
// 安装缓存监控器
for (CacheMonitorInstaller i : cacheBuilderTemplate.getCacheMonitorInstallers()) {
i.addMonitors(this, cache, config);
}
return cache;
}
在上面代码中会根据配置的缓存类型,分别创建内存缓存和远程缓存,如果缓存类型为BOTH时先创建内存缓存,再创建远程缓存并调用MultiLevelCacheBuilder.createMultiLevelCacheBuilder()构建多级缓存实例,并将内存缓存和远程缓存实例添加到远程缓存实例中,这个顺序很重要,多级缓存在对缓存进行增删改查时就按照这个顺序串行执行的。源码如下:
/**
* 多级缓存构建器类,支持构建多级缓存配置。
* @param <T> 继承自MultiLevelCacheBuilder的泛型类型,支持泛型的自我引用。
*/
public class MultiLevelCacheBuilder<T extends MultiLevelCacheBuilder<T>> extends AbstractCacheBuilder<T> {
/**
* 多级缓存构建器的具体实现类。
*/
public static class MultiLevelCacheBuilderImpl extends MultiLevelCacheBuilder<MultiLevelCacheBuilderImpl> {
}
/**
* 创建一个MultiLevelCacheBuilder实例。
* @return 返回一个新的MultiLevelCacheBuilderImpl实例。
*/
public static MultiLevelCacheBuilderImpl createMultiLevelCacheBuilder() {
return new MultiLevelCacheBuilderImpl();
}
/**
* 构建函数,配置缓存构建过程。
* 通过配置函数参数指定如何根据配置构建多级缓存实例。
*/
protected MultiLevelCacheBuilder() {
buildFunc(config -> new MultiLevelCache((MultiLevelCacheConfig) config));
}
/**
* 获取配置实例,如果不存在则新建。
* @return 返回多级缓存配置实例。
*/
@Override
public MultiLevelCacheConfig getConfig() {
if (config == null) {
config = new MultiLevelCacheConfig();
}
return (MultiLevelCacheConfig) config;
}
/**
* 向缓存配置中添加一个或多个缓存实例。
* @param caches 缓存实例数组。
* @return 返回当前构建器实例,支持链式调用。
*/
public T addCache(Cache... caches) {
for (Cache c : caches) {
getConfig().getCaches().add(c);
}
return self();
}
/**
* 设置缓存实例列表。
* @param caches 缓存实例列表。
*/
public void setCaches(List<Cache> caches) {
getConfig().setCaches(caches);
}
/**
* 配置是否使用子缓存的过期时间。
* @param useExpireOfSubCache 是否使用子缓存的过期时间。
* @return 返回当前构建器实例,支持链式调用。
*/
public T useExpireOfSubCache(boolean useExpireOfSubCache) {
getConfig().setUseExpireOfSubCache(useExpireOfSubCache);
return self();
}
/**
* 设置是否使用子缓存的过期时间。
* @param useExpireOfSubCache 是否使用子缓存的过期时间。
*/
public void setUseExpireOfSubCache(boolean useExpireOfSubCache) {
getConfig().setUseExpireOfSubCache(useExpireOfSubCache);
}
/**
* 不支持设置键的转换器,抛出异常。
* @param keyConvertor 键转换器函数。
* @return 无返回值,始终抛出UnsupportedOperationException。
* @throws UnsupportedOperationException 当调用此方法时抛出,表示不支持此操作。
*/
@Override
public T keyConvertor(Function<Object, Object> keyConvertor) {
throw new UnsupportedOperationException("MultiLevelCache do not need a key convertor");
}
/**
* 不支持设置键的转换器,抛出异常。
* @param keyConvertor 键转换器函数。
* @throws UnsupportedOperationException 当调用此方法时抛出,表示不支持此操作。
*/
@Override
public void setKeyConvertor(Function<Object, Object> keyConvertor) {
throw new UnsupportedOperationException("MultiLevelCache do not need a key convertor");
}
/**
* 不支持设置访问后过期时间,抛出异常。
* @param defaultExpire 默认过期时间。
* @param timeUnit 时间单位。
* @return 无返回值,始终抛出UnsupportedOperationException。
* @throws UnsupportedOperationException 当调用此方法时抛出,表示不支持此操作。
*/
@Override
public T expireAfterAccess(long defaultExpire, TimeUnit timeUnit) {
throw new UnsupportedOperationException("MultiLevelCache do not support expireAfterAccess");
}
/**
* 不支持设置访问后过期时间(以毫秒为单位),抛出异常。
* @param expireAfterAccessInMillis 访问后过期时间(毫秒)。
* @throws UnsupportedOperationException 当调用此方法时抛出,表示不支持此操作。
*/
@Override
public void setExpireAfterAccessInMillis(long expireAfterAccessInMillis) {
throw new UnsupportedOperationException("MultiLevelCache do not support expireAfterAccess");
}
}
在上面代码中,内存缓存和远程缓存已经添加到MultiLevelCacheConfig实例的字段caches中,多级缓存构建器中需要调用buildCache()函数才会创建多级缓存实例,buildCache源码如下:
/**
* 构建缓存实例。
* 本方法首先检查是否存在构建函数(buildFunc),若不存在则抛出缓存配置异常。
* 接着执行构建前的准备工作,并从配置中克隆一份配置实例。
* 使用构建函数根据配置实例创建一个缓存实例。
* 如果配置中指定了加载器(Loader),但未指定刷新策略,则将创建的缓存实例包装为LoadingCache;
* 如果既指定了加载器,又指定了刷新策略,则包装为RefreshCache。
* 最后返回经过可能的包装后的缓存实例。
*
* @param <K> 缓存键的类型。
* @param <V> 缓存值的类型。
* @return 构建完成的缓存实例。
* @throws CacheConfigException 如果未提供构建函数。
*/
@Override
public final <K, V> Cache<K, V> buildCache() {
if (buildFunc == null) { // 检查构建函数是否为空,为空则抛出异常
throw new CacheConfigException("no buildFunc");
}
beforeBuild(); // 执行构建前的准备工作
CacheConfig c = getConfig().clone(); // 克隆配置
Cache<K, V> cache = buildFunc.apply(c); // 使用构建函数根据配置创建缓存实例
if (c.getLoader() != null) { // 检查是否存在加载器
if (c.getRefreshPolicy() == null) { // 有加载器但无刷新策略时,使用LoadingCache包装
cache = new LoadingCache<>(cache);
} else { // 既有加载器又有刷新策略时,使用RefreshCache包装
cache = new RefreshCache<>(cache);
}
}
return cache; // 返回经过可能包装的缓存实例
}
在上面的buildFunc.apply(c)会执行new MultiLevelCache((MultiLevelCacheConfig) config)创建多级缓存实例。
接下来看看MultiLevelCache的源码如下:
/**
* 多级缓存类,继承自AbstractCache,支持多级缓存策略。
*
* @param <K> 键的类型
* @param <V> 值的类型
*/
public class MultiLevelCache<K, V> extends AbstractCache<K, V> {
private Cache[] caches; // 缓存数组
private MultiLevelCacheConfig<K, V> config; // 多级缓存配置
/**
* 已弃用的构造函数,使用可变参数初始化多级缓存。
*
* @param caches 缓存实例数组
* @throws CacheConfigException 配置异常
*/
@SuppressWarnings("unchecked")
@Deprecated
public MultiLevelCache(Cache... caches) throws CacheConfigException {
this.caches = caches;
checkCaches();
CacheConfig lastConfig = caches[caches.length - 1].config();
config = new MultiLevelCacheConfig<>();
config.setCaches(Arrays.asList(caches));
config.setExpireAfterWriteInMillis(lastConfig.getExpireAfterWriteInMillis());
config.setCacheNullValue(lastConfig.isCacheNullValue());
}
/**
* 使用多级缓存配置构造函数。
*
* @param cacheConfig 多级缓存配置
* @throws CacheConfigException 配置异常
*/
@SuppressWarnings("unchecked")
public MultiLevelCache(MultiLevelCacheConfig<K, V> cacheConfig) throws CacheConfigException {
this.config = cacheConfig;
this.caches = cacheConfig.getCaches().toArray(new Cache[]{});
checkCaches();
}
/**
* 检查缓存配置是否合法。
*/
private void checkCaches() {
if (caches == null || caches.length == 0) {
throw new IllegalArgumentException();
}
for (Cache c : caches) {
if (c.config().getLoader() != null) {
throw new CacheConfigException("Loader on sub cache is not allowed, set the loader into MultiLevelCache.");
}
}
}
/**
* 获取缓存数组。
*
* @return 缓存数组
*/
public Cache[] caches() {
return caches;
}
/**
* 获取缓存配置。
*
* @return 多级缓存配置
*/
@Override
public MultiLevelCacheConfig<K, V> config() {
return config;
}
/**
* 存储键值对到缓存中。
*
* @param key 键
* @param value 值
* @return 缓存操作结果
*/
@Override
public CacheResult PUT(K key, V value) {
if (config.isUseExpireOfSubCache()) {
return PUT(key, value, 0, null);
} else {
return PUT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
}
}
/**
* 批量存储键值对到缓存中。
*
* @param map 键值对映射
* @return 缓存操作结果
*/
@Override
public CacheResult PUT_ALL(Map<? extends K, ? extends V> map) {
if (config.isUseExpireOfSubCache()) {
return PUT_ALL(map, 0, null);
} else {
return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);
}
}
/**
* 从缓存中获取值。
*
* @param key 键
* @return 值获取结果
*/
@Override
protected CacheGetResult<V> do_GET(K key) {
for (int i = 0; i < caches.length; i++) {
Cache cache = caches[i];
CacheGetResult result = cache.GET(key);
if (result.isSuccess()) {
CacheValueHolder<V> holder = unwrapHolder(result.getHolder());
checkResultAndFillUpperCache(key, i, holder);
return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);
}
}
return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
}
/**
* 解包缓存值持有者。
*
* @param h 缓存值持有者
* @return 解包后的缓存值持有者
*/
private CacheValueHolder<V> unwrapHolder(CacheValueHolder<V> h) {
Objects.requireNonNull(h);
if (h.getValue() instanceof CacheValueHolder) {
return (CacheValueHolder<V>) h.getValue();
} else {
return h;
}
}
/**
* 检查获取结果,并填充上级缓存。
*
* @param key 键
* @param i 缓存索引
* @param h 缓存值持有者
*/
private void checkResultAndFillUpperCache(K key, int i, CacheValueHolder<V> h) {
Objects.requireNonNull(h);
long currentExpire = h.getExpireTime();
long now = System.currentTimeMillis();
if (now <= currentExpire) {
if(config.isUseExpireOfSubCache()){
PUT_caches(i, key, h.getValue(), 0, null);
} else {
long restTtl = currentExpire - now;
if (restTtl > 0) {
PUT_caches(i, key, h.getValue(), restTtl, TimeUnit.MILLISECONDS);
}
}
}
}
/**
* 批量获取缓存值。
*
* @param keys 键集合
* @return 缓存批量获取结果
*/
@Override
protected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {
HashMap<K, CacheGetResult<V>> resultMap = new HashMap<>();
Set<K> restKeys = new HashSet<>(keys);
for (int i = 0; i < caches.length; i++) {
if (restKeys.size() == 0) {
break;
}
Cache<K, CacheValueHolder<V>> c = caches[i];
MultiGetResult<K, CacheValueHolder<V>> allResult = c.GET_ALL(restKeys);
if (allResult.isSuccess() && allResult.getValues() != null) {
for (Map.Entry<K, CacheGetResult<CacheValueHolder<V>>> en : allResult.getValues().entrySet()) {
K key = en.getKey();
CacheGetResult result = en.getValue();
if (result.isSuccess()) {
CacheValueHolder<V> holder = unwrapHolder(result.getHolder());
checkResultAndFillUpperCache(key, i, holder);
resultMap.put(key, new CacheGetResult(CacheResultCode.SUCCESS, null, holder));
restKeys.remove(key);
}
}
}
}
for (K k : restKeys) {
resultMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);
}
return new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);
}
/**
* 在指定缓存中存储键值对。
*
* @param key 键
* @param value 值
* @param expire 过期时间
* @param timeUnit 时间单位
* @return 缓存操作结果
*/
@Override
protected CacheResult do_PUT(K key, V value, long expire, TimeUnit timeUnit) {
return PUT_caches(caches.length, key, value, expire, timeUnit);
}
/**
* 在所有缓存中批量存储键值对。
*
* @param map 键值对映射
* @param expire 过期时间
* @param timeUnit 时间单位
* @return 缓存操作结果
*/
@Override
protected CacheResult do_PUT_ALL(Map<? extends K, ? extends V> map, long expire, TimeUnit timeUnit) {
CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);
for (Cache c : caches) {
CacheResult r;
if(timeUnit == null) {
r = c.PUT_ALL(map);
} else {
r = c.PUT_ALL(map, expire, timeUnit);
}
future = combine(future, r);
}
return new CacheResult(future);
}
/**
* 在指定缓存层级存储键值对。
*
* @param lastIndex 最后一个缓存索引
* @param key 键
* @param value 值
* @param expire 过期时间
* @param timeUnit 时间单位
* @return 缓存操作结果
*/
private CacheResult PUT_caches(int lastIndex, K key, V value, long expire, TimeUnit timeUnit) {
CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);
for (int i = 0; i < lastIndex; i++) {
Cache cache = caches[i];
CacheResult r;
if (timeUnit == null) {
r = cache.PUT(key, value);
} else {
r = cache.PUT(key, value, expire, timeUnit);
}
future = combine(future, r);
}
return new CacheResult(future);
}
/**
* 合并多个CompletableFuture结果。
*
* @param future 当前CompletableFuture
* @param result 新的缓存结果
* @return 合并后的CompletableFuture
*/
private CompletableFuture<ResultData> combine(CompletableFuture<ResultData> future, CacheResult result) {
return future.thenCombine(result.future(), (d1, d2) -> {
if (d1 == null) {
return d2;
}
if (d1.getResultCode() != d2.getResultCode()) {
return new ResultData(CacheResultCode.PART_SUCCESS, null, null);
}
return d1;
});
}
/**
* 从缓存中移除指定键的条目。
*
* @param key 键
* @return 缓存操作结果
*/
@Override
protected CacheResult do_REMOVE(K key) {
CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);
for (Cache cache : caches) {
CacheResult r = cache.REMOVE(key);
future = combine(future, r);
}
return new CacheResult(future);
}
/**
* 从缓存中移除指定键集合的条目。
*
* @param keys 键集合
* @return 缓存操作结果
*/
@Override
protected CacheResult do_REMOVE_ALL(Set<? extends K> keys) {
CompletableFuture<ResultData> future = CompletableFuture.completedFuture(null);
for (Cache cache : caches) {
CacheResult r = cache.REMOVE_ALL(keys);
future = combine(future, r);
}
return new CacheResult(future);
}
在上面MultiLevelCache的caches组合中,第一个要素是内存缓存,第二个要素是远程缓存。根据上面的代码可以发现,除了读操作是内存缓存查询到数据后直接返回外,增删改的操作会同时执行内存缓存和远程缓存。
代理缓存
什么是代理缓存?其作用是什么呢?我们知道代理模式,代理缓存就是采用代理模式,在不改变缓存对象的前提下,通过代理对象对缓存对象进行扩展。
我们分析了上面的内存缓存、远程缓存和多级缓存,我们发现缓存实例可以对缓存进行增删改查;如果缓存数据不存在,是需要加载数据到缓存中的,这个步骤在缓存实例中并没有实现,虽然AbstractCache的computeIfAbsent函数可以显示的传一个缓存数据加载的函数,来实现相应的功能,但是查询缓存的函数GET函数却没有实现computeIfAbsent函数类似的功能。
除此之外,缓存数据都有生命周期的,一旦到了到期时间就要从缓存中删掉,这时候再查缓存数据时,就要重新加载数据到缓存中,相应的性能会存在波动;这时候我们可能会希望有一套机制,能够定时的刷新缓存中的数据,这样不仅可以确保缓存中的数据是新的,同时查询的过程也不会存在性能波动。
为了解决上面的问题,代理缓存应运而生。接下来我们将详细介绍一下代理缓存涉及到的类。
ProxyCache
ProxyCache是代理缓存的基类,源码如下:
/**
* 代理缓存接口,继承自Cache接口。提供了一种对缓存进行代理的机制,允许在缓存层面上添加额外的功能或逻辑。
* @param <K> 缓存键的类型
* @param <V> 缓存值的类型
*/
public interface ProxyCache<K, V> extends Cache<K, V> {
/**
* 获取目标缓存对象。此方法用于获取被代理的缓存实例。
* @return Cache<K, V> 目标缓存对象,实现了Cache接口。
*/
Cache<K, V> getTargetCache();
/**
* 将当前缓存对象解包为指定类型的对象。此方法会尝试将当前缓存的代理对象解包,
* 实现与{@link Cache}接口中相同的行为,具体行为取决于目标缓存的实现。
* @param clazz 需要解包的目标类型。
* @param <T> 解包后的对象类型。
* @return T 解包后的对象实例,如果无法解包则返回null。
*/
@Override
default <T> T unwrap(Class<T> clazz) {
return getTargetCache().unwrap(clazz);
}
}
代理缓存接口,继承自Cache接口。提供了一种对缓存进行代理的机制,允许在缓存层面上添加额外的功能或逻辑。
SimpleProxyCache
简单代理缓存类,实现了 ProxyCache 接口,用于对 Cache 进行简单的代理封装。、
/**
* 简单代理缓存类,实现了 ProxyCache 接口,用于对 Cache 进行简单的代理封装。
*
* @param <K> 缓存键的类型
* @param <V> 缓存值的类型
*/
public class SimpleProxyCache<K, V> implements ProxyCache<K, V> {
protected Cache<K, V> cache; // 被代理的缓存对象
/**
* 构造函数,初始化代理缓存。
*
* @param cache 被代理的缓存对象。
*/
public SimpleProxyCache(Cache<K, V> cache) {
this.cache = cache;
}
/**
* 获取缓存配置。
*
* @return 缓存配置对象。
*/
@Override
public CacheConfig<K, V> config() {
return cache.config();
}
/**
* 获取目标缓存对象。
*
* @return 目标缓存对象。
*/
@Override
public Cache<K, V> getTargetCache() {
return cache;
}
/**
* 根据键获取缓存值。
*
* @param key 缓存键。
* @return 缓存的值,如果不存在则返回 null。
*/
@Override
public V get(K key) {
return cache.get(key);
}
/**
* 根据键集合批量获取缓存值。
*
* @param keys 缓存键的集合。
* @return 缓存值的映射表,键为缓存键,值为对应的缓存值。
*/
@Override
public Map<K, V> getAll(Set<? extends K> keys) {
return cache.getAll(keys);
}
/**
* 将键值对存入缓存。
*
* @param key 缓存键。
* @param value 缓存值。
*/
@Override
public void put(K key, V value) {
cache.put(key, value);
}
/**
* 批量存入多个键值对到缓存。
*
* @param map 键值对的映射表。
*/
@Override
public void putAll(Map<? extends K, ? extends V> map) {
cache.putAll(map);
}
/**
* 如果给定的键在缓存中不存在,则将键值对存入缓存。
*
* @param key 缓存键。
* @param value 缓存值。
* @return 如果成功存入则返回 true,如果已存在则返回 false。
*/
@Override
public boolean putIfAbsent(K key, V value) {
return cache.putIfAbsent(key, value);
}
/**
* 从缓存中移除指定键的条目。
*
* @param key 缓存键。
* @return 如果成功移除则返回 true,如果不存在则返回 false。
*/
@Override
public boolean remove(K key) {
return cache.remove(key);
}
/**
* 批量从缓存中移除多个键对应的条目。
*
* @param keys 缓存键的集合。
*/
@Override
public void removeAll(Set<? extends K> keys) {
cache.removeAll(keys);
}
/**
* 尝试解包缓存对象为指定的类型。
*
* @param clazz 需要解包的目标类类型。
* @param <T> 目标类的类型。
* @return 解包后的对象,如果无法解包则返回 null。
*/
@Override
public <T> T unwrap(Class<T> clazz) {
return cache.unwrap(clazz);
}
/**
* 尝试获取锁,并在获取成功后执行指定操作。
*
* @param key 锁的键。
* @param expire 锁的过期时间。
* @param timeUnit 锁的过期时间单位。
* @return 如果获取锁并执行操作成功则返回 true,否则返回 false。
*/
@Override
public AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit) {
return cache.tryLock(key, expire, timeUnit);
}
/**
* 尝试获取锁,并在获取成功后执行指定操作。
*
* @param key 锁的键。
* @param expire 锁的过期时间。
* @param timeUnit 锁的过期时间单位。
* @param action 获取锁成功后执行的操作。
* @return 如果获取锁并执行操作成功则返回 true,否则返回 false。
*/
@Override
public boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action) {
return cache.tryLockAndRun(key, expire, timeUnit, action);
}
/**
* 根据键获取缓存值,如果不存在则尝试使用给定的加载器加载。
*
* @param key 缓存键。
* @param loader 缓存值的加载器。
* @return 缓存的值,或者加载器加载的值。
*/
@Override
public V computeIfAbsent(K key, Function<K, V> loader) {
return cache.computeIfAbsent(key, loader);
}
/**
* 根据键获取缓存值,如果不存在则尝试使用给定的加载器加载,并指定是否缓存 null 值。
*
* @param key 缓存键。
* @param loader 缓存值的加载器。
* @param cacheNullWhenLoaderReturnNull 当加载器返回 null 时,是否缓存 null 值。
* @return 缓存的值,或者加载器加载的值。
*/
@Override
public V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull) {
return cache.computeIfAbsent(key, loader, cacheNullWhenLoaderReturnNull);
}
/**
* 根据键获取缓存值,如果不存在则尝试使用给定的加载器加载,并指定是否缓存 null 值以及过期时间。
*
* @param key 缓存键。
* @param loader 缓存值的加载器。
* @param cacheNullWhenLoaderReturnNull 当加载器返回 null 时,是否缓存 null 值。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 缓存项的写入过期时间单位。
* @return 缓存的值,或者加载器加载的值。
*/
@Override
public V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull, long expireAfterWrite, TimeUnit timeUnit) {
return cache.computeIfAbsent(key, loader, cacheNullWhenLoaderReturnNull, expireAfterWrite, timeUnit);
}
/**
* 将键值对存入缓存,并指定过期时间。
*
* @param key 缓存键。
* @param value 缓存值。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 缓存项的写入过期时间单位。
*/
@Override
public void put(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
cache.put(key, value, expireAfterWrite, timeUnit);
}
/**
* 存储键值对到缓存中。
*
* @param key 缓存键。
* @param value 缓存值。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult PUT(K key, V value) {
return cache.PUT(key, value);
}
/**
* 存储键值对到缓存中,并指定过期时间。
*
* @param key 缓存键。
* @param value 缓存值。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 缓存项的写入过期时间单位。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
return cache.PUT(key, value, expireAfterWrite, timeUnit);
}
/**
* 批量存储键值对到缓存中。
*
* @param map 键值对映射表。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult PUT_ALL(Map<? extends K, ? extends V> map) {
return cache.PUT_ALL(map);
}
/**
* 批量存储键值对到缓存中,并指定过期时间。
*
* @param map 键值对映射表。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 缓存项的写入过期时间单位。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult PUT_ALL(Map<? extends K, ? extends V> map, long expireAfterWrite, TimeUnit timeUnit) {
return cache.PUT_ALL(map, expireAfterWrite, timeUnit);
}
/**
* 从缓存中移除指定键的条目。
*
* @param key 缓存键。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult REMOVE(K key) {
return cache.REMOVE(key);
}
/**
* 批量从缓存中移除多个键对应的条目。
*
* @param keys 缓存键集合。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult REMOVE_ALL(Set<? extends K> keys) {
return cache.REMOVE_ALL(keys);
}
/**
* 如果给定的键在缓存中不存在,则将键值对存入缓存,并指定过期时间。
*
* @param key 缓存键。
* @param value 缓存值。
* @param expireAfterWrite 缓存项的写入过期时间。
* @param timeUnit 缓存项的写入过期时间单位。
* @return 操作结果,成功或失败。
*/
@Override
public CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {
return cache.PUT_IF_ABSENT(key, value, expireAfterWrite, timeUnit);
}
/**
* 关闭缓存对象,释放资源。
*/
@Override
public void close() {
cache.close();
}
}
SimpleProxyCache中的所有函数都是直接调用代理的缓存类的相应方法来实现缓存的功能的,并没有进行相应的扩展。
LoadingCache
带有加载功能的缓存类,扩展自SimpleProxyCache,支持通过CacheLoader动态加载缓存数据。当查询缓存数据时,如果缓存数据不存在,就会调用CacheLoader类的调用业务端的函数获取对应的数据,并写入到缓存中。
/**
* 带有加载功能的缓存类,扩展自SimpleProxyCache,支持通过CacheLoader动态加载缓存数据。
*
* @param <K> 缓存键的类型
* @param <V> 缓存值的类型
*/
public class LoadingCache<K, V> extends SimpleProxyCache<K, V> {
protected Consumer<CacheEvent> eventConsumer; // 缓存事件消费者
protected CacheConfig<K, V> config; // 缓存配置
/**
* 构造函数,初始化缓存。
*
* @param cache 基础缓存实例
*/
public LoadingCache(Cache<K, V> cache) {
super(cache);
this.config = config();
eventConsumer = CacheUtil.getAbstractCache(cache)::notify; // 初始化事件消费者
}
/**
* 获取缓存值,如果缓存中不存在,则通过CacheLoader加载。
*
* @param key 缓存键
* @return 缓存值,如果不存在且无法加载,则可能返回null
* @throws CacheInvokeException 加载缓存数据时发生异常
*/
@Override
public V get(K key) throws CacheInvokeException {
CacheLoader<K, V> loader = config.getLoader();
if (loader != null) {
return AbstractCache.computeIfAbsentImpl(key, loader,
config.isCacheNullValue() ,0, null, this);
} else {
return cache.get(key);
}
}
/**
* 判断是否需要更新缓存。
*
* @param loadedValue 加载的值
* @param loader 缓存加载器
* @return 如果满足更新条件,则返回true;否则返回false。
*/
protected boolean needUpdate(V loadedValue, CacheLoader<K, V> loader) {
if (loadedValue == null && !config.isCacheNullValue()) {
return false;
}
if (loader.vetoCacheUpdate()) {
return false;
}
return true;
}
/**
* 批量获取缓存值,缺失的值将通过CacheLoader加载。
*
* @param keys 缓存键集合
* @return 包含所有键值对的映射,可能包含加载失败的键
* @throws CacheInvokeException 加载缓存数据时发生异常
*/
@Override
public Map<K, V> getAll(Set<? extends K> keys) throws CacheInvokeException {
CacheLoader<K, V> loader = config.getLoader();
if (loader != null) {
MultiGetResult<K, V> r = GET_ALL(keys);
Map<K, V> kvMap;
if (r.isSuccess() || r.getResultCode() == CacheResultCode.PART_SUCCESS) {
kvMap = r.unwrapValues();
} else {
kvMap = new HashMap<>();
}
Set<K> keysNeedLoad = new LinkedHashSet<>();
keys.forEach((k) -> {
if (!kvMap.containsKey(k)) {
keysNeedLoad.add(k);
}
});
if (!config.isCachePenetrationProtect()) {
// 无防穿透保护时的加载逻辑
if (eventConsumer != null) {
loader = CacheUtil.createProxyLoader(cache, loader, eventConsumer);
}
Map<K, V> loadResult;
try {
loadResult = loader.loadAll(keysNeedLoad);
CacheLoader<K, V> theLoader = loader;
Map<K, V> updateValues = new HashMap<>();
loadResult.forEach((k,v)->{
if (needUpdate(v, theLoader)){
updateValues.put(k, v);
}
});
// 批量更新缓存
if (!updateValues.isEmpty()) {
PUT_ALL(updateValues);
}
} catch (Throwable e) {
throw new CacheInvokeException(e);
}
kvMap.putAll(loadResult);
} else {
// 有防穿透保护时的加载逻辑
AbstractCache<K, V> abstractCache = CacheUtil.getAbstractCache(cache);
loader = CacheUtil.createProxyLoader(cache, loader, eventConsumer);
for(K key : keysNeedLoad) {
Consumer<V> cacheUpdater = (v) -> {
if(needUpdate(v, config.getLoader())) {
PUT(key, v);
}
};
V v = AbstractCache.synchronizedLoad(config, abstractCache, key, loader, cacheUpdater);
kvMap.put(key, v);
}
}
return kvMap;
} else {
return cache.getAll(keys);
}
}
}
RefreshCache
自定义缓存刷新器,扩展自Guava的LoadingCache,支持自动刷新缓存。RefreshCache在代理的缓存类的基础上,增加了定时刷新缓存的功能。对应的源码如下:
/**
* 自定义缓存刷新器,扩展自Guava的LoadingCache,支持自动刷新缓存。
*
* @param <K> 缓存键的类型
* @param <V> 缓存值的类型
*/
public class RefreshCache<K, V> extends LoadingCache<K, V> {
private static final Logger logger = LoggerFactory.getLogger(RefreshCache.class);
// 锁定键和时间戳键的后缀
public static final byte[] LOCK_KEY_SUFFIX = "_#RL#".getBytes();
public static final byte[] TIMESTAMP_KEY_SUFFIX = "_#TS#".getBytes();
// 用于存储刷新任务的并发哈希映射
private ConcurrentHashMap<Object, RefreshTask> taskMap = new ConcurrentHashMap<>();
// 是否启用多级缓存
private boolean multiLevelCache;
/**
* 构造函数。
*
* @param cache 底层缓存实例
*/
public RefreshCache(Cache cache) {
super(cache);
multiLevelCache = isMultiLevelCache();
}
/**
* 停止所有刷新任务。
*/
protected void stopRefresh() {
List<RefreshTask> tasks = new ArrayList<>();
tasks.addAll(taskMap.values());
tasks.forEach(task -> task.cancel());
}
/**
* 关闭缓存,停止所有刷新任务。
*/
@Override
public void close() {
stopRefresh();
super.close();
}
/**
* 检查是否存在加载器。
*
* @return 如果存在加载器则返回true,否则返回false。
*/
private boolean hasLoader() {
return config.getLoader() != null;
}
/**
* 如果指定的键不存在于缓存中,则使用提供的加载器来计算值,并将结果添加到缓存中。
*
* @param key 要获取的项的键
* @param loader 用于加载值的函数
* @return 键对应的值
*/
@Override
public V computeIfAbsent(K key, Function<K, V> loader) {
return computeIfAbsent(key, loader, config().isCacheNullValue());
}
/**
* 如果指定的键不存在于缓存中,则使用提供的加载器来计算值,并根据参数决定是否将null值缓存。
*
* @param key 要获取的项的键
* @param loader 用于加载值的函数
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否缓存null值
* @return 键对应的值
*/
@Override
public V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull) {
return AbstractCache.computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,
0, null, this);
}
/**
* 如果指定的键不存在于缓存中,则使用提供的加载器来计算值,并根据参数决定是否将null值缓存,同时设置过期时间。
*
* @param key 要获取的项的键
* @param loader 用于加载值的函数
* @param cacheNullWhenLoaderReturnNull 当加载器返回null时,是否缓存null值
* @param expireAfterWrite 缓存项的写入过期时间
* @param timeUnit 过期时间的单位
* @return 键对应的值
*/
@Override
public V computeIfAbsent(K key, Function<K, V> loader, boolean cacheNullWhenLoaderReturnNull,
long expireAfterWrite, TimeUnit timeUnit) {
return AbstractCache.computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,
expireAfterWrite, timeUnit, this);
}
/**
* 获取实际使用的缓存实例,如果是多级缓存,则获取最底层的缓存实例。
*
* @return 实际使用的缓存实例
*/
protected Cache concreteCache() {
Cache c = getTargetCache();
while (true) {
if (c instanceof ProxyCache) {
c = ((ProxyCache) c).getTargetCache();
} else if (c instanceof MultiLevelCache) {
Cache[] caches = ((MultiLevelCache) c).caches();
c = caches[caches.length - 1];
} else {
return c;
}
}
}
/**
* 判断当前缓存是否为多级缓存。
*
* @return 如果是多级缓存则返回true,否则返回false。
*/
private boolean isMultiLevelCache() {
Cache c = getTargetCache();
while (c instanceof ProxyCache) {
c = ((ProxyCache) c).getTargetCache();
}
return c instanceof MultiLevelCache;
}
/**
* 根据缓存类型获取刷新任务的ID。
*
* @param key 缓存键
* @return 任务ID
*/
private Object getTaskId(K key) {
Cache c = concreteCache();
if (c instanceof AbstractEmbeddedCache) {
return ((AbstractEmbeddedCache) c).buildKey(key);
} else if (c instanceof AbstractExternalCache) {
byte[] bs = ((AbstractExternalCache) c).buildKey(key);
return ByteBuffer.wrap(bs);
} else {
logger.error("can't getTaskId from " + c.getClass());
return null;
}
}
/**
* 添加或更新一个刷新任务。
*
* @param key 缓存键
* @param loader 缓存加载器
*/
protected void addOrUpdateRefreshTask(K key, CacheLoader<K,V> loader) {
RefreshPolicy refreshPolicy = config.getRefreshPolicy();
if (refreshPolicy == null) {
return;
}
long refreshMillis = refreshPolicy.getRefreshMillis();
if (refreshMillis > 0) {
Object taskId = getTaskId(key);
RefreshTask refreshTask = taskMap.computeIfAbsent(taskId, tid -> {
logger.debug("add refresh task. interval={}, key={}", refreshMillis , key);
RefreshTask task = new RefreshTask(taskId, key, loader);
task.lastAccessTime = System.currentTimeMillis();
ScheduledFuture<?> future = JetCacheExecutor.heavyIOExecutor().scheduleWithFixedDelay(
task, refreshMillis, refreshMillis, TimeUnit.MILLISECONDS);
task.future = future;
return task;
});
refreshTask.lastAccessTime = System.currentTimeMillis();
}
}
/**
* 获取缓存值,如果配置了刷新策略且存在加载器,则添加或更新刷新任务。
*
* @param key 要获取的缓存键
* @return 缓存键对应的值
* @throws CacheInvokeException 缓存调用异常
*/
@Override
public V get(K key) throws CacheInvokeException {
if (config.getRefreshPolicy() != null && hasLoader()) {
addOrUpdateRefreshTask(key, null);
}
return super.get(key);
}
/**
* 批量获取缓存值,如果配置了刷新策略且存在加载器,则对所有键添加或更新刷新任务。
*
* @param keys 要获取的缓存键集合
* @return 缓存键对应的值的映射
* @throws CacheInvokeException 缓存调用异常
*/
@Override
public Map<K, V> getAll(Set<? extends K> keys) throws CacheInvokeException {
if (config.getRefreshPolicy() != null && hasLoader()) {
for (K key : keys) {
addOrUpdateRefreshTask(key, null);
}
}
return super.getAll(keys);
}
}
RefreshTask是用于刷新缓存的任务类,源码如下:
/**
* 用于刷新缓存的任务类,实现了Runnable接口。
*/
class RefreshTask implements Runnable {
private Object taskId; // 任务ID
private K key; // 缓存键
private CacheLoader<K, V> loader; // 缓存加载器
private long lastAccessTime; // 上次访问时间
private ScheduledFuture future; // 任务的计划未来对象
/**
* 构造函数,初始化刷新任务。
*
* @param taskId 任务ID
* @param key 缓存键
* @param loader 缓存加载器
*/
RefreshTask(Object taskId, K key, CacheLoader<K, V> loader) {
this.taskId = taskId;
this.key = key;
this.loader = loader;
}
/**
* 取消刷新任务。
*/
private void cancel() {
logger.debug("cancel refresh: {}", key);
future.cancel(false);
taskMap.remove(taskId);
}
/**
* 加载缓存数据。
*
* @throws Throwable 加载过程中发生的任何异常
*/
private void load() throws Throwable {
CacheLoader<K,V> l = loader == null? config.getLoader(): loader;
if (l != null) {
l = CacheUtil.createProxyLoader(cache, l, eventConsumer);
V v = l.load(key);
if (needUpdate(v, l)) {
cache.PUT(key, v);
}
}
}
/**
* 从外部缓存加载数据。
*
* @param concreteCache 实际的缓存实例
* @param currentTime 当前时间戳
* @throws Throwable 加载过程中发生的任何异常
*/
private void externalLoad(final Cache concreteCache, final long currentTime)
throws Throwable {
byte[] newKey = ((AbstractExternalCache) concreteCache).buildKey(key);
byte[] lockKey = combine(newKey, LOCK_KEY_SUFFIX);
long loadTimeOut = RefreshCache.this.config.getRefreshPolicy().getRefreshLockTimeoutMillis();
long refreshMillis = config.getRefreshPolicy().getRefreshMillis();
byte[] timestampKey = combine(newKey, TIMESTAMP_KEY_SUFFIX);
CacheGetResult refreshTimeResult = concreteCache.GET(timestampKey);
boolean shouldLoad = false;
if (refreshTimeResult.isSuccess()) {
shouldLoad = currentTime >= Long.parseLong(refreshTimeResult.getValue().toString()) + refreshMillis;
} else if (refreshTimeResult.getResultCode() == CacheResultCode.NOT_EXISTS) {
shouldLoad = true;
}
if (!shouldLoad) {
if (multiLevelCache) {
refreshUpperCaches(key);
}
return;
}
Runnable r = () -> {
try {
load();
concreteCache.put(timestampKey, String.valueOf(System.currentTimeMillis()));
} catch (Throwable e) {
throw new CacheException("refresh error", e);
}
};
boolean lockSuccess = concreteCache.tryLockAndRun(lockKey, loadTimeOut, TimeUnit.MILLISECONDS, r);
if(!lockSuccess && multiLevelCache) {
JetCacheExecutor.heavyIOExecutor().schedule(
() -> refreshUpperCaches(key), (long)(0.2 * refreshMillis), TimeUnit.MILLISECONDS);
}
}
/**
* 刷新上级缓存。
*
* @param key 缓存键
*/
private void refreshUpperCaches(K key) {
MultiLevelCache<K, V> targetCache = (MultiLevelCache<K, V>) getTargetCache();
Cache[] caches = targetCache.caches();
int len = caches.length;
CacheGetResult cacheGetResult = caches[len - 1].GET(key);
if (!cacheGetResult.isSuccess()) {
return;
}
for (int i = 0; i < len - 1; i++) {
caches[i].PUT(key, cacheGetResult.getValue());
}
}
/**
* 任务的运行方法,执行缓存刷新逻辑。
*/
@Override
public void run() {
try {
if (config.getRefreshPolicy() == null || (loader == null && !hasLoader())) {
cancel();
return;
}
long now = System.currentTimeMillis();
long stopRefreshAfterLastAccessMillis = config.getRefreshPolicy().getStopRefreshAfterLastAccessMillis();
if (stopRefreshAfterLastAccessMillis > 0) {
if (lastAccessTime + stopRefreshAfterLastAccessMillis < now) {
logger.debug("cancel refresh: {}", key);
cancel();
return;
}
}
logger.debug("refresh key: {}", key);
Cache concreteCache = concreteCache();
if (concreteCache instanceof AbstractExternalCache) {
externalLoad(concreteCache, now);
} else {
load();
}
} catch (Throwable e) {
logger.error("refresh error: key=" + key, e);
}
}
}