1 消息中间件

可以看成是在消息的传输过程中保存消息的容器(只保存但不做修改,把它看做日志级别的)。消息中间件在将消息从它的源中继到他的目标时充当中间人的作用。

通过提供消息队列模型和消息传递机制,可以在分布式环境下进行扩展进程间的通信。
开发人员不需要考虑网络协议和远程调用的问题,只需要通过各消息中间件所提供的API,就可以简单的完成消息推送和消息接收的业务功能。

1.1 组件构成

(1)Broker
消息服务器,提供消息核心服务。

(2)Producer
消息生产者,业务的发起方,负责生产消息传输给broker。

(3)Consumer
消息消费者,业务的处理方,负责从broker获取消息并进行业务逻辑处理。

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

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

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

1.2 传递模式

一、点对点模式

消息生产者将消息发送到消息队列,消息消费者从这个消息队列中取出消息。消息在传给接收者之前存储在消息队列中,队列消息可放在内存也可持久化,以保证在消息服务出现故障时消息能够正常传递。

kafka中间件的实时采集 kafka消息中间件_流处理

特点:
每个消息只有一个接收者。
发送者和接收者之间没有时间依赖关系。

二、发布/订阅模式(Pub/Sub)

通过一个内容节点进行发布和订阅消息,该节点称为主题(topic)。消息的发布者将消息发布到某个topic,消息的订阅者(0个或者多个)订阅这个主题的消息,topic作用相当于中介,它将消息的发布和订阅相互独立开来。

kafka中间件的实时采集 kafka消息中间件_kafka中间件的实时采集_02

特点:
每个消息可有多个订阅者;
订阅者只有订阅后才能接收到消息;
包括持久订阅和非持久订阅。

注意:发布者和订阅者之间有时间依赖:即订阅者需要与发布者建立订阅关系才能收到消息。

(1)持久传输和非持久传输
持久传输和非持久传输最大的区别是:采用持久传输时,传输的消息会保存到磁盘中(messages are persisted to disk/database),即“存储转发”方式。先把消息存储到磁盘中,然后再将消息“转发”给订阅者。当Borker宕机 恢复后,消息还在。

采用非持久传输时,发送的消息不会存储到磁盘中。Borker宕机重启后,消息丢失。

比如,当生产者将消息投递给Broker后,Broker将该消息存储到磁盘中,在Broker将消息发送给Subscriber之前,Broker宕机了,如果采用持久传输,Broker重启后,从磁盘中读出消息再传递给Subscriber;如果采用非持久传输,这条消息就丢失了。

(2)持久订阅者和非持久订阅者
持久订阅:订阅关系建立后,消息不会消失,不管订阅者是否在线;
非持久订阅:订阅者为了接收消息需要一直在线,当只有一个订阅者时类似于点对点模式。
持久订阅者和非持久订阅者针对的Domain是Pub/Sub,而不是P2P。
当Broker发送消息给订阅者时,如果订阅者处于inactive状态:持久订阅者可以收到消息,而非持久订阅者则收不到消息。
当持久订阅者处于inactive状态时,Broker需要为持久订阅者保存消息;会造成的影响是:如果持久订阅者订阅的消息太多则会溢出。(当消息投递成功之后,Broker就可以把消息删除了)

1.3 消费模式

1.3.1 pull模式

生产者(Producer)发出的消息到达后,消息服务器(broker)什么也不做,等待消费者(consumer)主动到broker来拉取消息。即消费者需要主动调用http数据接口,然后处理业务数据,处理完成后,调用http ack接口,确认数据处理完成。

优点:
客户端可以依据自己的消费能力进行消费,可控性好。

缺点:
毕竟是消费者去拉取消息,但是消费者怎么知道消息到了呢?所以只能去轮询,每过一段时间去pull一次。试想一下,如果mq中没有数据,消费者可能一致陷入循环, 一直返回空数据。 kafka本身采用的是pull模式,针对这一问题, Kafka采用的是长轮询机制

1.3.2 push模式

生产者(Producer)发出的消息到达后,消息服务器(broker)将消息推送到消费者(consumer)。即消费者只需要提供http回调接口即可,处理完业务逻辑后,返回结果数据即可。

优点:
服务端主动推送给客户端,及时性很高

缺点:
当客户端消费能力远低于服务端生产能力,那么一旦服务端推送大量消息到客户端时,就会导致客户端消息堆积,处理缓慢,甚至服务崩溃。(那么如何解决这个问题呢?需要mq提供流控制,也就是依据客户端消费能力做流控)。
服务端需要维护每次传输状态,以防消息传递失败进行重试。

1.3.3 Polling(轮询)和Long Polling(长轮询)

(1)Polling:无论broker数据是否有更新,Consumer每隔一段时间去请求pull一次数据,结果是可能有数据返回,或返回空。

(2)Long Polling:Consumer发起Long Polling后,此时如果broker没有数据,那么不会立刻返回结果(即broker保存相关请求,先不关闭请求连接),等有了相关数据,或等一定时间超时才会返回结果。返回后,Consumer会则立刻发起下一次Long Polling。不难发现,该方式是对 pull 模式的优化,解决了pull模式数据通知不及时问题,同时也可以减少无效轮询的次数。

因此:如果Broker一直有可读消息,long-polling 就相当于执行间隔为0的pull模式(因为Consumer每次收到结果就立刻发起下一次pull)。如果Broker没有可读消息,那么请求会被暂时阻塞,在产生了下一个可读消息或在请求“超时之前”响应请求给Consumer。

2 kafka高可用架构

Kafka里有一个概念叫Topic,暂且可以认为是一个数据集合。

举个例子,现在有一份用户数据要写入Kafka,可以弄一个Topic叫做user_log_topic来写入用户数据。如果要把订单数据写入Kafka,可以弄一个Topic叫做order_topic来记录。

那么可能会出现这样的情况,就用用户数据这个Topic举例,如果每天网里面写入几十TB 的数据,都放一台机器上靠谱吗?明显不行,所以Kafka里有一个东西叫Partition,就是把一个Topic数据集合拆分为多个数据分区,可以理解为数据分片,而每个Partition可以在不同的机器上储存部分数据。

这样一个大的数据集合就可以被分布式的存储在多台机器上了。如图:

kafka中间件的实时采集 kafka消息中间件_数据_03


相信聪明的发现这种情况下可能会有一些问题,万一某台机器突然挂掉,那这台机器上的 Partition管理的数据不就丢了吗?这咋搞?简单,多搞几个副本呗,就是每个Partition都搞一个副本放在其他的机器上,这样某台机器挂了,只是这台机器的 Partition 数据丢了而已,其他机器上还有呢。此外,如果一个Partition有很多副本,Kafka会选举其中一个Parititon副本成为Leader,然后其他的Partition副本是Follower。只有Leader对外提供读写操作,其他Follower只是从Leader同步数据。Leader如果突然宕机了,就会选举其他的Follower作为新的Leader 对外提供读写服务,这不就是高可用架构么?

kafka中间件的实时采集 kafka消息中间件_kafka_04

2.1 kafka如何保证消息顺序消费

kafka集群无法做到消费者在消费的时候全局有序,因为在创建topic时,会设置不同的分区(partition)并存储在不同的服务器上,生产者生产的消息虽然是有序的,但是经过分区后会被分到不同的partition,每个partition里的数据是按顺序存储的,但这只是局部有序,并不是全局有序。

比如生产者生产出100条数据之后,通过一定的分组策略存储到broker的partition中的时候,0到10这10条消息被存到了partition-0中,10到20这10条消息被存到了partition-1中,这样当消费者消费的时候从partition中取出消息并消费时便是无序的。

那么能否做到消费者在消费消息的时候全局有序呢?

(1)多数情况下做不到全局有序。特殊情况下可以,比如partition只有一个。但是这样的话,就没有分布式和负载均衡一说了。因此,全局有序无法在kafka要实现的大数据的场景下做到。只能保证某一个partition内部消费的有序性。

(2)在发送消息的时候指定key,那么这些消息就会被发送到指定的partition中,从而保证了有序性。

结论:针对一个topic里面的数据,只能做到partition内部有序,无法做到全局有序。消息在被追加到 partition时都会被分配一个特定的偏移量(offset)。Kafka 通过offset来保证消息在partition内的顺序性。

2.2 kafka如何保证消息不丢失

要保证消息不丢失,就需要在消息传递的过程中任何一个环节都不能丢失,即生产者、kafka本身以及消费者都得保证不丢失。

2.2.1 生产者

ack应答机制:Kafka 提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡来配置不同参数。

(1)ack=0:生产者在写入消息之前不会等待服务器的响应。一旦有问题生产者无法感知,消息就丢了(比如服务器没有写入磁盘就宕机了,造成数据丢失)。但这种方式也有好处,就是能以网络能够支持的最大速度发送消息,吞吐量高。

(2)ack=1:这个是默认值,只要集群中的leader( 某个Parititon 副本,其余副本为follower)收到消息,生产者就会收到一个来自服务器的成功响应。如果消息无法到达leader(比如leader崩溃,新的leader还没被选举出来),生产者会收到一个错误响应,此时生产者会重发消息为了防止数据丢失。但是,还有一个特殊情况会导致数据丢失,就是生产者收到了写成功通知,但是此时刚好leader还没来的及同步数据到follower,leader就崩溃了,这会导致数据丢失。

(3)ack=-1:只有当所有参与复制的节点(leader及其下的follower)都收到消息时,生产者才会收到一个来自服务器的成功响应,这种模式最安全,因为其可以保证不止一个服务器收到消息 。但是有一种情况,如果在follower同步完成后, broker发送ack之前,leader发生故障,那么会造成数据重复 。

2.2.2 kafka

一、什么情况下Kafka中写入数据会丢失呢?

写入数据是往某个Partition的leader里写,然后那个 Partition 的 Follower 会从 Leader 同步数据。那么问题来了,现在有一条数据刚写入Leader Partition,还没来得及同步给 Follower,Leader Partiton所在机器就宕机了,此时就会选举这个Partition的一个 Follower作为新的 Leader 对外提供服务,那这时消费者就读不到刚才写入的那条数据了,造成数据丢失。

二、如何保证不丢失呢,需同时满足两个条件?

每个Partition至少有 1 个Follower在 ISR 列表里,即跟上了 Leader 的数据同步。
每次写数据时,要求至少写入 Partition Leader 成功的同时,还至少有一个 ISR 里的 Follower 也写入成功。
如果不满足上述条件,那就一直写入失败,让生产者不停的尝试重试,直到满足上述条件为止,才认为写入成功。

(1)什么是ISR?

简单来讲,就是kafka自动监控有哪些Follower 及时的跟上了 Leader 的数据同步的一种机制。

kafka会自动给每个 Partition 维护一个 ISR 列表,列表里一定会有 Leader以及与 Leader 保持同步的Follower。就是只要某个 Follower 一直能跟其leader保持数据同步,那么它们就会存在于 ISR 列表。但是如果 Follower自身出现问题,导致不能及时与 其Leader 同步,那这个 Follower 会被认为是“out-of-sync”,就会从 ISR 列表里被踢出去。

(2)什么是OSR?
上面说到能跟上 Leader 数据同步的 Follower 会被保存在 ISR 列表中,那那些没有跟上 Leader 数据同步(数据滞后)的 Follower 呢?它们会被保存在一个叫 OSR 的列表中。

那么你可能会问了,如果 Leader 出现故障,怎么选举新 Leader 呢?我们知道 ISR 列表中维护了与 Leader 同步的 所有 Follower(包含Leader),如果 Leader 出现问题,那么它会从 ISR 列表中随机挑选一个 Follower 作为新 Leader,那么问题来了,如果 ISR 列表中所有副本都丢失了怎么办?此时可以有两种解决方法:

1)等待 ISR 列表中副本任何一个恢复,之后再对外提供服务,需要时间等待;
2)可以从 OSR 中选择一个副本当 Leader 继续对外提供服务,但是有数据丢失的风险;
具体采取哪种策略,看具体场景需要。

2.2.3 消费者

什么时候消费者会发生消息丢失?

kafka中间件的实时采集 kafka消息中间件_数据_05


假设前一次poll操作拉取的消息集是 [x+2,x+7],x+2 表示上一次提交的消费位移,此时说明已经完成了x+1之前(包括x+1在内)的所有消息的消费,x+5表示当前正在处理的位置。

如果拉取到消息之后就立刻进行了位移提交,即提交了x+8,此时如果消费 x+5 出现了异常,在故障恢复之后,重新拉取的消息是肯定从 x+8 开始的。显然,x+5到x+7之间的消息丢失了,未能被消费。

想要保证消息不丢失,可以将提交offset的方式改成手动方式,即每次消费完所拉取的消息之后再提交offset,这就可以避免拉取的消息还未消费便出先异常导致消息丢失的情况。

但是这样做可能会导致重复消费的情况:因为位移提交动作是在消费完所有拉取到的消息之后才执行的,那么当消费x+5的时候遇到异常,故障恢复之后,重新拉取的消息是从x+2开始的。显然,x+2到x+4之间的消息重新消费了一遍。

2.3 kafka如何保证消息不重复消费

什么情况会导致重复消费?

一、自动提交offset造成重复消费
自动提交并不是消费一条消息就提交一次offset,而是定期提交,定期的周期由客户端参数auto.commit.interval.ms配置,默认为5秒。假设刚提交完一次消费位移,然后拉取一批消息进行消费,在下一次自动提交消费位移之前,消费者崩溃了,那么又得从上一次位移提交的地方重新消费,发生了重复消费现象。

二、手动提交offset造成重复消费
异步提交Offset的时候会有失败的情况发生,此时就会进行重试。如果某一次异步提交的消费位移为 x,但是提交失败了,然后下一次异步提交的消费位移为x+y,这次成功了。

如果引入了重试机制,前一次的异步提交的消费位移在重试的时候提交成功了,那么此时的消费位移又变为了 x。恰巧此时发生故障,那恢复之后消费者就会从 x 处开始消费,这发生了重复消费现象。

如何解决?
落表(主键或者唯一索引的方式,避免重复数据) ,说白了就是先存起来呗,然后消费之前查一下有没有消费过,有就丢弃,没有就消费。

3 kafka的由来

Apache Kafka 是消息引擎系统,也是一个分布式流处理平台(Distributed Streaming Platform)
Apache Kafka源于LinkedIn,是一个分布式的发布-订阅消息系统。最初LinkedIn开发Kafka的目的,是将Kafka作为内部的活动流和运营数据处理管道处理的基础,因此,所开发的Kafka特点是基于Pull模式来处理消息,追求高吞吐量。2010年,LinkedIn将Kafka贡献给Apache,现在Kafka已经成为应用非常广泛的一个消息中间件。

3.1 问题

Kafka 是 LinkedIn 公司内部孵化的项目,LinkedIn最开始有强烈的数据强实时处理方面的需求,其内部的诸多子系统要执行多种类型的数据处理与分析,主要包括业务系统和应用程序性能监控,以及用户行为数据处理等。

当时他们碰到的主要问题包括:

(1)数据正确性不足
因为数据的收集主要采用轮询(Polling)的方式,如何确定轮询的间隔时间就变成了一个高度经验化的事情。虽然可以采用一些类似于启发式算法(Heuristic)来帮助评估间隔时间值,但一旦指定不当,必然会造成较大的数据偏差。
(2)系统高度定制化,维护成本高
各个业务子系统都需要对接数据收集模块,引入了大量的定制开销和人工成本。

3.2 解决方案

为了解决这些问题,LinkedIn工程师尝试过使用 ActiveMQ 来解决这些问题,但效果并不理想。显然需要有一个“大一统”的系统来取代现有的工作方式,而这个系统就是 Kafka。

Kafka 自诞生伊始是以消息引擎系统的面目出现在大众视野中的。如果翻看 0.10.0.0 之前的官网说明,你会发现 Kafka 社区将其清晰地定位为一个分布式、分区化且带备份功能的提交日志(Commit Log)服务

这里引出一个题外话,你可能好奇 Kafka 这个名字的由来,实际上 Kafka 作者之一 JayKreps 曾经谈及过命名的原因。

因为 Kafka 系统的写性能很强,所以找了个作家的名字来命名似乎是一个好主意。大学期间我上了很多文学课,非常喜欢 Franz Kafka 这个作家,另外为开源软件起这个名字听上去很酷。

kafka中间件的实时采集 kafka消息中间件_数据_06

弗兰兹·卡夫卡(Franz Kafka,1883年7月3日—1924年6月3日),生活于奥匈帝国(奥地利帝国和匈牙利组成的政合国)统治下的捷克德语小说家,本职为保险业职员。主要作品有小说《审判》《城堡》《变形记》等。

Kafka 在设计之初就旨在提供三个方面的特性:

提供一套 API 实现生产者和消费者;
降低网络传输和磁盘存储开销;
实现高伸缩性架构。

总之随着 Kafka 的不断完善,Jay 等大神们终于意识到将其开源惠及更多的人是一个非常棒的主意,因此在2011 年 Kafka 正式进入到 Apache 基金会孵化并于次年 10 月顺利毕业成为 Apache 顶级项目。

3.3 发展

开源之后的 Kafka 被越来越多的公司应用到它们企业内部的数据管道中,特别是在大数据工程领域,Kafka 在承接上下游、串联数据流管道方面发挥了重要的作用:所有的数据几乎都要从一个系统流入 Kafka 然后再流向下游的另一个系统中。这样的使用方式屡见不鲜以至于引发了 Kafka 社区的思考:与其我把数据从一个系统传递到下一个系统中做处理,我为何不自己实现一套流处理框架呢?

基于这个考量,Kafka 社区于 0.10.0.0 版本正式推出了流处理组件Kafka Streams,也正是从这个版本开始,Kafka 正式“变身”为分布式的流处理平台,而不仅仅是消息引擎系统了。今天 Apache Kafka 是和 Apache Storm、 Apache Spark 和 Apache Flink 同等级的实时流处理平台

诚然,目前国内对 Kafka 是流处理平台的认知还尚不普及,其核心的流处理组件 Kafka Streams 更是少有大厂在使用。但我们也欣喜地看到,随着在 Kafka 峰会上各路大神们的鼎力宣传,如今利用 Kafka 构建流处理平台的案例层出不穷,而了解并有意愿使用 Kafka Streams 的厂商也是越来越多,因此我个人对于 Kafka 流处理平台的前景也是非常乐观的。

作为流处理平台,Kafka 与其他主流大数据流式计算框架相比,优势在哪里呢?

第一点是更容易实现端到端的正确性(Correctness)
Google 大神 Tyler 曾经说过,流处理要最终替代它的“兄弟”批处理需要具备两点核心优势:要实现正确性和提供能够推导时间的工具。
实现正确性是流处理能够匹敌批处理的基石。正确性一直是批处理的强项,而实现正确性的基石则是要求框架能提供精确一次处理语义,即处理一条消息有且只有一次机会能够影响系统状态。

目前主流的大数据流处理框架都宣称实现了精确一次处理语义,但这是有限定条件的,即它们只能实现框架内的精确一次处理语义,无法实现端到端的。这是为什么呢?因为当这些框架与外部消息引擎系统结合使用时,它们无法影响到外部系统的处理语义,所以如果你搭建了一套环境使得 Spark 或 Flink 从 Kafka 读取消息之后进行有状态的数据计算,最后再写回 Kafka,那么你只能保证在 Spark 或 Flink 内部,这条消息对于状态的影响只有一次。但是计算结果有可能多次写入到 Kafka,因为它们不能控制 Kafka 的语义处理。

相反地,Kafka 则不是这样,因为所有的数据流转和计算都在 Kafka 内部完成,故 Kafka 可以实现端到端的精确一次处理语义。

可能助力 Kafka 胜出的第二点是它自己对于流式计算的定位
官网上明确标识 Kafka Streams 是一个用于搭建实时流处理的客户端库而非是一个完整的功能系统。这就是说,你不能期望着 Kafka 提供类似于集群调度、弹性部署等开箱即用的运维特性,你需要自己选择适合的工具或系统来帮助 Kafka 流处理应用实现这些功能。读到这你可能会说这怎么算是优点呢?坦率来说,这的确是一个“双刃剑”的设计,也是Kafka 社区“剑走偏锋”不正面 PK 其他流计算框架的特意考量。

大型公司的流处理平台一定是大规模部署的,因此具备集群调度功能以及灵活的部署方案是不可或缺的要素。但毕竟这世界上还存在着很多中小企业,它们的流处理数据量并不巨大,逻辑也并不复杂,部署几台或十几台机器足以应付。在这样的需求之下,搭建重量级的完整性平台实在是“杀鸡焉用牛刀”,而这正是 Kafka 流处理组件的用武之地。因此从这个角度来说,未来在流处理框架中,Kafka 应该是有一席之地的。除了消息引擎和流处理平台,Kafka 还有别的用途吗?当然有!你能想象吗,Kafka 能够被用作分布式存储系统。Kafka 作者之一 Jay Kreps 曾经专门写过一篇文章阐述为什么能把 Kafka 用作分布式存储。不过我觉得你姑且了解下就好了,我从没有见过在实际生产环境中,有人把 Kafka 当作持久化存储来用 。

说了这么多,我只想阐述这样的一个观点:Apache Kafka 从一个优秀的消息引擎系统起家,逐渐演变成现在分布式的流处理平台。你不仅要熟练掌握它作为消息引擎系统的非凡特性及使用技巧,最好还要多了解下其流处理组件的设计与案例应用。

3.4 使用场景

一、消息队列相关场景
消息队列是分布式系统中重要的组件,kafka就可以看做是一种消息队列,其大致使用场景:

1、应用解耦
降低模块与模块之间的调用强依赖关系,不直接调用管理系统。如业务中台系统,需要通知下游系统消息数据,如果直接由交给业务中台调用下游系统接口,会出现依赖倒置的情况。

2、异步通信
异步通信的消息,不需要等待其他系统的完成响应,提升系统的并发能

3、削峰填谷
减少并发流量,对服务器的压力。MQ消费者是通过自身消费能力从消息队列中获取到消息,防止并发流量导致服务器无法承载压力,从而导致服务器宕机的情况。
二、网站活动跟踪
实时收集网站活动数据(例如注册、登录、下单、充值、支付等),根据业务数据类型将消息发布到不同的Topic,然后利用订阅消息的实时投递,将消息流用于实时处理、实时监控或者加载到Hadoop、MaxCompute等离线数据仓库系统进行离线处理。

三、日志聚合
许多公司,例如淘宝、天猫等,每天都会产生大量的日志(一般为流式数据,例如搜索引擎PV、查询等)。相较于以日志为中心的系统,例如Scribe和Flume,消息队列Kafka版在具备高性能的同时,可以实现更强的数据持久化以及更短的端到端响应时间。消息队列Kafka版的这种特性决定它适合作为日志收集中心。消息队列Kafka版忽略掉文件的细节,可以将多台主机或应用的日志数据抽象成一个个日志或事件的消息流,异步发送到消息队列Kafka版集群,从而实现非常低的RT。消息队列Kafka版客户端可批量提交消息和压缩消息,对生产者而言几乎感觉不到性能的开支。消费者可以使用Hadoop、MaxCompute等离线仓库存储和Strom、Spark等实时在线分析系统对日志进行统计分析

四、流计算处理
在很多领域,如股市走向分析、气象数据测控、网站用户行为分析,由于数据产生快、实时性强且量大,很难统一采集这些数据并将其入库存储后再做处理,这便导致传统的数据处理架构不能满足需求。与传统架构不同,消息队列Kafka版以及Storm、Samza、Spark等流计算引擎的出现,就是为了更好地解决这类数据在处理过程中遇到的问题,流计算模型能实现在数据流动的过程中对数据进行实时地捕捉和处理,并根据业务需求进行计算分析,最终把结果保存或者分发给需要的组件。