第1章 了解消息队列和Kafka
本章的知识都是Kafka基础,学习起来会非常轻松。本章能够帮助读者从零开始认识Kafka,内容包含消息队列、Kafka的起源、Kafka的基础知识等。
1.1 本章教学视频说明
视频内容:什么是消息队列、消息队列与Kafka之间的联系、Kafka的基本概念、Kafka的工作机制,以及Kafka的使用范围等。
视频时长:10分钟。
视频截图见图1-1。
图1-1 本章教学视频截图
1.2 消息队列
在高并发的应用场景中,由于来不及同步处理请求,接收到的请求往往会发生阻塞。例如,大量的插入、更新请求同时到达数据库,这会导致行或表被锁住,最后会因为请求堆积过多而触发“连接数过多的异常”(Too Many Connections)错误。
因此,在高并发的应用场景中需要一个缓冲机制,而消息队列则可以很好地充当这样一个角色。消息队列通过异步处理请求来缓解系统的压力。
1.2.1 什么是消息队列
“消息队列”(Message Queue, MQ)从字面来理解,是一个队列,拥有先进先出(First Input First Output, FIFO)的特性。它主要用于不同进程或线程之间的通信,用来处理一系列的输入请求。
消息队列采用异步通信机制。即,消息的发送者和接收者无须同时与消息队列进行数据交互,消息会一直保存在队列中,直至被接收者读取。每一条消息记录都包含详细的数据说明,包括数据产生的时间、数据类型、特定的输入参数。
1.2.2 消息队列主要有哪些作用
在实际的应用中,消息队列主要有以下作用。
· 应用解耦:多个应用可通过消息队列对相同的消息进行处理,应用之间相互独立,互不影响;
· 异步处理:相比于串行和并行处理,异步处理可以减少处理的时间;
· 数据限流:流量高峰期,可通过消息队列来控制流量,避免流量过大而引起应用系统崩溃;
· 消息通信:实现点对点消息队列或聊天室等。
1.应用解耦
由于消息与平台和语言无关,并且在语法上也不再是函数之间的调用,因此,消息队列允许应用接口独立地进行扩展,只用应用接口遵守同样的接口约束。
举例,用户使用客户端上传一张个人图片,具体流程如图1-2所示。
图1-2 应用解耦实例图
(1)图片上传系统将图片信息(如唯一ID、图片类型、图片尺寸等)批量写入消息队列,写入成功后会将结果直接返回给客户端。
(2)人脸识别系统定时从消息队列中读取数据,完成对新增图片的识别。
图片上传系统无须关心人脸识别系统是否对上传的图片进行了处理,它只需要关心是否成功将图片信息写入消息队列。
由于用户无须立即知晓人脸识别的结果,因此人脸识别系统可选择不同的调度策略来处理消息队列中的图片信息。
2.异步处理
用户在注册账号时,服务程序需要给用户发送邮件注册信息和短信注册信息。比较传统的做法是——通过串行和并行的方式来实现。
(1)串行方式:先将用户注册信息写入数据库,然后发送短信注册信息,再发送邮件注册信息。以上三个任务全部完成后,才会将结果返回给用户。具体流程如图1-3所示。
图1-3 串行方式流程图
假设这三个阶段的耗时均为20 ms,不考虑网络等其他消耗,则整个过程需耗时60 ms。(2)并行方式:先将用户注册信息写入数据库,然后在发送短信注册信息的同时还发送邮件注册信息。以上任务全部完成后才会将结果返回给用户。具体流程如图1-4所示。
图1-4 并行处理流程图
假设这三个阶段的耗时均为20 ms,不考虑网络等其他消耗,则整个过程需耗时40 ms。
提示:
与串行的不同之处是,并行处理提高了处理效率,减少了处理时间。
针对上述应用场景,采传统方式时,系统的性能(如并发量、吞吐量、响应时间等)会产生瓶颈。此时需要引入消息队列异步处理非必要业务环节。具体架构如图1-5所示。
图1-5 更改并行处理流程图
用户将注册信息写入数据库约耗时20ms(和串行和并行的处理时间相同)。短信和邮件注册信息写入消息队列后会直接将结果返回给用户。由于写入消息队列的速度非常快,基本可以忽略。
另外,“通过异步读取消息队列中的短信注册信息”过程和“邮件注册信息”过程相当于同时进行的,那么整个过程约耗时20ms。
提示:
从上面的分析可以看出,在调整架构后,系统的整体处理时间是串行方式的1/3,是并行方式的1/2。
3.数据限流
数据限流也是消息队列的常用场景之一,一般在促销和“秒杀”活动中使用得较为广泛。
例如,在电商的“双11”活动中,由于瞬间的数据访问量过大,服务器接收到的数据请求过大,则导致服务器上的应用服务无法处理请求而崩溃。
为了解决这类问题,一般需要先将用户请求写入消息队列(相当于用消息队列做一次缓冲),然后服务器上的应用服务再从消息队列中读取数据。具体流程如图1-6所示。
图1-6 数据限流流程图
数据限流具有以下优点:
· 用户请求写数据到消息队列时,不与应用业务服务直接接触,中间存在一次缓冲。这极大地减少了应用服务处理用户请求的压力。
· 可以设置队列的长度,用户请求遵循FIFO原则。后来的用户请求处于队列之外时,是无法秒杀到商品的,这些请求会直接被舍弃,返给用户“商品已售完”的结果。
提示:
FIFO(First Input First Output,先进先出)是一种较为传统的执行方法,按照请求的进入顺序依次进行处理。
4.消息通信
消息队列具有高效的通信机制,所以其在点对点通信和聊天室通信中被广泛应用。具体流程如图1-7和1-8所示。
图1-7 点对点通信流程图
图1-8 聊天室通信流程图
1.3 为什么需要Kafka
Kafka起源于LinkedIn公司。起初,LinkedIn需要收集各个业务系统和应用的指标数据来进行数据分析,原先是使用“自定义开发”系统来实现的。但这期间需要采集的数据量非常大,且内容很复杂。除要采集操作系统的基础指标(例如:内存、CPU、磁盘、网络等)外,还要采集很多和业务相关的数据指标。
随着数据量的增长、业务需求的复杂度提高,这个“自定义开发”系统的问题也越来越多。例如,在处理一个HTTP请求数据时,由于数据内容是以XML数据格式进行传输的,需要先对这部分数据做解析处理,然后才能拿来做离线分析。由于这样一个自定义开发系统不够稳定,且XML数据格式的解析过程也非常复杂,所以系统经常出现问题。出现问题后,定位分析也比较麻烦,需要很长的处理时间,所以无法做到实时服务。
之后,LinkedIn想寻找一种可支持大数据实时服务并且支持水平扩展的解决方案。尝试过使用ActiveMQ,但是它不支持水平扩展,并且ActiveMQ内部有很多Bug。
说明:
ActiveMQ是一个开源的消息系统,完全采用Java编程语言来实现,因此能很好地兼容Java消息服务(Java Message Service, JMS)规范。
于是,LinkedIn团队开发了一个既满足实时处理需求,又可支持水平拓展的消息系统——Kafka,它还拥有高吞吐量特性。
2010年,Kafka项目被托管到Github开源社区。一时间,大量开发者被这个项目所吸引。2011年,Kafka成为Apache项目基金会的一个开源项目。2012年,Apache项目基金会开始对Kafka项目进行孵化。之后,不断有LinkedIn员工和社区成员来维护和改善Kafka项目,Kafka项目得到持续不断地改进。如今,Kafka项目成为Apache项目基金会的顶级项目之一。
1.4 Kafka的基本概念
Kafka是一个分布式实时数据流平台,可独立部署在单台服务器上,也可部署在多台服务器上构成集群。它提供了发布与订阅功能。用户可以发送数据到Kafka集群中,也可以从Kafka集群中读取数据。
Kafka系统中有几个核心概念,下面分别介绍。
1.4.1 代理、生产者、消费者、消费者组
1.代理(Broker)
在Kafka集群中,一个Kafka进程(Kafka进程又称为Kafka实例)被称为一个代理(Broker)节点。代理节点是消息队列中的一个常用概念。通常,在部署分布式Kafka集群时,一台服务器上部署一个Kafka实例。
2.生产者(Producer)
在Kafka系统中,生产者通常被称为Producer。
Producer将消息记录发送到Kafka集群指定的主题(Topic)中进行存储,同时生产者(Producer)也能通过自定义算法决定将消息记录发送到哪个分区(Partition)。
例如,通过获取消息记录主键(Key)的哈希值,然后使用该值对分区数取模运算,得到分区索引。计算公式如下。
# 计算主题分区的索引值 分区索引值 = 键的哈希值取绝对值 % 分区数 # 计算公式翻译成Java代码 int partition = Math.abs(key.hashCode()) % numPartitions;
3.消费者(Consumer)
消费者(Consumer)从Kafka集群指定的主题(Topic)中读取消息记录。
在读取主题数据时,需要设置消费组名(GroupId)。如果不设置,则Kafka消费者会默认生成一个消费组名称。
4.消费者组(Consumer Group)
消费者程序在读取Kafka系统主题(Topic)中的数据时,通常会使用多个线程来执行。
一个消费者组可以包含一个或多个消费者程序,使用多分区和多线程模式可以极大提高读取数据的效率。
提示:
一般而言,一个消费者对应一个线程。
在给应用程序设置线程数量时,遵循“线程数小于等于分区数”原则。如果线程数大于分区数,则多余的线程不会消费分区中的数据,这样会造成资源浪费。
1.4.2 主题、分区、副本、记录
1.主题(Topic)
Kafka系统通过主题来区分不同业务类型的消息记录。
例如,用户登录数据存储在主题A中,用户充值记录存储在主题B中,则如果应用程序只订阅了主题A,而没有订阅主题B,那该应用程序只能读取主题A中的数据。
2.分区(Partition)
每一个主题(Topic)中可以有一个或者多个分区(Partition)。在Kafka系统的设计思想中,分区是基于物理层面上的,不同的分区对应着不同的数据文件。
Kafka通过分区(Partition)来支持物理层面上的并发读写,以提高Kafka集群的吞吐量。
每个主题(Topic)下的各分区(Partition)中存储数据的具体流程如图1-9所示。
图1-9 各分区存储数据的流程
每个分区(Partition)内部的消息记录是有序的,每个消息都有一个连续的偏移量序号(Offset)。
一个分区只对应一个代理节点(Broker),一个代理节点可以管理多个分区。
3.副本(Replication)
在Kafka系统中,每个主题(Topic)在创建时会要求指定它的副本数,默认是1。通过副本(Replication)机制来保证Kafka分布式集群数据的高可用性。
提示:
在创建主题时,主题的副本系数值应如下设置:
(1)若集群数量大于等于3,则主题的副本系数值可以设置为3;
(2)若集群数量小于3,则主题的副本系数值可以设置为小于等于集群数量值。
例如,集群数为2,则副本系数可以设置为1或者2;集群数为1,则副本系数只能设置为1。
通常情况下,当集群数量大于等于3时,为了保证集群数据不丢失,会将副本系数值设置为3。当然,集群数量大于等于3时,副本系数值也可以设置为1或者2,但是会存在数据丢失的风险。
4.记录(Record)
被实际写入到Kafka集群并且可以被消费者应用程序读取的数据,被称为记录(Record)。每条记录包含一个键(Key)、值(Value)和时间戳(Timestamp)。
1.5 了解Kafka的工作机制——生产消息/消费消息
Kafka作为一个消息队列系统,其核心机制就是生产消息和消费消息。
在Kafka基本结构中,生产者(Producer)组件和消费者(Consumer)组件互不影响,但又是必须存在的。缺少生产者和消费者中的任意一方,整个Kafka消息队列系统将是不完整的。
Kafka消息队列系统最基本的结构如图1-10所示。
图1-10 Kafka消息中间件系统基本结构
· 生产者(Producer)负责写入消息数据。将审计日志、服务日志、数据库、移动App日志,以及其他类型的日志主动推送到Kafka集群进行存储。
· 消费者(Consumer)负责读取消息数据。例如,通过Hadoop的应用接口、Spark的应用接口、Storm的应用接口、ElasticSearch的应用接口,以及其他自定义服务的应用接口,主动拉取Kafka集群中的消息数据。
另外,Kafka是一个分布式系统,用Zookeeper来管理、协调Kafka集群的各个代理(Broker)节点。当Kafka集群中新添加了一个代理节点,或者某一台代理节点出现故障时,Zookeeper服务将会通知生产者应用程序和消费者应用程序去其他的正常代理节点读写。
提示:
这里只需对Kafka的基本结构有一个宏观的认知即可,后面章会详细介绍Kafka的具体内容。
1.6 Kafka的使用范围
Kafka作为一个分布式消息队列系统,拥有处理海量数据的能力。它不仅在实时业务场景中有天然优势,而且在处理某些场景中的离线任务时也表现不俗,这得益于Kafka底层的通用性和其强大的应用接口(API)。
在实时业务场景中,Kafka能够和Spark、Flink、Storm等实时计算引擎完美地结合。同时,Kafka也提供了应用接口(API),可以将主题(Topic)中的数据导出到Hive仓库做离线计算。
1.6.1 Kafka的设计初衷
Kafka雏形由LinkedIn开发,设计之初被LinkedIn用来处理活动流数据和运营数据。
提示:
活动流数据,是指浏览器访问记录、页面搜索记录、查看网页详细记录等站点内容。
运营数据,是指服务器的基本指标,例如CPU、磁盘I/O、网络、内存等。
在后续版本迭代中,Kafka被设计成一个统一的平台,可用来处理大公司所有的实时数据。需要它能够满足以下需求。
1.高吞吐量
日常生活中所使用的支付宝、微信、QQ这类软件的用户量非常庞大,每秒产生的数据流量也非常巨大。面对这类场景,若要实时地聚合消息日志,必须具有高吞吐量才能支持高容量事件流。
2.高可用队列
分布式消息队列系统都具有异步处理机制。另外,分布式消息队列系统一般都拥有处理大量数据积压能力,以便支持其他离线系统的定期数据加载。
3.低延时
实时应用场景对时延的要求极为严格。耗时越少,则结果越理想。这意味着,设计出来的系统必须拥有低延迟处理能力。
4.分布式机制
系统还需具有支持分区、分布式、能实时处理消息等特点,并能在机器出现故障时保证数据不丢失。
为满足这些需求,Kafka拥有了许多独特的特性,这使得它更类似于数据库日志,而不是传统的消息传递系统。在1.6.2小节将介绍这些独特的特性。
1.6.2 Kafka的特性
如今Kafka的社区非常活跃,有大量的开发人员不断地改善Kafka的不足。在Kafka迭代过程中,每个版本中都会添加很多新特性。
1.异步生产数据
从Kafka 0.8.2起,生产者(Producer)写数据时不再区分同步和异步,所有的操作请求均以异步的方式发送,这样大大地提高了客户端写数据的效率。
异步方式将数据批量的发送到Kafka不同的代理(Broker)节点,因此也减少了Kafka服务端的资源开销。这种方式在与Kafka系统进行网络通信时,能够有效地减少等待时间。
2.偏移量迁移
在Kafka 0.8.2之前,消费者(Consumer)每次读取Kafka集群主题(Topic)中的数据时,会产生“消费”记录,比如偏移量(Offset)、“消费”线程信息、时间戳等信息。这些信息都保存在Zookeeper集群中,这样Zookeeper的性能会严重影响Kafka集群的吞吐量。
从Kafka 0.8.2版本开始,消费者(Consumer)应用程序可以把“消费”记录提交到Kafka集群,并以内部主题的方式进行存储,Kafka系统将其命名为_ _consumer_offsets。一直持续到Kafka 0.10.0版本,Kafka官网才将该特性设置为默认属性。
说明:
在Kafka集群主题中,_ _consumer_offsets属于内部主题。外界客户端应用程序无法直接读取该主题内的数据,需要设置特别的属性才能实现。
3.安全机制
在Kafka 0.9之前,Kafka系统是没有安全机制的,在通过外网进行数据传输时,只能通过设置Linux操作系统的防火墙或者网络安全来控制。如果用户使用的数据是非常敏感的(比如银行的交易流水记录),应用Kafka是让人非常担忧的,因为数据的安全性难以得到保证。
在Kafka 0.9版本以后,系统添加了安全机制,可以通过SSL和SASL安全机制来进行身份确认。生产者(Producer)和消费者(Consumer)必须进行身份验证,才能操作Kafka集群。
另外,Kafka代理(Broker)与Zookeeper集群进行连接时也需要身份验证。在设置了安全机制的Kafka集群中,数据均采用加密方式进行传输。由于加密方式依赖操作系统的CPU和Java虚拟机(Java Virtual Machine, JVM),所以,在采用加密方式传输数据时性能可能会降低。
提示:
在后面的章中会详细介绍Kafka的安全机制,这里作为一个特性让读者先有所了解。
4.连接器
Kafka在0.9版本中,添加了一个名为Connect的模块,即连接器。从命名上来看,它可以在外部系统与数据集之间建立一个数据流管道,以实现数据的读与写。
Kafka使用了一个通用的框架,可以在这个框架上非常便捷地开发和管理Kafka连接器(Connect)接口。Kafka连接器还支持在分布式模式或者单机模式下运行,并可以通过REST API提交和管理Kafka集群。
5.机架感知
Kafka 0.10及以后版本中添加了机架感知功能。引入机架感知的概念,能够显著提升Kafka集群的可用性。
如果所有备份数据都在一个单个机架上,一旦这个机架出现故障,则导致所有的备份数据变得不可用,这样是很危险的。所以,需要使用机架感知来让Kafka的备份数据分布到不同的机架上,以保证数据的高可用性。
6.数据流
在Kafka 0.10及以后版本中,添加了数据流特性。在实际业务场景中,如需将Kafka集群中的数据进行流处理之后再重新回写到Kafka集群中,那使用Kafka Streams(数据流)这一特性能够很轻易地实现。
Kafka Streams是一个用来处理流式数据的库,属于Java类库。它并不是一个流处理框架,与Flink、Storm、Spark等这类流处理框架是不一样的。
Kafka Streams不仅只是一个类库,它依然拥有一系列流处理功能,例如连接(JOIN)、过滤(Filter)、聚合(Aggregate)等,能够实现一个功能齐全、低延时的实时流处理。
7.时间戳
在Kafka 0.10及以后版本中,生产者(Producer)写入的每一条消息记录都加入了时间戳(Timestamp)。在写入消息的过程中,如果用户没有指定该消息的时间,则该消息的时间会被自动添加上。
Kafka数据流(Streams)实现了基于时间事件的实时流处理,用户可以使用时间戳来跟踪和查找消息记录。
8.消息语义
在Kafka 0.11.0.0版本中,实现了消息记录只处理一次(Exactly Once Semantics, EOS)。
在Kafka中,单个代理(Broker)节点可能会出现宕机,或者生产者(Producer)在向Kafka集群主题(Topic)发送消息时出现网络故障。Kafka生产者在处理这类异常行为时会有以下几种不同语义。
· 至少一次:如果在Kafka中设置ACKS=ALL,则意味着写入的消息至少有一条。如果生产者(Producer)等待Kafka集群服务端确认发生超时,或者收到服务端响应的错误码,则会触发重试机制。若是Kafka代理(Broker)节点在发送确认之前失败了,但是消息却成功写入到了Kafka集群主题(Topic),由于失败再次触发重试机制导致消息被重写,最终导致结果不正确。
· 至多一次:生产者(Produce)在发送消息到Kafka集群主题(Topic)时,最多允许消息成功写入一次,这样可避免数据重复。
· 精准一次:这是最符合要求的,但是也是最困难的。因为它需要消息传递系统与生产者和消费者的应用程序之间进行配合。在成功读取一条消息后,如果用户将Kafka的偏移量(Offset)的值回退到原点,则用户将会从回退的偏移量值开始读取消息,一直读取到最新的消息为止。
1.6.3 Kafka适用于哪些场景
在实际的使用场景中,Kafka有着广泛的应用。例如,日志收集、消息系统、活动追踪、运营指标、流式处理、事件源等。
1.日志收集
在实际工作中,系统和应用程序都会产生大量的日志。为了方便管理这些日志,可以利用Kafka将这些零散的日志收集到Kafka集群中,然后通过Kafka的统一接口将这些数据开放给不同的消费者(Consumer)。统一接口包括:Hadoop的应用接口、HBase的应用接口、ElasticSearch的应用接口等。
2.消息系统
线上业务流量很大的应用,可以使用Kafka作为缓冲,以减少服务端的压力。这样能够有效地解耦生产者(Producer)和消费者(Consumer),以及缓冲消息数据。
3.用户轨迹
可使用Kafka记录浏览器用户或者手机App用户产生的各种记录,例如浏览的网页、搜索的内容、点击的内容等。
这些用户活动信息会被服务器收集Kafka集群中进行存储,然后消费者通过“消费”这些活动数据来做实时分析,或者加载到Hive数据仓库做离线数据分析与挖掘。
4.记录运营监控数据
Kafka也可用来记录运营监控数据,包括收集各种分布式应用系统的数据(如Hadoop系统、Hive系统、HBase系统等)。
5.实现流处理
Kafka是一个流处理平台,所以在实际应用场景中也会与其他大数据套件结合使用,例如Spark Streaming、Storm、Flink等。
6.事件源
事件源是一种应用程序的设计风格,其中状态更改会产生一条带有时间戳的记录,然后将这条以时间序列产生的记录进行保存。在面对非常大的存储数据时,可以使用这种方式来构建非常优秀的后端程序。
1.7 小结
本章介绍了什么是消息队列,并引出了Kafka的相关知识。分别介绍了Kafka的起源、基本概念、基本结构,以及使用范围。
通过本章的学习,读者可以对Kafka有了一个大概的了解,知道了Kafka在实际工作中能做哪些事情。