持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
Redis 的发布和订阅
什么是发布和订阅
- Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
- Redis 客户端可以订阅任意数量的频道。
Redis 的发布和订阅
- Redis 发布订阅的消息不会被持久化,所以新订阅的客户端将收不到历史消息。
- 消息及时通讯(redis不像中间件一样,不会对发布的消息进行缓存,确认,重试等机制)
- 客户端可以订阅频道如下图:
- 当给这个频道发布消息后,消息就会发送给订阅的客户端:
发布订阅命令行实现
打开一个客户端订阅 channel1:
SUBSCRIBE channel [channel ...]- 订阅给定的一个或多个频道的信息。
- 返回值:接收到的信息(请参见下面的代码说明)。
redis> SUBSCRIBE channel1 channel2
Reading messages... (press Ctrl-C to quit)
1) "subscribe" # 返回值的类型:显示订阅成功
2) "channel1" # 订阅的频道名字
3) (integer) 1 # 目前已订阅的频道数量
1) "subscribe"
2) "channel2"
3) (integer) 2
打开另一个客户端,给 channel1 发布消息 hello channel:
PUBLISH channel message- 将信息 message 发送到指定的频道 channel 。
- 返回值:接收到信息 message 的订阅者数量。
# 对没有订阅者的频道发送信息
redis> publish channel1 "channel1"
(integer) 0
# 向有一个订阅者的频道发送信息
redis> publish channel2 "channel2"
(integer) 1
# 向有多个订阅者的频道发送信息
redis> publish channel2 "channel3"
(integer) 3
打开第一个客户端可以看到发送的消息:
订阅一个或多个符合给定模式的频道
-
订阅一个或多个符合给定模式的频道。
-
每个模式以 * 作为匹配符,比如 it* 匹配所有以 it 开头的频道( it.news 、 it.blog 、it.tweets 等等), news.* 匹配所有以 news. 开头的频道( news.it 、 news.global.today 等等),诸如此类。
-
订阅
redis> psubscribe news.* tweet.*
Reading messages... (press Ctrl-C to quit)
1) "psubscribe" # 返回值的类型:显示订阅成功
2) "news.*" # 订阅的模式
3) (integer) 1 # 目前已订阅的模式的数量
1) "psubscribe"
2) "tweet.*"
3) (integer) 2
- 收到的消息
1) "pmessage" # 返回值的类型:信息
2) "news.*" # 信息匹配的模式
3) "news.it" # 信息本身的目标频道
4) "hello hello" # 信息的内容
1) "pmessage"
2) "tweet.*"
3) "tweet.huangz"
4) "hello"
适用场景
- 优惠券到期到期失效
- 微信粉丝关注公众号
- 配置字典加载到缓存,订阅一个频道,当配置更新时动态刷新缓存
RedisTemplate 操作 发布订阅
yaml
# Redis服务器地址
spring:
redis:
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis数据库索引(默认为0)
database: 0
# Redis数据库密码
password:
# 连接超时时间(毫秒)
timeout: 1800000
lettuce:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 20
# 最大阻塞等待时间(负数表示没限制)
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 5
# 连接池中的最小空闲连接
min-idle: 0
消息对象
@Data
public class TaskSynData implements Serializable {
private static final long serialVersionUID = -1681130765215741845L;
/**
* 任务名字
*/
private String name;
/**
* 任务类型
*/
private String type;
订阅者
多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。
MailReceiver
@Component
public class MailReceiver {
public void receiveMessage(TaskSynData taskSynData ) {
System.out.println("mail 收到消息为:" + taskSynData);
}
}
SmsReceiver
@Component
public class SmsReceiver {
public void receiveMessage(TaskSynData taskSynData ) {
System.out.println("sms 收到消息为:" + taskSynData);
}
}
配置
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
// ##### 上方为redis通用配置
/**
* 绑定消息监听者和接收监听的方法,必须要注入这个监听器,不然会报错
*/
@Bean
public MessageListenerAdapter mailAdapter(MailReceiver mailReceiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(mailReceiver, "receiveMessage");
//配置序列化对象
Jackson2JsonRedisSerializer<TaskSynData> serializer = new Jackson2JsonRedisSerializer<>(TaskSynData.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
messageListenerAdapter.setSerializer(serializer);
return messageListenerAdapter;
}
@Bean
public MessageListenerAdapter smsAdapter(SmsReceiver smsReceiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(smsReceiver, "receiveMessage");
//配置序列化对象
Jackson2JsonRedisSerializer<TaskSynData> serializer = new Jackson2JsonRedisSerializer<>(TaskSynData.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
messageListenerAdapter.setSerializer(serializer);
return messageListenerAdapter;
}
/**
* Redis消息监听
*
* @param redisConnectionFactory redis连接工厂
* @return 结果
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory,
MessageListenerAdapter mailAdapter,
MessageListenerAdapter smsAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
// 各自订阅自己的频道
container.addMessageListener(mailAdapter, new PatternTopic("mail"));
container.addMessageListener(smsAdapter, new PatternTopic("sms"));
// 订阅一个公共频道
container.addMessageListener(mailAdapter, new PatternTopic("commen"));
container.addMessageListener(smsAdapter, new PatternTopic("commen"));
return container;
}
}
主要的配置可以分解为:
- 定义MessageListenerAdapter (规定消息的处理方式)
new MessageListenerAdapter(mailReceiver, "receiveMessage");
消息会被传递给,mailReceiver 的 receiveMessage 方法.
@Bean
public MessageListenerAdapter mailAdapter(MailReceiver mailReceiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(mailReceiver, "receiveMessage");
//配置序列化对象
Jackson2JsonRedisSerializer<TaskSynData> serializer = new Jackson2JsonRedisSerializer<>(TaskSynData.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
messageListenerAdapter.setSerializer(serializer);
return messageListenerAdapter;
}
@Bean
public MessageListenerAdapter smsAdapter(SmsReceiver smsReceiver) {
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(smsReceiver, "receiveMessage");
//配置序列化对象
Jackson2JsonRedisSerializer<TaskSynData> serializer = new Jackson2JsonRedisSerializer<>(TaskSynData.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
messageListenerAdapter.setSerializer(serializer);
return messageListenerAdapter;
}
- 将订阅者订阅到频道
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory redisConnectionFactory,
MessageListenerAdapter mailAdapter,
MessageListenerAdapter smsAdapter) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisConnectionFactory);
// 各自订阅自己的频道
container.addMessageListener(mailAdapter, new PatternTopic("mail"));
container.addMessageListener(smsAdapter, new PatternTopic("sms"));
// 订阅一个公共频道
container.addMessageListener(mailAdapter, new PatternTopic("commen"));
container.addMessageListener(smsAdapter, new PatternTopic("commen"));
return container;
}
发送消息
@SpringBootTest
@Slf4j
class RedisReceiverTest {
@Resource
private RedisTemplate redisTemplate;
public String redisSend(int i,String channel) {
TaskSynData taskSynData = new TaskSynData();
taskSynData.setType("type");
taskSynData.setName("测试-"+i);
redisTemplate.convertAndSend(channel, taskSynData);
return "发送成功";
}
}
动态添加发布者和订阅者
失败:目前没找到合适的api,不确定是否支持这个功能
@Test
public void test2(){
log.info("发送config配置的频道-{}",redisSend(0,"sms"));
// 订阅频道
redisMessageListenerContainer.addMessageListener(buildMessageListenerAdapter(new NewReceiver()),new PatternTopic("new"));
log.info("发送新配置的频道-{}",redisSend(1,"new"));
}
public MessageListenerAdapter buildMessageListenerAdapter(NewReceiver newReceiver){
MessageListenerAdapter messageListenerAdapter = new MessageListenerAdapter(newReceiver, "receiveMessage");
//配置序列化对象
Jackson2JsonRedisSerializer<TaskSynData> serializer = new Jackson2JsonRedisSerializer<>(TaskSynData.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(objectMapper);
messageListenerAdapter.setSerializer(serializer);
return messageListenerAdapter;
}
接收消息报错
2022-10-12 17:16:22.413 INFO 101210 --- [ main] org.example.config.RedisReceiverTest : 发送config配置的频道-发送成功
2022-10-12 17:16:22.428 INFO 101210 --- [ main] org.example.config.RedisReceiverTest : 发送新配置的频道-发送成功
sms 收到消息为:TaskSynData(name=测试-0, type=type)
2022-10-12 17:16:22.470 ERROR 101210 --- [enerContainer-4] o.s.d.r.l.a.MessageListenerAdapter : Listener execution failed