Redis 发布订阅机制深入探索

Redis 的发布订阅(pub/sub)机制是一种消息传递模式,允许消息的发送者(发布者)和消息的接收者(订阅者)通过一个中介层(频道)进行通信,而无需彼此直接交互。以下是 Redis 发布订阅机制的工作原理的详细解释:

  1. 基于事件驱动的架构

    Redis 服务器使用一个事件驱动的模型来处理所有的网络通信和客户端请求。这种架构允许 Redis 以非阻塞方式高效地处理多个并发连接。

  2. 发布(Publish)

    当一个客户端想要发送消息时,它会将消息发布到一个指定的频道(channel)上。

    发布操作是通过 PUBLISH 命令实现的。

    例如,PUBLISH channel_name message 会将 message 发送到名为 channel_name 的频道。

    这里返回的代表有3个监听者

  3. 订阅(Subscribe)

    客户端使用 SUBSCRIBE 命令来监听一个或多个频道的消息。

    当客户端订阅了一个频道后,它会接收到发送到这个频道的所有消息。例如,通过执行 SUBSCRIBE channel_name,客户端就可以监听 channel_name 频道上的消息。

    订阅会返回3个参数:

    1:代表订阅

    2:订阅频道

    3:发布者数量

当发布着发布消息后,订阅者会被推送如下消息

  1. 消息传递
    一旦有消息被发布到频道上,Redis 服务器会将这个消息分发给所有订阅了该频道的客户端。
    这种消息传递方式是多对多的:多个发布者可以向同一个频道发送消息,而所有订阅该频道的客户端都可以接收到这些消息。
  2. 非持久化的消息
    Redis 发布订阅机制中的消息是非持久化的。这意味着一旦消息被发送,它不会被存储在服务器上,无法被之后订阅该频道的客户端接收。
  3. 多路复用和非阻塞 I/O
    Redis 使用多路复用技术(如 epoll、kqueue)来同时监听多个网络连接,这使得服务器能够同时处理多个发布和订阅操作。
    使用非阻塞 I/O 确保单个操作不会阻塞整个服务器,从而提高整体性能。

在springboot项目中可以如下:

发布:

java 复制代码
@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 同时监听多个频道

java 复制代码
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;
    }

}
相关推荐
一行玩python11 分钟前
SQLAlchemy,ORM的Python标杆!
开发语言·数据库·python·oracle
MXsoft61831 分钟前
华为服务器(iBMC)硬件监控指标解读
大数据·运维·数据库
TheITSea42 分钟前
云服务器宝塔安装静态网页 WordPress、VuePress流程记录
java·服务器·数据库
王ASC2 小时前
ORA-01461: 仅能绑定要插入 LONG 列的 LONG 值。ojdbc8版本23.2.0.0驱动BUG【已解决】
数据库·sql·oracle
Dlwyz2 小时前
问题: redis-高并发场景下如何保证缓存数据与数据库的最终一致性
数据库·redis·缓存
如意机反光镜裸2 小时前
如何快速将Excel数据导入到SQL Server数据库
数据库
飞升不如收破烂~3 小时前
redis的List底层数据结构 分别什么时候使用双向链表(Doubly Linked List)和压缩列表(ZipList)
redis
不爱学习的啊Biao3 小时前
初识mysql数据库
数据库·mysql·oracle
宛唐羁客4 小时前
ODBC连接PostgreSQL数据库后,网卡DOWN后,客户端进程阻塞问题解决方法
网络·数据库
吴半杯4 小时前
Redis-monitor安装与配置
数据库·redis·缓存