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

}
相关推荐
誰能久伴不乏4 小时前
从零开始:用Qt开发一个功能强大的文本编辑器——WPS项目全解析
数据库·qt·wps
40岁的系统架构师5 小时前
17 一个高并发的系统架构如何设计
数据库·分布式·系统架构
安的列斯凯奇6 小时前
Redis篇 Redis如何清理过期的key以及对应的解决方法
数据库·redis·缓存
小小虫码6 小时前
MySQL和Redis的区别
数据库·redis·mysql
飞翔的佩奇8 小时前
Java项目: 基于SpringBoot+mybatis+maven+mysql实现的图书管理系统(含源码+数据库+答辩PPT+毕业论文)
java·数据库·spring boot·mysql·spring·毕业设计·图书管理
一 乐9 小时前
基于vue船运物流管理系统设计与实现(源码+数据库+文档)
前端·javascript·数据库·vue.js·spring boot·后端·船运系统
jerry60910 小时前
注解(Annotation)
java·数据库·sql
飞火流星0202711 小时前
docker安装Redis:docker离线安装Redis、docker在线安装Redis、Redis镜像下载、Redis配置、Redis命令
redis·docker·docker安装redis·redis镜像下载·redis基本操作·redis配置
lwprain11 小时前
springboot 2.7.6 security mysql redis jwt配置例子
spring boot·redis·mysql
vcshcn11 小时前
DBASE DBF数据库文件解析
数据库·dbase