前言
本文通过Redis实现类似于消息中间件MQ
的消息队列功能,生产者
,消费者
,Topic(消息通道)
生产者生产消息,消费者通过订阅的Topic去消费消息。
一、生产者、消费者、消息通道?
-
生产者:
用于发送消息到消息中介。 -
消费者:
用于从消息中介获得消息并交给业务系统使用。 -
消息通道:
可以理解为Topic,两者之间的中介,生产者生产消息给对应的消息通道,消费者通过订阅相应的消息通道来消费消息
示例:
【生产者】A:生产了一段消息,对应消息通道为 8
【消费者】B:订阅了 8 消息通道
【消费者】C:订阅了 9 消息通道
此时:这条消息只有订阅了消息通道 8 的消费者B可以正常消费,而消费者C无法消费此消息
二、代码实现
1、引入依赖
<!-- redis 缓存操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- pool 对象池 -->
<!-- 使用lettuce客户端需要引入commons-pool2依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- json 序列化操作 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
2、yml配置
spring:
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口,默认为6379
port: 6379
# 密码
password: ''
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
3、消息接收配置类创建(消费者)
3.1 消息监听器(实现MessageListener方式)
注意MessageListener引入包为:import org.springframework.data.redis.connection.MessageListener;
/**
* 实现MessageListener接口方式
*/
@Component
public class DemoListener implements MessageListener {
@Autowired
private RedisTemplate redisTemplate;
/**
*
* @param message:消息对象
* 获取消息通道:message.getChannel()
* 获取消息体:message.getBody()
* @param pattern:消息体,实际就是message对象里的getChannel
*/
@Override
public void onMessage(Message message, byte[] pattern) {
System.err.println("实现MessageListener接口。。。。。");
// System.out.println("消息通道:"+new String(pattern));
//获取通道名称
String channel = (String) redisTemplate.getStringSerializer().deserialize(message.getChannel());
String body = (String) redisTemplate.getStringSerializer().deserialize(message.getBody());
Map<String,Object> parse = (Map<String, Object>) JSON.parse(body);
System.out.println("消息通道为:"+channel);
System.out.println("收到的消息为:"+parse);
}
}
3.2 创建消息适配器(MessageListenerAdapter)
/**
* Redis消息适配器
*/
@Component
public class DemoAdapter {
/**
* AA通道反射方法
* @param message:消息体
*/
public void onMessage(String message,String channel){
Map<String,Object> map = (Map<String, Object>) JSON.parse(message);
Object name = map.get("name");
System.out.println("onMessage通道为:" + channel);
System.out.println("onMessage监听到消息:" + map );
}
/**
* BB通道反射方法
* @param message:消息体
*/
public void onMessage2(String message,String channel){
System.out.println("onMessage2通道为:" + channel);
System.out.println("onMessage2监听到消息:" + message);
}
}
4、RedisConfig配置
/**
* RedisConfig配置
*/
@Configuration
public class RedisConfig {
/**
* 自定义配置RedisTemplate
* @param factory
* @return
*/
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用string的序列化
redisTemplate.setKeySerializer(stringRedisSerializer);
//hash的key采用string的序列化
redisTemplate.setHashKeySerializer(stringRedisSerializer);
//value序列化采用jackson
redisTemplate.setValueSerializer(stringRedisSerializer);
//将 Hash 的值序列化为字符串类型,注意存入的时候转成JSON字符串
redisTemplate.setHashValueSerializer(stringRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
*
* @param factory:redis工厂类
* @param listenerAdapter1:消息适配器1
* @param listenerAdapter2:消息适配器2
* -此处的设配器名称需要跟下方@Bean注入的适配器名称相同,多个适配器的话会出现注入多个Bean的情况
* -或者使用@Qualifier("messageListenerAdapter1")注解来确定要将哪一个 Bean 注入到方法中。
* @param demoListener:消息监听器(实现MessageListener接口的类)
* @return
*/
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory factory,
MessageListenerAdapter listenerAdapter1,
MessageListenerAdapter listenerAdapter2,
DemoListener demoListener){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(factory);
container.addMessageListener(listenerAdapter1,new PatternTopic("AA"));
container.addMessageListener(listenerAdapter2,new PatternTopic("BB"));
container.addMessageListener(demoListener,new PatternTopic("CC"));
// container.setTopicSerializer(geterializer());//不需要设置序列化方式,在上方redisTemplate配置中已经设置了
return container;
}
/**
* TODO 注意:此处不能设置序列化对象,否则无法收到消息
* 设置Redis消息接收设配器1
* @param adapter:消息监听的类
* @param onMessage:消息监听类中的方法名称
* @return
*/
@Bean
public MessageListenerAdapter listenerAdapter1(DemoAdapter adapter){
MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(adapter,"onMessage");
return listenerAdapter;
}
/**
* TODO 注意:此处不能设置序列化对象,否则无法收到消息
* 设置Redis消息接收设配器1
* @param adapter:消息监听的类
* @param onMessage:消息监听类中的方法名称
* @return
*/
@Bean
public MessageListenerAdapter listenerAdapter2(DemoAdapter adapter){
MessageListenerAdapter listenerAdapter = new MessageListenerAdapter(adapter,"onMessage2");
return listenerAdapter;
}
/**
* TODO 设置序列化对象
* @return
*/
// public Jackson2JsonRedisSerializer geterializer(){
// Jackson2JsonRedisSerializer seria = new Jackson2JsonRedisSerializer(Object.class);
// ObjectMapper objectMapper = new ObjectMapper();
// objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// seria.setObjectMapper(objectMapper);
// return seria;
// }
}
4、消息推送测试(生产者)
生产者非常简单,只需要通过RedisTemplate.convertAndSend()这个方法向某个通道(参数1)推送一条消息(第二个参数)
@Component
public class RedisTest {
@Autowired
RedisTemplate redisTemplate;
/**
* 初始化线程
*/
@Bean
public void init(){
new Thread(new MyRun(redisTemplate)).start();
}
/**
* 创建线程
*/
class MyRun implements Runnable{
private RedisTemplate redisTemplate;
public MyRun(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public void run() {
System.err.println("服务已启动。");
while (true){
Scanner scanner = new Scanner(System.in);
System.out.println("输入1发送到AA,输入2发送到BB,输入3发送到CC,输入99退出");
int i = scanner.nextInt();
if (i==1){
HashMap<Object, Object> map = new HashMap<>();
map.put("name","张三");
redisTemplate.convertAndSend("AA",JSON.toJSONString(map));
}else if (i == 2){
HashMap<Object, Object> map = new HashMap<>();
map.put("name","李四");
redisTemplate.convertAndSend("BB",JSON.toJSONString(map));
}else if (i == 3){
HashMap<Object, Object> map = new HashMap<>();
map.put("name","王五");
redisTemplate.convertAndSend("CC",JSON.toJSONString(map));
}else if (i==99){
break;
}
}
}
}
}
5、测试结果
- AA通道绑定的是适配器1
- BB通道绑定的是适配器2
- CC通道绑定的是实现MessageListener接口的类(文中的DemoListener)