« 上一篇下一篇 »

以Kafka 为中心的解决方案

以Kafka 为中心的解决方案

《Apache Kafka源码剖析》第1章是Kafka的快速入门,其中介绍了Kafka的背景、特性以及应用场景。之后介绍了笔者在实践中遇到的一个以Kafka为中心的案例,并分析了在此案例中选择使用Kafka的具体原因和Kafka起到的关键作用。最后介绍了Kafka中的核心概念和Kafka源码调试环境的搭建。本节以Kafka 为中心的解决方案。 

1.2 以Kafka 为中心的解决方案

在大数据、高并发的系统中,为了突破瓶颈,会将系统进行水平扩展和垂直拆分,形成独立的服务。每个独立的服务背后,可能是一个集群在对外提供服务。这就会碰到一个问题,整个系统是由多个服务(子系统)组成的,数据需要在各个服务中不停流转。如果数据在各个子系统中传输时,速度过慢,就会形成瓶颈,降低整个系统的性能。

下面介绍的场景是笔者工作中遇到的一个案例,在一个政企信息化的云平台网站上,用户与网站交互的很多操作行为(例如,浏览某些新闻等)都会被记录下来,等待后台的多个子系统进行消费,其中比较重要的几个子系统是利用这些数据进行机器学习或是数据挖掘,产生用户的侧写。这样,网站就可以根据用户的侧写,推送给他们需要的配置和查询信息。图1-1 就是这个云平台系统的架构图,其中每一个箭头都表示一条数据流。

上面的架构中涉及的子系统、存储、服务种类繁多,而且它们之间都存在较强的耦合,会出现下面的问题:

由于子系统之间存在的耦合性,两个存储之间要进行数据交换的话,开发人员就必须了解这两个存储系统的API,不仅是开发成本,就连维护成本也会很高。一旦其中一个子系统发生变化,就可能影响其他多个子系统,这简直就是一场灾难。

在某些应用场景中,数据的顺序性尤为重要,一旦数据出现乱序,就会影响最终的计算结果,降低用户体验,这就提高了开发的难度。

除了考虑数据顺序性的要求,还要考虑数据重传等提高可靠性的机制,毕竟通过网络进行传输并不可靠,可能出现丢失数据的情况。

进行数据交换的两个子系统,无论哪一方宕机,重新上线之后,都应该恢复到之前的传输位置,继续传输。尤其是对于非幂等性的操作,恢复到错误的传输位置,就会导致错误的结果。

随着业务量的增长,系统之间交换的数据量会不断地增长,水平可扩展的数据传输方式就显得尤为重要。

针对这个案例,我们看看Kafka 是如何有效地解决上面的这些问题的(Kafka 中的相关概念可以参见下文相关内容)。

解耦合

将Kafka 作为整个系统的中枢,负责在任意两个系统之间传递数据。架构如图1-2 所示,所有的存储只与Kafka 通信,开发人员不需要再去了解各个子系统、服务、存储的相关接口,只需要面向Kafka 编程即可。这样,在需要进行数据交换的子系统之间形成了一个基于数据的接口层,只有这两者知道消息存放的Topic、消息中数据的格式。当需要扩展消息格式时,只需要修改相关子系统的Kafka 客户端即可。这样,与Kafka 通信的模块就可以实现复用,Kafka 则承担数据总线的作用。更简单点说,就像是一个生产者—消费者模式,而Kafka 则扮演其中“队列”的角色。

数据持久化

在分布式系统中,各个组件是通过网路连接起来的。一般认为网络传输是不可靠的,当数据在两个组件之间进行传递的时候,传输过程可能会失败。除非数据被持久化到磁盘,否则就可能造成消息的丢失。Kafka把数据以消息的形式持久化到磁盘,即使Kafka出现宕机,也可以保证数据不会丢失,通过这一方式规避了数据丢失风险。为了避免磁盘上的数据不断增长,Kafka提供了日志清理、日志压缩等功能,对过时的、已经处理完成的数据进行清除。在磁盘操作中,耗时最长的就是寻道时间,这是导致磁盘的随机I/O性能很差的主要原因。为了提高消息持久化的性能,Kafka采用顺序读写的方式访问,实现了高吞吐量。

扩展与容灾

Kafka的每个Topic(主题)都可以分为多个Partition(分区),每个分区都有多个Replica(副本),实现消息冗余备份。每个分区中的消息是不同的,这类似于数据库中水平切分的思想,提高了并发读写的能力。而同一分区的不同副本中保存的是相同的消息,副本之间是一主多从的关系,其中Leader副本负责处理读写请求,Follower副本则只与Leader副本进行消息同步,当Leader副本出现故障时,则从Follower副本中重新选举Leader副本对外提供服务。这样,通过提高分区的数量,就可以实现水平扩展;通过提高副本的数量,就可以提高容灾能力。

Kafka的容灾能力不仅体现在服务端,在Consumer端也有相关设计。Consumer使用pull方式从服务端拉取消息,并且在Consumer端保存消费的具体位置,当消费者宕机后恢复上线,可以根据自己保存的消费位置重新拉取需要的消息进行消费,这就不会造成消息丢失。也就是说,Kafka不决定何时、如何消费消息,而是Consumer自己决定何时、如何消费消息。

Kafka还支持Consumer的水平扩展能力。我们可以让多个Consumer加入一个Consumer Group(消费组),在一个Consumer Group中,每个分区只能分配给一个Consumer消费,当Kafka服务端通过增加分区数量进行水平扩展后,我们可以向Consumer Group中增加新的Consumer来提高整个Consumer Group的消费能力。当Consumer Group中的一个Consumer出现故障下线时,会通过Rebalance操作将下线Consumer负责处理的分区分配给其他Consumer继续处理;当下线Consumer重新上线加入Consumer Group时,会再进行一次Rebalance操作,重新分配分区。当然,一个COnsumer Group可以订阅很多不同的Topic,每个Consumer可以同时处理多个分区。

顺序保证

在很多场景下,数据处理的顺序都很重要,不同的顺序就可能导致不同的计算结果。Kafka保证一个Partition内消息的有序性,但是并不保证多个partition之间的数据有顺序。

缓冲&峰值处理能力

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如图1-3 所示,在9 点到10 点之间,是此云平台系统的访问峰值,而其他时间的访问量则很少。

如果按照处理这类峰值请求量为标准来投入资源的话,会有相当一部分资源处于待命状态,这无疑是巨大的浪费。使用Kafka 能够使关键组件顶住突发的访问压力,而不会因为突发的峰值请求而使系统完全崩溃不可用。

异步通信

Kafka 为系统提供了异步处理能力。例如,两个系统需要通过网络进行数据交换,其中一端可以把一个消息放入Kafka 中后立即返回继续执行其他路基,不需要等待对端的响应。待后者将处理结果放入Kafka 中之后,前者可以从其中获取并解析响应。