My Octopress Blog

life and I

Apache Kafka 介绍

| Comments

来源

此项目最开始由Linkedln开发并开源的一个消息系统。Linkdeln将其作为 可读写的流以及服务状态的数据管道。对于一个网站来说,读写流 经常用来记录页面浏览信息,展示信息以及搜素信息,这些信息一般由日志 系统进行记录。服务状态包括当前机器的cpu, IO, 请求时间, 服务日志等。 近年来,服务状态日益成为衡量网站质量的标志。

使用读写流以及服务状态的例子

  • 消息广播(News feed) 朋友活动的广播
  • 用户评分以及相关性计算
  • 安全:需要可以进行监测恶意攻击,平衡集群负载等操作
  • 监控
  • 报表以及离线数据处理:hadoop

活跃数据特性

大流量的数据活跃无法确定大小。传统的日志方式是一种离线的处理方式 比如离线的报告和压缩。对于实时系统,这种方式时延就泰高了。现有的消息队列系统 处理周期。kafka作为一个队列系统可以分别处理离线以及实时的问题。

linkdedln的模块结构

一个kafka的队列系统可以对应于多个模块的数据处理。还可以利用其进行不同数据中心的 数据备份。

kafka集群并不是在数据中心中集中进行部署并提供服务,而是根据数据流拓扑部署多个 集群,不同集群间通过同步来进行数据流处理,其中镜像集群只是源集群的一个消费者。 下面的图示表示了这个过程

kafka设计

  • kafka为一般情况下的持久化消息队列系统
  • 设计约束主要考量吞吐量而不是功能
  • 消息处理模块记录所处理消息的状态,而不是消息提供模块
  • kafka被设计为分布式部署,其中消息的生产者,代理者和消费者可以分布于整个集群

基础

消息是模块间通信的基础单元,消息被发布到代理不同主题的服务器上,某些消费模块 订阅该主题,那么所有被发布的消息都被订阅该主题的消费者收到。

kafka的分布式方案对于生产者和代理者来说比较明晰,但是对于消费者,需要一个特别的 设置和支持。消费者组的概念可以类似于JMS中的队列以及主题,这里的消费者组 可以作为一个逻辑上的单一消费者出现在集群中。对于队列,可以把所有的消费者 作为一个组,而将不同的消费者组成不同的组则对应了主题。一般的情况是根据 不同的主题分为不同的逻辑组,逻辑组作为一个单独的对象出现在集群中。kafka的一个 额外特性是在大量的数据下,不论一个主题内有多少消费者,一个消息只存储一次。

消息持久化和缓存

Kafka的持久化和缓存依赖于文件系统。对于硬盘来说,顺序读写的性能是随机读写性能 的10000倍,某些情况下顺序访问硬盘比随机访问内存要快。

现代操作系统都会为硬盘申请内存作为缓存。操作系统会倾向于将所有空闲的内存 分配给硬盘作为缓存,虽然这样会造成内存重新申请的性能损失。硬盘通过该缓冲区进行 读写,这个特性除非用直接的I/O操作,否则不会轻易被关闭。所以,即使一个进程缓存了 所有的数据,该数据也可能被复制进系统页缓存中,这样造成所有的东西被存储了2次

另外,基于JVM的应用对于内存的使用存在两个问题

  1. 对象使用内存很高基本两倍于对象存储的内存
  2. 在堆内数据增长的时候,垃圾回收机制比较简单和消耗性能

由于上面两点,利用文件系统缓存机制要优于直接利用内存缓存或类似机制。因此 kafka至少保持可用内存的两倍大小的缓存,如果需要存储压缩数据,则变成4倍大小 因此,对于一个32GB内存的机器,kafka会保持28-30GB的缓存,而不需要担心GC影响。 另外,一旦服务重启,这个缓存可以快速的重新载入,而进程内缓存需要在内存中重新申请 或者由代码进行初始化。所以这种机制简化了代码逻辑,对于内存和文件系统的同步 由操作系统完成。

基于以上,这种设计非常简单:不是在内存中保存数据,然后需要的时候flush进硬盘 ,而是反过来,首先将所有数据写入文件系统的一个持久化日志中而不是由进程调用 flush数据操作。这意味着只要将数据写入内核页缓存中,然后给系统一个配置,多长 时间或者多大数据后将数据flush进硬盘中即可。

恒定的时间消耗

一般来说消息系统的元数据都是BTree存储的,虽然其可操作性强,时间开销是O(logN). 但是结合硬盘操作后,随机查找增多造成性能损耗严重。而日志系统的读取是顺序, 写入是直接在文件后进行添加,虽然比BTree没有更好的操作性,但是时间开销是O(1) 的,而且读取不会锁住写入操作或者彼此加锁。这样设计的一个显著优势是不再受数据大小的限制。 这样可以保存数据很长的时间,而不是一旦消息被分发就会被删除。

效率优化手段

假设消息量较大,但是对于每个发布的消息而言,消费的次数要大于生成的次数,因此 优化的手段在消费者更加有效。

两个影响性能的部分为

  • 大量的网络请求
  • 多余的数据拷贝

对于网络请求,根据消息特性进行分组,可以将同组消息打包为”message set”,每次 可以将多个消息打包后发送,而不是单独发送每个消息。对于MessageSet,接口简单 一般不需要进行序列化和反序列化

消息日志被broker保存其硬盘上。因此,即使单个字节的消息也可以被不同的brokeer和 consumer共享。

现代unix系统可以支持代码直接将页面缓存的数据直接发送给网络缓存,减少中间拷贝 次数。Linux中利用sendfile系统调用完成这个功能,Java提供了标准库调用FileChannel.transferTo 接口

一般来说对于通过socket发送系统中的数据需要通过以下几个步骤

  1. 内核空间上,硬盘读取到页面缓存
  2. 用户空间读取内核空间的数据
  3. 在将数据从用户空间写入到内核空间的socket缓存
  4. 系统将socket缓存中的数据拷贝到NIC缓存中

需要4此拷贝,2次系统调用。利用sendfile,可以直接将页面缓存拷贝到网络中, 因此只有第4步拷贝到NIC缓存中

对于同个主题的多个consumer,利用zero-copy技术,数据只需要拷贝到页面缓存中 一次,并且对于多个网络输出进行复用,因此速度基本可达网络硬件的上限。

端到端的压缩

某些时候,系统瓶颈不再于cpu而是网络能力。而由用户简单的将消息压缩后传送给 消息系统无法降低消息间的冗余,更有效的消息压缩应该是将一组消息进行压缩后传输, 更理想的是端到端的方式,数据传输前被压缩后通过producer传输给server,server 不解压直接传送给cosumer,然后由consumer进行解压。

kafka支持递归的消息集合。一组消息被打包后传输给server,然后这组消息被传输给 所有的consumer进行解压后处理。

消费者状态

分布式

生产者

支持hadoop和其他数据载入

实现细节

API设计

网络层设计

消息设计及格式

日志

分布式

#

Comments