【系统架构】聊聊开源消息中间件的架构和原理

说到消息中间件,身在互联网的童鞋们确定下意识的就是高并发,高性能io调度等浮如今脑海,可是对应用来讲,可能他的做用远不止性能这么简单,尤为是对与交易,金融打交道的业务平台来讲。java

ok,下面给你们介绍一下金融交易平台中,哪些场景是须要咱们用到消息中间件的?为何要使用?怎么设计中间件私有云让开发比较爽?(鉴于不一样同窗语言擅长不相同,这里只聊设计原理和机制方面的内容,本文会涉及市面上流行的开源产品,如activemq、rabbitmq、kafka、metaq等)mysql

消息中间件的做用就是用来异步化并发能力的一个载体,不只如此,它仍然须要在架构上保证不少能力,高可用,高并发,可扩展,可靠性,完整性,保证顺序等,光是这些都已经让各类设计者比较头疼了; 更有一些变态的需求,例如慢消费,不可重复等须要花的设计代价是至关高的,因此不要盲目的迷信开源大牛,对于不少机制,几乎都要重建;创建一个符合全部业务,好用,通用的私有云,没那么简单。算法

若是说一个支付系统天天要处理亿级业务单的话,那么消息中间件的处理能力至少得达到近百亿,由于不少系统都是依赖于中间件的集群能力,而且要保证不能出错,so,让咱们从架构的一些层面上来一点点来分析中间件是怎么作到的?sql

高可用

高可用是一个永恒的话题,这个也是在金融界是否靠谱的一个衡量标准,要知道,金融界的架构师们会千方百计的让数据不会丢失,哪怕是一条数据,可是事实上,这个东西从理论上来说,得靠人品。。。这个不是忽悠。数据库

举一个例子来讲,互联网数据架构中一份数据至少要存三份才叫高保证,可是事实上,谷歌的比利时数据中心在8.13日遭到雷劈后数据中心永久丢失0.000001%,不到0.05%的磁盘未能修复,这里要说的是,天时地利人和很重要,极限条件下没有什么不可能,必定会有架构漏洞,下面看一下mq高可用的通常作法:
下图是activemq的HA方案:
【系统架构】聊聊开源消息中间件的架构和原理api

activemq的HA 经过master/slave的failover进行托管,其中主从切换能够经过多种方式进行切换:
1:经过一个nfs或其它共享磁盘设备进行一个共享锁,经过对共享文件锁的占有,来标记master的状态,当m挂掉之后,对应的slave会占有shared_lock而转换为master缓存

2:经过zookeeper进行集群的管理,比较常见,这里再也不介绍
下图是metaq的HA方案
【系统架构】聊聊开源消息中间件的架构和原理
如上图,一模一样,也是经过zk管理broker的主从结点。 服务器

固然这个只是其中的一个failover机制,只能保证消息在broker挂掉时转换到slave上,可是不能保证在这中间过程当中的消息的丢失 网络

当消息从broker流经时,颇有可能由于宕机或是其它硬件故障而致使后,就有可能致使消息丢失掉,这个时候,就须要有相关的存储介质对消息的进行一个保障了数据结构

那么咱们举kafka的存储机制做为一个参考,要知道消息中间件对存储的依赖不但要求速度快,而且要求IO的需求成本很是低,kafka本身设计了一套存储机制来知足上述的需求,这里简单介绍一下。

首先kafka中的topic在分布式部署下分作多个分区,分区的就至关于消息进行了一个负载,而后由多台机器进行路由,举个例子: 一个topic,debit_account_msg会切分为 debit_account_msg_0, debit_account_msg_1, debit_account_msg_2。。。等N个partition,每一个partition会在本地生成一个目录好比/debit_account_msg/topic

里面的文件会分出不少segment,每一个segment会定义一个大小,好比500mb一个segment,一个file分为index和log二个部分
00000000000000000.index
00000000000000000.log
00000000000065535.index
00000000000065535.log
其中数字表明msgId的值的索引发点,对应的数据结构以下图:
【系统架构】聊聊开源消息中间件的架构和原理
1,0表明msgId为1的消息,0表明在这个文件中的偏移量,读取到这个文件后再寻找到查询到对应的segment log文件读取对应的msg信息,对应的信息是一个固定格式消息体:
【系统架构】聊聊开源消息中间件的架构和原理
显然,这种机制单纯应用确定是不能知足高并发IO的,首先二分查找segmentfile,而后再经过offset找到对应数据,再读取msgsize,再读取报体,至少是4次磁盘io,开销较大,可是在拉取时候是使用的顺序读取,基本上影响不大。

除了要上面所说的查询外。其实在写入磁盘以前都是在os上的pagecache上进行读写的,而后经过异步线程对硬盘进行定时的flush(LRU策略),但其实这个风险很大的,由于一旦os宕掉,会致使数据的丢失,尤为是在进行慢消费多积压不少数据的状况下,可是kafka他弟metaq对这块已经作了不少改造,对这些分区文件进行了replication机制(阿里内部使用),因此在这个层面上再怎么遭雷劈丢消息的机率就会比较小了,固然也不排除主机房光缆被人挖掉会有什么样的状况发生。

说了这么多,彷佛看起来的比较完美和美好,可是实际上运维成本彷佛很大。由于这些都是文件,一旦发生问题,须要人工去处理起来至关麻烦,并且是在一台一台机器上,须要比较大的运维成本去作一些运维规范以及api调用设施等。

因此,在这块咱们能够经过改造,将数据存储在一些nosql上,好比mongoDB上,固然mysql也是能够,可是io能力和nosqldb彻底不在一个水平线上,除非咱们有强烈的事务处理机制,而在金融里的确对这块要求比较至关严谨。像在支付宝后面就使用了metaq,由于以前的中间件tbnotify在处理慢消费的状况下会很被动,而metaq在这块会有极大的优点,为何,请听后面分解。

高并发

最开始你们使用mq很大部分工程师都用于解决性能和异步化的问题,其实对于同一个点来讲,一个io调度其实并非那么耗资源,废话少说让咱们看下mq里的一些高并发点,首先在这里先介绍一下几个比较有名的中间件背景:

activemq当时就是专门的企业级解决方案,遵照jee里的jms规范,其实性能也仍是不错的,可是拉到互联网里就是兔子抱西瓜,无能为力了

rabbitmq采用erlang语言编写,遵照AMQP协议规范,更具备跨平台性质,模式传递模式要更丰富,而且在分布式

rocketmq(metaq3.0现今最新版本, kafka也是metaq的前身,最开始是linkedIn开源出来的日志消息系统 ),metaq基本上把kafka的原理和机制用java写了一遍,通过屡次改造,支持事务,发展速度很快,而且在阿里和国内有很比较好的社区去作这块的维护 。

性能比较,这里从网上找一些数据,仅供参考:
【系统架构】聊聊开源消息中间件的架构和原理

说实话来说,这些数据级别来说,相差没有太离谱,可是咱们能够经过分析一些共性来说,这些主要性能差异在哪里?
rocketmq是metaq的后继者,除了在一些新特性和机制方面有改进外,性能方面的原理都差很少,下面说下这些高性能的一些亮点:

  • rocketmq的消费主要采用pull机制,因此对于broker来说,不少消费的特性都不须要在broker上实现,只须要经过consumer来拉取相关的数据便可,而像activemq,rabbitmq都是采起比较老的方式让broker去dispatch消息,固然些也是jms或amqp的一些标准投递方式

  • 文件存储是顺序存储的,因此来拉消息的时候只须要经过调用segment的数据就能够了,而且consumer在作消费的时候是最大程度的去消费信息,不太可能产生积压,并且能够经过设置io调度算法,像noop模式,能够提升一些顺序读取的性能 。

  • 经过pagecache去命中在os缓存中的数据达到一个热消费

  • metaq的批量磁盘IO以及网络IO,尽可能让数据在一次io中运转,消息起来都是批量的,这样对io的调度不太须要消耗太多资源

  • NIO传输,以下图,这个是最初metaq的一个架构,最初metaq使用的是taobao内部的gecko和notify-remoting集成的一些高性能的NIO框架去分发消息
    【系统架构】聊聊开源消息中间件的架构和原理
  • 消费队列的轻量化,要知道咱们的消息能力是经过队列来获取的

看下面的图:
【系统架构】聊聊开源消息中间件的架构和原理
metaq在消费的物理队列上添加了逻辑队列,队列对应的磁盘数据是串行化的,队列的添加不会添加磁盘的iowait负担,写入能够顺序,可是在读取的时候仍然须要去用随机读,首先是逻辑队列 ,而后再读取磁盘,因此pagecache很重要,尽可能让内存大一些,这块分配就会充分获得利用。

其实作到上面这些已经基本上能保证咱们的性能在一个比较高的水平; 可是有时候性能并非最重要的,最重要的是要和其它的架构特性作一个最佳的平衡,毕竟还有其它的机制要知足。由于在业界基本上最难搞定的三个问题:高并发,高可用,一致性是互相冲突的。

可扩展

这是一个老生常谈的问题,对于通常系统或是中间件,能够较好的扩展,可是在消息中间件这块,一直是一个麻烦事,为何?

先说下activemq的扩展起来的局限性,由于activemq的扩展须要业务性质,做为broker首先要知道来源和目的地,可是这些消息若是都是分布式传输的话,就会变的复杂,下面看一下activemq的负载是怎么玩转的
【系统架构】聊聊开源消息中间件的架构和原理
咱们假设producer去发topicA的消息,若是正常状况下全部的consumer都连到每个broker上的,辣么假如broker上有producer上的消息过来,是能够transfer到对应的consumer上的。
可是若是像图中 broker2中若是没有对应的消息者链接到上面,这种状况下怎么办呢?由于假设同一个topic的应用系统(producer)和依赖系统 (consumer)节点不少,那又该如何扩容呢?activemq是能够作上图中正常部分,可是须要改变producer,broker,consumer的对应的配置,至关麻烦。
固然activemq也能够经过multicast的方式来作动态的查找(也有人提到用lvs或f5作负载,可是对于consumer同样存在较大的问题,并且这种负载配置对于topic的分发,没实质性做用),可是,仍然会有我说的这个问题,若是topic太大,每一个broker都须要链接全部的producer或是consumer, 否则就会出现我说的状况,扩容这方面activemq是至关的麻烦

下面来讲一下metaq是如何作这块事情的,看图说话
【系统架构】聊聊开源消息中间件的架构和原理
metaq上是以topic为分区的,在这个层面来说,咱们只要配置topic的分区有多少个就行了,这样切片起来就是有个"业务"概念做为路由规则;通常一个broker机器上配置有多个topic,每一个topic在一个机器上通常是只有一个分区,假如机器不够了,也是能够支持多个分区的,通常来讲,咱们能够经过业务id来取模自定义分区,经过获取发区参数便可。
【系统架构】聊聊开源消息中间件的架构和原理
metaq的消费者也是经过group(这个分组通常根据partition的能力来配置)负载的方式去partition去拉消息,假若有多的消费者,不须要参与消费。通常线上都是这种状况,由于毕竟应用服务器要远大于消息服务器。
【系统架构】聊聊开源消息中间件的架构和原理
另一种状况,当分区过多时,以下图
【系统架构】聊聊开源消息中间件的架构和原理
这样的负载在多依赖的核心消息时,对于服务器broker的要求仍是比较高的,毕竟依赖的量较大,另外对于消息具备广播特色的话,可能会更大,因此对于broker而言,须要高io的硬盘以及大内存作pagecache,真正须要的运算并不须要太大
【系统架构】聊聊开源消息中间件的架构和原理

可靠性

可靠性是消息中间件的重要特性,看下mq是怎么流转这些消息的,拿activemq来先来作下参考,它是基于push&push机制。

如何保证每次的消息发送都被消费到?Activemq的生产者发送消息后都须要收到一条broker的ack才会确认消收到,一样对于broker到consumer也是一样的保障。

Metaq的机制也是一样的,可是broker到consumer是经过pull的方式,因此它的到达保障要看consumer的能力如何,可是通常状况下,应用服务器集群不太可能出现雪崩效应。

如何保证消息的幂等性?目前来讲基本上activemq,metaq都不能保证消息的幂等性,这就须要一些业务来保证了。由于一旦broker超时,就会重试,重试的话都会产生新的消息,有可能broker已经落地消息了,因此这种状况下无法保证同一笔业务流水产生二条消息出来

消息的可靠性如何保证?这点上activemq和metaq基本上机制同样:
生产者保证:生产数据后到broker后必需要持久化才能返回ACK给来源
broker保证:metaq服务器接收到消息后,经过定时刷新到硬盘上,而后这些数据都是经过同步/异步复制到slave上,来保证宕机后也不会影响消费
activemq也是经过数据库或是文件存储在本地,作本地的恢复

消费者保证:消息的消费者是一条接着一条地消费消息,只有在成功消费一条消息后才会接着消费下一条。若是在消费某条消息失败(如异常),则会尝试重试消费这条消息(默认最大5次),超过最大次数后仍然没法消费,则将消息存储在消费者的本地磁盘,由后台线程继续作重试。而主线程继续日后走,消费后续的消息。所以,只有在MessageListener确认成功消费一条消息后,meta的消费者才会继续消费另外一条消息。由此来保证消息的可靠消费。

一致性

mq的一致性咱们讨论二个场景:
1:保证消息不会被屡次发送/消费

2:保证事务
刚才上面介绍的一些mq都是不能保证一致性的,为何不去保证?代价比较大,只能说,这些都是能够经过改造源码来进行保证的,并且方案比较相对来讲不是太复杂,可是额外的开销比较大,好比经过额外的缓存集群来保证某段时间的不重复性,相信后面应该会有一些mq带上这个功能。

Activemq支持二种事务,一个是JMS transaction,一个是XA分布式事务,若是带上事务的话,在交互时会生成一个transactionId去到broker,broker实现一些TM去分配事务处理,metaq也支持本地事务和XA,遵照JTA标准这里activemq和metaq的事务保证都是经过redo日志方式来完成的,基本上一致。

这里的分布式事务只在broker阶段后保证,在broker提交以前会把prepare的消息存储在本地文件中,到commit阶段才将消息写入队列,最后经过TM实现二阶段提交。

小结

像公司内部也有一些性能很不错的消息中间件,但愿后面也能作到开源给到更多的人去使用。针对如今流行的一些消息中间件咱们能够针对不一样的应用,不一样的成本,不一样的开发定制不一样的架构,固然,这些架构必定是须要咱们通过多方面考量。

推荐阅读:

精心整理 | 2017下半年文章目录
关于缓存和数据库强一致的可行方案
用户进程缓冲区和内核缓冲区
经过金矿故事介绍动态规划(上)

专一服务器后台技术栈知识总结分享

欢迎关注交流共同进步

【系统架构】聊聊开源消息中间件的架构和原理

码农有道 coding

码农有道,为您提供通俗易懂的技术文章,让技术变的更简单!