巧用Redis的发布订阅机制,解决数据同步问题

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

上一期分享了一篇关于阿里巴巴JetCache框架的文章,其中有一处小编表示疑惑。那就是线下测试时,多个节点更新缓存后,无法同步更新其他节点的本地缓存。结果还提了一个issuesgithub.com/alibaba/jet...

小编越想越不对,拥有5.4k star不可能有这样的Bug。于是又查看了已经closedissues,才发现有不少人遇到同样的问题,并且已经解决。

我们一起一探究竟!

02 缘起

因为本地测试,所以当时并没有选用所有配置,而是节选了其中的部分配置,其中最关键的一个属性如图:

正是为了解决二级缓存更新后同步其他JVM的Local Cache。小编高高兴兴的去增加配配置:

properties 复制代码
jetcache.remote.default.broadcastChannel=boot-cache

发现并没有生效,一通搜索下来才了解到@Cached的一个属性必须添加,默认是关闭的,我们需要开启:

java 复制代码
@Cached(syncLocal = true)

问题终于解决了。从注释里面可以知道是通过BroadcastManager广播实现的,因为我用的是Jedis客户端,所以直接看com.alicp.jetcache.redis.RedisBroadcastManager即可。

03 源码追踪

com.alicp.jetcache.anno.aop.JetCacheInterceptor

从源码可以看到,缓存的更新和删除都会触发Redis的发布机制。

com.alicp.jetcache.redis.RedisBroadcastManager

截图上可以看到,直接调用了Jedis客户端的发布的方法。

Redis的订阅是在创建com.alicp.jetcache.anno.method.CacheInvokeContext时启用的,如图:

一步步跟踪就会发现,最终还是走到了com.alicp.jetcache.redis.RedisBroadcastManager#startSubscribe方法:

继续看看订阅的消息最后怎么处理的:

从结果来看,缓存的更新和删除对于本地缓存的处理都是直接删除。

04 发布订阅Jedis版

我们也可以自己手动显示一下Redis的发布订阅功能。

java 复制代码
public class JedisTest {
    
    static JedisCluster cluster = null;

    /** 创建集群(也可以使用单机) */
    @BeforeAll
    static void before() {
        Set<HostAndPort> sets = new HashSet<>();
        sets.add(new HostAndPort("127.0.0.1", 7000));
        sets.add(new HostAndPort("127.0.0.1", 7001));
        sets.add(new HostAndPort("127.0.0.1", 7002));
        cluster = new JedisCluster(sets);
    }
	/** 发布者 */
    @Test
    void test01() {
        long publish = cluster.publish("jedis-test-channel", "总部发布了一则寻人启事");
        System.out.println(publish);
    }

    /** 订阅者:由于使用测试用例,所以需要使用System.in.read()阻塞线程 */
    @Test
    void test02() throws IOException {
        cluster.subscribe(new JedisPubSub() {
            @Override
            public void onMessage(String channel, String message) {
                System.out.println("channel:" + channel + ", message" + message);
            }
        }, "jedis-test-channel");
        System.in.read();
    }
}

效果

可以看到test01发布了消息,test02也订阅到了消息

05 Spring Boot的使用

我们使用的客户端是org.springframework.data.redis.core.StringRedisTemplate

5.1 Maven

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

5.2 配置

Spring Boot的配置帮我们封装了很多可用的类,我们只要配置到Spring 容器中即可:

  • org.springframework.data.redis.listener.RedisMessageListenerContainer
  • org.springframework.data.redis.listener.adapter.MessageListenerAdapter

配置文件

properties 复制代码
spring.redis.cluster.nodes[0]=127.0.0.1:7000
spring.redis.cluster.nodes[1]=127.0.0.1:7001
spring.redis.cluster.nodes[2]=127.0.0.1:7002

配置项

java 复制代码
@Configuration
public class RedisConfig {

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.addMessageListener(listenerAdapter, new ChannelTopic("redis-publish"));
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(RedisMessageLister redisMessageLister) {
        return new MessageListenerAdapter(redisMessageLister, "testMessage");
    }
}

需要说明的是,这里的RedisMessageListenerContainerMessageListenerAdapter必须配置。通过RedisMessageListenerContainer添加MessageListenerAdapter并指定广播的通道名称。

MessageListenerAdapter构造的器如果使用:

java 复制代码
public MessageListenerAdapter(Object delegate, String defaultListenerMethod) {
    this(delegate);
    setDefaultListenerMethod(defaultListenerMethod);
}

RedisMessageLister类可以任意定义,并通过defaultListenerMethod指定方法即可。

java 复制代码
@Component
public class RedisMessageLister {
    
    public void testMessage(String message) {
        System.out.println("Received message: " + message);
    }
}

如何使用下面的构造:

java 复制代码
public MessageListenerAdapter(Object delegate) {
    initDefaultStrategies();
    setDelegate(delegate);
}

自定义的方法只能是handleMessage,因为默认的就是handleMessage。当然可以直接实现org.springframework.data.redis.connection.MessageListener接口,无需关心方法名。

java 复制代码
@Component
public class RedisMessageLister implements MessageListener{

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String channel = new String(message.getChannel());
        String body = new String(message.getBody());
        String patterns = new String(pattern);

        System.out.println("收到的消息: " + body + " from channel: " + channel + ",patterns=" + patterns);
    }
}

这是为什么?请看源码:

5.3 定义发布者

java 复制代码
@Slf4j
@RestController
@RequestMapping("redis")
public class ReidsPushlishController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @RequestMapping("message")
    public String publish(String message) {
        stringRedisTemplate.convertAndSend("redis-publish", message);
        log.info("总部发布了一条消息: " + message);
        return "消息发布成功!";
    }
}

06 小结

Redis 发布订阅提供了一种轻量级的实时消息传递机制,在 Java 中可通过 Jedis 或 Spring Data Redis 快速集成。关键要认清其"即发即忘"的本质------适用于允许丢失消息的实时场景。对于需要持久化、事务支持、高可靠性的场景,建议结合 Redis Stream 或专业消息队列使用。

相关推荐
Reggie_L35 分钟前
Stream流-Java
java·开发语言·windows
黑哒哒的盟友35 分钟前
JMeter groovy 编译成.jar 文件
java·jmeter·jar
巴伦是只猫39 分钟前
Java 高频算法
java·开发语言·算法
袁煦丞1 小时前
有Nextcloud家庭共享不求人:cpolar内网穿透实验室第471个成功挑战
前端·程序员·远程工作
超浪的晨1 小时前
Java 实现 B/S 架构详解:从基础到实战,彻底掌握浏览器/服务器编程
java·开发语言·后端·学习·个人开发
Littlewith1 小时前
Java进阶3:Java集合框架、ArrayList、LinkedList、HashSet、HashMap和他们的迭代器
java·开发语言·spring boot·spring·java-ee·eclipse·tomcat
追逐时光者2 小时前
一款超级经典复古的 Windows 9x 主题风格 Avalonia UI 控件库,满满的回忆杀!
后端·.net
进击的码码码码N2 小时前
HttpServletRequestWrapper存储Request
java·开发语言·spring
weixin_lynhgworld3 小时前
旧物回收小程序系统开发——开启绿色生活新篇章
java·小程序·生活
Python涛哥3 小时前
go语言基础教程:【1】基础语法:变量
开发语言·后端·golang