Недостатки Spring Cache
Spring Cache — очень хороший компонент кэширования.
Но в процессе использования Spring Cache Сяохэй также столкнулся с некоторыми болями.
Например, теперь есть требование: получать информацию о пользователе пакетами через несколько идентификаторов пользователей.
план 1
На данный момент наш код может выглядеть так:
List<User> users = ids.stream().map(id -> {
return getUserById(id);
})
.collect(Collectors.toList());
@Cacheable(key = "#p0", unless = "#result == null")
public User getUserById(Long id) {
// ···
}
Недостатком этого способа написания является:
Управляйте redis в цикле for. Это нормально, если данные попадают в кеш, но как только кеш промахнется, доступ к базе данных будет осуществлен.
Сценарий 2
Некоторые учащиеся могут также сделать следующее:
@Cacheable(key = "#ids.hash")
public Collection<User> getUsersByIds(Collection<Long> ids) {
// ···
}
Проблема с этим подходом заключается в следующем:
Кэш основан на хэш-коде списка идентификаторов, и кеш будет срабатывать только в том случае, если значения хэш-кода списка идентификаторов равны. Кроме того, после изменения одного из данных в списке весь кэш списка очищается.
Например:
Первый запрос списка идентификаторов
1,2,3,
Список идентификаторов для второго запроса
1,2,4
В этом случае два кеша до и после не могут использоваться совместно.
Если данные с id 1 изменятся, то кэш по обоим запросам будет очищен
Узнайте, что говорит Весна
Весенний выпуск:
Просто переведите его, и читатели смогут обратиться к соответствующему выпуску за конкретным содержанием.
Перевод:
Спасибо за ваш отчет. Абстракция кеша не имеет понятия об этом состоянии, если вы возвращаете коллекцию, которую вы просите сохранить в кеше. Также ничто не заставляет вас сохранять один и тот же тип элемента для данного кеша, поэтому это предположение не поддается такой высокоуровневой абстракции.
Насколько я понимаю, для высокоуровневой абстрактной структуры, такой как Spring Cache, Cache основан на методах.Если метод возвращает коллекцию, вся коллекция представляет собой контент, который необходимо кэшировать.
мое решение
После долгой борьбы Сяохэй решил построить колесо самостоятельно.
Итак, какого эффекта я хочу добиться?
Я надеюсь, что для такого рода операции получения кеша пакетами на основе нескольких ключей можно сначала посмотреть кеш на основе одного ключа, и если его нет в кеше, загрузить данные, а потом положить данные в кэше одновременно.
Не много ерунды, переходим непосредственно к исходному коду:
Кратко опишите общую идею:
-
основной интерфейс
-
com.github.shenjianeng.easycache.core.Cache
-
com.github.shenjianeng.easycache.core.MultiCacheLoader
-
Кэш-интерфейс
Интерфейс Cache определяет некоторые общие операции кэширования. В отличие от большинства фреймворков Cache, он поддерживает пакетную выборку кэшей на основе ключей.
/**
* 根据 keys 缓存中获取,缓存中不存在,则返回null
*/
@NonNull
Map<K, V> getIfPresent(@NonNull Iterable<K> keys);
/**
* 根据 keys 从缓存中获取,如果缓存中不存在,调用 {@link MultiCacheLoader#loadCache(java.util.Collection)} 加载数据,并添加到缓存中
*/
@NonNull
Map<K, V> getOrLoadIfAbsent(@NonNull Iterable<K> keys);
Интерфейс MultiCacheLoader
@FunctionalInterface
public interface MultiCacheLoader<K, V> {
@NonNull
Map<K, V> loadCache(@NonNull Collection<K> keys);
default V loadCache(K key) {
Map<K, V> map = loadCache(Collections.singleton(key));
if (CollectionUtils.isEmpty(map)) {
return null;
}
return map.get(key);
}
}
MultiCacheLoader — это функциональный интерфейс. вызовCache#getOrLoadIfAbsent
метод, если кеш не существует, данные будут загружены через MultiCacheLoader, а затем данные будут добавлены в кеш.
RedisCache
RedisCache в настоящее время является единственной реализацией интерфейса Cache. Как и имя класса, это реализация кэша на основе Redis.
Давайте сначала поговорим об общей идее реализации:
- Используйте команду mget для redis, чтобы получить кеш партиями. Для обеспечения эффективности каждый раз получается максимум 20 партий.
- Если есть данные, которых нет в кеше, определите, нужно ли загружать данные автоматически, и если да, загрузите данные через MultiCacheLoader.
- Храните данные в кэше. В то же время поддерживается zset для сохранения известных ключей кеша для очистки использования кеша.
Не много ерунды, сразу к исходному коду.
private Map<K, V> doGetOrLoadIfAbsent(Iterable<K> keys, boolean loadIfAbsent) {
List<String> cacheKeyList = buildCacheKey(keys);
List<List<String>> partitions = Lists.partition(cacheKeyList, MAX_BATCH_KEY_SIZE);
List<V> valueList = Lists.newArrayListWithExpectedSize(cacheKeyList.size());
for (List<String> partition : partitions) {
// Get multiple keys. Values are returned in the order of the requested keys.
List<V> values = (List<V>) redisTemplate.opsForValue().multiGet(partition);
valueList.addAll(values);
}
List<K> keysList = Lists.newArrayList(keys);
List<K> missedKeyList = Lists.newArrayList();
Map<K, V> map = Maps.newHashMapWithExpectedSize(partitions.size());
for (int i = 0; i < valueList.size(); i++) {
V v = valueList.get(i);
K k = keysList.get(i);
if (v != null) {
map.put(k, v);
} else {
missedKeyList.add(k);
}
}
if (loadIfAbsent) {
Map<K, V> missValueMap = multiCacheLoader.loadCache(missedKeyList);
put(missValueMap);
map.putAll(missValueMap);
}
return map;
}
Реализация метода очистки кеша:
public void evictAll() {
Set<Serializable> serializables = redisTemplate.opsForZSet().rangeByScore(knownKeysName, 0, 0);
if (!CollectionUtils.isEmpty(serializables)) {
List<String> cacheKeys = Lists.newArrayListWithExpectedSize(serializables.size());
serializables.forEach(serializable -> {
if (serializable instanceof String) {
cacheKeys.add((String) serializable);
}
});
redisTemplate.delete(cacheKeys);
redisTemplate.opsForZSet().remove(knownKeysName, cacheKeys);
}
}
скажи больше
Для получения дополнительной информации об исходном коде, если читатели заинтересованы, они могут прочитать исходный код самостоятельно:easy-cache
Добро пожаловать на форк или оставьте сообщение в области комментариев, чтобы обсудить, письмо не очень хорошее, пожалуйста, дайте мне больше советов~~
План на будущее:
- Поддержка кэширования нулевых значений
- Декларативное кэширование с поддержкой аннотаций