Redis发布订阅模式解决Guava Cache本地缓存刷新问题

为什么要用本地缓存

可以加快资源访问速度,减少第三方IO延迟,也避免了网络调用的开销,将数据存储在本地jvm内存中
可以减少外部系统的压力,可以将频繁访问、且更新场景较少的数据缓存起来,降低对远程服务或者数据库的请求次数,降低外部系统负载,提供系统整体的稳定性
缺点:
但是同时也得注意限制本地缓存的数据量,避免影响正常业务流程,防止系统OOM,不适合存储大量或者大数据结构的场景
数据不能持久化,重启系统将会导致本地缓存数据丢失,最好搭配持久化数据库进行访问,比如持久化到mysql, 用Redis做一级缓存,用本地缓存做二级缓存
数据一致性问题,当应用部署在集群环境,无法保证数据一致性,且各个节点之间的数据无法自动同步,如果需要解决这一问题,需要设计复杂的缓存同步机制

常用的本地缓存

Guava Cache
Google Guava库提供的Cache实现,可以实现缓存失效(基于时间或访问次数)、自动加载数据,、缓存统计等
EHCache
Ehcache是一个广泛使用的Java分布式缓存解决方案,虽然主要用于本地缓存,但也支持集群和分布式缓存。Ehcache 提供了丰富的配置选项,可以设定缓存项的生命周期、最大条目数以及其他复杂的缓存策略,并且与Hibernate等框架集成良好。
HashMap
最简单粗暴的本地缓存实现,无需引入额外的依赖,但不提供缓存回收、过期策略等问题

本地缓存缺点

本地缓存最大的缺点就在于数据不一致,每个服务节点都有自己的缓存副本,但是可以实现最终一致性,对于实时性要求不高的情况下,不建议使用,还是乖乖的使用redis这些分布式缓存,
但是优点也是异常的明显,几乎没有网络延迟,磁盘读取等,性能极高

实际使用中会搭配持久化数据库以及redis来使用,可以很大的提高性能,对于实时性要求不是很高的情况下,本文采用redis的订阅发布模式,发送广播消息来实现清除和更新缓存数据,基本上实现本地缓存更新的方案

Guava Cache简单使用


申明缓存以及根据key获取缓存

Redis Pub/Sub

命令介绍
https://redis.io/commands/?group=pubsub

SUBSCRIBE

SUBSCRIBE channel [channel...]
客户端订阅指定的频道

PUBLISH

PUBLISH channel message
指定频道发送消息,在Redis集群中,可以发布到任何一个节点,Redis会确保需要转发的消息,因此客户端可以连接到任一节点来订阅任意频道

UNSCRIBE

UNSUBSCRIBE [channel [channel ...]]
取消订阅消息,可以指定取消定于的channel,如果参数没有,则取消之前订阅的全部频道

这里我们新建channel名为mongo

执行publish命令可以看到消息已经过来了

值得注意的是,redis 订阅发布模式无法查看当前channel的订阅者列表,但是可以通过以下命令查看订阅频道的列表

PUBSUB CHANNELS [pattern]

PUBSUB NUMSUB [channel [channel ...]]

可以查看当前频道的订阅者数量,

spring项目中使用

回归到更新本地缓存问题,如果是通过其他方式,线上服务如果是集群的话,每次请求访问肯定会负载到其中某一个节点,无法保证所有节点的缓存更新,
如果是spring项目的话,可以在项目启动的时候进行订阅频道,项目销毁的时候进行取消订阅,因为是不同的节点订阅,所以可以保证每个节点都会订阅成功
可以利用spring的几个扩展点,比如实现InitializingBean,好在已经有了现成的轮子,RedisMessageListenerContainer

@Configuration
public class RedisMessageConfig {

    @Bean
    public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory factory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(factory);

        // 1.订阅监听
        container.addMessageListener(new MessageListenerAdapter(new RedisMessageListener()), new ChannelTopic("mongo"));

        return container;
    }

    private class RedisMessageListener extends MessageListenerAdapter {
        @Override
        public void onMessage(Message message, byte[] pattern) {
            logger.info("redis消息接收成功:" + message.toString());
        }
    }
}

我们只需要注入RedisMessageListenerContainer即可,在RedisMessageListener执行清除本地缓存的操作


在MessageListenerAdapter中执行清除缓存的操作

新启动几个节点进行测试一下,可以在idea config 中设置添加 Add VM options 中添加 -Dserver.port=8081,或者在Program arguments中添加 --server.port=8081进行配置
这里启动两个节点进行测试


两个节点基本上无延迟,可以在一定程序上保证不同节点缓存的一致性

扩展延伸
1、使用Redis的订阅发布模式仅仅为其中一种思路,因为不同jvm暂时无法通信,所以智能借助第三方中间件,也可以使用不同类型的消息队列,比如RabbitMQ,RocketMQ,进行广播消费,可以达到相同的效果
2、可以借助订阅发布模式,定义一个频道,通过不同的消息内容来区分需要订阅的场景,一个项目只存在这一个频道,可以将订阅发布的用处达到最大化,比如通过订阅频道刷新别的数据

posted @ 2024-03-26 19:01  木马不是马  阅读(200)  评论(0编辑  收藏  举报