.net core Redis消息队列中间件【InitQ】
前言
这是一篇拖更很久的博客,不知不觉InitQ在nuget下载量已经过15K了,奈何胸无点墨也不晓得怎么写(懒),随便在github上挂了个md,现在好好唠唠如何在redis里使用队列

队列缓存分布式 异步调优堆配置 ------(来自某位不知名码友)
诞生背景
redis在项目中使用的越来越频繁,通常我们是用来做缓存,使用较多的就是String,Hash这两种类型,以及分布式锁,redis的List类型,就可以用于消息队列,使用起来更加简单,且速度更快,非常适合子服务内部之间的消息流转,创造灵感来自于杨老板的CAP(地址:https://www.cnblogs.com/tibos/p/11858095.html),采用注解的方式消费队列,让业务逻辑更加的清晰,方便维护
安装环境
- .net core版本:2.1
- redis版本:3.0以上
特点
1.通过注解的方式,订阅队列
2.可以设置消费消息的频次
3.支持消息广播
4.支持延迟队列
使用介绍
1.获取initQ包
方案A. install-package InitQ
方案B. nuget包管理工具搜索 InitQ2.添加中间件(该中间件依赖 StackExchange.Redis)
services.AddInitQ(m=>
{
m.SuspendTime = 1000;
m.IntervalTime = 1000;
m.ConnectionString = "127.0.0.1,connectTimeout=15000,syncTimeout=5000,password=123456";
m.ListSubscribe = new List<Type>() { typeof(RedisSubscribeA), typeof(RedisSubscribeB) };
m.ShowLog = false;
});
3.配置说明
public class InitQOptions
{
/// <summary>
/// redis连接字符串
/// </summary>
public string ConnectionString { get; set; } /// <summary>
/// 没消息时挂起时长(毫秒)
/// </summary>
public int SuspendTime { get; set; } /// <summary>
/// 每次消费消息间隔时间(毫秒)
/// </summary>
public int IntervalTime { get; set; } /// <summary>
/// 是否显示日志
/// </summary>
public bool ShowLog { get; set; } /// <summary>
/// 需要注入的类型
/// </summary>
public IList<Type> ListSubscribe { get; set; } public InitQOptions()
{
ConnectionString = "";
IntervalTime = 0;
SuspendTime = 1000;
ShowLog = false;
}
}
消息发布/订阅
消息的发布/订阅是最基础的功能,这里做了几个优化
- 采用的是长轮询模式,可以控制消息消费的频次,以及轮询空消息的间隔,避免资源浪费
- 支持多个类订阅消息,可以很方便的根据业务进行分类,前提是这些类 必须注册
- 支持多线程消费消息(在执行耗时任务的时候,非常有用)
示例如下(Thread.Sleep):
public class RedisSubscribeA: IRedisSubscribe
{
[Subscribe("tibos_test_1")]
private async Task SubRedisTest(string msg)
{
Console.WriteLine($"A类--->当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg}");
Thread.Sleep(3000); //使用堵塞线程模式,同步延时
Console.WriteLine($"A类<---当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg} 完成");
}
}

public class RedisSubscribeA: IRedisSubscribe
{
[Subscribe("tibos_test_1")]
private async Task SubRedisTest(string msg)
{
Console.WriteLine($"A类--->当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg}");
Thread.Sleep(3000); //使用堵塞线程模式,同步延时
Console.WriteLine($"A类<---当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg} 完成");
}
[Subscribe("tibos_test_1")]
private async Task SubRedisTest2(string msg)
{
Console.WriteLine($"A类--->当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg}");
Thread.Sleep(3000); //使用堵塞线程模式,同步延时
Console.WriteLine($"A类<---当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg} 完成");
}
}

示例如下(Task.Delay):
[Subscribe("tibos_test_1")]
private async Task SubRedisTest(string msg)
{
Console.WriteLine($"A类--->当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg}");
await Task.Delay(3000); //使用非堵塞线程模式,异步延时
Console.WriteLine($"A类<---当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者A消费消息:{msg} 完成");
}

根据业务情况,合理的选择堵塞模式
- 1.订阅发布者
using (var scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
//redis对象
var _redis = scope.ServiceProvider.GetService<ICacheService>();
//循环向 tibos_test_1 队列发送消息
for (int i = 0; i < 1000; i++)
{
await _redis.ListRightPushAsync("tibos_test_1", $"我是消息{i + 1}号");
}
}
- 2.定义消费者类 RedisSubscribeA
public class RedisSubscribeA: IRedisSubscribe
{
[Subscribe("tibos_test_1")]
private async Task SubRedisTest(string msg)
{
Console.WriteLine($"A类--->订阅者A消息消息:{msg}");
} [Subscribe("tibos_test_1")]
private async Task SubRedisTest1(string msg)
{
Console.WriteLine($"A类--->订阅者A1消息消息:{msg}");
} [Subscribe("tibos_test_1")]
private async Task SubRedisTest2(string msg)
{
Console.WriteLine($"A类--->订阅者A2消息消息:{msg}");
} [Subscribe("tibos_test_1")]
private async Task SubRedisTest3(string msg)
{
Console.WriteLine($"A类--->订阅者A3消息消息:{msg}");
}
}
- 3.定义消费者类 RedisSubscribeB
public class RedisSubscribeB : IRedisSubscribe
{
/// <summary>
/// 测试
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
[Subscribe("tibos_test_1")]
private async Task SubRedisTest(string msg)
{
Console.WriteLine($"B类--->订阅者B消费消息:{msg}");
}
}
消息广播/订阅
消息广播是StackExchange.Redis已经封装好的,我们只用起个线程监听即可,只要监听了这个key的线程,都会收到消息
- 1.订阅消息通道,订阅者需要在程序初始化的时候启动一个线程侦听通道,这里使用HostedService来实现,并注册到容器
public class ChannelSubscribeA : IHostedService, IDisposable
{
private readonly IServiceProvider _provider;
private readonly ILogger _logger; public ChannelSubscribeA(ILogger<TestMain> logger, IServiceProvider provider)
{
_logger = logger;
_provider = provider;
}
public void Dispose()
{
_logger.LogInformation("退出");
} public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("程序启动");
Task.Run(async () =>
{
using (var scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
//redis对象
var _redis = scope.ServiceProvider.GetService<ICacheService>();
await _redis.SubscribeAsync("test_channel", new Action<RedisChannel, RedisValue>((channel, message) =>
{
Console.WriteLine("test_channel" + " 订阅服务A收到消息:" + message);
})); }
});
return Task.CompletedTask;
} public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("结束");
return Task.CompletedTask;
}
}
public class ChannelSubscribeB : IHostedService, IDisposable
{
private readonly IServiceProvider _provider;
private readonly ILogger _logger; public ChannelSubscribeB(ILogger<TestMain> logger, IServiceProvider provider)
{
_logger = logger;
_provider = provider;
}
public void Dispose()
{
_logger.LogInformation("退出");
} public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("程序启动");
Task.Run(async () =>
{
using (var scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
//redis对象
var _redis = scope.ServiceProvider.GetService<ICacheService>();
await _redis.SubscribeAsync("test_channel", new Action<RedisChannel, RedisValue>((channel, message) =>
{
Console.WriteLine("test_channel" + " 订阅服务B收到消息:" + message);
})); }
});
return Task.CompletedTask;
} public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("结束");
return Task.CompletedTask;
}
}
- 2.将HostedService类注入到容器
services.AddHostedService<ChannelSubscribeA>();
services.AddHostedService<ChannelSubscribeB>();
- 3.广播消息
using (var scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
//redis对象
var _redis = scope.ServiceProvider.GetService<ICacheService>();
for (int i = 0; i < 1000; i++)
{
await _redis.PublishAsync("test_channel", $"往通道发送第{i}条消息");
}
}
延迟消息
延迟消息非常适用处理一些定时任务的场景,如订单15分钟未付款,自动取消, xxx天后,自动续费...... 这里使用zset+redis锁来实现,这里的操作方式,跟发布/定义非常类似
写入延迟消息:SortedSetAddAsync
注解使用:SubscribeDelay
1.定义发布者
Task.Run(async () =>
{ using (var scope = _provider.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
//redis对象
var _redis = scope.ServiceProvider.GetService<ICacheService>(); for (int i = 0; i < 100; i++)
{
var dt = DateTime.Now.AddSeconds(3 * (i + 1));
//key:redis里的key,唯一
//msg:任务
//time:延时执行的时间
await _redis.SortedSetAddAsync("test_0625", $"延迟任务,第{i + 1}个元素,执行时间:{dt.ToString("yyyy-MM-dd HH:mm:ss")}", dt);
}
}
});
2.定义消费者
//延迟队列
[SubscribeDelay("test_0625")]
private async Task SubRedisTest1(string msg)
{
Console.WriteLine($"A类--->当前时间:{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")} 订阅者延迟队列消息开始--->{msg}");
//模拟任务执行耗时
await Task.Delay(TimeSpan.FromSeconds(3));
Console.WriteLine($"A类--->{msg} 结束<---");
}

版本
- V1.0 更新时间:2019-12-30
版本库:
作者:提伯斯
.net core Redis消息队列中间件【InitQ】的更多相关文章
- Delayer 基于 Redis 的延迟消息队列中间件
Delayer 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP.Golang 等多种语言客户端. 参考 有赞延迟队列设计 中的部分设计,优化后实现. 项目链接:http ...
- java-spring基于redis单机版(redisTemplate)实现的分布式锁+redis消息队列,可用于秒杀,定时器,高并发,抢购
此教程不涉及整合spring整合redis,可另行查阅资料教程. 代码: RedisLock package com.cashloan.analytics.utils; import org.slf4 ...
- 基于硬件的消息队列中间件 Solace 简介之二
前言...... 前面简单介绍了Solace来自于哪家公司, 主要能做哪些事情. 本篇主要进一步介绍Solace作为消息传递的中间件如何工作的. 传统意义上来讲, 每当我们谈到消息中间件时, 首先想到 ...
- redis消息队列简单应用
消息队列出现的原因 随着互联网的高速发展,门户网站.视频直播.电商领域等web应用中,高并发.大数据已经成为基本的标识.淘宝双11.京东618.各种抢购.秒杀活动.以及12306的春运抢票等,他们这些 ...
- c#开源消息队列中间件EQueue 教程
一.简介 EQueue是一个参照RocketMQ实现的开源消息队列中间件,兼容Mono,具体可以参看作者的文章<分享一个c#写的开源分布式消息队列equeue>.项目开源地址:https: ...
- 常用的消息队列中间件mq对比
原文地址:https://blog.csdn.net/qq_30764991/article/details/80239076 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量 ...
- logstash解耦之redis消息队列
logstash解耦之redis消息队列 架构图如下: 说明:通过input收集日志消息放入消息队列服务中(redis,MSMQ.Resque.ActiveMQ,RabbitMQ),再通过output ...
- 预热一下吧《实现Redis消息队列》
应用场景 为什么要用redis?二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成ja ...
- nodejs一个函数实现消息队列中间件
消息队列中间件(Message Queue)相信大家不会陌生,如Kafka.RabbitMQ.RocketMQ等,已经非常成熟,在大大小小的公司和项目中也已经广泛使用. 有些项目中,如果是只使用初步的 ...
随机推荐
- 【tee小白的第一篇随笔】keystone代码略读
武大信安在读,最近在自学Risc-v架构的可信执行环境. (实验报告多半是为了交差.临时起意写写博客,分享一些自己读代码的心得理解.) 本篇内容由队和我友总结而成,如有错误欢迎指正交流. keysto ...
- 如何解决 shell 脚本重复执行的问题
在开发过程中,经常会使用shell脚本去完成定时备份的任务,普遍的做法是通过系统的定时任务定时执行备份脚本 设想这样一种场景,本次备份时间到了,自动执行备份脚本,如果备份比较耗时的话,会一直持续到下一 ...
- [bug] C:error: initializer element is not constant
参考 http://codingdict.com/questions/45121
- GCC链接时库顺序问题
GCC或G++在编译链接时,如果命令行中含有库,则要特别注意了.根据<C专家编程>5.3节中的提示,GCC在链接时对命令行时的处理顺序是从左到右.证据是GCC的MAN: -l librar ...
- traceroute排查网络故障 www.qq.com排查网络故障网络不通 先ping自己
网络不通 先ping自己 在ping网关 再ping外网 再ping别人的ip 背景需求 Linux 因为其强大的网络处理能力,被广泛用于网关(实例链接)和服务器(实例链接).实际工作中,快速排查这些 ...
- Ubuntu 获取升级信息失败。可能网络有问题
升级Ubuntu系统时遇到的这个问题, 原因是默认的源不是国内的,而且还可能导致下载慢等问题. 1.备份源 cd /etc/apt/ sudo cp sources.list sources.list ...
- gcc 编译过程详解-(转自CarpenterLee)
前言 C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程. 编写hello world C程序: // hello.c #include ...
- Stm32高级定时器(转自:luowei_memory)
1 定时器的用途 2 高级定时器框图 3 时基单元 4 通道 1 定时器的用途 已知一个波形求另一个未知波形(信号长度和占空比) 已知波形的信号长度和占空比产生一个相应的波形 增量正交编码器驱动电机获 ...
- /usr/bin/docker-current: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused "process_linux.go:245: running exec setns .....
docker创建容器时报错如下: containerd: start container" error="oci runtime error: container_linux.go ...
- Python对比两个txt文件内容
difflib模块作为python的标准库模块,无需安装,作用是比对文本之间的差异,且支持输出可读性比较强的html格式.#!coding=utf-8 # 2018-9-19 import sys i ...