net core3.1使用RocketMQ
工具:
vs2019 netcore3.1 阿里云RocketMQ V1.0.1
参考:阿里云官方示例:https://github.com/aliyunmq/mq-http-csharp-sdk
RocketMQ 是什么
Github 上关于 RocketMQ 的介绍:
RcoketMQ 是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。具有以下特性:
- 支持发布/订阅(Pub/Sub)和点对点(P2P)消息模型
- 在一个队列中可靠的先进先出(FIFO)和严格的顺序传递
- 支持拉(pull)和推(push)两种消息模式
- 单一队列百万消息的堆积能力
- 支持多种消息协议,如 JMS、MQTT 等
- 分布式高可用的部署架构,满足至少一次消息传递语义
- 提供 docker 镜像用于隔离测试和云集群部署
- 提供配置、指标和监控等功能丰富的 Dashboard
专业术语
Producer
消息生产者,生产者的作用就是将消息发送到 MQ,生产者本身既可以产生消息,如读取文本信息等。也可以对外提供接口,由外部应用来调用接口,再由生产者将收到的消息发送到 MQ。
Producer Group
生产者组,简单来说就是多个发送同一类消息的生产者称之为一个生产者组。在这里可以不用关心,只要知道有这么一个概念即可。
Consumer
消息消费者,简单来说,消费 MQ 上的消息的应用程序就是消费者,至于消息是否进行逻辑处理,还是直接存储到数据库等取决于业务需要。
Consumer Group
消费者组,和生产者类似,消费同一类消息的多个 consumer 实例组成一个消费者组。
Topic
Topic 是一种消息的逻辑分类,比如说你有订单类的消息,也有库存类的消息,那么就需要进行分类,一个是订单 Topic 存放订单相关的消息,一个是库存 Topic 存储库存相关的消息。
Message
Message 是消息的载体。一个 Message 必须指定 topic,相当于寄信的地址。Message 还有一个可选的 tag 设置,以便消费端可以基于 tag 进行过滤消息。也可以添加额外的键值对,例如你需要一个业务 key 来查找 broker 上的消息,方便在开发过程中诊断问题。
Tag
标签可以被认为是对 Topic 进一步细化。一般在相同业务模块中通过引入标签来标记不同用途的消息。
Broker
Broker 是 RocketMQ 系统的主要角色,其实就是前面一直说的 MQ。Broker 接收来自生产者的消息,储存以及为消费者拉取消息的请求做好准备。
Name Server
Name Server 为 producer 和 consumer 提供路由信息。
第一步:阿里云创建实例
创建完实例后,可以得到我们需要的下面这些参数值。
// 设置HTTP接入域名(此处以公共云生产环境为例) private const string _endpoint = "${HTTP_ENDPOINT}"; // AccessKey 阿里云身份验证,在阿里云服务器管理控制台创建 private const string _accessKeyId = "${ACCESS_KEY}"; // SecretKey 阿里云身份验证,在阿里云服务器管理控制台创建 private const string _secretAccessKey = "${SECRET_KEY}"; // 所属的 Topic private const string _topicName = "${TOPIC}"; // Topic所属实例ID,默认实例为空 private const string _instanceId = "${INSTANCE_ID}"; // 您在控制台创建的 Consumer ID(Group ID) private const string _groupId = "${GROUP_ID}";
第二步:引用RocketMQ
第三步:创建消息生产类:
using System; using System.Collections.Generic; using System.Threading; using Aliyun.MQ.Model; using Aliyun.MQ.Model.Exp; using Aliyun.MQ.Util; namespace Aliyun.MQ.Sample { public class ProducerSample { // 设置HTTP接入域名(此处以公共云生产环境为例) private const string _endpoint = "${HTTP_ENDPOINT}"; // AccessKey 阿里云身份验证,在阿里云服务器管理控制台创建 private const string _accessKeyId = "${ACCESS_KEY}"; // SecretKey 阿里云身份验证,在阿里云服务器管理控制台创建 private const string _secretAccessKey = "${SECRET_KEY}"; // 所属的 Topic private const string _topicName = "${TOPIC}"; // Topic所属实例ID,默认实例为空 private const string _instanceId = "${INSTANCE_ID}"; private static MQClient _client = new Aliyun.MQ.MQClient(_accessKeyId, _secretAccessKey, _endpoint); static MQProducer producer = _client.GetProducer(_instanceId, _topicName); static void Main(string[] args) { try { // 循环发送4条消息 for (int i = 0; i < 4; i++) { TopicMessage sendMsg; if (i % 2 == 0) { sendMsg = new TopicMessage("dfadfadfadf"); // 设置属性 sendMsg.PutProperty("a", i.ToString()); // 设置KEY sendMsg.MessageKey = "MessageKey"; } else { sendMsg = new TopicMessage("dfadfadfadf", "tag"); // 设置属性 sendMsg.PutProperty("a", i.ToString()); // 定时消息, 定时时间为10s后 sendMsg.StartDeliverTime = AliyunSDKUtils.GetNowTimeStamp() + 10 * 1000; } TopicMessage result = producer.PublishMessage(sendMsg); Console.WriteLine("publis message success:" + result); } } catch (Exception ex) { Console.Write(ex); } } } }
第四步,创建消息消费者类
using System; using System.Collections.Generic; using System.Threading; using Aliyun.MQ.Model; using Aliyun.MQ.Model.Exp; using Aliyun.MQ; namespace Aliyun.MQ.Sample { public class ConsumerSample { // 设置HTTP接入域名(此处以公共云生产环境为例) private const string _endpoint = "${HTTP_ENDPOINT}"; // AccessKey 阿里云身份验证,在阿里云服务器管理控制台创建 private const string _accessKeyId = "${ACCESS_KEY}"; // SecretKey 阿里云身份验证,在阿里云服务器管理控制台创建 private const string _secretAccessKey = "${SECRET_KEY}"; // 所属的 Topic private const string _topicName = "${TOPIC}"; // Topic所属实例ID,默认实例为空 private const string _instanceId = "${INSTANCE_ID}"; // 您在控制台创建的 Consumer ID(Group ID) private const string _groupId = "${GROUP_ID}"; private static MQClient _client = new Aliyun.MQ.MQClient(_accessKeyId, _secretAccessKey, _endpoint); static MQConsumer consumer = _client.GetConsumer(_instanceId, _topicName, _groupId, null); static void Main(string[] args) { // 在当前线程循环消费消息,建议是多开个几个线程并发消费消息 while (true) { try { // 长轮询消费消息 // 长轮询表示如果topic没有消息则请求会在服务端挂住3s,3s内如果有消息可以消费则立即返回 List<Message> messages = null; try { messages = consumer.ConsumeMessage( 3, // 一次最多消费3条(最多可设置为16条) 3 // 长轮询时间3秒(最多可设置为30秒) ); } catch (Exception exp1) { if (exp1 is MessageNotExistException) { Console.WriteLine(Thread.CurrentThread.Name + " No new message, " + ((MessageNotExistException)exp1).RequestId); continue; } Console.WriteLine(exp1); Thread.Sleep(2000); } if (messages == null) { continue; } List<string> handlers = new List<string>(); Console.WriteLine(Thread.CurrentThread.Name + " Receive Messages:"); // 处理业务逻辑 foreach (Message message in messages) { Console.WriteLine(message); Console.WriteLine("Property a is:" + message.GetProperty("a")); handlers.Add(message.ReceiptHandle); } // Message.nextConsumeTime前若不确认消息消费成功,则消息会重复消费 // 消息句柄有时间戳,同一条消息每次消费拿到的都不一样 try { consumer.AckMessage(handlers); Console.WriteLine("Ack message success:"); foreach (string handle in handlers) { Console.Write("\t" + handle); } Console.WriteLine(); } catch (Exception exp2) { // 某些消息的句柄可能超时了会导致确认不成功 if (exp2 is AckMessageException) { AckMessageException ackExp = (AckMessageException)exp2; Console.WriteLine("Ack message fail, RequestId:" + ackExp.RequestId); foreach (AckMessageErrorItem errorItem in ackExp.ErrorItems) { Console.WriteLine("\tErrorHandle:" + errorItem.ReceiptHandle + ",ErrorCode:" + errorItem.ErrorCode + ",ErrorMsg:" + errorItem.ErrorMessage); } } } } catch (Exception ex) { Console.WriteLine(ex); Thread.Sleep(2000); } } } } }