Redis 的发布订阅(pub/sub)机制是一种消息传递模式,允许消息的发送者(发布者)和消息的接收者(订阅者)通过一个中介层(频道)进行通信,而无需彼此直接交互。以下是 Redis 发布订阅机制的工作原理的详细解释:
-
基于事件驱动的架构
Redis 服务器使用一个事件驱动的模型来处理所有的网络通信和客户端请求。这种架构允许 Redis 以非阻塞方式高效地处理多个并发连接。 -
发布(Publish)
当一个客户端想要发送消息时,它会将消息发布到一个指定的频道(channel)上。
发布操作是通过 PUBLISH 命令实现的。
例如,PUBLISH channel_name message 会将 message 发送到名为 channel_name 的频道。
这里返回的代表有3个监听者 -
订阅(Subscribe)
客户端使用 SUBSCRIBE 命令来监听一个或多个频道的消息。
当客户端订阅了一个频道后,它会接收到发送到这个频道的所有消息。例如,通过执行 SUBSCRIBE channel_name,客户端就可以监听 channel_name 频道上的消息。
订阅会返回3个参数:
1:代表订阅
2:订阅频道
3:发布者数量
当发布着发布消息后,订阅者会被推送如下消息
- 消息传递
一旦有消息被发布到频道上,Redis 服务器会将这个消息分发给所有订阅了该频道的客户端。
这种消息传递方式是多对多的:多个发布者可以向同一个频道发送消息,而所有订阅该频道的客户端都可以接收到这些消息。 - 非持久化的消息
Redis 发布订阅机制中的消息是非持久化的。这意味着一旦消息被发送,它不会被存储在服务器上,无法被之后订阅该频道的客户端接收。 - 多路复用和非阻塞 I/O
Redis 使用多路复用技术(如 epoll、kqueue)来同时监听多个网络连接,这使得服务器能够同时处理多个发布和订阅操作。
使用非阻塞 I/O 确保单个操作不会阻塞整个服务器,从而提高整体性能。
在springboot项目中可以如下:
发布:
@Resource
private RedisTemplate redisTemplate;
@PostMapping(value = "/add")
public Result<?> add(@RequestBody SysApplication sysApplication) {
sysApplicationService.save(sysApplication);
//这里需要用JSONObject.toJSONString转成string序列化,否则直接上传对象会带上对象路径信息等
redisTemplate.convertAndSend(RedisListenerConstant.APP_CHANNEL, JSONObject.toJSONString(sysApplication));
return Result.OK("添加成功!");
}
订阅者: 这里写了2个listener,因为想说明可以在RedisMessageConfig 同时监听多个频道
package com.sungrowpower.redis;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sungrowpower.common.system.dto.SysApplicationPermissionDto;
import com.sungrowpower.service.IGoodsSpuService;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import javax.annotation.Resource;
@Configuration
public class RedisMessageAppListener implements MessageListener {
@Resource
private IGoodsSpuService iGoodsSpuService;
@Override
public void onMessage(Message message, byte[] bytes) {
// 假设 message 体是一个 JSON 字符串
String body = new String(message.getBody());
// 去除类路径
// String json = body.substring(body.indexOf("{"), body.lastIndexOf("}") + 1);
String msg= (String) JSON.parse(body);
SysApplicationPermissionDto app = JSONObject.parseObject(msg, SysApplicationPermissionDto.class);
System.out.println("s========"+msg);
try {
//这里可以调用本身的业务逻辑
iGoodsSpuService.operateGoodsSpu(app);
} catch (Exception e) {
throw new RuntimeException();
}
}
}
package com.sungrowpower.redis;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.sungrowpower.common.exception.AutoBizException;
import com.sungrowpower.common.system.dto.SysPermission;
import com.sungrowpower.service.IGoodsSpuService;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class RedisMessageMenuListener implements MessageListener {
@Resource
private IGoodsSpuService iGoodsSpuService;
@Override
public void onMessage(Message message, byte[] bytes) {
// 假设 message 体是一个 JSON 字符串
String body = new String(message.getBody());
// 去除类路径
// String json = body.substring(body.indexOf("{"), body.lastIndexOf("}") + 1);
String msg= (String) JSON.parse(body);
SysPermission menu = JSONObject.parseObject(msg, SysPermission.class);
System.out.println("s========"+msg);
try {
//这里可以调用本身的业务逻辑
iGoodsSpuService.operateAttrValue(menu);
} catch (Exception e) {
throw new RuntimeException();
}
}
}
package com.sungrowpower.redis;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
@Configuration
public class RedisMessageConfig {
/**
* 消息监听器适配器menu
* @return org.springframework.data.redis.listener.adapter.MessageListenerAdapter
*/
@Bean
public MessageListenerAdapter listenerAdapterMenu(RedisMessageMenuListener receiver) {
//这个地方是给 messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“onMessage”
return new MessageListenerAdapter(receiver, "onMessage");
}
/**
* 消息监听器适配器app
* @return org.springframework.data.redis.listener.adapter.MessageListenerAdapter
*/
@Bean
public MessageListenerAdapter listenerAdapterApp(RedisMessageAppListener receiver) {
//这个地方是给 messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“onMessage”
return new MessageListenerAdapter(receiver, "onMessage");
}
/**
* redis消息监听器容器
* @return org.springframework.data.redis.listener.RedisMessageListenerContainer
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapterMenu,
MessageListenerAdapter listenerAdapterApp) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// 订阅了一个叫 my-channel 的频道
container.addMessageListener(listenerAdapterMenu, new PatternTopic("menu_channel"));
container.addMessageListener(listenerAdapterApp, new PatternTopic("app_channel"));
// 这个container 可以添加多个 messageListener
return container;
}
}