1. Зачем обновлять кэш кофеина?
1.1, преимущества и недостатки кофеинового кеша
В производственной среде caffeine cache — это локальный кеш, который мы используем в приложении.
Его преимущество в том, что он существует в приложении и имеет самую высокую скорость доступа. Обычно он отвечает менее чем за 1 мс.
Недостатком является то, что его неудобно управлять, потому что он существует на нескольких веб-серверах с балансировкой нагрузки,
его трудно обновлять и удалять, как при управлении кешем Redis.
1.2, обычно мы устанавливаем время кэширования кофеина на 5 или 10 минут,
Но когда начнется масштабная акция, если срок действия кеша еще не истек,
данные, отображаемые веб-сервисом, не будут обновляться сразу.Как
мы обновляем кеш в приложении на нескольких веб-серверах?
Одним из решений является использование подписки на сообщения redis.Мы
отправляем сообщение в redis из фона, и
веб-служба, подписанная на redis, может обрабатывать кеш после получения сообщения,
чтобы обновить кеш на нескольких веб-серверах.
1.3 многоуровневое кэширование обычно используется в производственной среде,
Когда мы обновляем кеш кофеина,
мы не должны обращаться к базе данных, чтобы избежать одновременного доступа к базе данных.Вместо
этого после обновления Redis
локальный кеш получает данные от Redis,
а одновременный доступ порядка сотен или тысяч очень напряжен для Редис Смолл
2. Информация о демонстрационном проекте
pom.xml добавьте pom по мере необходимости
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
Инициализировать redisTemple
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.omg.CORBA.portable.UnknownException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean(name = "redisTemplate")
@ConditionalOnClass(RedisOperations.class)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>
(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(mapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 指定 key 的序列化格式
template.setKeySerializer(stringRedisSerializer);
// 指定 hash 的 key 的序列化格式
template.setHashKeySerializer(stringRedisSerializer);
// 指定 value 的序列化格式
template.setValueSerializer(jackson2JsonRedisSerializer);
// 指定 hash 的 value 的序列化格式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
добавить слушателя
import club.chuige.appmanagement.common.tool.cache.CacheManagerTool;
import club.chuige.appmanagement.common.consts.CacheConstants;
import club.chuige.appmanagement.common.consts.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisListenerConfig {
@Autowired
CacheManagerTool cacheManagerTool;
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory redisConnectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
container.addMessageListener(listenerAdapter, new PatternTopic(CacheConstants.USER_CACHE));
return container;
}
@Bean
MessageListenerAdapter listenerAdapter() {
return new MessageListenerAdapter(this, "receiveMessage");
}
// 接受到通知,清除本地缓存
public void receiveMessage(String message) {
String[] messages = message.split(Constants.SPLIT_COLON);
CaffeineCache cache = cacheManagerTool.getCacheByName(messages[0]);
cache.evict(messages[1]);
}
}
Aop окружает операции обновления и удаления, отправляет широковещательные сообщения и делает недействительными локальные кэши других серверов.
@Autowired
private RedisTemplate<String, String> redisTemplate;
private void sendRedisTopic(String topic, String message) {
redisTemplate.convertAndSend(topic, message);
}