SpringBoot中如何操作Redis发布订阅

283 阅读2分钟

在springboot中如何操作redis

修改pom文件,引入spring-boot-starter-data-redis

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <version>3.2.0</version>
</dependency>

spring-data-redis默认使用lettuce作为redis的客户端库。

源码分析

类图

Topic

PatternTopic

支持通配符的消息订阅模式,该模式下可以订阅一类具有相同前缀的Channel。

ChannelTopic

指定Channel进行订阅,是基于channel名称的完全匹配。

消息监听容器

public class RedisMessageListenerContainer implements InitializingBean, DisposableBean, BeanNameAware, SmartLifecycle {

}

容器实现的接口

  • InitializingBean Bean初始化完成
  • DisposableBean Spring容器关闭时会调用destory方法
  • BeanNameAware 用于绑定bean的名称
  • SmartLifecycle 基于Bean生命周期控制的接口

设置任务处理线程池

简单任务处理器(非线程池)

public class SimpleAsyncTaskExecutor {
    public void execute(Runnable task, long startTimeout) {
		Assert.notNull(task, "Runnable must not be null");
		Runnable taskToUse = (this.taskDecorator != null ? this.taskDecorator.decorate(task) : task);
        // ConcurrencyThrottleSupport.concurrencyLimit >= 0 && startTimeout > 0
		if (isThrottleActive() && startTimeout > TIMEOUT_IMMEDIATE) {
            //任务限速
			this.concurrencyThrottle.beforeAccess();
			doExecute(new ConcurrencyThrottlingRunnable(taskToUse));
		}
		else {
			doExecute(taskToUse);
		}
	}

    //每次执行新任务就创建一个线程
	protected void doExecute(Runnable task) {
		Thread thread = (this.threadFactory != null ? this.threadFactory.newThread(task) : createThread(task));
		thread.start();
	}
}

当消息量大时,会创建大量的线程最终导致OOM。

消息监听器

package org.springframework.data.redis.connection;

import org.springframework.lang.Nullable;

/**
 * Listener of messages published in Redis.
 */
public interface MessageListener {

	/**
	 * Callback for processing received objects through Redis.
	 */
	void onMessage(Message message, @Nullable byte[] pattern);
}

向容器中添加监听器

通过MessageListenerAdapter处理消息
@Configuration
public class RedisMessageListenerContainerConfig {
    
    @Resource
    private MyMessageReceiver myMessageReceiver;
    
    @Bean
	public RedisMessageListenerContainer container(){
		RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        // 默认基于handleMessage进行消息转发
        //messageReceiver.setDefaultListenerMethod("handleMessage");
		MessageListenerAdapter adapter = new MessageListenerAdapter(myMessageReceiver, "handleMessage");
		container.addMessageListener(adapter, new PatternTopic("patternTopic"));
		return container;
	}
}

是适配器模式的一种实现,允许普通的Java对象转成消息监听器。

实现MessageListener接口
@Configuration
public class RedisMessageListenerContainerConfig {

    @Resource
    private MyMessageReceiver myMessageReceiver;
    
    @Bean
	public RedisMessageListenerContainer container(){
	RedisMessageListenerContainer container = new RedisMessageListenerContainer();
	container.addMessageListener(myMessageReceiver, new PatternTopic("patternTopic"));
    container.addMessageListener(myMessageReceiver, new ChannelTopic("channelTopic"));
	return container;
}
}

多实例消息防重复消费

在多应用实例下,发布消息会被多个客户端进行订阅,造成重复消费问题。需要加锁进行防重复消费。

Redis原生命令

发布消息

PUBLISH channel message

时间复杂度

O(N+M),N是订阅channel的客户端数量,M是订阅pattern的客户端数量

订阅消息

基于channel订阅

SUBSCRIBE channel [channel ...]

时间复杂度

O(N),N是订阅channel的数量

基于pattern订阅

PSUBSCRIBE pattern [pattern ...]

时间复杂度

O(N),N是订阅pattern的数量