jedis 实现Pub/Sub
redis支持的Pub/Sub消息模式,类似JMS的“topic" 功能,但是这些消息不支持持久化,而且redis的订阅端需要独占链接,消息接收将是阻塞的。
Redis 的消息即发即失,sever不会保存消息,如果publish 的消息没有任何client 处于subscribe状态,消息将会丢失,如果client在subscribe时,链接断开后重连,消息将会丢失。
##jedis 实现Pub/Sub
-
引入jedis-client jar包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>${jedis.version}</version> </dependency>
-
使用Spring 来配置jedis 连接池和RedisUtil的注入。(spring-redis.xml )
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${connect.redis.maxActive}" /> <property name="maxIdle" value="${connect.redis.maxIdle}" /> <property name="timeBetweenEvictionRunsMillis" value="${connect.redis.timeBetweenEvictionRunsMillis}" /> <property name="minEvictableIdleTimeMillis" value="${connect.redis.minEvictableIdleTimeMillis}" /> <property name="testOnBorrow" value="true" /> </bean> <bean id="jedisPool" class="redis.clients.jedis.JedisPool"> <constructor-arg index="0" ref="jedisPoolConfig" /> <constructor-arg index="1" value="${connect.redis.host.name}" /> <constructor-arg index="2" value="${connect.redis.port}" type="int" /> <constructor-arg index="3" value="${redis.timeout}" type="int"/> <constructor-arg index="4" value="${connect.redis.password}" /> </bean> <bean id="jedisClient" class="com.osgh.master.core.cache.JedisClient"> <property name="pool" ref="jedisPool" /> <property name="db" value="1" /> </bean> <bean id="jedisUtil" class="com.osgh.master.core.util.JedisUtil"> <property name="jedisClient" ref="jedisClient" /> </bean>
-
创建一个订阅者
要使用Jedis的pub/sub功能,订阅者必须实现JedisPubSub 自己的方法,功能如下:
/**
* 处理消息即接收推送消息体
*
* @param channel
* 订阅频道
* @param message
* 订阅的消息体
* @author zhy
* @date 2016年5月26日
*/
public class Subscriber extends JedisPubSub {
private static final Logger logger = Logger.getLogger(JedisMessageListener.class);
private JpushMessageService jpushMessageService;
@Override
public void onMessage(String channel, String message) {
ApplicationContext context = SpringContextUtil.getApplicationContext();
jpushMessageService = (JpushMessageService) context.getBean("jpushMessageService");
logger.info("Received subscribed message: " + message);
try {
List<String> obdId = new ArrayList<>();
obdId.add("101d85590947c721b38");
obdId.add("1a1018970aa69c0328f");
int li = message.indexOf("-");
String realMessage = message.substring(li + 1);
logger.info("what is real message is <<" + realMessage + ">>");
String msgContent = "【嘉禾影城】" + realMessage;
String topic = "小提醒";
// 发送推送消息
boolean flag = jpushMessageService.sendCustomMessage(obdId, topic, msgContent);
if (!flag) {
logger.error("save registrationInfo failed! regInfo=" + msgContent);
}
} catch (Exception e) {
logger.error("Handle message failed!message=" + message, e);
}
}
@Override
public void onPMessage(String pattern, String channel, String message) {
logger.info(String.format("PMessage. Pattern: %s, Channel: %s, Msg: %s", pattern, channel, message));
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
logger.info("onSubscribe");
}
@Override
public void onUnsubscribe(String channel, int subscribedChannels) {
logger.info("onUnsubscribe");
}
@Override
public void onPUnsubscribe(String pattern, int subscribedChannels) {
logger.info("onPUnsubscribe");
}
@Override
public void onPSubscribe(String pattern, int subscribedChannels) {
logger.info("onPSubscribe");
}
}
- 启动一个发布者,只需要调用Jedis的publish 方法即可。
/**
* 发布者
*
* @author zhy
*
*/
public class Publisher {
private static final Logger LOGGER = Logger.getLogger(Publisher.class);
private Jedis publisherJedis;
private final String channel;
public Publisher(Jedis publisherJedis,String channel){
this.publisherJedis = publisherJedis;
this.channel = channel;
}
public void startPublish(){
LOGGER.info("Read message from channel(quit for terminate)");
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String message = reader.readLine();
if(!"quit".equals(message)){
publisherJedis.publish(channel, message);
}else{
break;
}
}
} catch (Exception e) {
LOGGER.error("IO failuer while reading input",e);
}
}
}
- 主程序,首先定义了channel的名字,并获取配置文件中jedisPool 的实例。
/**
* 主程序
*
* @author zhy
*
*/
public class MainProgram {
private static final Logger LOGGER = Logger.getLogger(MainProgram.class);
private static final String QUEUE_MESSAGE_SUBSCRIBE = "queue:message:subscribe"; //定义channel名字
public static ApplicationContext springContext;
public static void main(String[] args){
// 主要是获取reids连接
springContext = new ClassPathXmlApplicationContext("applicationContext.xml");
final JedisPool jedisPool = (JedisPool) springContext.getBean("jedisPool");
final Subscriber subscriber = new Subscriber();
// 订阅线程:接收消息
new Thread(new Runnable() {
public void run() {
LOGGER.info("Subscribing to \"Mychannel\". This tread will be block !");
//线程进入订阅模式,阻塞
jedisPool.getResource().subscribe(subscriber, QUEUE_MESSAGE_SUBSCRIBE);
//当unsubscribe 方法被调用时,才执行以下代码
LOGGER.info("Subscribtion ended !");
}
}).start();
// 主线程:发布消息到QUEUE_MESSAGE_SUBSCRIBE频道上
new Publisher(jedisPool.getResource(), QUEUE_MESSAGE_SUBSCRIBE).startPublish();
jedisPool.getResource().close();
//unsubscribe
subscriber.unsubscribe();
jedisPool.getResource().close();
}
}
由于消息订阅者比较特殊,需要独占链接,因此在进入订阅状态后会阻塞线程,需要我们为它创建新的线程作为订阅线程,并且用主线程来发布消息,sub()方法将一直被阻塞,直到调用unsubscribe方法才结束。