消息队列应用
正文
简介
它是一种异步传输模式,可以在不同的应用之间实现相互通信,相互通信的应用可以分布在同一台机器上,也可以分布于相连的网络空间中的任一位置。
它的实现原理是:消息的发送者把自己想要发送的信息放入一个Message中,然后把它保存至一个系统公用空间的消息队列(Message Queue)中;本地或者是异
地的消息接收程序再从该队列中取出发给它的消息进行处理。如图所示:
优缺点与使用场景
-
优缺点
优点:支持离线通讯;有消息优先级;有保障的消息传递和执行许多业务处理的可靠的防故障机制;息传递机制使得消息通信的双方具有不同的物理平台成为可能。
缺点:很难满足实时交互需求。
-
使用场景
1、数据采集:适合多设备多应用数据采集功能。
2、辅助实时交互:在大并发系统中,某一个操作涉及到很多步骤,某些步骤是不需要及时处理的,将不需要及时处理的步骤提出来,用消息队列处理。
比如:在一个高并发购物网站,一个顾客下单,顾客的操作记录、顾客的余额记录、商品相关的统计记录是不需要及时处理的,这些可以放消息队列处理,
延时处理。
3、多线程流水线处理:可以应用于生产者和消费者模式里面。比如:批量文件解压+解析+入库处理。为了降低服务器压力,将各个步骤分布在不同的
服务器上面,每个步骤的结果可以放入消息队列。
准备工作
-
环境准备
在使用MSMQ开发之前,需要安装消息队列。按顺序操作:控制面板-》程序-》打开或关闭Windows功能,勾选MSMQ服务器所有选项,如图所示:
-
存放位置
在调试、操作队列的时候,插入和取出队列信息以后,需要查看操作是否成功,就得去存放队列的服务器中查看队列信息。操作顺序:计算机-》管理-》
服务和应用程序,在里面有相应队列存储的信息,如图所示:
MSMQ的代码封装
-
基础准备
文件作用:
MSMQ队列操作的属性设置:
/// <summary> /// MSMQ队列操作的属性设置 /// </summary> public class MessageQueueSettings { /// <summary> /// 专用队列的基本路径 /// </summary> private string basePath = "private$"; /// <summary> /// 队列的ID /// </summary> public Guid MessageQueueId { get; set; } /// <summary> /// 队列的标签 /// </summary> public string MessageQueueLabel { get; set; } = "由程序创建的默认消息队列"; /// <summary> /// 访问队列的路径,默认是本机,记做“.” /// </summary> public string RemotePath { get; set; } = "."; /// <summary> /// 队列名称:默认是 msmqdefault /// </summary> public string MessageQueueName { get; set; } = ConfigSource.DefaultMSMQName; /// <summary> /// 队列的路径 /// </summary> public string MessageQueuePath => $"{RemotePath}\\{basePath}\\{MessageQueueName}"; /// <summary> /// 消息队列类型 /// </summary> public QueueType MessageQueueType { get; set; } = QueueType.PrivateQueue; /// <summary> /// 获取或设置队列日志的大小 /// </summary> public long MaximumJournalSize { get; set; } = uint.MaxValue; /// <summary> /// 是否启用日志 /// </summary> public bool UseJournalQueue { get; set; } = true; /// <summary> /// 获取或设置队列的大小 /// </summary> public long MaximumQueueSize { get; set; } = uint.MaxValue; /// <summary> /// 是否启用队列事务 /// </summary> public bool IsUseTransaction { get; set; } = true; /// <summary> /// 队列的访问方式 /// </summary> public QueueAccessMode AccessMode { get; set; } = QueueAccessMode.SendAndReceive; /// <summary> /// 设置/获取是否经过身份验证 /// </summary> public bool Authenticate { get; set; } = false; /// <summary> /// 获取或设置基优先级,“消息队列”使用该基优先级在网络上传送公共队列的消息。 /// </summary> public short BasePriority { get; set; } = 0; }
队列类型enum:
/// <summary> /// 队列类型 /// </summary> public enum QueueType { /// <summary> /// 私有队列 /// </summary> PrivateQueue = 0, /// <summary> /// 公共队列 /// </summary> PublicQueue = 1, /// <summary> /// 管理队列 /// </summary> ManageQueue = 2, /// <summary> /// 响应队列 /// </summary> ResponseQueue = 3 }
-
创建队列
逻辑代码里面不直接调用队列创建方法,直接用操作基类的构造方法创建。思路:初始化参数实体-》初始化待参数实体的操作基类实例-》实例里面构造方法
判断是否存在存在该队列,如果存在就返回该队列实例,如果不存在,就创建队列,返回实例。
基础操作类:
#region 构造函数 /// <summary> /// 默认构造函数:不对外开放 /// </summary> private MessageQueueBase() { } /// <summary> /// 构造函数 /// </summary> /// <param name="setting">消息队列的配置</param> public MessageQueueBase(MessageQueueSettings setting) { try { this.msetting = setting; // 校验是否存在队列 if (msetting.RemotePath == "." && !Exists()) this.mqueue = Create(); else this.mqueue = new MessageQueue(msetting.MessageQueuePath, msetting.AccessMode); if (msetting.RemotePath == ".") { // 设置是否经过身份验证:默认为否 this.mqueue.Authenticate = msetting.Authenticate; // 是否启用日志队列 this.mqueue.UseJournalQueue = msetting.UseJournalQueue; // 最大日志队列长度:最大值为uint.MaxValue this.mqueue.MaximumJournalSize = msetting.MaximumJournalSize > uint.MaxValue ? uint.MaxValue : msetting.MaximumJournalSize; // 最大队列长度:最大值为uint.MaxValue this.mqueue.MaximumQueueSize = msetting.MaximumQueueSize > uint.MaxValue ? uint.MaxValue : msetting.MaximumJournalSize; // 队列标签 this.mqueue.Label = msetting.MessageQueueLabel; } // 设置基优先级,默认是0 this.mqueue.BasePriority = msetting.BasePriority; // 获取消息队列的id msetting.MessageQueueId = mqueue.Id; } catch (Exception ex) { throw ex; } } ~MessageQueueBase() { this.mqueue?.Close(); GC.SuppressFinalize(this); } #endregion #region 操作方法 /// <summary> /// 验证队列是否存在 /// </summary> /// <returns>验证结果</returns> public bool Exists() { var successed = false; try { successed = MessageQueue.Exists(msetting.MessageQueuePath); } catch (Exception ex) { throw ex; } return successed; } /// <summary> /// 创建队列 /// </summary> /// <param name="type">队列类型(默认是专用队列)</param> /// <returns>队列对象</returns> public MessageQueue Create() { MessageQueue queue = default(MessageQueue); try { queue = MessageQueue.Create(msetting.MessageQueuePath, msetting.IsUseTransaction); } catch (Exception ex) { throw ex; } return queue; } /// <summary> /// 删除队列 /// </summary> public void Delete() { try { MessageQueue.Delete(msetting.MessageQueuePath); } catch (Exception ex) { throw ex; } } #endregion
业务调用类:
/// <summary> /// 创建消息队列 /// </summary> /// <param name="queueName">队列名称</param> /// <param name="transaction">是否是事务队列</param> /// <returns></returns> public bool CreateMessageQueue(string queueName, bool transaction) { bool result = false; MessageQueueSettings mqs = new MessageQueueSettings() { MessageQueueName= queueName, IsUseTransaction= transaction }; IMessageQueueBase messageQueueBase = new MessageQueueBase(mqs); result=messageQueueBase.Exists(); return result; }
测试结果:
-
发送消息
发送消息分为单条发送和列表发送,是否启用事务,以当前需求和队列属性决定。
基础操作类:
#region 发送操作 /// <summary> /// 发送数据到队列 /// </summary> /// <typeparam name="T">发送的数据类型</typeparam> /// <param name="t">发送的数据对象</param> public void Send<T>(T t) { MessageQueueTransaction tran = null; try { tran = msetting.IsUseTransaction || mqueue.Transactional ? new MessageQueueTransaction() : null; using (Message message = new Message()) { tran?.Begin(); message.Body = t; message.Label = $"推送时间[{DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss.fff")}]"; message.UseDeadLetterQueue = true; message.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); message.TimeToBeReceived = new TimeSpan(30, 0, 0, 0); message.TimeToReachQueue = new TimeSpan(30, 0, 0, 0); if (tran != null) { mqueue.Send(message, tran); } else { mqueue.Send(message); } tran?.Commit(); tran?.Dispose(); } } catch (Exception ex) { tran?.Abort(); tran?.Dispose(); throw ex; } } /// <summary> /// 发送多条数据 /// </summary> /// <typeparam name="T">发送的数据类型</typeparam> /// <param name="ts">发送的数据对象集合</param> public void SendList<T>(IList<T> ts) { MessageQueueTransaction tran = null; try { tran = msetting.IsUseTransaction || mqueue.Transactional ? new MessageQueueTransaction() : null; tran?.Begin(); foreach (var item in ts) { using (Message message = new Message()) { message.Body = item; message.Label = $"推送时间[{DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss.fff")}]"; message.UseDeadLetterQueue = true; message.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); message.TimeToBeReceived = new TimeSpan(30, 0, 0, 0); message.TimeToReachQueue = new TimeSpan(30, 0, 0, 0); if (tran != null) { mqueue.Send(message, tran); } else { mqueue.Send(message); } } } tran?.Commit(); tran?.Dispose(); } catch (Exception ex) { tran?.Abort(); tran?.Dispose(); throw ex; } } #endregion
业务调用类:
/// <summary> /// 发送消息 /// </summary> /// <param name="queueName">队列名称</param> /// <param name="priority">优先级</param> /// <param name="isTransaction">是否启用队列事务</param> /// <param name="userInfo">新增用户信息</param> public void SendMessage(string queueName, short priority, bool isTransaction, user userInfo) { MessageQueueSettings mqs = new MessageQueueSettings() { MessageQueueName = queueName, BasePriority = priority, IsUseTransaction = isTransaction }; IMessageQueueBase messageQueueBase = new MessageQueueBase(mqs); messageQueueBase.Send<user>(userInfo); }
测试结果:
-
接收消息
消息接收分为同步接收和异步接收。
基础操作类:
#region 接收方法 /// <summary> /// 同步获取队列消息 /// </summary> /// <typeparam name="T">返回的数据类型</typeparam> /// <param name="action">获取数据后的回调</param> public void Receive<T>(Action<T> action) where T : class { try { mqueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); Message message = mqueue.Receive(); T obj = message.Body as T; if (obj != null) action(obj); else throw new InvalidCastException("队列获取的类型与泛型类型不符"); } catch (Exception ex) { throw ex; } } /// <summary> /// 同步查询但不移除队列第一条数据 /// </summary> /// <typeparam name="T">返回的数据类型</typeparam> /// <param name="action">获取数据后的回调</param> public void Peek<T>(Action<T> action) where T : class { try { mqueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); Message message = mqueue.Peek(); T obj = message.Body as T; if (obj != null) action(obj); else throw new InvalidCastException("队列获取的类型与泛型类型不符"); } catch (Exception ex) { throw ex; } } /// <summary> /// 获取所有消息 /// </summary> /// <typeparam name="T">数据类型</typeparam> /// <returns>取出来的数据</returns> public IList<T> GetAll<T>() where T : class { try { mqueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); var enumerator = mqueue.GetMessageEnumerator2(); var ret = new List<T>(); while (enumerator.MoveNext()) { var messageInfo = enumerator.Current.Body as T; if (messageInfo != null) { ret.Add(messageInfo); } enumerator.RemoveCurrent(); enumerator.Reset(); } return ret; } catch (Exception ex) { throw ex; } } /// <summary> /// 异步接收队列消息,并删除接收的消息数据 /// </summary> /// <typeparam name="T">返回的数据类型</typeparam> /// <param name="action">获取数据后的回调</param> public void ReceiveAsync<T>(Action<T> action) where T : class { MessageQueueTransaction tran = null; try { tran = msetting.IsUseTransaction || mqueue.Transactional ? new MessageQueueTransaction() : null; if (!initReceiveAsync) { mqueue.ReceiveCompleted += (sender, e) => { Message message = mqueue.EndReceive(e.AsyncResult); T obj = message.Body as T; if (obj != null) action(obj); else throw new InvalidCastException("队列获取的类型与泛型类型不符"); }; initReceiveAsync = true; } mqueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); tran?.Begin(); mqueue.BeginReceive(); tran?.Commit(); } catch (Exception ex) { throw ex; } } /// <summary> /// 启动一个没有超时设定的异步查看操作。直到队列中出现消息时,才完成此操作。 /// </summary> /// <typeparam name="T">获取的消息数据的类型</typeparam> /// <param name="action">获取数据后的回调</param> public void PeekAsync<T>(Action<T> action) where T : class { MessageQueueTransaction tran = null; try { tran = msetting.IsUseTransaction || mqueue.Transactional ? new MessageQueueTransaction() : null; if (!initPeekAsync) { mqueue.PeekCompleted += (sender, e) => { Message message = mqueue.EndPeek(e.AsyncResult); T obj = message.Body as T; if (obj != null) action(obj); else throw new InvalidCastException("队列获取的类型与泛型类型不符"); }; initPeekAsync = true; } mqueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); tran?.Begin(); mqueue.BeginPeek(); tran?.Commit(); } catch (Exception ex) { throw ex; } } /// <summary> /// 异步循环获取队列消息,调用此方法将一直接收队列数据,直到IsStopReceiveLoop被设置为true /// </summary> /// <typeparam name="T">返回的数据类型</typeparam> /// <param name="action">获取数据后的回调</param> public void ReceiveAsyncLoop<T>(Action<T> action) where T : class { MessageQueueTransaction tran = null; try { tran = msetting.IsUseTransaction || mqueue.Transactional ? new MessageQueueTransaction() : null; if (!initReceiveAsync) { mqueue.ReceiveCompleted += (sender, e) => { Message message = mqueue.EndReceive(e.AsyncResult); T obj = message.Body as T; if (obj != null) action(obj); else throw new InvalidCastException("队列获取的类型与泛型类型不符"); //只要不停止就一直接收 if (!IsStopReceiveLoop) { tran?.Begin(); mqueue.BeginReceive(); tran?.Commit(); } }; initReceiveAsync = true; } mqueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) }); IsStopReceiveLoop = false; tran?.Begin(); mqueue.BeginReceive(); tran?.Commit(); } catch (Exception ex) { throw ex; } } #endregion
业务调用类:
/// <summary> /// 接收消息 /// </summary> /// <param name="queueName">队列</param> /// <param name="isAsy">是否异步</param> public user ReceiveMessage(string queueName, bool isAsy) { user userInfo = new user(); MessageQueueSettings mqs = new MessageQueueSettings() { MessageQueueName = queueName }; IMessageQueueBase messageQueueBase = new MessageQueueBase(mqs); if (!isAsy) { messageQueueBase.Receive<user>(p => { userInfo = p; }); } else { messageQueueBase.ReceiveAsync<user>(p => { userInfo = p; }); } return userInfo; } /// <summary> /// 获取全部 /// </summary> /// <param name="queueName"></param> /// <returns></returns> public IList<user> GetAll(string queueName) { MessageQueueSettings mqs = new MessageQueueSettings() { MessageQueueName = queueName }; IMessageQueueBase messageQueueBase = new MessageQueueBase(mqs); return messageQueueBase.GetAll<user>(); }
测试结果:
点击插入数据,在队列中插入N条数据。同步接收和异步接收每次只获取队列第一条数据;异步循环是每次获取一条数据,循环获取,知道队列没有数据;
接收全部是一次性获取指定队列所有数据。
补充
下一篇写Dapper应用或Redis应用。