Caffeine结合Mq广播模式,实现缓存一致性!

868 阅读1分钟

- - 问题场景

本地缓存数据在做完修改后,在分布式环境下,会导致其他服务缓存没有变更,出现数据查询不一致问题。这时就需要通过相应的操作告知其他服务更新缓存内容。

解决方法

实际的解决方法有很多种,这里主要写下通过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来做区分

image.png

@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真的强