【Redis七】Redis的發佈和訂閱(Pub/Sub)

一、什麼是redis的發佈/訂閱

       首先什麼是發佈/訂閱模式,Pub/Sub這種訂閱模式是一種常用的通信模式,採用事件作爲通信機制,當訂閱者(客戶端)以事件訂閱的方式去表達願意接受一種感興趣的事情,而發佈者(服務端)可以隨時向訂閱者發佈其訂閱的相關消息。就像是微信中的公衆號,當你關注某個公衆號,你即爲訂閱者,而該公衆號則爲發佈者,當公衆號有新的內容推送的時候,發佈一條新的文章,作爲訂閱者則可以收到這條又發佈者發佈的消息,閱讀或者做其他操作。

       然後什麼是Redis的發佈/訂閱,首先Redis中的發佈/訂閱(pub/sub)可以解除訂閱者和發佈者之間的耦合性,Redis是pub/sub的Server,所以在此Redis是作爲一個路由器的。訂閱者可以通過subscribepsubscribe命令向Redis server 訂閱自己感興趣的消息類型。當發佈者通過publish命令向Redis server發送特定類型的信息時,訂閱該信息類型的全部client(訂閱者)都會收到此消息。redis將信息類型稱爲通道(channel)。

 二、關於Redis的發佈/訂閱的命令

       

三、使用Jedis來實現發佈/訂閱

       思路大致爲:先要有jedis的maven依賴,然後按照訂閱/消費順序來安排 

<dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
     <version>2.9.0</version>
</dependency>
  1. 先訂閱一個名爲:mychannel的頻道,要有一個處理訂閱相關事件的類
  2. 需要一個進行消息發佈的類
  3. 需要一個線程專門進行訂閱
  4. 在訂閱者這端將在onMessage收到消息
import redis.clients.jedis.JedisPubSub;

/**步驟一:
 * 用於處理訂閱相關事件,繼承自JedisPubSub
 * @author Administrator
 */
public class JedisSubscribe extends JedisPubSub {
    public JedisSubscribe() {
    }

    /**
     * 訂閱一個頻道
     * @param channel
     * @param subscribedChannels
     */
    @Override
    public void onSubscribe(String channel, int subscribedChannels) {
        System.out.println(
                String.format("訂閱redis頻道成功 channel %s, subscribedChannels %d",
                channel, subscribedChannels));
    }

    /**
     * 解除訂閱
     * @param channel
     * @param subscribedChannels
     */
    @Override
    public void onUnsubscribe(String channel, int subscribedChannels) {

        System.out.println(
                String.format("取消訂閱 redis頻道, channel %s, subscribedChannels %d",
                channel, subscribedChannels));
    }

    @Override
    public void onMessage(String channel, String message) {
        //每次監聽到頻道channel發送的消息message後就在控制檯打印
        System.out.println("頻道"+channel+"發出消息:"+message);
    }

}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.io.BufferedReader;
import java.io.InputStreamReader;
//步驟二
public class JedisPublisher {
    private final JedisPool jedisPool;
    public JedisPublisher(JedisPool jedisPool) {
        this.jedisPool = jedisPool;
    }
    public void start() throws Throwable {
        //爲了方便控制檯測試
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        //連接池中取出一個連接
        Jedis jedis = jedisPool.getResource();
        while (true) {
            String line = null;
            try {
                line = reader.readLine();
                if (!"quit".equals(line)) {
                    //將消息不斷的發送給訂閱mychannel頻道的訂閱者
                    jedis.publish("mychannel", line);
                } else {
                    break;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
/**
 * 步驟三
 */
public class SubThread extends Thread {
    private final JedisPool jedisPool;
    //訂閱者
    private final JedisSubscribe subscriber = new JedisSubscribe();
    //訂閱的頻道
    private final String channel = "mychannel";

    public SubThread(JedisPool jedisPool) {
        super("SubThread");
        this.jedisPool = jedisPool;
    }

    @Override
    public void run() {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.subscribe(subscriber, channel);
            System.out.println(
            String.format("訂閱redis的頻道, channel %s, thread will be blocked", channel));
        } catch (Exception e) {
            System.out.println(String.format("訂閱redis的頻道出錯 channel error, %s", e));
        } finally {
            if (jedis != null) {
                jedisPool.close();
                jedis.close();
            }
        }
    }
}
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.apache.commons.lang3.Validate;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.io.FileInputStream;
import java.net.URL;
import java.util.Properties;
/**
  步驟四
 * redis的線程池線性安全,
   聲明一個全局的JedisPool變量來保存JedisPool對象的引用,
   在其他地方使用都是同一個jedisPool,"
 */
public class PubSubMain {
    private static JedisPool jedisPool = null;
    private static int INDEX_ID;
    static {
        try {
            URL url = PubSubMain.class.getClassLoader().getResource("redis.properties");
            Validate.notNull(url, "redis.properties not exist", new Object[0]);
            Properties properties = new Properties();
            properties.load(new FileInputStream(url.getPath()));
            String host = properties.getProperty("host");
            String password = properties.getProperty("password");
            String port = properties.getProperty("port");
            INDEX_ID = Integer.parseInt(properties.getProperty("indexId"));
            String timeOut = properties.getProperty("timeOut");
            String maxTotal = properties.getProperty("maxTotal");
            String minIdle = properties.getProperty("minIdle");
            String maxIdle = properties.getProperty("maxIdle");
            String maxWaitMillis = properties.getProperty("maxWaitMillis");
            String testOnBorrow = properties.getProperty("testOnBorrow");
            String testOnReturn = properties.getProperty("testOnReturn");
            System.out.println("----連接 " + host + " redis啓動---");
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(Integer.parseInt(maxTotal));
            config.setMinIdle(Integer.parseInt(minIdle));
            config.setMaxIdle(Integer.parseInt(maxIdle));
            config.setMaxWaitMillis((long)Integer.parseInt(maxWaitMillis));
            config.setTestOnBorrow(Boolean.parseBoolean(testOnBorrow));
            config.setTestOnReturn(Boolean.parseBoolean(testOnReturn));
            jedisPool = new JedisPool(config, host, Integer.parseInt(port), Integer.parseInt(timeOut), password);
            System.out.println("----redis成功---");
        } catch (Exception var13) {
            System.out.println("----redis失敗---");
            var13.printStackTrace();
        }

    }


    public static void main(String[] args) throws Throwable {
        Session session =null;
        JedisPool jedisPool01 = null;
        try{
           jedisPool = new JedisPool(new JedisPoolConfig(), redisIp, reidsPort);
            if (jedisPool01.getResource()==null){
                System.out.println("redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool");
            }
            SubThread subThread = new SubThread(jedisPool01);
            subThread.start();

            JedisPublisher publisher = new JedisPublisher(jedisPool01);
            publisher.start();

        }catch (Throwable throwable){
            throwable.printStackTrace();
        }finally {
            if (jedisPool01!=null){

                jedisPool01.close();
            }
            if (session!=null){
                session.disconnect();
            }
        }
    }
}

四、使用場景
第一階段可以實現消息羣發功能
場景:根據應用類型,服務端發送消息,客戶端及時收取消息。公衆號的訂閱信息和發佈信息,微博上接收到關注的某個博主的動態等等。
第二階段可以根據辦理業務,單點推送
場景:手機用戶辦理一筆業務時,及時推送反饋。
第三階段可以將郵件,短信推送這些有共性的東西整合爲一個整體
設計思路:
1.服務端發送消息(含標題,內容),標題按照一定規則存入redis,內容存入oracle(內容不多,訪問量大,併發量高時內容存入redis也可以),同時標題(以最少的信息量)
推送到客戶端,客戶點擊標題時,獲取相應的內容閱讀.
2.若果可以未讀取,可以提示多少條未讀,redis能夠很快記數
3.根據一定時間清理緩存,入oracle表記錄

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