【框架学习与探究之消息队列--EasyNetQ(1)】

前言

本文欢迎转载实属原创,本文原始链接地址:http://www.cnblogs.com/DjlNet/p/7603554.html


废话

既然都是废话了,所以大家就可以跳过了,这里是博主有事没事儿的一点瞎说哈,国庆节+中秋节一共8天,有些人回家了,有些人堵在路上了,有些人可能还要加班或者值班,233333,博主这里还好是没有加班一般也不需要值班,可能偶尔需要的时候远程瞅瞅就行。相信很多小伙伴,在放假前夕都做好了要去哪里玩,要去哪里吃,要怎么过好节假日,当然也有要看书的,有假期学习计划,有锻炼身体的,说说自己吧,博主算是综合性了,餐了聚了、黑也开了、电影也看了、书还没看、计划学习搁置+延期了、身体也没啥锻炼、剧倒是跟进了一些,看到这里是不是觉得博主的生活是不是冥冥之中在哪里见过,对,是的,多多少少许多人都“中招”了(当然了每个人有自己的理解和生活方式,没有人可以批判谁对谁错,所以,没有对错,只有利弊!!!),说着说着博主放下了手中的“屠刀”,陷入了思考,想着不远千里啊,为了啥,还不还是为了未来,正当博主思考之际,电话响了起来,外卖:帅哥,您的外卖到了,请麻烦下来拿一下!!!,一个叫阿龙的人,心里如有所思的走下楼拿了外卖回到了电脑前面,打开了S7世界赛的画面津津有味的看起来直播.......以上言语,纯属瞎扯,如有雷同,那也只能这样了,哈哈!!!


正文

再说此框架之前呐,有些前提概念或者知识需要了解一下的,念与博主本身这方面知识也有所不足(需要理解充电),所以部分知识只限于提供链接地址和部分理解所悟(可能后面单独出文再加以叙述说明吧),所以园友如若发现不当之处,可尽情提出出来一起谈论便是,由于园内很多次框架相关文章,但是或多或少都显得有些零散或者是入门系列不过是我们作为参考的辅助资料,博主将会集中式整理一些方便查阅,当然此篇文章或许有些入门知识但是更着重的是划出博主认为需要注意的重点项,大致梳理顺序逻辑,还是按照前提概念知识补充、框架文档与实践、划重点与问题探究....接下来我们一步一步的扒光式的来学习....


背景

在此之前多多少少就知道一些关于消息队列方面的东西,博主先前的时候就做过一些MSMQ+WCF的项目实践,不过呐这种组合在当前的技术流就显得力不从心,一方面呐部署环境的受限,运维大兄弟对Linux更加的得心应手,再者就是这种组合本身的局限性和开发的坑略多了些,那么基于当前的来做消息交互、解耦、限流或者削锋等通常情况下选择无非就是,当前使用较多的消息队列有RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,而部分数据库如Redis、Mysql以及phxsql也可实现消息队列的功能。由于我们主要基于RabbitMQ上面来做说明和研究使用( .NET社区通常的选择也是目前博主公司的选择,所以得先从这下手知其一而百通,哈哈 ),其他情况这里博主找到一篇好文: https://cloud.tencent.com/community/article/129032 消息队列及常见消息队列介绍 ,文中关于消息队列的使用场景和各自的优缺点对比说明都有一定的说明,大家可以跳过去看看,并且文中结尾还有些各自MQ的参考学习链接,也可以作为学习途径......

这里博主觉得还是有必要备注一下(引用来自于上文链接):消息队列在实际应用中包括如下四个场景(链接中对每个场景有举例分析,可参考):
应用耦合:多应用间通过消息队列对同一消息进行处理,避免调用接口失败导致整个过程失败;
异步处理:多应用对消息队列中同一消息进行处理,应用间并发处理消息,相比串行处理,减少处理时间;
限流削峰:广泛应用于秒杀或抢购活动中,避免流量过大导致应用系统挂掉的情况;
消息驱动的系统:系统分为消息队列、消息生产者、消息消费者,生产者负责产生消息,消费者(可能有多个)负责对消息进行处理;


知识前提RabbitMQ

从官网https://www.rabbitmq.com我们可以得知,它已经走过了10个年头了,是一个在AMQPhttp://www.amqp.org/(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。

这里说明一下RabbitMQ主要特性:
可靠性: 提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性机制、投递确认、发布者证实和高可用性机制;
灵活的路由: 消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用;
消息集群:在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用;
队列高可用:队列可以在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全;
多种协议的支持:支持多种消息队列协议;
服务器端用Erlang语言编写,支持只要是你能想到的所有编程语言;
管理界面: RabbitMQ有一个易用的用户界面,使得用户可以监控和管理消息Broker的许多方面;
跟踪机制:如果消息异常,RabbitMQ提供消息跟踪机制,使用者可以找出发生了什么;
插件机制:提供了许多插件,来从多方面进行扩展,也可以编写自己的插件;
使用RabbitMQ需要:ErLang语言包RabbitMQ安装包
RabbitMQ可以运行在Erlang语言所支持的平台之上:Solaris BSD 、Linux、 MacOSX 、TRU64 、Windows NT/2000/XP/Vista/Windows 7/Windows 8、
Windows Server 2003/2008/2012、Windows 95, 98、VxWorks
优点:
1、由于erlang语言的特性,mq 性能较好,高并发;
2、健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
3、有消息确认机制和持久化机制,可靠性高;
4、高度可定制的路由;管理界面较丰富,在互联网公司也有较大规模的应用;
5、社区活跃度高;
缺点:
1、尽管结合erlang语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
2、实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得RabbitMQ易于使用和部署,但是使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;
3、需要学习比较复杂的接口和协议,学习和维护成本较高;
参考链接地址:
RabbitMQ主页 https://www.rabbitmq.com/
RabbitMQ学习教程 https://www.rabbitmq.com/getstarted.html
专栏:RabbitMQ从入门到精通 http://blog.csdn.net/column/details/rabbitmq.html
RabbitMQ能为你做些什么 http://rabbitmq.mr-ping.com/description.html
RabbitMQ指南(1)-特性及功能 https://blog.zenfery.cc/archives/79.html
以上总结来自于链接地址:https://cloud.tencent.com/community/article/129032 所以仅供参考学习


园中RabbitMQ文章收集参考

当然这里博主就选取一些值得借鉴参考的博文来记录与备份,例如什么安装介绍基本使用集群经验总结一类的文章:
http://www.cnblogs.com/liqingwen/p/6412089.html [.NET] RabbitMQ 的行为艺术,此文介绍了RabbitMQ大致组织结构成分职责说明,以及环境搭建梳理和HelloWord、交换机的基本说明,居介绍性质多一些
http://www.cnblogs.com/sheng-jie/p/7192690.html RabbitMQ知多少 ,该文从基本介绍到环境搭建以及后续每个功能点说明,文章排版阅读友好性强,居实践功能点多一些
http://www.cnblogs.com/zhangweizhong/category/855479.html RabbitMQ学习系列文,系列文从介绍到基本使用,以及后面的集群高阶使用,算是一个由浅入深的系列好文
http://www.cnblogs.com/wangiqngpei557/p/6158094.html RabbitMQ 高可用集群搭建及电商平台使用经验总结,从题目就可以看出是题主在实践项目中实战得出经验总结,想必是值得博主本身阅读学习的
http://www.cnblogs.com/dongkuo/p/6001791.html 消息队列——RabbitMQ学习笔记
http://www.cnblogs.com/panzi/p/6337568.html .NET操作RabbitMQ组件EasyNetQ使用中文简版文档
http://www.cnblogs.com/stulzq/p/7551819.html .NET Core 使用RabbitMQ
这里博主列出阅读过的此类文章,并不是在说谁好谁坏,责在学习文中的写得好的地方顺便备份一下,对我们理解开发有意义的地方是值得借鉴和学习的,同时我们发现在文末留言中发现,园友在使用时存在多多少少许多问题,至此博主将会以身试法般的带着文档去实践,毕竟实践是检验理论的唯一标准.....


EasyNetQ文档跟进式学习与实践

这里可能有人要问了,为什么不使用官方的nuget包呐:RabbitMQ.Client(官方还在积极对.net core做升级去兼容.net standrad,这挺好,https://www.nuget.org/packages/RabbitMQ.Client/5.1.0-pre1),要说为什么,其实无非就是原始的官方包你说要用吧也可以用,就是需要学习成本,让小组成员都要熟悉API又是一番功夫且低级API方法不是那么通俗易懂,当然拉有能力的当然可以借助官方包二次封装以适应自己项目或者Team的需求,前提是对交互机制和对原始API有较为全面的了解,所以综上所说呐,在社区的支持下提供了 框架:EasyNetQ,从其名便可以知道,方便了我们使用高阶API的同时,依然保留了对原始高级功能点的访问,所以这也体现框架作者的对框架的理解也基本上足够我们当前的使用需求了,当然了此框架本身也是依赖于 RabbitMQ.Client 的也是一个二次封装的结果,所以呐博主选取它作为学习或者项目集成方面,都不愧是一个不错的选择!相信使用过的老铁,都知道它的好,希望作者延续出core的版本吧.....同时博主不希望只仅仅是简单的跟着文档滚一边,而且想以一种正确优雅的姿势把此消息队列框架集成在系统当中,且巧妙的设计以及避开一些坑,实现在业务与消息之间微妙的关系(例如通过事件总线来统筹,MQ消息来传递),同时保证消息交互一个不稳定的情况下,如何实现尽可能的消息落地与确保,这也是当今面对分布式事务的一种婉转但不失优雅的解决方案.....


EasyNetQ文档划重点与测试

这里是博主认为有必须要视为重点且需要实验验证的点,如若没有提及的地方,可以提出来一起讨论学习

1、发布者确认(Publisher Confirms):简而言之就是确保消息的成功投递,通俗的讲就是我要知道,我投递的消息到底有木有成功被接受连接字符增加 publisherConfirms=true; 即可,这里博主建议不要为了那么一丢丢的确认性能的考虑而放弃发送消息确认配置,同时为了保证消息的投递成功,RabbitMQ也推荐使用该方式因为没有实现事务方式,且发送消息之后不管是同步方法(Publish)还是异步方法(PublishAsync)在自身内部都会在超时还没到的时间段内,一直等待消息确认接收的反馈讯息,再然后同步方法才会返回,异步方法也会返回一个完成状态的Task对象,当然可能因为网络或者MQServer服务宕机等一系列问题导致同步异常爆发或者异步返回异常的Task对象,所以还需要对后续异常情况处理和日志记录机制的的实施,至此这种发布者确认带来的消息推送保障对于我们的系统来说很重要,关乎到系统交互的责任归属问题(例如:你说你投递成功了,我咋知道你投递成功了没有,我这里没收到啊,这样比较搞笑的对话了)

这里补充说明一点就是,日常开发中博主见过的消息推送目前的两种方式

a、要求消息的发送与业务逻辑耦合在一个数据库的事务作用域当中,且消息发送需要放在业务逻辑的最后面(你懂的),这样就形成了一个利用数据库层面的强制原子操作,包含消息发送日志记录当作证据,达到消息和业务绑定关系为同生共死“要么都成功,要么都失败”,好处:实现了逻辑的一致性以及消息的及时发送,可以满足某些特殊需求,坏处:消息发送需要远程服务器交互(内网或者外网)需要等待耗时且一定程度拉低了系统处理能力受MQ消息服务器的影响,并且发送失败会导致业务处理失败浪费了前期逻辑处理,所以也不存在消息的重试机制了,直接重新操作业务逻辑就可以重试发送了;当然依然需要异常处理与日志记录

b、将业务逻辑产生的消息归档到一个等待发送消息DB(Table)中做持久化处理,然后通过定时器框架配合处理,轮询取出一定数量应该发送的消息(这里包括没发送成功需要重试的消息,一直怼)到MQServer中成功无异常后重置消息的发送标记位flag=true,重试消息的话RetryCount++,这里当然也是在一个DB事务当中且消息发送依然在尾部逻辑(你懂的),好处:消息发送与业务逻辑分离,职责划分明确增加了系统各自部分的稳定性不受消息服务影响,相互独立不受牵制,且已经具备了消息发送日志记录(前面说的等待发送消息持久化证明)作为证据,坏处:消息发送具有一定得延迟性也就是定时器的间隔时间,同理这方式依然需要异常处理与日志记录

当然以上两种方式都是基于额外数据库事务支持的情况下(加上PublisherConfirms共同保证消息落地性),至于选择何种方式或者是说怎么样来灵活的组合使用,来应对系统消息发布的场景,看上面的解释也能大致有数了吧,其实也需要根据开发时的业务场景、网络、服务器资源等等综合考虑,不过一般情况博主推荐使用方式b即可,一般一般一般情况下对于消息的及时性要求不高,控制在一定的忍耐程度就行了,也是目前博主采取的方式来操作,不过得注意得时相关与网络传输交互都需要异常处理和日志记录来做问题得检查和恢复得支撑,至于上面两种方式的代码集成就比较明显了,毕竟思路明确了,博主这里打算后面框架集成部分给出参考吧。

2、prefetchcount在RabbitMQServer获取Consumer消费者反馈的ack(确认消费标志)之前,RabbitMQ服务能够分发消息给消费者的最大值,连接字符串配置:prefetchcount=50;,默认是50,且这些消息处于内存队列中处于准备状态,从控制台界面查看就是(注意红框处就是设置值,这里博主测试设置了为:prefetchcount=5;,然后故意让消费者延迟反馈ack确认消息回执)

测试代码如下:

IBus bus = RabbitHutch.CreateBus("host=localhost;virtualHost=/;username=yourname;password=yourpassword;publisherConfirms=true;timeout=10;prefetchcount=5");
            bus.Subscribe<MyClass>(string.Empty, x =>
            {
                Console.WriteLine(x.Text);
                Task.Delay(TimeSpan.FromSeconds(5)).Wait();
            });

当然这个配置是作用于消费者,那么这里每个消费者可以自定义该配置来覆盖连接字符串中的全局配置,来达到控制个别消费者的细粒度,参考如下代码与效果图


设置0表示不限制可以无限发送消息(不推荐),设置1表示消费者以公平方式接受消息(多个消费者共享一个队列的时候,消费顺序按照订阅而定),其实说白了,这个值的设定是要看消息接收方处理能力而定的,所以具体生成环境看情况而定吧,采用默认设置也可以。

3、Ack:表示订阅者接受并处理消息之后回执。
例如:一个订阅了MyMessage的订阅者,如果程序崩溃会发生什么?为了提高效率,EasyNetQ实现了一个用于订阅的内部内存队列。消息通过网络从RabbitMQ接收并放置在此队列上。单个订阅线程依次将消息从队列中取出,并将它们传递给您提供的回调。一旦回调完成EasyNetQ将发送一个“Ack”回RabbitMQ在收到“Ack”之前,消息不会从RabbitMQ队列中删除如果您的服务在处理消息时中止,还没有Ack的回传,消息(以及EasyNetQ内存中队列中的所有消息)将保留在RabbitMQ队列中,一旦您的服务重新连接,消息将被重新发送,从上而知也知道ACK的作用和意义。

当然消费消息的时候发生异常:如果您的订阅回调引发EasyNetQException异常EasyNetQ将会收到正在消费的消息,并将其包装在特殊的错误消息中。错误消息将被发布EasyNetQ错误队列(名为EasyNetQ_Default_Error_Queue)您应该监视任何消息的错误队列。错误消息包括重新发布原始消息以及异常类型,消息和堆栈跟踪所需的所有信息。您可以使用EasyNetQ.Hosepipe实用程序重新发布错误消息。请参阅下面的EasyNetQ.Hosepipe部分

这里默认EasyNetQ已经帮我们做好了消息处理的部分逻辑,包括异常处理和消息ACK回执
那么我们在生产环境如何保证消费消息做到尽可能消息100%落地
这里博主的大致解决办法描述如下:这里消息体的格式协定以及消息基本验证什么的,不再讨论方法之类,就如同API方法参考校验一个性质,这里相信大部分订阅者处理消息都参与了数据库逻辑,所以这里博主就必须把传递过来的消息持久化到数据库中当做消费证明,当然这里又可以使用数据库的ACID的事务机制来包裹消息日记录ConsumerLog以及之前的一些业务逻辑数据库写操作或者其他Web请求逻辑,当然可能会由于网络请求或者数据库本身的问题出现爆发异常,导致事务回滚(业务逻辑同时消费消息日志记录失败,这种就是除本身逻辑异常之外的不可控的异常爆发),那么这种还需要进一步保证消息落地怎么办呐?回答:没有百分百的保证,首先还是再一次的向数据库或者NoSql再次写入消息,不过这里是写入异常消费失败的消息ConsumerErrorLog(那么就需要定时器消息重试消费逻辑的机制),如果上述还是不行(因为这里还可能还是失败)就需要文本日志来记录Message消息体做进一步保证了,其次就是Log记录异常原因,最最后我还可以利用上述EasyNetQ提供了EasyNetQException异常机制将消费失败的消息进一步在EasyNetQ错误队列(名为EasyNetQ_Default_Error_Queue)也保留一份存档方便对照查阅,到这里相信基本上能够尽可能保证消息消费的正确姿势了,当然上述思路可能依然并不是最合适的,最好是结合自己的情况进一步定制化自己的消息处理框架吧。


小总结

首先需要说明的是未完待续(发现一篇文还不够说明全部,不出意外近期更新,哈哈),后面作进一步更新,更新内容可能依然来源于文档划重点部分,当然园友也可以说一下注意点和需要试验证明的,或者关于消息队列的一些思考和想法。然后大致总结一下上面的内容,我们从背景介绍与使用场景的说明,到当前主流的消息队列介绍和对比,然后选取了热门的RabbitMQ做了进一步的介绍和评比,再者这里也方便自己查阅收录一些当前园中的一些相关文章链接地址,再然后直接通过文档与在使用时比较关心的几个问题做了说明和可行性实验的研究,框架文档基本过了一两遍了,大致使用与集成博主大致心中有底,毕竟是在站人家的肩膀搞事情。当然就算是基于框架也可以有考虑的遗留项,所以我们尽可能了解RabbitMQ自身的一些原理和对原始Rabbit.Client API有一定了解肯定是更加对集成有好处的.....

最后呐,可能此文前部分有些“敷衍”或者“老酒新装”之意,毕竟了解这瓶“老酒”肯定是需要些时日才能品出味道的呐,但是博主觉得就算是吧,也是对自己知识的多一层熟悉和铺垫吧,当然博主是打破砂锅问到底的那种,在问题上比较纠结,想弄清楚何为正确姿势!!所以上面的理解基本都是基于博主本身的思考与实验,可能略显不对,但凡请指出讨论,在此感激不尽,哈哈哈,最最后,如果文章对您或许有那么一丢丢的帮助,您的评论和点赞都是对博主很大的支持呐!!!O(∩_∩)O嗯!

posted @ 2017-10-11 09:51  DJLNET  阅读(3127)  评论(2编辑  收藏  举报