一、订阅发布

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

如下面的图:

[图来自百科]

消息发布者是消息载体的生产者,其通过某些主题来向调度中心发送消息;

而消息订阅者会事先向调度中心订阅其"感兴趣"的主题,随后会获得新消息。

在这里,调度中心是一个负责消息控制中转的逻辑实体,可以是消息队列如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篇" ,期待更多精彩内容-

补习系列(13)-springboot redis 与发布订阅的更多相关文章

  1. 补习系列(14)-springboot redis 整合-数据读写

    目录 一.简介 二.SpringBoot Redis 读写 A. 引入 spring-data-redis B. 序列化 C. 读写样例 三.方法级缓存 四.连接池 小结 一.简介 在 补习系列(A3 ...

  2. 【springboot】【redis】springboot+redis实现发布订阅功能,实现redis的消息队列的功能

    springboot+redis实现发布订阅功能,实现redis的消息队列的功能 参考:https://www.cnblogs.com/cx987514451/p/9529611.html 思考一个问 ...

  3. 13、Redis的发布订阅模式

     写在前面的话:读书破万卷,编码如有神 -------------------------------------------------------------------------------- ...

  4. Springboot+Redis(发布订阅模式)跨多服务器实战

    一:redis中发布订阅功能(http://www.redis.cn/commands.html#pubsub) PSUBSCRIBE pattern [pattern -]:订阅一个或者多个符合pa ...

  5. SpringBoot+Redis 实现消息订阅发布

    什么是 Redis Redis 是一个开源的使用 ANSI C语言编写的内存数据库,它以 key-value 键值对的形式存储数据,高性能,读取速度快,也提供了持久化存储机制. Redis 通常在项目 ...

  6. 补习系列(15)-springboot 分布式会话原理

    目录 一.背景 二.SpringBoot 分布式会话 三.样例程序 四.原理进阶 A. 序列化 B. 会话代理 C. 数据老化 小结 一.背景 在 补习系列(3)-springboot 几种scope ...

  7. [翻译] C# 8.0 新特性 Redis基本使用及百亿数据量中的使用技巧分享(附视频地址及观看指南) 【由浅至深】redis 实现发布订阅的几种方式 .NET Core开发者的福音之玩转Redis的又一傻瓜式神器推荐

    [翻译] C# 8.0 新特性 2018-11-13 17:04 by Rwing, 1179 阅读, 24 评论, 收藏, 编辑 原文: Building C# 8.0[译注:原文主标题如此,但内容 ...

  8. 【spring boot】【redis】spring boot 集成redis的发布订阅机制

    一.简单介绍 1.redis的发布订阅功能,很简单. 消息发布者和消息订阅者互相不认得,也不关心对方有谁. 消息发布者,将消息发送给频道(channel). 然后是由 频道(channel)将消息发送 ...

  9. netty-socketio(二)整合redis实现发布订阅

    1.Redis 发布订阅 参考:https://www.runoob.com/redis/redis-pub-sub.html Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub ...

随机推荐

  1. InfluxDB介绍

    InfluxDB介绍 InfluxDB用Go语言编写的一个开源分布式时序.事件和指标数据库,和传统是数据库相比有不少不同的地方. 类似的数据库有Elasticsearch.Graphite等. 特点 ...

  2. BZOJ_2962_序列操作_线段树

    Description 有一个长度为n的序列,有三个操作1.I a b c表示将[a,b]这一段区间的元素集体增加c,2.R a b表示将[a,b]区间内所有元素变成相反数,3.Q a b c表示询问 ...

  3. 【已解决】【Mac】 运行adb提示command not found,需要配置adb环境

    问题:运行adb提示command not found  解决措施: 1.下载安装:android-sdk-macosx 下载路径:http://down.tech.sina.com.cn/page/ ...

  4. MYSQL—— year类型的使用与注意点!

    mysql的日期与时间类型:分为time.date.datetime.timestamp.year,主要总结下year的用法: 1.类型支持:year 与 year(4),注意无year(2)的定义方 ...

  5. Resnet论文翻译

    摘要 越深层次的神经网络越难以训练.我们提供了一个残差学习框架,以减轻对网络的训练,这些网络的深度比以前的要大得多.我们明确地将这些层重新规划为通过参考输入层x,学习残差函数,来代替没有参考的学习函数 ...

  6. 我的微服务观,surging 2.0将会带来多大的改变

    Surging 自2017年6月16日开源以来,已收到不少公司的关注或者使用,其中既有以海克斯康超大型等外企的关注,也不乏深圳泓达康.重庆金翅膀等传统行业的正式使用,自2019年年初,surging2 ...

  7. 『简单积性函数和dirichlet卷积』

    简单积性函数 在学习欧拉函数的时候,相信读者对积性函数的概念已经有了一定的了解.接下来,我们将相信介绍几种简单的积性函数,以备\(dirichlet\)卷积的运用. 定义 数论函数:在数论上,对于定义 ...

  8. C#-Xamarin的Android项目开发(三)——发布、部署、打包

    前言 部署,通常的情况下,它其实也是项目开发的一个难点. 为什么这么说呢?因为,它不是代码开发,所以很多开发者本能的拒绝学习它. 并且一个项目配置好一次以后,部署的步骤和部署的人通常很固定,所以大部分 ...

  9. 第一课《.net之--泛型》

    今天我来学习泛型,泛型是编程入门学习的基础类型,从.net诞生2.0开始就出现了泛型,今天我们开始学习泛型的语法和使用. 什么是泛型? 泛型(generic)是C#语言2.0和通用语言运行时(CLR) ...

  10. 关于Mybatis的一些随笔

    Mapper.xml头文件 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http:/ ...