一、什麼是redis的發佈/訂閱
首先什麼是發佈/訂閱模式,Pub/Sub這種訂閱模式是一種常用的通信模式,採用事件作爲通信機制,當訂閱者(客戶端)以事件訂閱的方式去表達願意接受一種感興趣的事情,而發佈者(服務端)可以隨時向訂閱者發佈其訂閱的相關消息。就像是微信中的公衆號,當你關注某個公衆號,你即爲訂閱者,而該公衆號則爲發佈者,當公衆號有新的內容推送的時候,發佈一條新的文章,作爲訂閱者則可以收到這條又發佈者發佈的消息,閱讀或者做其他操作。
然後什麼是Redis的發佈/訂閱,首先Redis中的發佈/訂閱(pub/sub)可以解除訂閱者和發佈者之間的耦合性,Redis是pub/sub的Server,所以在此Redis是作爲一個路由器的。訂閱者可以通過subscribe和psubscribe命令向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>
- 先訂閱一個名爲:mychannel的頻道,要有一個處理訂閱相關事件的類
- 需要一個進行消息發佈的類
- 需要一個線程專門進行訂閱
- 在訂閱者這端將在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表記錄