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;
    }

}
相关推荐
旺仔不是程序员几秒前
LIMIT 1:PostgreSQL 只取一行的高效查询姿势
数据库·后端·sql
两锭子1 小时前
Redis基础
redis
烟沙九洲1 小时前
Redis 缓存穿透、缓存击穿、缓存雪崩
数据库·redis
Elastic 中国社区官方博客1 小时前
jina-ocr-v1:在低成本 GPU 上实现更快速的文档解析
大数据·数据库·elasticsearch·搜索引擎·全文检索·jina
字节探索1 小时前
Redis 只会当缓存用?这 10 大实战场景,让你的系统快到飞起
redis·后端
于平安1 小时前
MySQL-变量,流程控制与游标
数据库·mysql
我滴老baby1 小时前
检索的上限,在切块时就定了:一次〈民法典〉实测,看懂 ChatDOC 的“上下文检索”
数据库·全文检索·文档处理
Databend1 小时前
Jev 爆火之后,我们把它集成进了数据湖仓
大数据·数据库·agent
数字新视界2 小时前
数据中心基础设施管理系统的智能监测与运维整合剖析
数据库·物联网·数据中心·数据中心基础设施管理·dcim管理系统