消息队列 -- -- 入门

目录

消息队列 -- -- 入门 

消息队列(Message Queue 简称 MQ)简介

科普:

 把数据放到消息队列叫做生产者

 从消息队列里边取数据叫做消费者

消息中间件

1.消息中间件的简介

2.消息中间件的组成

3.消息中间件模式分类

3.1 点对点(P2P):使用queue作为通信载体 

应用场景:如果希望发送的每个消息都会被成功处理的话,那么需要P2P模式。

3.2 发布/订阅(Pub/Sub 广播):使用topic作为通信载体 

应用场景:如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub/Sub模型。

4.为什么要使用消息中间件?

 一般互联网项目用户请求不超过200ms体验是最好的

          还是举个简单的例子:

1.解耦

2.提速

3.广播

4.削峰

5.消息中间件的优势?

6.消息中间件应用场景

7.消息中间件常用协议

8.常见消息中间件MQ介绍

Kafka一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。

9.消息队列相关技术要点

9.1如何保证消息队列是高可用的?

以rcoketMQ为例,

以Kafka为例,

9.2如何保证消息不被重复消费?

9.3如何保证消费的可靠性传输?

RabbitMQ

kafka

9.4如何保证消息的顺序性?

1)rabbitmq保证数据的顺序性

2)kafka保证数据的顺序性


                                 消息队列 -- -- 入门 



消息队列(Message Queue 简称 MQ)简介

消息队列(又称 报文队列):指在消息的传输过程中用来保存消息的容器站在我们专业的角度上来讲就是:我们要把传输在计算机间的数据单位,用队列的存储方式保存起来。

消息(Message):指在一组(两台)计算机间传送的数据单位。  消息可以非常简单,例如只包含文本字符串;也可以更复杂,可能包含嵌入对象.

队列(Queue):队列虽然和栈(stack)一样,是一种操作受限制的线性表,但是队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,所以只有最早进入队列的元素才能最先从队列中删除,因此队列又被称为先进先出线性表。

FIFO(first in first out):新元素(等待进入队列的元素)总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取;每次读取一个元素,释放一个元素。


科普:

  •  把数据放到消息队列叫做生产者

  •  从消息队列里边取数据叫做消费者


消息中间件

  • 1.消息中间件的简介

目前对消息中间件(MOM)的定义还未形成统一的行业标准,我国也正加快对消息中间件技术的标准化研究工作。一般认为,消息中间件是一种由消息传送机制消息队列模式组成的消息中间件,利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。与其它消息中间件不同(例如ORB 和RPC),一般来说,消息中间件并不要求系统具备一个可靠的底部传输层,而是通过以消息的形式收发应用程序数据来连接运行于不同系统上的应用程序。信息可以同步传送,也支持异步传送。在异步方式下,应用程序并不需要消息即时即刻传送到对方,只是由MOM 确保把信息以消息的方式传送到适当的目的地,并且只传一次。

消息中间件属于中间件的一种,在Java中消息中间件一般指的是将具体业务和底层逻辑解耦的软件。拥有中间件的主要特点,但是自身的工作机制又具有特殊性,主要特点包括以下6 个方面:(1)异步传送;(2)防御通信;(3)并发执行;(4)日志通信;(5)多种通信方式;(6)应用程序与网络复杂性相隔离。

  • 2.消息中间件的组成

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

2.2 Producer:消息生产者,业务的发起方,负责生产消息传输给broker,

2.3 Consumer:消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理

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

2.5 Queue:队列,PTP模式下,特定生产者向特定queue发送消息,消费者订阅特定的queue完成指定消息的接收

2.6 Message:消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务数据,实现消息的传输



  • 3.消息中间件模式分类

3.1 点对点(P2P:使用queue作为通信载体 

它好比是两个人打电话,这两个人是独享这一条通信链路的。一方发送消息,另外一方接收,就这么简单。在点对点模式下,消息被保留在队列中。 一个或多个消费者可以消耗队列中的消息,但是特定消息只能由最多一个消费者消费。 一旦消费者读取队列中的消息,它就从该队列中消失。 该模式的典型示例,如订单处理系统,其中每个订单将由一个订单处理器处理,但多个订单处理器也可以同时工作。

总的来说:queue实现了负载均衡,将producer生产的消息发送到消息队列中,由多个消费者消费。但一个消息只能被一个消费者接受,当没有消费者可用时,这个消息会被保存直到有一个可用的消费者。 

  P2P模式

  P2P模式包含三个角色:消息队列(Queue),发送者(Sender),接收者(Receiver)。每个消息都被发送到一个特定的队列,接收者从队列中获取消息。队列保留着消息,直到他们被消费或超时。

  P2P的特点

  • 每个消息只有一个消费者(Consumer)(即一旦被消费,消息就不再在消息队列中)
  • 发送者和接收者之间在时间上没有依赖性,也就是说当发送者发送了消息之后,不管接收者有没有正在运行,它不会影响到消息被发送到队列
  • 接收者在成功接收消息之后需向队列应答成功

应用场景:如果希望发送的每个消息都会被成功处理的话,那么需要P2P模式。


3.2 发布/订阅Pub/Sub 广播):使用topic作为通信载体 

该模式有点类似于我们日常生活中订阅报纸。对于每一个订阅者来说,可以选择一份或者多份报纸。那么这些我们订阅的报纸,就相当于发布订阅模式里的topic。有很多个人订阅报纸,也有人可能和我订阅了相同的报纸。多人订阅了相同的报纸相当于多人在同一个topic里注册了。对于一份报纸发行方来说,它和所有的订阅者就构成了一个1对多的关系。在这种模式下,消息被保留在主题中。 与点对点模式不同,消费者可以订阅一个或多个主题并使用该主题中的所有消息。 该模式下消息生产者称为发布者,消息使用者称为订阅者。

总的来说:topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝。

  包含三个角色:主题(Topic),发布者(Publisher),订阅者(Subscriber) 。多个发布者将消息发送到Topic,系统将这些消息传递给多个订阅者。

  Pub/Sub的特点

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

应用场景:如果希望发送的消息可以不被做任何处理、或者只被一个消息者处理、或者可以被多个消费者处理的话,那么可以采用Pub/Sub模型。


消息中间件解决的就是分布式系统之间消息传递的问题。

                   

                    ①生产者和消费者之间通过某种方式(点对点或者订阅)实现"绑定"!

                    ②生产者生产数据,并发送到消息中间件,消息中间件进行落库处理!

                    ③订阅了消息的消费者通过监听器监听消息中间件,如果有属于自己的消息,进行消费!



  • 4.为什么要使用消息中间件?

回答这个问题个人建议可以从这三个关键点(或者说应用场景)去回答:解耦、异步、削峰。

个人认为消息队列的主要特点是异步处理,主要目的是减少请求响应时间和解耦。比方说,在高并发环境下,由于来不及同步处理,请求往往会发生堵塞,例如有大量的insert,update之类的请求同时到达MySql,将会直接导致无数的行锁表锁,甚至最后请求会堆积过多,从而触发too many connections错误。所以使用消息队列主要的使用场景就是将比较耗时而且不需要即时(同步)返回结果的操作作为消息放入消息队列,从而缓解系统的压力。由于使用了消息队列,我们只需要保证消息格式不变,消息的发送方和接收方并不需要彼此联系,也不需要受对方的影响,即解耦和。

解耦
异步

                一般互联网项目用户请求不超过200ms体验是最好的

削峰

还是举个简单的例子:-----(来自知乎 祁达方的经典回答

业务场景:翠花是小强的姐姐,翠花希望小强多读书,所有经常寻找好书给小强看。之前的方式(传统模式)是这样:翠花问小强什么时候有空,把书给小强送去,并亲眼监督小强读完书才走。久而久之,两人都觉得麻烦;后来的方式(中间件模式)改成了:翠花对小强说「我放到书架上的书你都要看」,然后翠花每次发现不错的书都放到书架上,小强则看到书架上有书就拿下来看。

书架就相当于我们所说的  一个消息队列(容器),而翠花是生产者,小强则是消费者。

这带来的好处有:

1.翠花想给小强书的时候,不必问小强什么时候有空,亲手把书交给他了,翠花只把书放到书架上就行了。这样翠花小强的时间都更自由。

2.翠花相信小强的读书自觉和读书能力,不必亲眼观察小强的读书过程,翠花只要做一个放书的动作,很节省时间。

3.当明天有另一个爱读书的小伙伴小强加入,翠花仍旧只需要把书放到书架上,小强和小强从书架上取书即可(唔,姑且设定成多个人取一本书可以每人取走一本吧,可能是拷贝电子书或复印,暂不考虑版权问题)。

4.书架上的书放在那里,小强阅读速度快就早点看完,阅读速度慢就晚点看完,没关系,比起翠花把书递给小强并监督小强读完的方式,小强的压力会小一些。

这就是我们消息队列的四大优点

1.解耦

每个成员不必受其他成员影响,可以更独立自主,只通过一个简单的容器来联系。

翠花甚至可以不知道从书架上取书的是谁,小强也可以不知道往书架上放书的人是谁,在他们眼里,都只有书架,没有对方。

毫无疑问,与一个简单的容器打交道,比与复杂的人打交道容易一万倍,翠花小强可以自由自在地追求各自的人生。

2.提速

翠花选择相信「把书放到书架上,别的我不问」,为自己节省了大量时间。

翠花很忙,只能抽出五分钟时间,但这时间足够把书放到书架上了。

3.广播

翠花只需要劳动一次,就可以让多个小伙伴有书可读,这大大地节省了她的时间,也让新的小伙伴的加入成本很低。

4.削峰

假设小强读书很慢,如果采用翠花每给一本书都监督小强读完的方式,小强有压力,翠花也不耐烦。

反正翠花给书的频率也不稳定,如果今明两天连给了五本,之后隔三个月才又给一本,那小强只要在三个月内从书架上陆续取走五本书读完就行了,压力就不那么大了。

当然,使用消息队列也有其成本:

1.引入复杂度

毫无疑问,「书架」这东西是多出来的,需要地方放它,还需要防盗。

2.暂时的不一致性

假如妈妈问翠花「小强最近读了什么书」,在以前的方式里,翠花因为亲眼监督小强读完书了,可以底气十足地告诉妈妈,但新的方式里,翠花回答妈妈之后会心想「小强应该会很快看完吧……」

这中间存在着一段「妈妈认为小强看了某书,而小强其实还没看」的时期,当然,小强最终的阅读状态与妈妈的认知会是一致的,这就是所谓的「最终一致性」。

那么,该使用消息队列的情况需要满足什么条件呢?

1.生产者不需要从消费者处获得反馈

引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明明下层的动作还没做,上层却当成动作做完了继续往后走——即所谓异步——成为了可能。

翠花放完书之后小强到底看了没有,翠花根本不问,她默认他是看了,否则就只能用原来的方法监督到看完了。

2.容许短暂的不一致性

妈妈可能会发现「有时候据说小强看了某书,但事实上他还没看」,只要妈妈满意于「反正他最后看了就行」,异步处理就没问题。

如果妈妈对这情况不能容忍,对翠花大发雷霆,翠花也就不敢用书架方式了。

3.确实是用了有效果

即解耦、提速、广播、削峰这些方面的收益,超过放置书架、监控书架这些成本。

否则如果是盲目照搬,「听说老赵家买了书架,咱们家也买一个」,买回来却没什么用,只是让步骤变多了,还不如直接把书递给对方呢,那就不对了。

所以回归现实:当我们在软件的正常功能开发中,并不需要去刻意的寻找消息队列的使用场景,而是当出现性能瓶颈时,去查看业务逻辑是否存在可以异步处理的耗时操作,如果存在的话便可以引入消息队列来解决。否则盲目的使用消息队列可能会增加维护和开发的成本却无法得到可观的性能提升,那就得不偿失了。



5.消息中间件的优势?

5.1 系统解耦

交互系统之间没有直接的调用关系,只是通过消息传输,故系统侵入性不强,耦合度低。

5.2 提高系统响应时间

例如原来的一套逻辑,完成支付可能涉及先修改订单状态、计算会员积分、通知物流配送几个逻辑才能完成;通过MQ架构设计,就可将紧急重要(需要立刻响应)的业务放到该调用方法中,响应要求不高的使用消息队列,放到MQ队列中,供消费者处理。

5.3 为大数据处理架构提供服务

通过消息作为整合,大数据的背景下,消息队列还与实时处理架构整合,为数据处理提供性能支持。

5.4 Java消息服务——JMS

Java消息服务(Java Message Service,JMS)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。 
JMS中的P2P和Pub/Sub消息模式:点对点(point to point, queue)与发布订阅(publish/subscribe,topic)最初是由JMS定义的。这两种模式主要区别或解决的问题就是发送到队列的消息能否重复消费(多订阅)。



  • 6.消息中间件应用场景

6.1 异步通信

有些业务不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。

6.2 解耦

降低工程间的强依赖程度,针对异构系统进行适配。在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。通过消息系统在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口,当应用发生变化时,可以独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。

6.3 冗余

有些情况下,处理数据的过程会失败。除非数据被持久化,否则将造成丢失。消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理系统明确的指出该消息已经被处理完毕,从而确保你的数据被安全的保存直到你使用完毕。

6.4 扩展性

因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的,只要另外增加处理过程即可。不需要改变代码、不需要调节参数。便于分布式扩容。

6.5 过载保护

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量无法提取预知;如果以为了能处理这类瞬间峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。

6.6 可恢复性

系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

6.7 顺序保证

在大多使用场景下,数据处理的顺序都很重要。大部分消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。

6.8 缓冲

在任何重要的系统中,都会有需要不同的处理时间的元素。消息队列通过一个缓冲层来帮助任务最高效率的执行,该缓冲有助于控制和优化数据流经过系统的速度。以调节系统响应时间。

6.9 数据流处理

分布式系统产生的海量数据流,如:业务日志、监控数据、用户行为等,针对这些数据流进行实时或批量采集汇总,然后进行大数据分析是当前互联网的必备技术,通过消息队列完成此类数据收集是最好的选择。



7.消息中间件常用协议

7.1 AMQP协议 -- -- 高级消息队列协议

AMQP即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同开发语言等条件的限制。 
优点:可靠、通用

7.2 MQTT协议

MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。 
优点:格式简洁、占用带宽小、移动端通信、PUSH、嵌入式系统

7.3 STOMP协议

STOMP(Streaming Text Orientated Message Protocol)是流文本定向消息协议,是一种为MOM(Message Oriented Middleware,面向消息的中间件)设计的简单文本协议。STOMP提供一个可互操作的连接格式,允许客户端与任意STOMP消息代理(Broker)进行交互。 
优点:命令模式(非topic\queue模式)

7.4 XMPP协议

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

7.5 其他基于TCP/IP自定义的协议

有些特殊框架(如:redis、kafka、zeroMq等)根据自身需要未严格遵循MQ规范,而是基于TCP\IP自行封装了一套协议,通过网络socket接口进行传输,实现了MQ的功能。



8.常见消息中间件MQ介绍

8.1 RocketMQ

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

具有以下特点:

  • 能够保证严格的消息顺序
  • 提供针对消息的过滤功能
  • 提供丰富的消息拉取模式
  • 高效的订阅者水平扩展能力
  • 实时的消息订阅机制
  • 亿级消息堆积能力

官方提供了一些不同于kafka的对比差异: https://rocketmq.apache.org/docs/motivation/


8.2 RabbitMQ

RabbitMQ是使用Erlang编写的一个开源的消息队列,是AMQP(高级消息队列协议)的标准实现,除此之外还支持很多的协议:XMPP, SMTP,STOMP,也正是如此,使它变的非常重量级,更适合于企业级的开发。同时实现了Broker架构,核心思想是生产者不会将消息直接发送给队列,消息在发送给客户端时先在中心队列排队。对路由(Routing),负载均衡(Load balance)、数据持久化都有很好的支持。多用于进行企业级的ESB整合以及它在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面也表现不俗。

AMQP协议模型
RabbitMQ消息是如何实现流转的
RabbitMQ结构图

上图中有几个重要概念:

  • Broker:简单来说就是消息队列服务器实体。
  • Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
  • Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
  • Binding:绑定,它的作用就是把Exchange和Queue按照路由规则绑定起来。
  • Routing Key:路由关键字,Exchange根据这个关键字进行消息投递。
  • vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
  • producer:消息生产者,就是投递消息的程序。
  • consumer:消息消费者,就是接受消息的程序。
  • channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。

消息队列的使用过程,如下:

  1. 客户端连接到消息队列服务器,打开一个channel。
  2. 客户端声明一个exchange,并设置相关属性。
  3. 客户端声明一个queue,并设置相关属性。
  4. 客户端使用routing key,在exchange和queue之间建立好绑定关系。
  5. 客户端投递消息到exchange。

  exchange接收到消息后,就根据消息的key和已经设置的binding,进行消息路由,将消息投递到一个或多个队列里。


8.3 ActiveMQ

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在当今的J2EE应用中间仍然扮演着特殊的地位。
ActiveMQ特性如下:

  1. 多种语言和协议编写客户端。
    语言: Java,C,C++,C#,Ruby,Perl,Python,PHP。
    应用协议: OpenWire,Stomp REST,WS Notification,XMPP,AMQP
  2. 完全支持JMS1.1和J2EE 1.4规范 (持久化,XA消息,事务)
  3. 对Spring的支持。
    ActiveMQ可以很容易内嵌到使用Spring的系统里面去,而且也支持Spring2.0的特性
  4. 通过了常见J2EE服务器(如 Geronimo,JBoss 4,GlassFish,WebLogic)的测试,其中通过JCA 1.5 resource adaptors的配置,可以让ActiveMQ可以自动的部署到任何兼容J2EE 1.4 商业服务器上
  5. 支持多种传送协议:in-VM,TCP,SSL,NIO,UDP,JGroups,JXTA
  6. 支持通过JDBC和journal提供高速的消息持久化
  7. 从设计上保证了高性能的集群,客户端-服务器,点对点
  8. 支持Ajax
  9. 支持与Axis的整合
  10. 可以很容易得调用内嵌JMS provider,进行测试

8.4 Redis

使用C语言开发的一个Key-Value的NoSQL数据库,开发维护很活跃,虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。实验表明:入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。


8.5 Kafka

Kafka是Apache下的一个子项目,使用scala实现的一个高性能分布式Publish/Subscribe消息队列系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是在现代网络上的许多社会功能的一个关键因素。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。 对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。Kafka的目的是通过Hadoop的并行加载机制来统一线上和离线的消息处理,也是为了通过集群机来提供实时的消费。
Kafka是一种高吞吐量的分布式发布订阅消息系统,有如下特性:

  • 快速持久化:通过磁盘顺序读写与零拷贝机制,可以在O(1)的磁盘数据结构提供消息的持久化,这种结构对于即使数以TB的消息存储也能够保持长时间的稳定性能。(文件追加的方式写入数据,过期的数据定期删除)
  • 支持Hadoop数据并行加载:对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。
  • 完全的分布式系统:Broker、Producer、Consumer都原生自动支持分布式,依赖zookeeper自动实现复杂均衡;
  • 高吞吐量:即使是非常普通的硬件(服务器)上Kafka也可以支持每秒数百万的吞吐速率。
  • 高堆积:支持topic下消费者较长时间离线,消息堆积量大;
  • 支持通过Kafka服务器和消费机集群来分区消息。

Kafka相关概念

  Broker:Kafka集群包含一个或多个服务器,这种服务器被称为broker

  Topic:每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)

  Partition:Parition是物理上的概念,每个Topic包含一个或多个Partition.

  Producer:负责发布消息到Kafka Broker

  Consumer:消息消费者,向Kafka Broker读取消息的客户端。

  Consumer Group:每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)。

Kafka一般应用在大数据日志处理或对实时性(少量延迟),可靠性(少量丢数据)要求稍低的场景使用。


8.6 ZeroMQ

号称最快的消息队列系统,专门为高吞吐量/低延迟的场景开发,在金融界的应用中经常使用,偏重于实时数据通信场景。ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,开发成本高。因此ZeroMQ具有一个独特的非中间件的模式,更像一个socket library,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序本身就是使用ZeroMQ API完成逻辑服务的角色。但是ZeroMQ仅提供非持久性的队列,如果down机,数据将会丢失。如:Twitter的Storm中使用ZeroMQ作为数据流的传输。

ZeroMQ 它实际类似于Socket的一系列接口,他跟Socket的区别是:普通的socket是端到端的(1:1的关系),而ZMQ却是可以N:M 的关系,人们对BSD套接字了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而ZMQ屏蔽了这些细节,让你的网络编程更为简单。ZMQ用于node与node间的通信,node可以是主机或者是进程;ZeroMQ套接字是与传输层无关的:ZeroMQ套接字对所有传输层协议定义了统一的API接口。默认支持 进程内(inproc) ,进程间(IPC) ,多播,TCP协议,在不同的协议之间切换只要简单的改变连接字符串的前缀。可以在任何时候以最小的代价从进程间的本地通信切换到分布式下的TCP通信。ZeroMQ在背后处理连接建立,断开和重连逻辑。
  引用官方的说法: “ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的传输层,像框架一样的一个socket library,他使得Socket编程更加简单、简洁和性能更高。是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD套接字之上的一层封装。ZMQ让编写高性能网络应用程序极为简单和有趣。”

特点是:

  • 高性能,非持久化;
  • 跨平台:支持Linux、Windows、OS X等。
  • 多语言支持; C、C++、Java、.NET、Python等30多种开发语言。
  • 可单独部署或集成到应用中使用;
  • 可作为Socket通信库使用。

与RabbitMQ相比,ZMQ并不像是一个传统意义上的消息队列服务器,事实上,它也根本不是一个服务器,更像一个底层的网络通讯库,在Socket API之上做了一层封装,将网络通讯、进程通讯和线程通讯抽象为统一的API接口。支持“Request-Reply “,”Publisher-Subscriber“,”Parallel Pipeline”三种基本模型和扩展模型。

ZeroMQ高性能设计要点:

  1. 无锁的队列模型
    对于跨线程间的交互(用户端和session)之间的数据交换通道pipe,采用无锁的队列算法CAS;在pipe两端注册有异步事件,在读或者写消息到pipe的时,会自动触发读写事件。
  2. 批量处理的算法
    对于传统的消息处理,每个消息在发送和接收的时候,都需要系统的调用,这样对于大量的消息,系统的开销比较大,zeroMQ对于批量的消息,进行了适应性的优化,可以批量的接收和发送消息。
  3. 多核下的线程绑定,无须CPU切换
    区别于传统的多线程并发模式,信号量或者临界区, zeroMQ充分利用多核的优势,每个核绑定运行一个工作者线程,避免多线程之间的CPU切换开销。


常见主流消息中间件的比较

综上所述:

一般的业务系统要引入 MQ,最早大家都用 ActiveMQ,但是现在确实大家用的不多了,没经过大规模吞吐量场景的验证,社区也不是很活跃,所以我个人不推荐用这个了;现在很多企业开始尝试用 RabbitMQ,但是确实 erlang 语言阻止了大量的 Java 工程师去深入研究和掌控它,对公司而言,几乎处于不可控的状态,但是确实人家是开源的,比较稳定的支持,活跃度也高;不过现在也有很多的公司,会去用 RocketMQ,确实很不错(阿里出品),但社区可能有突然黄掉的风险,对自己公司技术实力有绝对自信的,推荐用 RocketMQ,否则回去老老实实用 RabbitMQ 吧,人家有活跃的开源社区,绝对不会黄。

(1)中小型软件公司,技术实力较为一般,技术挑战不是特别高,建议选RabbitMQ.一方面,erlang语言天生具备高并发的特性,而且他的管理界面用起来十分方便。正所谓,成也萧何,败也萧何!他的弊端也在这里,虽然RabbitMQ是开源的,然而国内有几个能定制化开发erlang的程序员呢?所幸,RabbitMQ的社区十分活跃,可以解决开发过程中遇到的bug,这点对于中小型公司来说十分重要。不考虑rocketmq和kafka的原因是,一方面中小型软件公司不如互联网公司,数据量没那么大,选消息中间件,应首选功能比较完备的,所以kafka排除。不考虑rocketmq的原因是,rocketmq是阿里出品,如果阿里放弃维护rocketmq,中小型公司一般抽不出人来进行rocketmq的定制化开发,因此不推荐。
(2)大型软件公司,根据具体使用在rocketMq和kafka之间二选一。一方面,大型软件公司,具备足够的资金搭建分布式环境,也具备足够大的数据量。针对rocketMQ,大型软件公司也可以抽出人手对rocketMQ进行定制化开发,毕竟国内有能力改JAVA源码的人,还是相当多的。至于kafka,根据业务场景选择,如果有日志采集功能,肯定是首选kafka了。具体该选哪个,看使用场景。

(3)如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没问题,社区活跃度很高,绝对不会黄,何况几乎是全世界这个领域的事实性规范。

  • 9.消息队列相关技术要点

9.1如何保证消息队列是高可用的?

回答:这问题,其实要对消息队列的集群模式要有深刻了解,才好回答。


以rcoketMQ为例,

RabbitMQ 是比较有代表性的,因为是基于主从(非分布式)做高可用性的。

RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式。

(1)单机模式:就是 Demo 级别的,一般就是你本地启动了玩玩儿的?,没人生产用单机模式。

(2)普通集群模式(无高可用性):意思就是在多台机器上启动多个 RabbitMQ 实例,每个机器启动一个。你创建的 queue,只会放在一个 RabbitMQ 实例上,但是每个实例都同步 queue 的元数据(元数据可以认为是 queue 的一些配置信息,通过元数据,可以找到 queue 所在实例)。你消费的时候,实际上如果连接到了另外一个实例,那么那个实例会从 queue 所在实例上拉取数据过来。

rcoketMQ多master多slave模式部署架构图
普通集群模式

这种方式确实很麻烦,也不怎么好,没做到所谓的分布式,就是个普通集群。因为这导致你要么消费者每次随机连接一个实例然后拉取数据,要么固定连接那个 queue 所在实例消费数据,前者有数据拉取的开销,后者导致单实例性能瓶颈;而且如果那个放 queue 的实例宕机了,就会导致其他实例无法再次从那个queue实例拉取数据,如果你开启了消息持久化,让 RabbitMQ 落地存储消息的话,消息不一定会丢,但是这得等这个实例恢复了,然后才可以继续从这个 queue 拉取数据;所以这就没有什么所谓的高可用性,这方案主要是提高吞吐量的,就是说让集群中多个节点来服务某个 queue 的读写操作。

其实我们第一眼看到这个图,可能会觉得它和kafka好像,只是NameServer集群,在kafka中是用zookeeper代替,都是用来保存和发现master和slave用的。通信过程如下:

Producer 与 NameServer集群中的其中一个节点(随机选择)建立长连接,定期从 NameServer 获取 Topic 路由信息,并向提供 Topic 服务的 Broker Master 建立长连接,且定时向 Broker 发送心跳。Producer 只能将消息发送到 Broker master,但是 Consumer 则不一样,它同时和提供 Topic 服务的 Master 和 Slave建立长连接,既可以从 Broker Master 订阅消息,也可以从 Broker Slave 订阅消息。

那么kafka呢,为了对比说明直接上kafka的拓补架构图

kafka的拓补架构图

如上图所示,一个典型的Kafka集群中包含若干Producer(可以是web前端产生的Page View,或者是服务器日志,系统CPU、Memory等),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干Consumer Group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。

(3)镜像集群模式(高可用性):

镜像集群模式

这种模式,才是所谓的 RabbitMQ 的高可用模式。跟普通集群模式不一样的是,在镜像集群模式下,你创建的 queue,无论是元数据还是 queue 里的消息都会存在于多个实例上,就是说,每个 RabbitMQ 节点都有这个 queue 的一个完整镜像,包含 queue 的全部数据的意思。然后每次你写消息到 queue 的时候,都会自动把消息同步到多个实例的 queue 上。

这样的话,好处在于,你任何一个机器宕机了,其它机器(节点)依然可以继续使用,因为其他机器也包含了这个 queue 的完整数据,别的 consumer 都可以到其它节点上去消费数据;坏处在于,第一,这个性能开销太大了,消息需要同步到所有机器上,导致网络带宽压力和消耗很重!第二,这不是分布式的,就没有扩展性可言了,如果某个 queue 负载很重,你加机器,新增的机器也包含了这个 queue 的所有数据,并没有办法线性扩展你的 queue。你想,如果这个 queue 的数据量很大,大到这个机器上的容量无法容纳了,此时该怎么办呢?

开启这个镜像集群模式:其实很简单,RabbitMQ 有很好的管理控制台,就是在后台新增一个策略,这个策略是镜像集群模式的策略,指定的时候是可以要求数据同步到所有节点的,也可以要求同步到指定数量的节点,然后你再次创建 queue 的时候,应用这个策略,就会自动将数据同步到其他的节点上去了。

实际上rabbitmq之类的,并不是分布式消息队列,他就是传统的消息队列,只不过提供了一些集群、HA的机制而已,因为无论怎么样,rabbitmq一个queue的数据都是放在一个节点里的,镜像集群下,也是每个节点都放这个queue的完整数据.


以Kafka为例,

kafka一个最基本的架构认识:由多个broker组成,每个broker是一个节点。你创建一个topic,这个topic可以划分为多个partition,每个partition可以存在于不同的broker上,每个partition就放一部分数据,这就是天然的分布式消息队列。就是说一个topic的数据,是分散放在多个机器上的,每个机器就放一部分数据。

kafka的高可用性

kafka 0.8以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,没法写也没法读,没有什么高可用性可言。  

kafka 0.8以后,才提供了HA机制,就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的多个replica副本。然后所有replica会选举一个leader出来,那么生产和消费都跟这个leader打交道,然后其他replica就是follower。写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上数据即可。只能读写leader?很简单,要是你可以随意读写每个follower,那么就要care数据一致性的问题,系统复杂度太高,很容易出问题。kafka会均匀的将一个partition的所有replica分布在不同的机器上,这样才可以提高容错性。  

  这样一来就有所谓的高可用性了,因为如果某个broker宕机了,没事儿,那个broker上面的partition在其他机器上都有副本的,如果这上面有某个partition的leader,那么此时会重新选举一个新的leader出来,大家继续读写那个新的leader即可,这就有所谓的高可用性了。写数据的时候,生产者就写leader,然后leader将数据落地写本地磁盘,接着其他follower自己主动从leader来pull数据。一旦所有follower同步好数据了,就会发送ack给leader,leader收到所有follower的ack之后,就会返回写成功的消息给生产者。(当然,这只是其中一种模式,还可以适当调整这个行为)消费的时候,只会从leader去读,但是只有一个消息已经被所有follower都同步成功返回ack的情况下,这个消息才会被消费者读到。


redis 实现高并发主要依靠主从架构,一主多从,一般来说,单主用来写入数据,单机几万 QPS,多从用来查询数据,多个从实例可以提供每秒 10w 的 QPS。如果想要在实现高并发的同时,容纳大量的数据,那么就需要 redis 集群,使用 redis 集群之后,可以提供每秒几十万的读写并发。redis 高可用,如果是做主从架构部署,那么加上哨兵就可以了,就可以实现,任何一个实例宕机,可以进行主备切换。(redis具体知识,后面专门整理一篇文档)


9.2如何保证消息不被重复消费?

这个问题其实换一种问法就是,如何保证消息队列的幂等性?这个问题可以认为是消息队列领域的基本问题。

先来说一下为什么会造成重复消费?

其实无论是那种消息队列,造成重复消费原因其实都是类似的。正常情况下,消费者在消费消息时候,消费完毕后,会发送一个确认信息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的消息队列发送的确认信息形式不同,例如RabbitMQ是发送一个ACK确认消息,RocketMQ是返回一个CONSUME_SUCCESS成功标志,kafka实际上有个offset的概念,简单说一下,就是每个消息写进去,都有一个offset,代表他的序号,然后consumer消费了数据之后,每隔一段时间,会把自己消费过的消息的offset提交一下,代表我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的offset来继续消费吧。

但是凡事总有意外,比如我们之前生产经常遇到的,就是你有时候重启系统,看你怎么重启了,如果碰到点着急的,直接kill进程了,再重启。这会导致consumer有些消息处理了,但是没来得及提交offset,尴尬了。重启之后,少数消息会再次消费一次。

其实重复消费不可怕,可怕的是你没考虑到重复消费之后,怎么保证幂等性。

给你举个例子吧。假设你有个系统,消费一条就往数据库里插入一条,要是你一个消息重复两次,你不就插入了两条,这数据不就错了?但是你要是消费到第二次的时候,自己判断一下已经消费过了,直接扔了,不就保留了一条数据?

一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性

幂等性,我通俗点说,就一个数据,或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。

kafka如何避免消息被重复消费

那造成重复消费的原因?就是因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道自己已经消费过该消息了,再次将该消息分发给其他的消费者。

如何解决?这个问题其实还是得结合业务来思考,针对业务场景来答分以下几点

(1)比如你拿到这个消息是用来做数据库的insert操作。

那就容易了,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧;如果没有你就直接给这个消息做一个唯一主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。
(2)再比如,你拿到这个消息做redis的set的操作。

那就容易了,直接不用解决,因为你无论set几次结果都是一样的,set操作本来就是天然幂等操作。
(3)如果上面两种情况还不行,上大招。

那做的稍微复杂一点,准备一个第三方介质,来做消费记录,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理;如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis。那消费者开始消费前,先去redis中查询有没消费记录即可。

还有比如基于数据库的唯一键来保证重复数据不会重复插入多条,我们之前线上系统就有这个问题,就是拿到数据的时候,每次重启可能会有重复,因为kafka消费者还没来得及提交offset,重复数据拿到了以后我们插入的时候,因为有唯一键约束了,所以重复数据只会插入报错,不会导致数据库中出现脏数据

如何保证MQ的消费是幂等性的,需要结合具体的业务来看

 

9.3如何保证消费的可靠性传输?

其实回答这个问题,我们只需要从MQ的三个角度来分析:生产者弄丢数据、消息队列弄丢数据、消费者弄丢数据

RabbitMQ

(1)生产者丢数据

生产者将数据发送到rabbitmq的时候,可能数据就在半路给搞丢了,因为网络啥的问题,都有可能。

此时可以选择用rabbitmq提供的事务功能,就是生产者发送数据之前开启rabbitmq事务(channel.txSelect),然后发送消息,如果消息没有成功被rabbitmq接收到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit)。但是问题是,rabbitmq事务机制一搞,基本的吞吐量会降下来,因为太耗性能。

所以一般来说,如果你要确保说写rabbitmq的消息别丢,可以开启confirm模式,在生产者那里设置开启confirm模式之后,你每次写的消息都会分配一个唯一的id,然后如果写入了rabbitmq中,rabbitmq会给你回传一个ack消息,告诉你说这个消息ok了。如果rabbitmq没能处理这个消息,会回调你一个nack接口,告诉你这个消息接收失败,你可以重试。而且你可以结合这个机制自己在内存里维护每个消息id的状态,如果超过一定时间还没接收到这个消息的回调,那么你可以重发。

事务机制和cnofirm机制最大的不同在于,事务机制是同步的,你提交一个事务之后会阻塞在那儿,但是confirm机制是异步的,你发送个消息之后就可以发送下一个消息,然后那个消息rabbitmq接收了之后会异步回调你一个接口通知你这个消息接收到了。

所以一般在生产者这块避免数据丢失,都是用confirm机制的。因此,按照大多数大佬的经验,生产上用confirm模式的居多。一旦channel进入confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,rabbitMQ就会发送一个Ack给生产者(包含消息的唯一ID),这就使得生产者知道消息已经正确到达目的队列了.如果rabiitMQ没能处理该消息,则会发送一个Nack消息给你,你可以进行重试操作。处理Ack和Nack的代码如下所示

channel.addConfirmListener(new ConfirmListener() {  
      @Override  
      public void handleNack(long deliveryTag, boolean multiple) throws IOException { 
          System.out.println("nack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
      }  
       @Override  
       public void handleAck(long deliveryTag, boolean multiple) throws IOException {  
          System.out.println("ack: deliveryTag = "+deliveryTag+" multiple: "+multiple);  
      }  
  });  

(2)rabbitmq丢数据

rabbitmq自己弄丢了数据

就是rabbitmq自己弄丢了数据,这个你必须开启rabbitmq的持久化磁盘的配置,这个持久化配置可以和confirm机制配合使用,你可以在消息持久化磁盘后,才会给生产者发送一个Ack信号。这样哪怕是rabbitmq自己挂了,数据丢了,生产者收不到ack,你也是可以自己重发的。这样恢复之后会自动读取之前存储的数据,一般数据不会丢。除非极其罕见的是,rabbitmq还没持久化,自己就挂了,可能导致少量数据会丢失的,但是这个概率较小。

设置持久化有两个步骤,第一个是创建queue的时候将queue的持久化标识durable设置为true,则代表是一个持久的队列,这样就可以保证rabbitmq持久化queue的元数据,但是不会持久化queue里的数据;第二个是发送消息的时候将消息的deliveryMode设置为2,就是将消息设置为持久化的,此时rabbitmq就会将消息持久化到磁盘上去。必须要同时设置这两个持久化才行,rabbitmq哪怕是挂了,再次重启,也会从磁盘上重启恢复queue,恢复这个queue里的数据。

总的来说:rabbitmq如果丢失了数据,主要是因为你消费的时候,刚消费到,还没处理,结果进程挂了,比如重启了,那么就尴尬了,rabbitmq认为你都消费了,这数据就丢了。

(3)消费者丢数据
消费者丢数据一般是因为采用了自动确认消息模式。这种模式下,消费者会自动确认收到信息。这时rahbitMQ会立即将消息删除,这种情况下如果消费者出现异常而没能处理该消息,就会丢失该消息。
至于解决方案,采用手动确认消息即可。这个时候得用rabbitmq提供的ack机制,简单来说,就是你关闭rabbitmq自动ack,可以通过一个api来调用就行,然后每次你自己代码里确保处理完的时候,再程序里ack一把。这样的话,如果你还没处理完,不就没有ack?那rabbitmq就认为你还没处理完,这个时候rabbitmq会把这个消费分配给别的consumer去处理,消息是不会丢的。


kafka

kafka Replication的数据流向图

Producer在发布消息到某个Partition时,先通过ZooKeeper找到该Partition的Leader,然后无论该Topic的Replication Factor为多少(也即该Partition有多少个Replica),Producer只将该消息发送到该Partition的Leader。Leader会将该消息写入其本地Log。每个Follower都从Leader中pull数据。
针对上述情况,得出如下分析
(1)生产者丢数据
在kafka生产中,基本都有一个leader和多个follwer。follwer会去同步leader的信息。因此,为了避免生产者丢数据,做如下两点配置

  1. 第一个配置要在producer端设置acks=all。这个配置保证了,follwer同步完成后,才认为消息发送成功。
  2. 在producer端设置retries=MAX,一旦写入失败,这无限重试

(2)消息队列丢数据
针对消息队列丢数据的情况,这块比较常见的一个场景,就是kafka某个broker宕机,然后重新选举partiton的leader时,数据还没同步,leader就挂了,这时zookpeer会将其他的follwer切换为leader,那数据就丢失了。所以此时针对这种情况,一般是要求起码设置如下4个参数:

①给这个topic设置replication.factor参数:这个值必须大于1,即要求每个partition必须有至少2个副本

②在kafka服务端设置min.insync.replicas参数:这个值必须大于1,这个是要求一个leader至少感知到有至少一个follower还跟自己保持联系,没掉队,这样才能确保leader挂了还有一个follower吧

③在producer端设置acks=all:这个是要求每条数据,必须是写入所有replica之后,才能认为是写成功了

④在producer端设置retries=MAX(很大很大很大的一个值,无限次重试的意思):这个是要求一旦写入失败,就无限重试,卡在这里了

我们生产环境一般都按照上述要求配置的,这样配置之后,至少在kafka broker端就可以保证在leader所在broker发生故障,进行leader切换时,数据不会丢失

(3)消费者丢数据

唯一可能导致消费者弄丢数据的情况,就是说,你那个消费到了这个消息,然后消费者那边自动提交了offset,让kafka以为你已经消费好了这个消息,其实你刚准备处理这个消息,你还没处理,你自己就挂了,此时这条消息就丢咯。

再强调一次offset是干嘛的,offset:指的是kafka的topic中的每个消费组消费的下标。简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加一那里开始消费。
比如一个topic中有100条数据,我消费了50条并且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。
解决方案也很简单,改成手动提交即可。大家都知道kafka会自动提交offset,那么只要关闭自动提交offset,在处理完之后自己手动提交offset,就可以保证数据不会丢。但是此时确实还是会重复消费,比如你刚处理完,还没提交offset,结果自己挂了,此时肯定会重复消费一次,自己保证幂等性就好了。然而生产环境还可能碰到的这样的问题,就是说我们的kafka消费者消费到了数据之后是写到一个内存的queue里先缓冲一下,结果有的时候,你刚把消息写入内存queue,然后消费者会自动提交offset。然后此时我们重启了系统,就会导致内存queue里还没来得及处理的数据就丢失了。

kafka消费端弄丢了数据

9.4如何保证消息的顺序性?

针对这个问题,我们通过某种算法,将需要保持先后顺序的消息放到同一个消息队列中,然后只用一个消费者去消费该队列。

1)rabbitmq保证数据的顺序性

如果存在多个消费者,那么就让每个消费者对应一个queue,然后把要发送 的数据全都放到一个queue,这样就能保证所有的数据只到达一个消费者从而保证每个数据到达数据库都是顺序的。

rabbitmq:拆分多个queue,每个queue一个consumer,就是多一些queue而已,确实是麻烦点;或者就一个queue但是对应一个consumer,然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理

rabbitmq保证数据的顺序性

2)kafka保证数据的顺序性

 kafka 写入partion时指定一个key,列如订单id,那么消费者从partion中取出数据的时候肯定是有序的,当开启多个线程的时候可能导致数据不一致,这时候就需要内存队列,将相同的hash过的数据放在一个内存队列里,这样就能保证一条线程对应一个内存队列的数据写入数据库的时候顺序性的,从而可以开启多条线程对应多个内存队列

kafka:一个topic,一个partition,一个consumer,内部单线程消费,写N个内存queue,然后N个线程分别消费一个内存queue即可kafka保证数据的顺序性

kafka保证数据的顺序性

https://blog.csdn.net/u012431703/article/details/95939288

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章