Spring Data Redis实现消息队列——发布/订阅模式
一般来说,消息队列有两种场景,一种是发布者订阅者模式,一种是生产者消费者模式。利用redis这两种场景的消息队列都能够实现。
定义:
生产者消费者模式:生产者生产消息放到队列里,多个消费者同时监听队列,谁先抢到消息谁就会从队列中取走消息;即对于每个消息只能被最多一个消费者拥有。
发布者订阅者模式:发布者生产消息放到队列里,多个监听队列的消费者都会收到同一份消息;即正常情况下每个消费者收到的消息应该都是一样的。
下面就以Spring Data Redis实现简单的消息“发布/订阅”服务。
spring-redis使用RedisMessageListenerContainer进行消息监听,客户程序需要自己实现MessageListener,并以指定的topic注册到RedisMessageListenerContainer,这样,在指定的topic上如果有消息,RedisMessageListenerContainer便会通知该MessageListener。
下面是在spring配置文件中配置spring-redis:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <!-- 引入jedis配置文件 --> <context:property-placeholder location="classpath*:resource/redis.properties" /> <context:component-scan base-package="com.ljq.durian" /> <!-- jedis pool配置 --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.maxIdle}" /> <property name="maxTotal" value="${redis.maxActive}" /> <property name="maxWaitMillis" value="${redis.maxWait}" /> <property name="testOnBorrow" value="${redis.testOnBorrow}" /> <property name="testOnReturn" value="${redis.testOnReturn}" /> </bean> <!-- spring data redis --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.host}"/> <property name="port" value="${redis.port}"/> <property name="poolConfig" ref="jedisPoolConfig"></property> <property name="timeout" value="${redis.timeout}"></property> <property name="usePool" value="true"></property> </bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"> <property name="connectionFactory" ref="jedisConnectionFactory"/> <property name="defaultSerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> </property> </bean> <bean id="redisMessageListener" class="com.ljq.durian.common.listener.RedisMessageListener"> <property name="redisTemplate" ref="redisTemplate"/> </bean> <bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer"> <property name="connectionFactory" ref="jedisConnectionFactory" /> <property name="messageListeners"> <map> <entry key-ref="redisMessageListener"> <list> <!-- 普通订阅,订阅具体的频道 --> <bean class="org.springframework.data.redis.listener.ChannelTopic"> <constructor-arg value="topic.channel" /> </bean> <!-- 模式订阅,支持模式匹配订阅,*为模糊匹配符 --> <bean class="org.springframework.data.redis.listener.PatternTopic"> <constructor-arg value="topic.*" /> </bean> <!-- 匹配所有频道 --> <bean class="org.springframework.data.redis.listener.PatternTopic"> <constructor-arg value="*" /> </bean> </list> </entry> </map> </property> </bean> </beans>
上面例子中,最后三个bean的配置是实现发布/订阅服务的关键,RedisMessageListener是自己写的实现了org.springframework.data.redis.connection.MessageListener的业务类,并以“topic.channel” 这个topic注册到RedisMessageListenerContainer。RedisMessageListenerContainer在消息到达后负责通知MessageListener。
下面是RedisMessageListener的代码:
package com.ljq.durian.common.listener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; public class RedisMessageListener implements MessageListener { @Autowired private RedisTemplate<String, String> redisTemplate; @Override public void onMessage(Message message, byte[] pattern) { RedisSerializer<?> serializer = redisTemplate.getValueSerializer(); Object channel = serializer.deserialize(message.getChannel()); Object body = serializer.deserialize(message.getBody()); System.out.println("主题: " + channel); System.out.println("消息内容: " + String.valueOf(body)); } public RedisTemplate<String, String> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) { this.redisTemplate = redisTemplate; } }
这样,应用启动时,消息的订阅方(subscriber)就注册好了。这时候只要使用一个简单的程序,模拟publisher,向指定topic发布消息,RedisMessageListener就可以接收到消息,spring-redis的写法是这样:
redisTemplate.convertAndSend("topic.channel", "hello world!");