轉載請註明出處:
1.zset為什麼可以做訊息佇列
-
有序性:zset中所有元素都被自動排序。這讓zset很適合用於有序的訊息佇列,因為可以根據一個或多個標準(比如訊息的到達時間或優先順序)按需檢索訊息。
-
元素唯一性:zset的每個元素都是獨一無二的,這對於實現某些訊息需求(比如冪等性)是非常有幫助的。
-
成員和分數之間的對映關係:有序集合中的每個成員都有一個分數,這樣就可以將相同的資料劃分到不同的 queue 中,以及為每個 queue 設定不同的延時。
-
高效的新增刪除操作:因為zset會自動維護元素之間的順序,所以在新增或刪除元素時無需進行手動排序,從而能提升操作速度。
綜上所述,Redis的zset天然支援按照時間順序的訊息佇列,可以利用其成員唯一性的特性來保證訊息不被重複消費,在實現高吞吐率等方面也有很大的優勢。
Redis的zset有序集合是可以用來實現訊息佇列的,一般是按照時間戳作為score的值,將訊息內容作為value存入有序集合中。
實現步驟:
-
客戶端將訊息推送到Redis的有序集合中。
-
有序集合中,每個成員都有一個分數(score)。在這裡,我們可以設成訊息的時間戳,也就是當時的時間。
-
當需要從訊息佇列中獲取訊息時,客戶端獲取有序集合前N個元素並進行操作。一般來說,N取一個適當的數值,比如10。
需要注意的是,Redis的zset是有序集合,它的元素是有序的,並且不能有重複元素。因此,如果需要處理有重複訊息的情況,需要在訊息體中加入某些唯一性標識來保證不會重複。
Java可以透過Redis的Java客戶端包Jedis來使用Redis,Jedis提供了豐富的API來操作Redis,下面是一段實現用Redis的zset型別實現的訊息佇列的程式碼。
import redis.clients.jedis.Jedis;
import java.util.Set;
public class RedisMessageQueue {
private Jedis jedis; //Redis連線物件
private String queueName; //佇列名字
/**
* 建構函式
* @param host Redis主機地址
* @param port Redis埠
* @param password Redis密碼
* @param queueName 佇列名字
*/
public RedisMessageQueue(String host, int port, String password, String queueName){
jedis = new Jedis(host, port);
jedis.auth(password);
this.queueName = queueName;
}
/**
* 傳送訊息
* @param message 訊息內容
*/
public void sendMessage(String message){
//獲取當前時間戳
long timestamp = System.currentTimeMillis();
//將訊息新增到有序集合中
jedis.zadd(queueName, timestamp, message);
}
/**
* 接收訊息
* @param count 一次接收的訊息數量
* @return 返回接收到的訊息
*/
public String[] receiveMessage(int count){
//設定最大輪詢時間
long timeout = 5000;
//獲取當前時間戳
long start = System.currentTimeMillis();
while (true) {
//獲取可用的訊息數量
long size = jedis.zcount(queueName, "-inf", "+inf");
if (size == 0) {
//如果無訊息,休眠50ms後繼續輪詢
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//計算需要獲取的訊息數量count與當前可用的訊息數量size的最小值
count = (int) Math.min(count, size);
//獲取訊息
Set<String> messages = jedis.zrange(queueName, 0, count - 1);
String[] results = messages.toArray(new String[0]);
//移除已處理的訊息
jedis.zremrangeByRank(queueName, 0, count - 1);
return results;
}
//檢查是否超時
if (System.currentTimeMillis() - start > timeout) {
return null; //超時返回空
}
}
}
/**
* 銷燬佇列
*/
public void destroy(){
jedis.del(queueName);
jedis.close();
}
}
使用示例:
public static void main(String[] args) {
//建立訊息佇列
RedisMessageQueue messageQueue = new RedisMessageQueue("localhost", 6379, "password", "my_queue");
//生產者傳送訊息
messageQueue.sendMessage("message1");
messageQueue.sendMessage("message2");
//消費者接收訊息
String[] messages = messageQueue.receiveMessage(10);
System.out.println(Arrays.toString(messages)); //輸出:[message1, message2]
//銷燬佇列
messageQueue.destroy();
}
在實際應用中,可以結合執行緒池或者訊息監聽器等方式,將訊息接收過程放置於獨立的執行緒中,以提高訊息佇列的處理效率。
+inf 是 Redis 中用於表示正無窮大的一種特殊值,也就是無限大。在使用 Redis 的 zset 集合時,+inf 通常用作 ZREVRANGEBYSCORE 命令的上限值,表示查詢 zset 集合中最大的分數值。+inf 後面的 -inf 表示 zset 中最小的分數值。這兩個值一起可以用來獲取 zset 集合中的所有元素或一個特定範圍內的元素。例如:
# 獲取 zset 集合中所有元素
ZREVRANGE queue +inf -inf WITHSCORES
# 獲取 zset 集合中第1到第10個元素(分數從大到小排列)
ZREVRANGE queue +inf -inf WITHSCORES LIMIT 0 9
# 獲取 zset 集合中分數在 1581095012 到當前時間之間的元素
ZREVRANGEBYSCORE queue +inf 1581095012 WITHSCORES
在這些命令中,+inf 代表了一個最大的分數值,-inf 代表了一個最小的分數值,用於確定查詢的分數值範圍。
Redis 使用 List 和 ZSET 都可以實現訊息佇列,但是二者有以下不同之處:
-
資料結構不同:List 是一個有序的字串列表,ZSET 則是一個有序集合,它們的底層實現機制不同。
-
儲存方式不同:List 只能儲存字串型別的資料,而 ZSET 則可以儲存帶有權重的元素,即除了元素值外,還可以為每個元素指定一個分數。
-
功能不同: List 操作在元素新增、刪除等方面比較方便,而 ZSET 在處理資料排序和範圍查詢等方面比 List 更加高效。
-
應用場景不同: 對於需要精細控制排序和分值的場景可以選用 ZSET,而對於只需要簡單的佇列操作,例如先進先出,可以直接採用 List。
綜上所述,List 和 ZSET 都可以用於訊息佇列的實現,但如果需要更好的效能和更高階的排序功能,建議使用 ZSET。而如果只需要簡單的佇列操作,則 List 更加適合。