- - 问题场景
本地缓存数据在做完修改后,在分布式环境下,会导致其他服务缓存没有变更,出现数据查询不一致问题。这时就需要通过相应的操作告知其他服务更新缓存内容。
解决方法
实际的解决方法有很多种,这里主要写下通过mq的广播模式的实现方式,mq采用的是rabbitmq。
代码
首先我这里通过Caffeine配置了三个缓存容器
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> caffeineCaches = new ArrayList<>();
caffeineCaches.add(menuCache());
caffeineCaches.add(rolePermCache());
caffeineCaches.add(dictCache());
cacheManager.setCaches(caffeineCaches);
return cacheManager;
}
@Bean
public CaffeineCache menuCache() {
CaffeineCache menuCache = new CaffeineCache(CacheConstant.MENU_CACHE,
Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build());
return menuCache;
}
@Bean
public CaffeineCache rolePermCache() {
CaffeineCache rolePermCache = new CaffeineCache(CacheConstant.ROLE_PERM_CACHE,
Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(100)
.expireAfterAccess(30, TimeUnit.MINUTES)
.build());
return rolePermCache;
}
@Bean
public CaffeineCache dictCache() {
CaffeineCache dictCache = new CaffeineCache(CacheConstant.DICT_CACHE,
Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build());
return dictCache;
}
}
接着定义我们的mq,队列这里我采用的是服务的ip来做区分
@Configuration
public class CacheMqConfig {
/**
* 交换机
*/
@Bean
public FanoutExchange cacheExchange() {
return new FanoutExchange(MqConstants.CACHE_EXCHANGE, true, false);
}
/**
* 队列
*/
@Bean
public Queue cacheQueue() {
return new Queue(MqUniqueName.getCacheQueueName(), true, false, false);
}
/**
* 队列交换机绑定
*/
@Bean
public Binding cacheBinding(FanoutExchange cacheExchange, Queue cacheQueue) {
return BindingBuilder.bind(cacheQueue).to(cacheExchange);
}
}
public class MqUniqueName {
public static String getCacheQueueName() {
String hostIp = IpUtils.getHostIp();
return MqConstants.CACHE_QUEUE + "-" + hostIp;
}
}
最后通过监听自己服务的队列完成消费
@Slf4j
@Component
@RequiredArgsConstructor
public class CaffeineCacheListener {
@RabbitListener(queues = "#{cacheQueue.name}")
public void cacheProcess(Message message, Channel channel) {
try {
// 获取缓存容器(这里需要将换成容器Name传递进来)
String cacheClassName = new String(message.getBody());
CaffeineCache caffeineCache = SpringContextUtils.getBean(cacheClassName, CaffeineCache.class);
caffeineCache.invalidate();
log.info("【系统缓存】变更广播消息消费成功------message:【{}】", message);
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
try {
log.info("【系统缓存】广播消息消费失败------【{}】", e);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
}
总结:Caffeine真的强