java實現Pub/Sub

jedis 實現Pub/Sub

redis支持的Pub/Sub消息模式,類似JMS的“topic” 功能,但是這些消息不支持持久化,而且redis的訂閱端需要獨佔鏈接,消息接收將是阻塞的。
Redis 的消息即發即失,sever不會保存消息,如果publish 的消息沒有任何client 處於subscribe狀態,消息將會丟失,如果client在subscribe時,鏈接斷開後重連,消息將會丟失。

jedis 實現Pub/Sub

  1. 引入jedis-client jar包

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>${jedis.version}</version>
    </dependency>
    
  2. 使用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>
  3. 創建一個訂閱者
    要使用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");

    }

}
  1. 啓動一個發佈者,只需要調用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);
        }
    }
}
  1. 主程序,首先定義了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方法才結束。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章