冪等性
MQ消費者的冪等性的解決一般使用全局ID或者寫個唯一標識比如時間戳或者UUID或者訂單消費者消費MQ中的消息也可利用MQ的該id來判斷,或者可按自己的規則生成一個全局唯一id,每次消費消息時用該id先判斷該消息是否已消費過
在海量訂單生成的業務高峯期,生產端有可能就會重複發生了消息,這時候消費端就要實現冪等性,這就意味着我們的消息永遠不會被消費多次,即使我們收到了一樣的消息。業界主流的冪等性有兩種操作:a.唯一ID+指紋碼機制,利用數據庫主鍵去重, b.利用redis的原子性去實現
場景 | 解決思路 | 描述 |
---|---|---|
消費端的冪等性保障 | 唯一ID+指紋碼機制 | 指紋碼:我們的一些規則或者時間戳加別的服務給到的唯一信息碼,它並不一定是我們系統生成的,基本都是由我們的業務規則拼接而來,但是一定要保證唯一性,然後就利用查詢語句進行判斷這個id是否存在數據庫中,優勢就是實現簡單就一個拼接,然後查詢判斷是否重複;劣勢就是在高併發時,如果是單個數據庫就會有寫入性能瓶頸當然也可以採用分庫分表提升性能,但也不是我們最推薦的方式 |
Redis原子性 | 利用redis執行setnx命令,天然具有冪等性。從而實現不重複消費 |
優先級隊列
場景
- 優先處理大客戶訂單(不同企業規模(500人,200人,100人)在京東下單購買辦公用品,優先給人多的企業發貨)
注意:設置隊列的最大優先級最大可設置0~255 官網推薦設置0~9 如果設置太高會比較佔用cup和內存
生產者代碼
using RabbitMQ.Client;
using System.Reflection;
using System.Text;
using System.Threading.Channels;
namespace _01.Rabbitmq.Producer
{
public class Program
{
private static string queueName = "hello";
static void Main(string[] args)
{
// 創建一個連接工廠
ConnectionFactory factory = new ConnectionFactory
{
HostName = "localhost",
UserName = "guest",
Password = "guest",
};
using var connection = factory.CreateConnection();
using var channel = connection.CreateModel();
/*
*生成一個隊列
*1.隊列名稱
*2.隊列裏面的消息是否持久化默認消息存儲在內存中
*3.該隊列是否只供一個消費者進行消費是否進行共享true可以多個消費者消費
*4.是否自動刪除最後一個消費者端開連接以後該隊列是否自動刪除true自動刪除*
*5.其他參數
*/
var arguments = new Dictionary<string, object>();
arguments.Add("x-max-priority", 9);
channel.QueueDeclare("hello", true, false, false, arguments);//創建一個名稱爲hello的消息隊列
//channel.QueueDeclare("hello", false, false, false, null);//創建一個名稱爲hello的消息隊列
for (int i = 0; i < 20; i++)
{
string message = i + "Hello World"; //控制檯傳遞的消息內容
var body = Encoding.UTF8.GetBytes(message);
/*
* 發送一個消息
* 1.發送到那個交換機
* 2.路由的 key 是哪個
* 3.其他的參數信息
* 4.發送消息的消息體
*/
if (i == 5)
{
var basicProperties = channel.CreateBasicProperties();
basicProperties.Priority = 5;
channel.BasicPublish(exchange: "", "hello", basicProperties, body); //開始傳遞
}
else
{
channel.BasicPublish(exchange:"", "hello", null, body); //開始傳遞
}
Console.WriteLine("已發送: {0}", message);
}
Console.ReadKey();
}
}
}
消費者者代碼
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System.Text;
namespace _01.Rabbitmq.Consumer
{
public class Program
{
private static string queueName = "hello";
static void Main(string[] args)
{
// 創建一個連接工廠
ConnectionFactory factory = new ConnectionFactory
{
HostName = "localhost",
UserName = "guest",
Password = "guest",
};
// publisher/consumer和broker之間的TCP連接
using var connection = factory.CreateConnection();
// Channel作爲輕量級的Connection極大減少了操作系統建立TCPconnection的開銷
using var channel = connection.CreateModel();
var arguments = new Dictionary<string, object>();
arguments.Add("x-max-priority",9);
// 創建隊列/交換機(如隊列/交換機已存在的情況可不用再次創建/此創建爲:確保先開啓消費者,生產者未創建隊列/交換機而引發報錯)
channel.QueueDeclare("hello", true, false, false, arguments);
//channel.QueueDeclare("hello", false, false, false, null);
// 事件對象
var consumer = new EventingBasicConsumer(channel);
/*
* 消費者消費消息
* 1.消費哪個隊列
* 2.消費成功之後是否要自動應答 true 代表自動應答 false 手動應答
* 3.消費者未成功消費的回調
*/
channel.BasicConsume("hello", true, consumer);
// 接收消息回調
consumer.Received += (sender, e) =>
{
var body = e.Body.ToArray();
var message = Encoding.UTF8.GetString(body);
Console.WriteLine("已接收: {0}", message);
};
Console.ReadKey();
}
}
}
測試效果
惰性隊列
場景
長時間不能消費消息造成堆積
配置
var arguments = new Dictionary<string, object>();
arguments.("x-queue-mode", "lazy");
channel.QueueDeclare("hello", true, false, false, arguments);
性能對比
在發送1百萬條消息,每條消息大概佔1KB的情況下,普通隊列佔用內存是1.2GB,而惰性隊列僅僅佔用1.5MB,但是消費惰性隊列的消息就比較慢,因爲要先從磁盤讀取在加載到內存中,根據場景選擇