8.1 应用场景
一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式。利用redis这两种场景的消息队列都能够实现。
定义:
生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列,谁先抢到消息谁就会从队列中取走消息;即对于每个消息只能被最多一个消费者拥有。 发布者订阅者模式:发布者生产消息放到队列里,多个监听队列的消费者都会收到同一份消息;即正常情况下每个消费者收到的消息应该都是一样的。
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。
这一功能最明显的用法就是构建实时消息系统,比如普通的即时聊天,群聊等功能。
微博,每个用户的粉丝都是该用户的订阅者,当用户发完微博,所有粉丝都将收到他的动态;
新闻,资讯站点通常有多个频道,每个频道就是一个主题,用户可以通过主题来做订阅(如RSS),这样当新闻发布时,订阅者可以获得更新
简单的应用场景的话, 以门户网站为例, 当编辑更新了某推荐板块的内容后:
- CMS发布清除缓存的消息到channel (推送者推送消息)
- 门户网站的缓存系统通过channel收到清除缓存的消息 (订阅者收到消息),更新了推荐板块的缓存
- 还可以做集中配置中心管理,当配置信息发生更改后,订阅配置信息的节点都可以收到通知消息
8.2 Redis 发布订阅架构
Redis提供了发布订阅功能,可以用于消息的传输,Redis的发布订阅机制包括三个部分,发布者,订阅者和Channel。
发布者和订阅者都是Redis客户端,Channel则为Redis服务器端,发布者将消息发送到某个的频道,订阅了这个频道的订阅者就能接收到这条消息。
Redis的这种发布订阅机制与基于主题的发布订阅类似,Channel相当于主题。
8.3 Redis发布订阅与ActiveMQ的比较
(1)ActiveMQ支持多种消息协议,包括AMQP,MQTT,Stomp等,并且支持JMS规范,但Redis没有提供对这些协议的支持;
(2)ActiveMQ提供持久化功能,但Redis无法对消息持久化存储,一旦消息被发送,如果没有订阅者接收,那么消息就会丢失;
(3)ActiveMQ提供了消息传输保障,当客户端连接超时或事务回滚等情况发生时,消息会被重新发送给客户端,Redis没有提供消息传输保障。
总之,ActiveMQ所提供的功能远比Redis发布订阅要复杂,毕竟Redis不是专门做发布订阅的,
但是如果系统中已经有了Redis,并且需要基本的发布订阅功能,就没有必要再安装ActiveMQ了,
因为可能ActiveMQ提供的功能大部分都用不到,而Redis的发布订阅机制就能满足需求。
8.4 代码示例
8.4.1 订阅消息
需要配置一个消息监听者容器,容器需要连接工厂以及消息监听器,在消息监听器中需要配置消息处理对象以及处理的方法.
代码:com.javablog.redis.demo.config.RedisConfig
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.addMessageListener(listenerAdapter,new PatternTopic(SystemConstants.TOPIC_NAME));
return container;
}
/**
* 绑定消息监听者和接收监听的方法,必须要注入这个监听器,不然会报错
*/
@Bean
public MessageListenerAdapter listenerAdapter(){
//这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用“receiveMessage”
//也有好几个重载方法,这边默认调用处理器的方法 叫handleMessage
return new MessageListenerAdapter(new MessageReceiver(),"receiveMessage");
}
订阅代码
package com.javablog.redis.demo.service.impl;
import com.javablog.redis.demo.service.PublishService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service("publishService")
public class PublishServiceImpl implements PublishService {
private final static Logger log = LoggerFactory.getLogger(PublishServiceImpl.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void publish(String topicName,String message) {
stringRedisTemplate.convertAndSend(topicName, message);
}
}
8.4.2 消费消息
代码:com.javablog.redis.demo.service.impl.PublishServiceImpl
package com.javablog.redis.demo.service.impl;
import com.javablog.redis.demo.service.PublishService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
@Service("publishService")
public class PublishServiceImpl implements PublishService {
private final static Logger log = LoggerFactory.getLogger(PublishServiceImpl.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 订阅主题
* @param topicName 发布的管道
* @param message 发布的内容
*/
@Override
public void publish(String topicName,String message) {
stringRedisTemplate.convertAndSend(topicName, message);
}
}
8.5 测试用例
代码: com.javablog.redis.demo.RedisPublishTest
package com.javablog.redis.demo;
import com.javablog.redis.demo.constants.SystemConstants;
import com.javablog.redis.demo.service.ListCacheService;
import com.javablog.redis.demo.service.PublishService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import java.util.ArrayList;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = CacheServiceApplication.class)
@WebAppConfiguration
public class RedisPublishTest {
private final static Logger log = LoggerFactory.getLogger(RedisPublishTest.class);
@Autowired
private PublishService publishService;
@Test
public void testPublishService(){
for(int i=0;i<10;i++) {
publishService.publish(SystemConstants.TOPIC_NAME, "这是我发第"+i+"条的消息啊");
}
}
}