巧用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 或专业消息队列使用。

相关推荐
摇滚侠6 分钟前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 属性优先级 行内写法 变量选择 笔记42
java·spring boot·笔记
滑水滑成滑头7 分钟前
**发散创新:多智能体系统的探索与实践**随着人工智能技术的飞速发展,多智能体系统作为当今研究的热点领域,正受到越来越多关注
java·网络·人工智能·python
摇滚侠10 分钟前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 总结 热部署 常用配置 笔记44
java·spring boot·笔记
十年小站10 分钟前
一、新建一个SpringBoot3项目
java·spring boot
2401_8414956413 分钟前
【数据结构】最长的最短路径的求解
java·数据结构·c++·python·算法·最短路径·图搜索
麦麦鸡腿堡15 分钟前
Java的代码块介绍与快速入门
java·开发语言
正点原子17 分钟前
《ESP32-S3使用指南—IDF版 V1.6》第四十二章 录音机实验
程序员·嵌入式·产品
PFinal社区_南丞18 分钟前
构建可维护的正则表达式系统-pfinal-regex-center设计与实现
后端·php
Imnobody18 分钟前
吴恩达 Prompt 工程课精讲②:写出高可靠 Prompt 的2大黄金法则
后端
yuuki23323322 分钟前
【C语言】程序的编译和链接(基础向)
c语言·后端