• 补习系列(13)-springboot redis 与发布订阅


    目录

    一、订阅发布

    订阅发布是一种常见的设计模式,常见于消息系统的场景。
    如下面的图:

    [图来自百科]
    消息发布者是消息载体的生产者,其通过某些主题来向调度中心发送消息;
    而消息订阅者会事先向调度中心订阅其"感兴趣"的主题,随后会获得新消息。
    在这里,调度中心是一个负责消息控制中转的逻辑实体,可以是消息队列如ActiveMQ,也可以是Web服务等等。

    常见应用

    • 微博,每个用户的粉丝都是该用户的订阅者,当用户发完微博,所有粉丝都将收到他的动态;
    • 新闻,资讯站点通常有多个频道,每个频道就是一个主题,用户可以通过主题来做订阅(如RSS),这样当新闻发布时,订阅者可以获得更新。

    二、Redis 与订阅发布

    Redis 支持 (pub/sub) 的订阅发布能力,客户端可以通过channel(频道)来实现消息的发布及接收。

    1. 客户端通过 SUBSCRIBE 命令订阅 channel;

    1. 客户端通过PUBLISH 命令向channel 发送消息;

    而后,订阅 channel的客户端可实时收到消息。

    除了简单的SUBSCRIBE/PUBLISH命令之外,Redis还支持订阅某一个模式的主题(正则表达式),
    如下:

    PSUBSCRIBE  /topic/cars/*

    于是,我们可以利用这点实现相对复杂的订阅能力,比如:

    • 在电商平台中订阅多个品类的商品促销信息;
    • 智能家居场景,APP可以订阅所有房间的设备消息。
      ...

    尽管如此,Redis pub/sub 机制存在一些缺点:

    • 消息无法持久化,存在丢失风险;
    • 没有类似 RabbitMQ的ACK机制;
    • 由于是广播机制,无法通过添加worker 提升消费能力;

    因此,Redis 的订阅发布建议用于实时且可靠性要求不高的场景。

    三、SpringBoot 与订阅发布

    接下来,看一下SpringBoot 怎么实现订阅发布的功能。

    spring-boot-starter-data-redis 帮我们实现了Jedis的引入,pom 依赖如下:

     <!-- redis -->
      <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis</artifactId>
       <version>${spring-boot.version}</version>
      </dependency>
    

    在 application.properties 中指定配置

    # redis 连接配置
    spring.redis.database=0 
    spring.redis.host=127.0.0.1
    spring.redis.password=
    spring.redis.port=6379
    spring.redis.ssl=false
    
    # 连接池最大数
    spring.redis.pool.max-active=10 
    # 空闲连接最大数
    spring.redis.pool.max-idle=10
    # 获取连接最大等待时间(s)
    spring.redis.pool.max-wait=600000

    A. 消息模型

    消息模型描述了订阅发布的数据对象,这要求生产者与消费者都能理解
    以下面的POJO为例:

        public static class SimpleMessage {
    
            private String publisher;
            private String content;
            private Date createTime;

    在SimpleMessage类中,我们声明了几个字段:

    字段名 说明
    publisher 发布者
    content 文本内容
    createTime 创建时间

    B. 序列化

    如下的代码采用了JSON 作为序列化方式:

    @Configuration
    public class RedisConfig {
    
        private static final Logger logger = LoggerFactory.getLogger(RedisConfig.class);
    
        /**
         * 序列化定制
         * 
         * @return
         */
        @Bean
        public Jackson2JsonRedisSerializer<Object> jackson2JsonSerializer() {
            Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                    Object.class);
    
            // 初始化objectmapper
            ObjectMapper mapper = new ObjectMapper();
            mapper.setSerializationInclusion(Include.NON_NULL);
            mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(mapper);
            return jackson2JsonRedisSerializer;
        }
    
        /**
         * 操作模板
         * 
         * @param connectionFactory
         * @param jackson2JsonRedisSerializer
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory connectionFactory,
                Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer) {
    
            RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
            template.setConnectionFactory(connectionFactory);
    
            // 设置key/hashkey序列化
            RedisSerializer<String> stringSerializer = new StringRedisSerializer();
            template.setKeySerializer(stringSerializer);
            template.setHashKeySerializer(stringSerializer);
    
            // 设置值序列化
            template.setValueSerializer(jackson2JsonRedisSerializer);
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
    
            return template;
        }

    C. 发布消息

    消息发布,需要先指定一个ChannelTopic对象,随后通过RedisTemplate方法操作。

    @Service
    public class RedisPubSub {  
        private static final Logger logger = LoggerFactory.getLogger(RedisPubSub.class);
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        private ChannelTopic topic = new ChannelTopic("/redis/pubsub");
    
      
        @Scheduled(initialDelay = 5000, fixedDelay = 10000)
        private void schedule() {
            logger.info("publish message");
            publish("admin", "hey you must go now!");
        }
    
        /**
         * 推送消息
         * 
         * @param publisher
         * @param message
         */
        public void publish(String publisher, String content) {
            logger.info("message send {} by {}", content, publisher);
    
            SimpleMessage pushMsg = new SimpleMessage();
            pushMsg.setContent(content);
            pushMsg.setCreateTime(new Date());
            pushMsg.setPublisher(publisher);
    
            redisTemplate.convertAndSend(topic.getTopic(), pushMsg);
        }

    上述代码使用一个定时器(@Schedule)来做发布,为了保证运行需要在主类中启用定时器注解:

    @EnableScheduling
    @SpringBootApplication
    public class BootSampleRedis{
    ...
    }

    D. 接收消息

    定义一个消息接收处理的Bean:

        @Component
        public static class MessageSubscriber {
    
            public void onMessage(SimpleMessage message, String pattern) {
                logger.info("topic {} received {} ", pattern, JsonUtil.toJson(message));
            }
        }

    接下来,利用 MessageListenerAdapter 可将消息通知到Bean方法:

           /**
             * 消息监听器,使用MessageAdapter可实现自动化解码及方法代理
             * 
             * @return
             */
            @Bean
            public MessageListenerAdapter listener(Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer,
                    MessageSubscriber subscriber) {
                MessageListenerAdapter adapter = new MessageListenerAdapter(subscriber, "onMessage");
                adapter.setSerializer(jackson2JsonRedisSerializer);
                adapter.afterPropertiesSet();
                return adapter;
            }

    最后,关联到消息发布的Topic:

            /**
             * 将订阅器绑定到容器
             * 
             * @param connectionFactory
             * @param listenerAdapter
             * @return
             */
            @Bean
            public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
                    MessageListenerAdapter listener) {
    
                RedisMessageListenerContainer container = new RedisMessageListenerContainer();
                container.setConnectionFactory(connectionFactory);
                container.addMessageListener(listener, new PatternTopic("/redis/*"));
                return container;
            }

    运行结果
    启动程序,从控制台可输出:

    .RedisPubSub : publish message
    .RedisPubSub : message send hey you must go now! by admin
    .RedisPubSub : topic /redis/* received {"publisher":"admin","content":"hey you must go now!","createTime":1543418694007} 

    这样,我们便完成了订阅发布功能。

    示例程序下载

    小结

    消息订阅发布是分布式系统中的常用手段,也经常用来实现系统解耦、性能优化等目的;
    当前小节结合SpringBoot 演示了 Redis订阅发布(pub/sub)的实现,在部分场景下可以参考使用。
    欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^

    作者:美码师

  • 相关阅读:
    C#中sizeof的用法
    C#托管堆对象实例包含什么
    C#引用类型转换的几种方式
    C#中结构(struct)的部分初始化和完全初始化
    C#值类型装箱后能改变其值吗
    C#程序集系列13,如何让CLR选择不同版本的程序集
    C#程序集系列12,C#编译器和CLR如何找寻程序集
    C#程序集系列11,全局程序集缓存
    C#程序集系列10,强名称程序集
    C#程序集系列09,程序集签名
  • 原文地址:https://www.cnblogs.com/2020-zhy-jzoj/p/13165523.html
Copyright © 2020-2023  润新知