JAVA后端知识点碎片化整理 基础篇(五) 消息队列

因为马上开始2019秋招、平时的学习比较琐碎、JAVA后端博大精深,想在暑假这段时间从头开始整理JAVA知识点查缺补漏,迎战2019秋招。主要参考(微信公众号)JAVA团长与(博客园)五月的仓颉的知识点复习线,对其列出的每一个的知识点再一次的咀嚼并谈谈自己的理解。(平时从这两位学到很多,也非常感谢身边同行的人)补充一下:其中知识点的讲解参考了之前看过的博客讲解或者书籍,之所以称之为基础篇,主要是加深对Java技术栈的宏观认识。

自己对消息队列的不太熟悉,更多的是对消息队列的收集与整理,再理解消息队列的设计。

1、JMS:JAVA消息服务(JAVA Message Service)应用程序接口,是一个JAVA平台关于面向消息中间件的API用于两个应用程序之间,或者分布式系统中发送消息,进行异步通信,JAVA消息服务是一个与具体平台无关,说白了就是两个应用程序直接的解耦。主要优势:异步Asynchronours JMS原本就是一个异步的消息服务,客户端获取消息的时候不需要主动发送消息,消息会自动发给可用的客户端。 可靠Reliable 可靠,JMS保证消息智慧传递一次,JMS帮你避免重复创建发送消息。

JMS两种通信模式:Point-to-point 点对点、Publish/Subscribe Message Domain(发布/订阅模式) 

(1)Point-to-point  就是发送方和接收方通过消息队列,进行通信,每个消息都被发送到一个特定的队列,接受者从队列中获取消息,队列中保留着消息,知道被消费或者超时。发送方不管队列中是否有信息或者没有信息,当发送完成就可以了。接受者也不用管发送者是否发送,只需要去队列中寻找(发送者与接受者都没有时间上的约束)。当接收方接受完消息需要一个应答机制告诉queue这个消息已经完成接受。(点对点的模式支持多个消费者,但是对于同一个消息,只能由一个消费者去消费,当没有消费者用于这个时,这个消息会被保存知道有一个可用的消费者去消费他

(2)Publish/subscribe发布/订阅通信模式  在发布一个消息后,消息通过中间Topic传递给所有客户端,该模式发布者与订阅者都是匿名的,即发布者与订阅者都不知道对方是谁。并且可以动态的发布和订阅Topic,之间Topic只要是用来保存消息知道消息被传递给客户端。这种模式一个消息可以有多个订阅者,一个消息可以有多个接受方,且发布者与订阅者有时间上的约束,他必须创建一个订阅者后,才能消费发布者的消息,而且为了消费消息,订阅者必须保持运行的状态。(发布到topic上的消息会被所有的订阅者所消费。是1-N个订阅者都能够得到这个消息)发布订阅模式是当发布者消息量很大的时候,显然单个订阅者处理能力是不足的。实际上显示场景中多个订阅者节点组成一个订阅组负载均衡消费topic消息即分组订阅,这样的订阅者模式实现了消费能力的线性扩展,可以看成一个topic下有多个Queue,每个Queue是点对点的方式,Queue之间是发布订阅方式。

支持订阅组的发布订阅模式:发布订阅模式下,当发布者消息量很大,显然单个订阅者的处理能力不足。实际上现实场景中是多个订阅者节点组成负载均衡消费topic消息即分组订阅,这样订阅者很容易实现消费能力的线性扩展。可以是多个topic下有多个Queue,每个Queue是点对点的方式,Queue之间是发布订阅模式。

传统的ActiveMQ遵循了JMS规范,实现了点对点的发布订阅模型,但其他流行了消息队列RabbitMQ和Kafka并没有遵循JMS规范。RabbitMQ实现了AQMP(Advanced Message Queuing Protocol,高级消息队列协议AQMP协议定义了路由规则的方式,生产端通过路由规则发送不到的Queue,消费端根据queue名称消费消息。

RabbitMQ既支持内存队列也支持持久化队列,消费端为推模型,消费状态和订阅关系由服务端负责维护,消息消费完后立即删除,不保留历史消息。(rabbit点对点与上面相同,多订阅时,发布者发送消息通过路由写到多个Queue,不同订阅组消费不同的Queue,所以支持多订阅,消息会多个拷贝,起源于金融系统,他的高可靠性,可用性与扩展性得到人们认可)

Kafka支持消息持久化,消费端为拉模型,消费状态和订阅关系由客户端负责维护,消息消费完后不会立即删除,会保留历史消息。因此支持多订阅时,消息只会存储一份,但是这样可能会出现重复消费的情况。(scala语言编写,它是一种高吞吐量分布式发布订阅系统,一水平扩展性能好和高吞吐率而被广泛使用)

消息队列推拉两种模式对比:拉模式如果没有消费者在队列监听,那么消息就保留在队列中,直至消费者连接如队列。消息不会直接推送给消费者,而是由消费者在队列中请求活动。优点:保证每条消息都被接受,消息不会丢失。推模式:消息会自动广播,消息消费者无需主动请求或轮询主题的方法来获取新消息。对比1不保证每条消息都被消费,2、发布消息时,只有正在监听该topic的能修改,如果没人监听,则消息丢失。

2、消息队列应用场景:(网络版本讲解)

(1)异步处理,场景说明用户注册后,需要发注册邮件和注册信息(三个业务节点分别为注册信息写入数据库,发送信息、发送邮件),通常我们有串行方式:将注册信息写入数据库,发送注册邮件,在发送注册短信。以上三个任务全部完成后再返回给客户端。(2)并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以减少处理的时间。           而引入消息队列后,用户响应的时间就是注册信息写入数据库就是50毫秒,注册邮件发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以直接忽略。

(2)应用解耦:用户下单后,订单系统需要通知库存系统,传统的做法是,订单系统通过调用库存系统的接口。传统模式的缺点:假如库存系统无法访问,则订单减库存失败,从而导致订单失败;订单系统与库存系统耦合。  传统模式的缺点:加入库存系统无法访问,则订单减库存将失败,从而导致订单失败;订单系统与库存系统耦合;              通过消息队列就不在关心其他的后序操作,只要订单系统完成处理后,将消息写入消息队列即可,实现了订单系统和库存系统的应用解耦。

(3)流量削峰:秒杀活动,一般因为流量过大,大致流量暴增。为了解决这个问题,一般需要在应用前端加入消息队列。       当用户的请求,服务器接收后,首先写入消息队列。加入消息队列长度超过最大数量,则直接抛弃用户请求或跳转到错误页面。秒杀业务根据接收到的请求消息,再进行处理。

(4)日志处理:日志处理时指将消息队列用在日志如理中,比如kafka解决大量日志传输。 日志采集客户端负责日志采集,定时写受写入kafka队列,kafka消息队列负责日志数据的接收存储和转发,日志处理应用,订阅并消费kafka队列中的日志数据。

(5)消息通讯:消息通讯是指,消息队列一般都内置了高效的通信机制,因此也可以用在纯的消息通讯。比如实现点对点的队列。

3、消息的幂等性

MQ消息的可靠性分为(1)消息落地(2)消息超时重传与确认,再次回顾消息总线的核心架构,他由发送端、服务端、固化存储、接受端四个部分组成。主要是为了避免收到重复的消息。举例说明:购买会员卡,上游支付系统负责给用户扣款,下游系统负责给用户发卡,通过MQ异步通知。不管是上游的ACK通知丢失导致MQ收到重复消息,还是下游ACK丢失导致,导致购卡系统收到重复的购卡通知,都可能出现,上游扣了一次钱,下游发了多张卡。

上半场的幂等性设计:MQ-client将消息发给服务端MQ-server/服务端MQ-server将消息落地/服务端将MQ-server回ACK给发送端MQ-client。如果3丢失,那么发送端MQ-client回ACK给发送端的MQ-client。此时重发是由MQclient发起的,为了避免步骤2的重复,每个消息都在mq内部系统生成一个独立的id,作为去重和幂等的依据。这个ID(1)全局唯一(2)MQ服务端生成,具备业务无关性,对消息发送方与消息接收方屏蔽。(保证上半场不会重复发送消息)

下半场的幂等性设计:MQ消息发送下半场,服务端MQ-Server将消息发送给接受端MQ-client,接受端再将MQ-client回ACK给服务端,服务端MQserver将落地消息删除。 需要强调的是MQ-client回ACK给服务端MQ-server,是消息消费业务方的主动调用行为,不能由MQ-client自动发起,因为MQ系统不知道消费方什么时候真正消费成功。 如果5丢失,MQ-server超时后重发消息,可能导致MQ-client收到重复消息。重发是MQ-server发起的,消息的处理是消费业务方,消息重发势必导致业务方法的重复消费,为了保证业务的幂等性,业务消息中,必须有一个id作为消费的唯一标志,(1)对于同一个业务场景,全局唯一(2)由业务消息发送方生成,业务相关,对MQ透明(3)由业务消息消费方判重,保证幂等。有了这个ID保证了下游消息消费服务即使收到重复消息,也只有一条消息被消费,保证了幂等。(保证下半场获取到重复消息时只接受一个)

4、如何设计一个消息队列    

一般来说设计消息队列的整体思路是先build一个整体的数据流,如producer发给broker,broker发送给consumer,consumer回复消费确认,broker删除/备份消息。

利用RPC将数据流串起来,然后考虑RPC的高可用性,尽量做到无状态方便水平扩展。之后考虑如何承载消息堆积,在合适的实际投递消息,而处理堆积的最佳方式就是存储,存储的选型考虑性能和可靠性和开发维护成本等诸多因素。为了实现广播功能,有需要维护消费关系,可以利用zk/config server保存消费关系。

rpc通信协议:刚才提到的所有的消息队列,无外乎是两次RPC加上一次转存,当然需要消费端最终做消费确认的话就是3次rpc。既然是rpc,就必然牵扯一系列的话题,比如负载均衡、服务发现(zk)、通信协议、序列化协议(netty等)利用好公司现有的RPC框架thrift也好dubbo也好,因为消息队列的rpc与普通rpc并无本质区别。简单来说就是服务端提供两个rpc服务,一个用来接收消息,一个用来确认消息,并且不管哪个server收到消息和确认消息,其结果一致即可。

高可用:依赖于RPC和存储的高可用来做的,先看RPC的高可用性,常见的dubboMTThrift的RPC框架,其本身就具有服务发现、负载均衡等功能,而消息队列的高可用性,只要保证broker接受消息和确认消息的几口是幂等,并且consumer的几台机器处理消息也是幂等,这样就把消息队列的可用性交给RPC框架来处理。保证幂等最简单的办法做一个共享存储,broker多个机器共享一个db或者kv文件系统,则处理消息自然是幂等的,就算是单点故障,其他节点也可以立刻顶上,另外failover可以依赖定时任务补偿,这个消息队列本身天然就可以支持的功能,存储系统本身的可用性,不用我们过多操心。

服务端承载消息堆积的能力:消息达到服务端如果不经过任何处理就到接受者,broker就失去了它的意义,为了满足我们错峰/流控/最终可达等一系列需求,把消息存储下来,然后选择时机投递就显得顺利成章。知识这个存储可以有很多个方式,比如存储到内存里,分布式kv里,磁盘、数据库都可以。但是主要分为两种,持久化和非持久化两种,持久化的形式更大程度保证消息的可靠性并且理论上承载更大限度的消息堆积。但不是每种消息都需要持久化存储,很多消息队列对投递性能的要求大于可靠性要求,例如数量极大的日志,这个时候回采用直接投递。

存储子系统的选择,我们如果从需要数据落地的情况来看,存储子系统的选择就尤为关键。从速度上来看文件系统-》分布式KV-》分布式文件系统-》数据库,而从可靠性来看截然相反。分布式KV(MongoDB,Hbase)或者持久化Redis,由于其接口编程较为友好,性能也比较可观,如果可靠性要求不那么高的场景,也不失为一个不错的选择。

消费关系的解析:需要发送接收关系,进行正确的消息的投递。实质上来说无外乎单播与广播的区别,所谓单播就是点到点,所谓广播就是一点到多点。当然就是对于互联网大部分应用来说,组间广播,组内广播都是常见的情形。消息通知到多个业务集群,而一个业务集群内有很多台机器,只要一台机器消费这个消息就可以了。关于广播关系维护一般是由zk完成,维护广播关系所要做的事情就是一致的:发送关系的维护,发送关系变更时的通知。

5、rabbitMQ(手敲自https://www.jianshu.com/p/79ca08116d57)是由Erlang语言开发,是一门面向高并发高扩展的的实时性的语言,起源于金融系统,主要特点就是在分布式系统中存储转发消息,在易扩展、扩展性、高可用性等方面表现不俗。

RabbitMQ中概念模型:

消息模型:所有mq产品从模型抽象上来说都是一样的过程,消费者订阅某个队列,生产者创建消息,然后发布到队列中,最后将消息发送到监听的消费者。

RabbitMQ基本概念,上面只是最简单抽象的描述,具体的RabbitMQ则有更详细的概念需要解释,上面介绍过RabbitMQ内部实际上是AMQP协议的一个开源实现。


1、Message 消息,消息是由消息头与消息体组成,消息是不透明的,而消息头则是一系列的可选属性组成,这些属性包括了routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(之处消息可能需要持久性存储)等

2、publisher消息的生产者,也是一个向交换器发布消息的客户端应用程序。

3、Exchange交换器,用来接收生产者发送消息并将这些消息发布到客户端应用程序。

4、binding绑定,用于消息队列和交换器之间的关联,一个绑定就是基于路由键值将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个绑定构成的路由表。

5、Queue消息队列,用来保存消息知道发送给消费者,他是消息的容器,也是消息的终点。一个消息可以投入一个或者多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

6、Connection网络连接,比如一个TCP连接。

7、Channel信道,多路复用连接中的一条独立的双向的数据通道,信道建立是在真实的TCP连接内的的,AMQP无论是发布消息还是接收消息,这些动作都是通过信道去完成的。因为对于操作系统来说建立和销毁TCP都是非常昂贵的事情,所以引入了信道的概念,以复用一条TCP连接。

8、Consumer消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

9、虚拟主机,表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证和加密黄金的独立服务器域,每个vhost就是一个相对对立的RabbitMQ,拥有自己队列,交换器、绑定和权限机智。vhost是AMQP概念的基础,必须在连接时指定。

10、Broker表示消息队列服务器的实体。

与JMS存在一些差别,AMQP中消息的路由过程和JAVA开发者熟悉的JMS存在一些差别,AMQP中增加了Exchange和Binding的角色。生产者吧消息发布到Exchange上,消息最终达到队列并被消费者接受,而Binding决定着交换器的消息该被发送哪个队列。

Exchange类型:分发消息时根据类型的不同分发策略有何区别,目前共四种类型driect,fanout,topic,headers。headers匹配AMQP消息的header而不是路由键,此外headers交换器和direct交互器完全一致,但是性能差很多,目前用不到了。

1、direct


direct交换器:消息中的路由键(routing key)如果Binding中binding key一致,交互器就将消息发送到对应的队列中。路由键routing key与队列名称完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发routing key标记为“dog”的消息,不会转发“dog.puppy”,也不会转发"dog.guard"等等,他是完全匹配的单播的模式。

2、fanout


fanout交换器:每个发到fanout类型交换器的消息都会被分到所有绑定的队列上去,fanout交换器不处理路由键,知识简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到该交换器绑定的所有队列上。像子网广播,每台子网内的主机都获得了一份复制的消息。fanout类型转发消息是最快的。

3、topic


topic交换器:topic交换器通过模式匹配分配消息的路由键属性,将路由键的某个 模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成很多单词,这些单词用.之间隔开。他同样能识别两个通配符,符号“#”和“ ”。#匹配0到多个单词,匹配不多不少一个单词。

猜你喜欢

转载自blog.csdn.net/weixin_39893439/article/details/80946181
今日推荐