Kafka 0.8翻译官网精华.md
1主要的设计元素
Kafka之所以和其它绝大多数信息系统不同,是因为下面这几个为数不多的比较重要的设计决策:
- Kafka在设计之时为就将持久化消息作为通常的使用情况进行了考虑。
- 主要的设计约束是吞吐量而不是功能。
- 有关哪些数据已经被使用了的状态信息保存为数据使用者(consumer)的一部分,而不是保存在服务器之上。
- Kafka是一种显式的分布式系统。它假设,数据生产者(producer)、代理(brokers)和数据使用者(consumer)分散于多台机器之上。
1.1.消息持久化(Message Persistence)及其缓存
- 在一个由6个7200rpm的SATA硬盘组成的RAID-5磁盘阵列上,线性写入(linear write)的速度大约是300MB/秒,但随即写入却只有50k/秒,其中的差别接近10000倍(在新的kafka集群里面,RAID-5,Kafka的磁盘写入速度,可以稳定在600M/s)。
- 消息系统元数据的持久化数据结构往往采用BTree,Btree运算的时间复杂度为O(log N)。
1.2.传输效率的最大化
导致低效率的原因常见的有两个:过多的网络请求和大量的字节拷贝操作。
为了将数据从页面缓存直接传送给socket,在Linux中这是通过sendfile这个系统调用实现的。通过Java中的API,FileChannel.transferTo
,由它来简洁的调用上述的系统调用。
为了理解sendfile所带来的效果,重要的是要理解将数据从文件传输到socket的数据路径(4次拷贝,2次系统调用):
- OS将数据从Disk中读取到内核空间里的页面缓存
- 应用程序将数据从内核空间读入到用户空间的缓冲区
- 应用程序将读到的数据写回内核空间并放入socke的缓冲区
- 操作系统将数据从socket的缓冲区拷贝到NIC(网络借口卡,即网卡)的缓冲区,到此数据才能通过网络发送出去
Kafka只有2步,第①和④步,数据只需拷贝到页面缓存中一次,然后让OS直接将数据从页面缓存发送到网络NIC的缓冲区。目的是让消息使用的速度就能接近网络连接的极限。
1.3.端到端的批量压缩
Kafka支持GZIP和Snappy压缩协议。
1.4.消息传递语义(Message delivery semantics)
Producer:①至少一次 ②最多一次
Consumer: 至少一次
2.生产者
2.1 生产者自动负载均衡 -- TODO:需要详细看一下
生产者在其内部为每一个代理维护了一个弹性的连接(同代理建立的连接)池。通过使用zookeeper监视器的回调函数(callback),该连接池在建立/保持同所有在线代理的连接时都要进行更新。当生产者要求进入某特定话题时,由分区者(partitioner)选择一个代理分区(参加语义分区小结)。从连接池中找出可用的生产者连接,并通过它将数据发送到刚才所选的代理分区。
2.2异步发送
对于可伸缩的消息系统而言,异步非阻塞式操作是不可或缺的。在Kafka中,生产者有个选项(producer.type=async)可用指定使用异步分发出产请求(produce request)。这样就允许用一个内存队列(in-memory queue)把生产请求放入缓冲区,然后再以某个时间间隔或者事先配置好的批量大小将数据批量发送出去。因为一般来说数据会从一组以不同的数据速度生产数据的异构的机器中发布出,所以对于代理而言,这种异步缓冲的方式有助于产生均匀一致的流量,因而会有更佳的网络利用率和更高的吞吐量。
2.3生产者 APIs
生产者 API 是给两个底层生产者的再封装
- kafka.producer.SyncProducerandkafka.producer.async.AsyncProducer.
生产者可以
- 对多个生产者请求进行排队/缓冲并异步发送批量数据 —— kafka.producer.Producer提供了在将多个生产请求序列化并发送给适当的Kafka代理分区之前,对这些生产请求进行批量处理的能力(producer.type=async)。批量的大小可以通过一些配置参数进行控制。当事件进入队列时会先放入队列进行缓冲,直到时间到了queue.time或者批量大小到达batch.size为止,后台线程(kafka.producer.async.ProducerSendThread)会将这批数据从队列中取出,交给kafka.producer.EventHandler进行序列化并发送给适当的kafka代理分区。通过event.handler这个配置参数,可以在系统中插入一个自定义的事件处理器。在该生产者队列管道中的各个不同阶段,为了插入自定义的日志/跟踪代码或者自定义的监视逻辑,如能注入回调函数会非常有用。通过实现kafka.producer.asyn.CallbackHandler接口并将配置参数callback.handler设置为实现类就能够实现注入。
- 提供基于zookeeper的代理自动发现功能 —— 通过使用zk.connect配置参数指定zookeeper的连接url,就能够使用基于zookeeper的代理发现和负载均衡功能。在有些应用场合,可能不太适合于依赖zookeeper。在这种情况下,生产者可以从broker.list这个配置参数中获得一个代理的静态列表,每个生产请求会被随即的分配给各代理分区。如果相应的代理宕机,那么生产请求就会失败。
- 通过使用一个可选性的、由用户指定的Partitioner,提供由软件实现的负载均衡功能 —— 数据发送路径选择决策受kafka.producer.Partitioner的影响。
2.4 消费者API
该API的中心是一个由KafkaStream这个类实现的迭代器(iterator)。每个KafkaStream都代表着一个从一个或多个分区到一个或多个服务器的消息流。每个流都是使用单个线程进行处理的,每个分区只会把数据发送给一个流中。
3.网络层
网络层就是一个特别直截了当的NIO服务器。sendfile是通过给MessageSet接口添加了一个writeTo方法实现的。这样就可以让基于文件的消息更加高效地利用transferTo实现,而不是使用线程内缓冲区读写方式。线程模型用的是一个单个的接收器(acceptor)线程和每个可以处理固定数量网络连接的N个处理器线程。
3.1 写操作
日志可以顺序添加,添加的内容总是保存到最后一个文件。当大小超过配置中指定的大小(比如说1G)后,该文件就会换成另外一个新文件。
有关日志的配置参数有两个,
- 一个是M,用于指出写入多少条消息之后就要强制OS将文件刷新到磁盘;
- 另一个是S,用来指定过多少秒就要强制进行一次刷新。这样就可以保证一旦发生系统崩溃,最多会有M条消息丢失,或者最长会有S秒的数据丢失,
3.2 读操作
可以通过给出消息的64位逻辑偏移量和S字节的数据块最大的字节数对日志文件进行读取。读取操作返回的是这S个字节中包含的消息的迭代器。S应该要比最长的单条消息的字节数大,但在出现特别长的消息情况下,可以重复进行多次读取,每次的缓冲区大小都加倍,直到能成功读取出这样长的一条消息。也可以指定一个最大的消息和缓冲区大小并让服务器拒绝接收比这个大小大一些的消息,这样也能给客户端一个能够读取一条完整消息所需缓冲区的大小的上限。很有可能会出现读取缓冲区以一个不完整的消息结尾的情况,这个情况用大小界定(size delimiting)很容易就能探知。
3.3 删除
一次只能删除一个日志Segment的数据。 LogManager允许通过可加载的删除策略设定删除的文件。 当前策略删除修改事件超过 N 天以上的文件,也可以选择保留最后 N GB 的数据。 为了避免删除时的读取锁定冲突,我们可以使用副本写入模式,以便在进行删除的同时对日志段的一个不变的静态快照进行二进制搜索。
3.4 数据正确性保证
在系统启动时会运行一个日志恢复过程,对最新的日志段内所有消息进行迭代,以对每条消息项的有效性进行验证。一条消息项是合法的,仅当其大小加偏移量小于文件的大小并且该消息中有效载荷的CRC32值同该消息中存储的CRC值相等。在探测出有数据损坏的情况下,就要将文件按照最后一个有效的偏移量进行截断。
3.5 Consumer
有一个Rebalance的触发时机以及对应的算法。
posted on 2017-02-06 14:50 BYRHuangQiang 阅读(312) 评论(0) 编辑 收藏 举报