消息队列本质上来讲是一个符合先进先出原则的单向队列:一方发送消息并存入消息队列尾部(生产者投递消息),一方从消息队列的头部取出消息(消费者消费消息)。但对于一个成熟可靠的消息队列来讲,所须要解决的主要问题还包括:高效可靠的消息投递、存储;能承受高并发的流量冲击,可经过集群部署来解决单点故障等等。java
因为消息队列具有了以上特色,所以在现在的微服务架构中可以做为一种中间件,提供许多重要的功能以解决微服务架构中的诸多痛点:linux
微服务架构中,存在着众多子系统,共同完成对外部用户的服务。shell
举个例子:当用户在订单系统下单时,订单子系统除了须要执行本身系统的业务逻辑以外,可能还须要调用库存子系统去扣减库存;调用会员子系统去增长用户的积分;调用数据分析子系统去插入用户下单的分析数据等等。用户的一个下单行为横跨了N个业务子系统,若是按照传统的同步串行方式一个接一个的调用,用户的下单操做将会执行较长的时间,对用户不友好。同时,因为是同步调用,一旦某一个子系统出现了宕机,访问超时等问题,整个下单业务都将陷入瘫痪。apache
消息队列能够将同步的系统调用转为异步的消息投递,必定程度上解除业务子系统间的耦合。当订单子系统执行完本地逻辑后,只需发送一个标识下单成功的消息,让下游依赖的子系统订阅此消息,消费处理消息来完成对应的业务。这样,用户的下单操做将很快完成,也没必要担忧下游子系统的故障会波及到订单系统。windows
虽然消息队列解除了业务子系统间的耦合,但同时也让业务子系统对消息队列系统有了很强的依赖关系,若是消息队列出现了故障,业务系统将会出现严重故障。缓存
但因为消息队列在设计之初的目的十分简单明确:就是为了可靠的收发消息。所以其可用性,稳定性比绝大多数业务系统要高的多。天下没有免费的午饭,在微服务系统中引入消息队列依然是利大于弊的。服务器
大多数系统的访问流量并非一天24小时均匀稳定的,而是存在着必定的突发性。例如电商的秒杀活动,系统配置在平时能承受住500qps,可在进行秒杀活动时,瞬时的qps可能达到了5000,为日常的10倍,若是不进行处理防御,将会致使服务瘫痪。架构
能够选择扩容服务器来应对可能的高峰流量,但扩容的服务器在秒杀活动过去以后多数会被闲置,从而形成很大的浪费;也能够设定并发的阈值,在访问并发数达到必定程度时就进行熔断限流,拒绝手慢的秒杀用户下单,可这样会让用户体验不好。并发
这时,消息队列就能派上用场了。咱们能够在系统中使用消息队列做为缓冲,将每个用户下单请求都做为一条消息存入消息队列,消息队列会根据消费者的消费速度以一种稳定的方式将流量传递给下游消费者系统,在消费者系统处理完下单操做后异步的通知用户下单结果。虽然用户可能会延迟一段时间才能获得反馈,但不管如何也比没法下单要好。负载均衡
消息队列就像一个漏桶,能够将瞬时的尖峰流量缓存起来,并以一种稳定的速度传递给下游消费者,从而达到流量削峰的目的。
沿用以前的例子,订单子系统的下单成功操做在业务上可能有许多其它系统须要对其作出响应(扣库存,加积分,核销优惠券等等)。
按照传统的方式,须要订单系统挨个调用其它子系统的接口。随着业务的变化,每当有新的子系统须要对下单成功操做作出响应时,就须要改动订单系统的代码逻辑去适应新的需求。
而若是引入了消息队列,则能够在下单成功以后由订单系统发送一条消息,让感兴趣的其它子系统去订阅下单成功消息。若是新的系统也出现了依赖下单成功动做的需求,自行订阅对应消息便可,并不须要订单系统作出任何的改变。
能够利用消息分发机制能够实现代码逻辑的解耦。
rocketmq是阿里巴巴团队使用java语言开发的一款分布式消息中间件,是一款低延迟,高可用,拥有海量消息堆积能力和灵活拓展性的消息队列。
rocketmq由四大核心模块组成:producer、consumer、brokerServer、nameServer。其中brokerServer和nameServer是rocketmq的服务端,二者一块儿独立的对外提供服务;而producer和consumer可看作是rocketmq的客户端,通常依附于业务应用程序。
producer负责发送消息。使用producer将消息发送到brokerServer,由brokerServer统一进行消息的分发。
rocketmq支持多种消息发送方式,如同步消息发送、异步回调消息发送、顺序消息发送以及单向消息发送(异步无回调)。除了单向消息发送,其他的发送方式均须要brokerServer返回发送结果的确认消息。
特别的,rocketmq的一大特点是支持发送事务消息(半消息),能必定程度上解决分布式事务的问题。
consumer 负责消费producer发送的消息。consumer会从brokerServer获取消息,并传递给应用程序。
rocketMQ使用的消息原语是At Least Once(至少一次成功消费),若是必定时间内没有接收到consumer消息确认消费的响应结果,会将同一条消息再次投递给consumer。rocketmq采用ack机制保证消息的消费成功,因此consumer可能会屡次收到同一条消息,须要consumer的业务方作好幂等防御。
从使用者的角度来看,consumer分为两种方式来获取信息。一种是推模式(push consume),推模式看起来像是brokerServer将消息推给了consumer;另外一种是拉模式(pull consume),拉模式看起来像是consumer主动的去brokerServer拉取消息(实际上,推模式是基于拉模式实现的)。
brokerServer负责消息的接收,存储和分发,是rocketmq最核心,最重量级的组成部分。
为实现高可用和高吞吐,brokerServer一般采用集群部署,共同对外提供服务。
nameServer负责提供路由元数据。例如,brokerServer一般是集群部署的,其拓扑结构会常常的发生变化。若是每次集群中broker机器的上下线都须要通知全部的消费者、生产者,效率过低。
所以,rocketmq引入了nameServer做为brokerServer路由信息的维护者,broker的每次上下线都和nameServer通讯,由nameServer来维护broker的路由信息,而producer和consumer经过访问nameServer得到对应broker的访问地址后,再向对应的broker发起请求。nameServer解除了broker和客户端的耦合依赖关系,大大提升了效率。
在其它主流消息队列中也存在着相似的维护元信息功能的组件,如zookeeper等。rocketmq的设计者认为zk的功能过于强大,杀鸡焉用牛刀,经过一个精简版的元数据服务nameServer,以减小对外部系统的耦合依赖,得以提供更可靠的服务。
nameServer一样能以集群形式对外提供服务。但和zk集群不一样的是,集群内的nameServer服务器并不会互相通讯,而是保持相互独立。
介绍完rocketmq的组成部分以后,还须要再引入一些相关概念才能更好的理解rocketmq:
topic主题,表明一系列消息的集合,任何消息只能属于一个topic主题,主题是rocketmq进行消息发布订阅的最小单位。业务方能够经过建立并订阅各式各样的主题来知足自身的业务要求。不一样主题之间的消息在逻辑上没有关联。
tag标签,tag从属于topic主题,主要用于对同一主题下的消息进行进一步区分。标签能够简单的认为是二级主题,经过tag标签功能,业务方能够方便的实现对各类二级主题的消费需求。
group组,表明着同一类客户端的集合。具体可分为消费者组(consumer group)和生产者组(producer group)两种。消费者组和生产者组之间没有任何关联(即便组名同样)。
消费者组:
消费者组表明着同一类型的消费者集群。同一消费者组内的消费者一般消费一样的消息且消息消费逻辑一致。消费者组的概念使得consumer集群在消费消息时,rocketmq能够经过负载均衡来作到消费消息时的高可用和容错。消费者组的更多做用将会在后面的集群/广播消费模式中继续讲解。
生产者组:
生产者组表明着同一类型的生产者集群。通常来讲,消息的生产者在发出了消息获得确认以后便完成了任务,彷佛没有必要为此抽象出生产者集群的概念。
前面说到,rocketmq具备发送事务消息的特性,发送事务消息简单来讲就是生产者先发送出一个半消息(预消息),而后执行本地的事务,在事务完成提交以后再跟着发送一个事务确认消息。半消息和普通消息的最大区别在于,半消息在投递给broker以后,broker不会立刻让消费者进行消费,而是等待。只有当接收到生产者后续对应的的事务确认消息后,预消息和确认消息合二为一,才将对应的事务消息交给消费者去消费;而若是最终没有接收到事务确认消息,则会将消息直接删除不投递给消费者,以达到相似事务回滚的效果。事务消息对消费者来讲是透明无感知的。
可若是生产者在发送了预消息以后挂了怎么办?为解决这个问题,broker会在必定时间没有收到确认消息后,定时的回查生产者当前事务消息的状态,回查的范围是整个生产者组中的某一个在线节点。这种状况下,生产者和消费者同样,也构成了一个集群监听来自broker的回查。这样,即便发送消息的生产者发生了故障,在必定条件下整个生产者集群的事务消息发送功能依然能够正常运转。
经过生产者组的概念,rocketmq实现了事务消息投递的高可用。
message消息是rocketmq中传递消息的主体,消息具备全局惟一的messageID属性,用户能够根据messageID查询进行消息的精确查询。
消息的内容能够是不超过rocketmq限制的、二进制的任意数据,rocketmq不会对消息承载的数据内容作任何干预。
集群消费:
对于任意一条被订阅的消息,同一消费者组下的节点只有一个节点对其进行消费;一个消费者组中的所有节点分摊全部消息。
广播消费:
对于任意一条被订阅的消息,同一消费者组下的全部节点都会对其进行消费;一个消费者组中的所有节点都能接收到全量的消息。
混合模式消费:
实质上是前二者的综合。同一应用集群构成一个消费者组,不一样应用集群之间构成多个不一样的消费者组,但却能够订阅同一个topic/tag下的消息。
对于任意一条被订阅的消息,同一消费者组之间只会有一个节点对其进行消费,不一样消费者组都会进行全量消息的消费。
介绍了rocketmq的一些基本概念以后,下面进行rocketmq的下载和安装,并进行基本的功能测试。简单起见,nameServer,broker都以单机模式启动。
注意:示例中新版本的rocketmq要求jvm的最低版本是1.8。
首先在rocketmq的官网能够找到下载资源,其中有已经编译完成的二进制资源(binary)和须要用户本身编译的源代码资源(source)两种。在这里选择下载已经编译完成的,更容易上手的二进制资源进行安装。
将下载好的资源解压缩到任意目录,能够看到以下文件夹和文件,其中命令行的脚本文件都集中放在bin文件夹下。(这里是windows环境下的操做,资源包中也包含了linux下一样功能的shell脚本文件,操做并无明显差别)
因为脚本文件依赖一个叫作ROCKETMQ_HOME的环境变量,代指rocketmq安装的主目录,所以咱们须要配置ROCKETMQ_HOME环境变量。
开启一个新的命令行窗口用于启动nameServer,将命令行路径指向bin文件目录,后续新开启的命令行窗口须要作一样的操做(也能够选择配置path路径,一劳永逸)。
执行"mqnamesrv.cmd",看到以下图的日志信息表明着nameServer已经启动成功。保持nameServer窗口开启,不要关闭。nameServer默认的启动端口是9876。
rocketmq 4.4.0版本的默认配置文件内存设置的比较大,若是启动时出现了jvm内存不足之类的错误,能够打开runserver.cmd对其进行编辑,将默认的jvm内存分配参数设置的小一点。
开启一个新的命令行窗口,执行"mqbroker.cmd -n localhost:9876",用于启动broker。前面提到,nameServer做为维护路由元数据的中心,broker会在启动时会先在nameServer进行注册,使得生产者和消费者可以及时得到broker相关的信息。命令后面的-n localhost:9876参数就是用于指定对应nameServer的地址。
当看到以下图所示日志信息时,说明broker已经启动完成。保持broker窗口开启,不要关闭。
和nameServer启动同样,若是出现了内存不足的问题,能够修改runbroker.cmd中的jvm启动参数以符合要求。
当nameServer和broker都启动完成后,rocketmq的服务端就已经能够对外提供服务了。
rocketmq的开发人员在rocketmq中添加了简单的demo消息收发测试程序,咱们能够经过tools.cmd调用来进行测试(和前面同样,其启动时的jvm参数直接在tools.cmd中修改便可)。
首先开启一个新的命令行窗口用于启动consumer,先执行"set NAMESRV_ADDR=localhost:9876"设定命令行窗口级别的环境变量,而后执行"tools.cmd org.apache.rocketmq.example.quickstart.Consumer",看到以下图所示提示信息时,表明consumer已经启动成功。保持窗口开启状态,此时consumer正在监听对应的消息,等待消费。
再开启一个新的命令行窗口用于启动producer,依然先执行"set NAMESRV_ADDR=localhost:9876",设定命令行窗口级别的环境变量,为生产者指定nameServer的地址。
接着执行"tools.cmd org.apache.rocketmq.example.quickstart.Producer",如无心外,会看到发送消息的刷屏日志,producer在一瞬间就发送了N条普通消息(1000条);
与此同时,consumer也接收到消息,并在控制台中打印出了消息消费日志。
至此,rocketmq的安装与基本功能的简单测试宣告完成。
这是"rocketmq学习"系列的第一篇博客,因此先以rockemq概念的介绍和安装入手。后续的博客内容将会有诸如rocketmq集群部署、图形化控制台安装等,并结合rocketmq的源码进一步理解rocketmq的工做原理。
写"rocketmq学习"系列博客的主要目的仍是为了巩固并加深本身对rocketmq的理解。由于对于一些知识点只有在写做的过程当中才会发现对其了解的并不透彻,经过写博客能够很好的查漏补缺。
若有理解不到位的地方,欢迎指正。