使用Reids实现简单消息队列
队列操作
简单队列
利用List数据结构可以实现简单的队列,在于使用List提供插入和移除api来完成简单队列操作;
准备数据
获取数据
后入先出
使用redis提供的apiLPOP
可以从队列左边获取数据,也就是从队列的头部获取到最后插入的数据,也可以使用BLPOP
设置超时时间,从而做到等待数据写入;
先入先出
与上面的一样,只是把获取数据的方向改成另一边,使用RPOP
可以从队列右边获取数据,也就是从队列的尾部获取到最先插入的数据,也可以使用BRPOP
设置超时时间,从而做到等待数据写入;
等待
使用BLPOP
、BRPOP
可以移除并获取列表头部/尾部获取第一个元素;如果把BLPOP
命令的超时时间设置为0,表示一直阻塞,直到数据写入;
实现
到这就可以将List当成一个简单的队列,推送数据的生产者,将数据写入到List,然后消费者等着List中有数据,然后获取;
生产者
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost");
var redis = connection.GetDatabase(1);
foreach (var i in Enumerable.Range(0,10))
{
await redis.ListLeftPushAsync("data",i);
Console.WriteLine($"写入 {i}");
Thread.Sleep(1000);
}
消费者
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost");
var redis = connection.GetDatabase(1);
while (true)
{
var res = await redis.ListRightPopAsync("data");
if (!string.IsNullOrEmpty(res))
{
Console.WriteLine($"读取 {res}");
}
Thread.Sleep(1000);
}
至此,消息已经消费,但是消息的多播并不能实现,队列中的消息被移除了,另外的消费者就拿不到这条消息,同时ListRightPopAsync方法对应的redisRPOP
命令,StackExchange.Redis中不支持Redis中对应的BLPOP
、BRPOP
命令,但是可以使用发布订阅模式实现;
发布订阅
生产者
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost");
var sub = connection.GetSubscriber();
foreach (var i in Enumerable.Range(0,10))
{
await sub.PublishAsync("channel", $"消息 {i}");
Console.WriteLine($"发布消息 {i}");
Thread.Sleep(1000);
}
Console.ReadLine();
消费者
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost");
var sub = connection.GetSubscriber();
sub.Subscribe("channel",
(channel, message) =>
{
Console.WriteLine($"接收消息:{message}");
});
Console.ReadLine();
通过消息生产者将消息发布到某个channel,同时消费者订阅该channel,即可获取到消息;当有多个消费者也可以接受到消息了,如果在消费者订阅之前消息就已消费则不能再获取到该消息;
开启两个消费者,都订阅了同一channel,利用Sleep使消费者2晚一秒订阅到channel,导致没有接收到消息0;
如果想要做到消息的持久化可以同时将Redis的List利用起来,或者使用其他介质存一份,在消息发布的同时使用将消息推到List中,然后在消费者中去将消息移除,历史消息可以在订阅channel前获取到当前List中消息,开启一个线程去执行业务操作并移除当前消息;
生产者
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost");
var redis = connection.GetDatabase(1);
var sub = connection.GetSubscriber();
foreach (var i in Enumerable.Range(0,100))
{
await redis.ListLeftPushAsync("historyMsg", $"消息 {i}");
await sub.PublishAsync("channel", $"消息 {i}");
Console.WriteLine($"发布消息 {i}");
Thread.Sleep(1000);
}
Console.ReadLine();
消费者
using StackExchange.Redis;
var connection = ConnectionMultiplexer.Connect("localhost");
var redis = connection.GetDatabase(1);
var sub = connection.GetSubscriber();
//这里可以选择不同消息持久化介质
var count = (await redis.ListRangeAsync("historyMsg")).Length;
if (count > 0)
{
await Task.Run(() =>
{
for (int i = 0; i < count; i++)
{
string result = redis.ListRightPop("historyMsg");
Console.WriteLine("处理历史消息:" + result);
}
});
}
sub.Subscribe("channel",
(channel, message) =>
{
redis.ListRightPop("historyMsg");
Console.WriteLine($"接收消息:{message}");
});
Console.ReadLine();