Redis作為分布式緩存,性能是比較好的。但服務器會嚴重依賴Redis服務,且每次請求都會查詢Redis獲取緩存內(nèi)容。
實現(xiàn)多級緩存時,有兩個措施可以保證本地緩存和Redis緩存數(shù)據(jù)一致性:
- 主動措施:Redis實現(xiàn)訂閱發(fā)布模型,即修改Redis內(nèi)容后,集群所有服務器均要收到通知,來修改本地緩存。
- 被動措施:本地緩存設置失效時間,緩存失效后主動查詢Redis獲取數(shù)據(jù)。
1. Redis的訂閱發(fā)布
加入依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--guava依賴-->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
訂閱者配置:
Redis使用list結構實現(xiàn)訂閱發(fā)布。訂閱者可以在不同的項目,但是訂閱者和發(fā)布者必須使用同一個Redis庫。
@Slf4j
@Configuration
public class RedisContainerConfig {
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
//設置監(jiān)聽的隊列,可以設置多個
container.addMessageListener(listenerAdapter, new PatternTopic("topic:service_xx:module"));
container.addMessageListener(listenerAdapter, new PatternTopic("topic:service_xx"));
return container;
}
//FuseInterceptor是一個普通的Bean對象,他的作用是處理監(jiān)聽到的內(nèi)容
@Bean
MessageListenerAdapter listenerAdapter(FuseInterceptor receiver) {
return new MessageListenerAdapter(new MessageListener() {
public void onMessage(Message message, byte[] pattern) {
try {
//監(jiān)聽的隊列信息
String type = new String(message.getChannel(), StandardCharsets.UTF_8.name());
//發(fā)送的內(nèi)容信息
String m = new String(message.getBody(), StandardCharsets.UTF_8.name());
log.info("redis監(jiān)聽到[{}]的消息[{}]",type,m);
receiver.refreshCache(type, m);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
});
}
}
代碼中使用了guava Cache實現(xiàn)本地緩存(并設置失效時間)。當監(jiān)聽到Redis修改時,主動修改本地緩存。緩存失效后,也會去修改本地緩存。
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@Component
@Slf4j
public class FuseInterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//設置超時時間。guava的Cache
public static Cache<String, Object> moduleCache = CacheBuilder.
newBuilder().
//寫入緩存后,10s后過期
expireAfterWrite(10, TimeUnit.SECONDS).
build();
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ExecutionException {
//先在本地緩存中獲取內(nèi)容,若獲取不到,直接查詢Redis并放入本地緩存(線程安全)
String moduleStatus = (String) moduleCache.get("topic:service_xx:module", () -> {
//若本地緩存獲取不到,那么去Redis查詢數(shù)據(jù)并放入緩存。
String value = stringRedisTemplate.opsForValue().get("service_xx:module");
return StringUtils.isBlank(value) ? "close": value;
});
//攔截器處理。
return true;
}
/**
* 訂閱發(fā)布獲取到最新的Redis內(nèi)容
*
* @param type 監(jiān)聽隊列的名
* @param message 監(jiān)聽到的信息
*/
public void refreshCache(String type, String message) {
if ("topic:service_xx".equals(type)) {
//若是這個隊列的消息,那么將其轉(zhuǎn)換為Set<String>結構,并放入緩存中
Set<String> patterns = JSONObject.parseObject(message,
new TypeReference<LinkedHashSet<String>>() {
}.getType());
moduleCache.put(type, patterns);
} else {
moduleCache.put(type, message);
}
}
}
發(fā)布者配置:
//發(fā)布消息
StringRedisTemplate.convertAndSend("topic:service_xx:module","發(fā)送的消息");
//將消息存儲到Redis中,以便本地緩存失效后查詢Redis緩存
StringRedisTemplate.opsForValue().set("service_xx:module","發(fā)送的消息");
推薦閱讀
SpringBoot2.x—SpringCache(5)使用多級緩存
SpringBoot2.x—SpringCache(6)緩存注意事項