【进阶之路】消息队列——原理及选型(一)

2021-04-02 11:42:27 浏览数 (1)

.markdown-body{word-break:break-word;line-height:1.75;font-weight:400;font-size:15px;overflow-x:hidden;color:#333}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{line-height:1.5;margin-top:35px;margin-bottom:10px;padding-bottom:5px}.markdown-body h1{font-size:30px;margin-bottom:5px}.markdown-body h2{padding-bottom:12px;font-size:24px;border-bottom:1px solid #ececec}.markdown-body h3{font-size:18px;padding-bottom:0}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:15px}.markdown-body h6{margin-top:5px}.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}.markdown-body img{max-width:100%}.markdown-body hr{border:none;border-top:1px solid #ddd;margin-top:32px;margin-bottom:32px}.markdown-body code{word-break:break-word;border-radius:2px;overflow-x:auto;background-color:#fff5f5;color:#ff502c;font-size:.87em;padding:.065em .4em}.markdown-body code,.markdown-body pre{font-family:Menlo,Monaco,Consolas,Courier New,monospace}.markdown-body pre{overflow:auto;position:relative;line-height:1.75}.markdown-body pre>code{font-size:12px;padding:15px 12px;margin:0;word-break:normal;display:block;overflow-x:auto;color:#333;background:#f8f8f8}.markdown-body a{text-decoration:none;color:#0269c8;border-bottom:1px solid #d1e9ff}.markdown-body a:active,.markdown-body a:hover{color:#275b8c}.markdown-body table{display:inline-block!important;font-size:12px;width:auto;max-width:100%;overflow:auto;border:1px solid #f6f6f6}.markdown-body thead{background:#f6f6f6;color:#000;text-align:left}.markdown-body tr:nth-child(2n){background-color:#fcfcfc}.markdown-body td,.markdown-body th{padding:12px 7px;line-height:24px}.markdown-body td{min-width:120px}.markdown-body blockquote{color:#666;padding:1px 23px;margin:22px 0;border-left:4px solid #cbcbcb;background-color:#f8f8f8}.markdown-body blockquote:after{display:block;content:""}.markdown-body blockquote>p{margin:10px 0}.markdown-body ol,.markdown-body ul{padding-left:28px}.markdown-body ol li,.markdown-body ul li{margin-bottom:0;list-style:inherit}.markdown-body ol li .task-list-item,.markdown-body ul li .task-list-item{list-style:none}.markdown-body ol li .task-list-item ol,.markdown-body ol li .task-list-item ul,.markdown-body ul li .task-list-item ol,.markdown-body ul li .task-list-item ul{margin-top:0}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-top:3px}.markdown-body ol li{padding-left:6px}.markdown-body .contains-task-list{padding-left:0}.markdown-body .task-list-item{list-style:none}@media (max-width:720px){.markdown-body h1{font-size:24px}.markdown-body h2{font-size:20px}.markdown-body h3{font-size:18px}}

导言

大家好,我是南橘,从接触java到现在也有差不多两年时间了,两年时间,从一名连java有几种数据结构都不懂超级小白,到现在懂了一点点的进阶小白,学到了不少的东西。知识越分享越值钱,我这段时间总结(包括从别的大佬那边学习,引用)了一些平常学习和面试中的重点(自我认为),希望给大家带来一些帮助

  • 【进阶之路】消息队列——原理及选型(一)
  • 【进阶之路】消息队列——RabbitMQ原理(二)

目前我在负责新的业务,业务中有一块是调用别的集群的服务进行扣款。之前的同事把这块做成异步通知调用然后等待返回,返回成功就在表里记录成功。由于两块业务在不同的集群,为了确保资金安全,防止出现资金风险,只能依靠第二天的对账来保证,但这种调取方式经常会出现资金差错,又需要人工介入。

和大佬讨论了一会,决定在两个服务之间加入消息队列,通过消息队列来保证数据的一致性,也减少出现资金差错的概率。

那么这一章我们就来学习一下消息中间件的内容。

一、什么时候需要消息队列

  • 异步处理:处理如短信下发、状态推送、用户注册、数据同步等功能,提高系统的并发能力,集中力量处理重要的部分(同步处理),将非核心功能丢给MQ。
  • 系统解耦:可在模块、服务、接口等不同粒度上实现解耦。
  • 重试补偿:在跨机器数据传输的整个过程中,只要任意一个环节出错,都会导致问题的产生。可以通过MQ的重试补偿机制去尽可能的处理掉这些异常。
  • 流量削锋:对于秒杀场景下的下单处理。服务器收到消息后,首先写入消息队列,然后按照自己的消息处理能力做处理。
  • 日志处理:可以定时将日志写入MQ,并且主动订阅日志记录。

二、消息队列核心概念

1、消息服务器Broker:消息服务器,作为server提供消息核心服务

2、消息生产者Producer:发送消息到消息队列。

3、消息消费者Consumer:从消息队列接收消息。

4、消息队列Queue:一个先进先出的消息存储区域。消息按照顺序发送接收,一旦消息被消费处理,该消息将从队列中删除。

  • 1、本地队列

本地队列按照功能可划分为初始化队列,传输队列,目标队列和死信队列

代码语言:javascript复制
    ★初始化队列:用作消息触发功能。
    
    ★传输队列:是暂存待传的消息,条件许可的情况下,通过管道将消息传送到其他的队列管理器。
    
    ★目标队列:是消息的目的地,可以长期存放消息。
    
    ★死信队列:如果消息不能送达目标队列,也不能再路由出去,则被自动放入死信队列保存。
  • 2、别名队列&远程队列

是一个队列定义,用来指定远端队列管理器的队列。使用了远程队列,程序就不需要知道目标队列的位置。

  • 3、模型队列

模型队列定义了一套本地队列的属性结合,一旦打开模型队列,队列管理器会按照这些属性动态地创建出一个本地队列。

5、主题Topic:主题,发布订阅模式下的消息统一汇集地,不同生产者向topic发送消息,由MQ服务器分发到不同的订阅者,实现消息的消费

6、消息体Message

三、消息模式

1、PTP点对点

特点:

  • 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
  • 发送者和接收者之间在时间上没有依赖性
  • 接收者在成功接收消息之后需向队列应答成功
  • 利用FIFO先进先出的特性,可以保证消息的顺序性。

终于,不用借大佬的图片,我来给大家画图

2、Pub/Sub发布订阅

特点:

  • 每个消息可以有多个消费者:和点对点方式不同,发布消息可以被所有订阅者消费
  • 发布者和订阅者之间有时间上的依赖性。
  • 针对某个主题(Topic)的订阅者,它必须创建一个订阅者之后,才能消费发布者的消息。
  • 为了消费消息,订阅者必须保持运行的状态。

通过这两张图片,我们应该能比较清晰的分辨出这两种模式的区别

三、常用协议

只要是通信之间,就会用到协议,就像我们说话的语言一样,不同的语言连通着不同的人群。 消息队列自然也是一样的也是一样,想要互相通信,就要使用同一种协议。

1、AMQP

AMQP即Advanced Message Queuing Protocol,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。AMQP是二进制协议,主要特征是面向消息、队列、路由(包括点对点和发布/订阅)

应用场景:当简单的发布-订阅模型不能满足使用要求。

优点:可靠、通用

2、STOMP

STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互,通常作用于发布-订阅的模型。

应用场景:信息交换基于文本,要求简单的场景。

优点:命令模式(非topicqueue模式)

3、XMPP

XMPP(可扩展消息处理现场协议,Extensible Messaging and Presence Protocol)是基于可扩展标记语言(XML)的协议,多用于即时消息(IM)以及在线现场探测。适用于服务器之间的准即时操作。核心是基于XML流传输,这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。但XML编码格式占用带宽大

应用场景:尽可能的简化客户端,复杂的都放在服务端。

优点:通用公开、兼容性强、可扩展、安全性高

4、JMS

JMS是基于JVM消息代理的规范,是对AMQP,MQTT,STOMP,XMPP等协议更高一层的抽象。JMS是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。

应用场景:在java体系中,多个服务可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差。

优点:异步、可靠

四、常用MQ对比分析

简然要做MQ选型,我们肯定要从大量的MQ做选择,什么样的MQ更适合我们的项目,因此,了解每一种MQ的特征和优缺点是必不可少的。

1、RocketMQ

RocketMQ是阿里系下开源的一款分布式、队列模型的消息中间件,原名Metaq,3.0版本名称改为RocketMQ,是阿里参照kafka设计思想使用java实现的一套MQ。同时将阿里系内部多款MQ产品(Notify、metaq)进行整合,只维护核心功能,去除了所有其他运行时依赖,保证核心功能最简化,在此基础上配合阿里上述其他开源产品实现不同场景下MQ的架构,目前主要多用于订单交易系统。

特点:

  • 基于队列模型:具有高性能、高可靠、高实时、分布式等特点;
  • Producer、Consumer、队列都支持分布式;
  • Producer向一些队列轮流发送消息,队列集合称为Topic。Consumer如果做广播消费,则一个Consumer实例消费这个Topic对应的所有队列,如果做集群消费,则多个Consumer实例平均消费这个Topic对应的队列集合;
  • 能够保证严格的消息顺序;
  • 提供丰富的消息拉取模式;
  • 高效的订阅者水平扩展能力;
  • 实时的消息订阅机制;
  • 亿级消息堆积 能力;
  • 较少的外部依赖。

优点:

  • RocketMQ的所有消息都是持久化的,先写入系统PAGECACHE,然后刷盘,可以保证内存与磁盘都有一份数据,而访问时,直接从内存读取;
  • 模型简单,接口易用(JMS的接口很多场合并不太实用);
  • 性能非常好,可以允许大量堆积消息在Broker中;
  • 支持多种消费模式,包括集群消费、广播消费等;
  • 各个环节分布式扩展设计,支持主从和高可用;
  • 开发度较活跃,版本更新很快。

缺点:

  • 支持的客户端语言不多,目前是Java及C ,其中C 还不成熟;
  • RocketMQ社区关注度及成熟度也不及前两者;
  • 没有Web管理界面,提供了一个 CLI (命令行界面) 管理工具带来查询、管理和诊断各种问题;
  • 没有在MQ核心里实现JMS等接口;

2、RabbitMQ

使用Erlang编写的一个开源的消息队列,本身支持很多的协议:AMQP,XMPP, SMTP,STOMP,也正是如此,使的它变的非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。多用于进行企业级的ESB整合。

特点:

  • 可靠性:提供了多种技术可以让你在性能和可靠性之间进行权衡。这些技术包括持久性机制、投递确认、发布者证实和高可用性机制;
  • 灵活的路由:消息在到达队列前是通过交换机进行路由的。RabbitMQ为典型的路由逻辑提供了多种内置交换机类型。如果你有更复杂的路由需求,可以将这些交换机组合起来使用,你甚至可以实现自己的交换机类型,并且当做RabbitMQ的插件来使用;
  • 消息集群:在相同局域网中的多个RabbitMQ服务器可以聚合在一起,作为一个独立的逻辑代理来使用;
  • 队列高可用:队列可以在集群中的机器上进行镜像,以确保在硬件问题下还保证消息安全;

支持多种协议:支持多种消息队列协议;

  • 支持多种语言:用Erlang语言编写,支持只要是你能想到的所有编程语言;
  • 管理界面:RabbitMQ有一个易用的用户界面,使得用户可以监控和管理消息Broker的许多方面;
  • 跟踪机制:如果消息异常,RabbitMQ 提供消息跟踪机制,使用者可以找出发生了什么;
  • 插件机制:提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。

优点:

  • 由于Erlang语言的特性,消息队列性能较好,支持高并发;
  • 健壮、稳定、易用、跨平台、支持多种语言、文档齐全;
  • 有消息确认机制和持久化机制,可靠性高;
  • 高度可定制的路由;
  • 管理界面较丰富,在互联网公司也有较大规模的应用,社区活跃度高

缺点:

  • 尽管结合 Erlang 语言本身的并发优势,性能较好,但是不利于做二次开发和维护;
  • 实现了代理架构,意味着消息在发送到客户端之前可以在中央节点上排队。此特性使得RabbitMQ易于使用和部署,但是使得其运行速度较慢,因为中央节点增加了延迟,消息封装后也比较大;需要学习比较复杂的接口和协议,学习和维护成本较高。

3、ActiveMQ

Apache下的一个子项目。使用Java完全支持JMS1.1和J2EE 1.4规范的 JMS Provider实现,少量代码就可以高效地实现高级应用场景。可插拔的传输协议支持,比如:in-VM, TCP, SSL, NIO, UDP, multicast, JGroups and JXTA transports。RabbitMQ、ZeroMQ、ActiveMQ均支持常用的多种语言客户端 C 、Java、.Net,、Python、 Php、 Ruby等。

特点:

  • 服从JMS规范:JMS 规范提供了良好的标准和保证,包括:同步 或 异步 的消息分发,一次和仅一次的消息分发,消息接收和订阅等等。遵从JMS规范的好处在于,不论使用什么JMS实现提供者,这些基础特性都是可用的;
  • 连接灵活性:ActiveMQ提供了广泛的连接协议,支持的协议有:HTTP/S,IP多播,SSL,TCP,UDP等等。对众多协议的支持让ActiveMQ拥有了很好的灵活性;

支持的协议种类多:OpenWire、STOMP、REST、XMPP、AMQP;

  • 持久化插件和安全插件:ActiveMQ提供了多种持久化选择。而且,ActiveMQ的安全性也可以完全依据用户需求进行自定义鉴权和授权;
  • 支持的客户端语言种类多:除了Java之外,还有:C/C ,.NET,Perl,PHP,Python,Ruby;

代理集群:多个ActiveMQ代理可以组成一个集群来提供服务;

  • 异常简单的管理:ActiveMQ是以开发者思维被设计的。所以,它并不需要专门的管理员,因为它提供了简单又使用的管理特性。有很多中方法可以监控ActiveMQ不同层面的数据,包括使用在JConsole或者在ActiveMQ的WebConsole中使用JMX。通过处理JMX的告警消息,通过使用命令行脚本,甚至可以通过监控各种类型的日志。

优点:

  • 跨平台(JAVA编写与平台无关,ActiveMQ几乎可以运行在任何的JVM上);
  • 可以用JDBC:可以将数据持久化到数据库。虽然使用JDBC会降低ActiveMQ的性能,但是数据库一直都是开发人员最熟悉的存储介质;
  • 支持JMS规范:支持JMS规范提供的统一接口;
  • 支持自动重连和错误重试机制;
  • 有安全机制:支持基于shiro,jaas等多种安全配置机制,可以对Queue/Topic进行认证和授权;
  • 监控完善:拥有完善的监控,包括WebConsole,JMX,Shell命令行,Jolokia的RESTful API;
  • 界面友善:提供的WebConsole可以满足大部分情况,还有很多第三方的组件可以使用,比如hawtio;

缺点:

  • 社区活跃度不高;
  • 根据其他用户反馈,会出莫名其妙的问题,会丢失消息;
  • 不适合用于上千个队列的应用场景;

4、Kafka

Kafka起初是由LinkedIn公司采用Scala语言开发的一个分布式、多分区、多副本且基于zookeeper协调的分布式消息系统,现已捐献给Apache基金会。它是一种高吞吐量的分布式发布订阅消息系统,以可水平扩展和高吞吐率而被广泛使用。目前越来越多的开源分布式处理系统如Cloudera、Apache Storm、Spark、Flink等都支持与Kafka集成。

特点:

  • 快速持久化:可以在O(1)的系统开销下进行消息持久化;
  • 高吞吐:在一台普通的服务器上既可以达到10W/s的吞吐速率;
  • 完全的分布式系统:Broker、Producer和Consumer都原生自动支持分布式,自动实现负载均衡;

支持同步和异步复制两种高可用机制;

  • 支持数据批量发送和拉取;
  • 零拷贝技术(zero-copy):减少IO操作步骤,提高系统吞吐量;
  • 数据迁移、扩容对用户透明;
  • 无需停机即可扩展机器;
  • 其他特性:丰富的消息拉取模型、高效订阅者水平扩展、实时的消息订阅、亿级的消息堆积能力、定期删除机制;

优点:

  • 客户端语言丰富:支持Java、.Net、PHP、Ruby、Python、Go等多种语言;
  • 高性能:单机写入TPS约在100万条/秒,消息大小10个字节;
  • 提供完全分布式架构,并有replica机制,拥有较高的可用性和可靠性,理论上支持消息无限堆积;
  • 支持批量操作;
  • 消费者采用Pull方式获取消息。消息有序,通过控制能够保证所有消息被消费且仅被消费一次;
  • 有优秀的第三方KafkaWeb管理界面Kafka-Manager;
  • 在日志领域比较成熟,被多家公司和多个开源项目使用。

缺点:

  • Kafka单机超过64个队列/分区时,Load时会发生明显的飙高现象。队列越多,负载越高,发送消息响应时间变长;
  • 使用短轮询方式,实时性取决于轮询间隔时间;
  • 消费失败不支持重试;
  • 支持消息顺序,但是一台代理宕机后,就会产生消息乱序;
  • 社区更新较慢。

5、四大MQ对比

这张图片相比很多同学都看过,那我就放出来再给大家看一下:

因为公司早就做好了MQ的选型,加之对业务场景的判断,最后我还是随大流的选择了RabbitMQ。

从社区活跃度来说:RabbitMQ应该是首选,国内的话有阿里的推广,RocketMQ也慢慢被越来越多的公司所使用。

从技术的实现上来说:公司早就搭建好了完整的Kafka和RabbitMQ的集群,只要不是特殊情况,最后还是需要在这两者之间选择。同时从可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统等方面来看,RabbitMQ也称得上是上佳的选择。

从并发量来说:RabbitmqMQ和RocketMQ都是好的选择。

另外,Kafka 的定位主要在日志等方面, 因为Kafka设计的初衷就是处理日志的,可以看做是一个日志(消息)系统一个重要组件,针对性很强,所以 如果业务方面还是建议选择 RabbitMq 。

还有就是,Kafka 的性能(吞吐量、TPS )比RabbitMq 要高出来很多。

结语

因为实际工作的时候讨论到了MQ,想了想在研究MQ的时候就顺便把文章整理出来了。因为公司早早搭建好了集群,也没必要我自己去搭建。但是,研究了一段时候后心里却有一点小想法,下一章会讲一讲MQ具体的内容吧,包括MQ的集群、MQ实现顺序消息、实现ACK之类的,不会像这章一样多是说特点了。 同时需要思维导图的话,可以联系我,毕竟知识越分享越香!

0 人点赞