Apache kafka 设计与实现

一、 设计 

kafka设计目标是实现一个高吞吐量的,低延迟的,实时数据处理的统一平台。在设计的时候从以下几个方面做了优化。

1.数据持久化

Kafka直接将数据写到了文件系统的日志中,依赖操作系统的page cache 实现read-ahead 和 write-behind。

这样做的好处是读和写都是 O(1) 的,并且读操作不会阻塞写操作和其他操作

2.消息传输的事务

数据传输的事务定义通常有以下三种级别: 

最多一次: 消息不会被重复发送,最多被传输一次,但也有可能一次不传输。 
最少一次: 消息不会被漏发送,最少被传输一次,但也有可能被重复传输. 
精确的一次(Exactly once): 不会漏传输也不会重复传输,每个消息都传输被一次而且仅仅被传输一次,这是大家所期望的。 

kafka 对传输事务的保证比较灵活,可以通过两阶段提交实现精确的一次。 也可以 实现成最多一次或者最少一次。

3.性能上的优化

3.1 消息传输批处理

Kafka建立了“消息集(message set)”,以消息集为单位,批量的处理消息。

3.2 字节拷贝优化

Kafka使用了标准的二进制消息格式,这个格式可以在producer,broker和producer之间共享而无需做任何改动。

3.3 通过sendfile和zero-copy  让消息从page cache  直接拷贝到网卡,数据不过java堆。

3.4 消息传输过程中可以选择GZIP,Snappy,LZ4压缩格式。

Kafka采用了端到端的压缩:因为有“消息集”的概念,客户端的消息可以一起被压缩后送到服务端,并以压缩后的格式写入日志文件,以压缩的格式发送到consumer,消息从producer发出到consumer拿到都被是压缩的,只有在consumer使用的时候才被解压缩,所以叫做“端到端的压缩”。

4.Produce 

Kafka允许用户实现分区函数,指定分区的key,将消息hash到不同的分区上(当然有需要的话,也可以覆盖这个分区函数自己实现逻辑).比如如果你指定的key是user id,那么同一个用户发送的消息都被发送到同一个分区上。经过分区之后,consumer就可以有目的的消费某个分区的消息。

批量发送可以很有效的提高发送效率。Kafka producer的异步发送模式允许进行批量发送,先将消息缓存在内存中,然后一次请求批量发送出去。这个策略可以配置的。

5.Consumer  

consumer从broker拉取消息。 

Kafka采用了不同的策略。Topic被分成了若干分区,每个分区在同一时间只被一个consumer消费。这意味着每个分区被消费的消息在日志中的位置仅仅是一个简单的整数:offset。这样就很容易标记每个分区消费状态就很容易了,仅仅需要一个整数而已。

6.离线处理消息 

高级的数据持久化允许consumer每个隔一段时间批量的将数据加载到线下系统中比如Hadoop或者数据仓库。这种情况下,Hadoop可以将加载任务分拆,拆成每个broker或每个topic或每个分区一个加载任务。Hadoop具有任务管理功能,当一个任务失败了就可以重启而不用担心数据被重新加载,只要从上次加载的位置继续加载消息就可以了。

二、实现

1. API

Producer APIs 

Procuder API有两种:kafka.producer.SyncProducer和kafka.producer.async.AsyncProducer.它们都实现了同一个接口: 

class Producer {

    /* Sends the data, partitioned by key to the topic using either the */
    /* synchronous or the asynchronous producer */
    public void send(kafka.javaapi.producer.ProducerData<K,V> producerData);

    /* Sends a list of data, partitioned by key to the topic using either */
    /* the synchronous or the asynchronous producer */
    public void send(java.util.List<kafka.javaapi.producer.ProducerData<K,V>> producerData);

    /* Closes the producer and cleans up */
    public void close();

    }

提供一个用户自定义的编码函数,实现数据编码

 interface Encoder<T> {
    public Message toMessage(T data);
    }

提供一个分片函数实现软负载聚合和数据分片

interface Partitioner<T> {
    int partition(T key, int numPartitions);
    }

Consumer API

low-level API

class SimpleConsumer {

    /* Send fetch request to a broker and get back a set of messages. */
    public ByteBufferMessageSet fetch(FetchRequest request);

    /* Send a list of fetch requests to a broker and get back a response set. */
    public MultiFetchResponse multifetch(List<FetchRequest> fetches);

    /**
    * Get a list of valid offsets (up to maxSize) before the given time.
    * The result is a list of offsets, in descending order.
    * @param time: time in millisecs,
    *              if set to OffsetRequest$.MODULE$.LATEST_TIME(), get from the latest offset available.
    *              if set to OffsetRequest$.MODULE$.EARLIEST_TIME(), get from the earliest offset available.
    */
    public long[] getOffsetsBefore(String topic, int partition, long time, int maxNumOffsets);
    }

high-level API

/* create a connection to the cluster */
    ConsumerConnector connector = Consumer.create(consumerConfig);

    interface ConsumerConnector {

    /**
    * This method is used to get a list of KafkaStreams, which are iterators over
    * MessageAndMetadata objects from which you can obtain messages and their
    * associated metadata (currently only topic).
    *  Input: a map of <topic, #streams>
    *  Output: a map of <topic, list of message streams>
    */
    public Map<String,List<KafkaStream>> createMessageStreams(Map<String,Int> topicCountMap);

    /**
    * You can also obtain a list of KafkaStreams, that iterate over messages
    * from topics that match a TopicFilter. (A TopicFilter encapsulates a
    * whitelist or a blacklist which is a standard Java regex.)
    */
    public List<KafkaStream> createMessageStreamsByFilter(
        TopicFilter topicFilter, int numStreams);

    /* Commit the offsets of all messages consumed so far. */
    public commitOffsets()

    /* Shut down the connector */
    public shutdown()
    }

2.传输中的消息格式

 /**
        * 1. 4 byte CRC32 of the message
        * 2. 1 byte "magic" identifier to allow format changes, value is 0 or 1
        * 3. 1 byte "attributes" identifier to allow annotations on the message independent of the version
        *    bit 0 ~ 2 : Compression codec.
        *      0 : no compression
        *      1 : gzip
        *      2 : snappy
        *      3 : lz4
        *    bit 3 : Timestamp type
        *      0 : create time
        *      1 : log append time
        *    bit 4 ~ 7 : reserved
        * 4. (Optional) 8 byte timestamp only if "magic" identifier is greater than 0
        * 5. 4 byte key length, containing length K
        * 6. K byte key
        * 7. 4 byte payload length, containing length V
        * 8. V byte payload
        */

参数说明:

关键字解释说明
8 byte offset在parition(分区)内的每条消息都有一个有序的id号,这个id号被称为偏移(offset),它可以唯一确定每条消息在parition(分区)内的位置。即offset表示partiion的第多少message
4 byte message sizemessage大小
4 byte CRC32用crc32校验message
1 byte “magic"表示本次发布Kafka服务程序协议版本号
1 byte “attributes"表示为独立版本、或标识压缩类型、或编码类型。
4 byte key length表示key的长度,当key为-1时,K byte key字段不填
K byte key可选
value bytes payload表示实际消息数据。

3. 日志

日志中消息格式

    offset         : 8 bytes 
    message length : 4 bytes (value: 4 + 1 + 1 + 8(if magic value > 0) + 4 + K + 4 + V)
    crc            : 4 bytes
    magic value    : 1 byte
    attributes     : 1 byte
    timestamp      : 8 bytes (Only exists when magic value is greater than zero)
    key length     : 4 bytes
    key            : K bytes
    value length   : 4 bytes
    value          : V bytes

写message

  • 消息从java堆转入page cache(即物理内存)。
  • 由异步线程刷盘,消息从page cache刷入磁盘。

读message

  • 消息直接从page cache转入socket发送出去。
  • 当从page cache没有找到相应数据时,此时会产生磁盘IO,从磁
    盘Load消息到page cache,然后直接从socket发出去
消息以下面形式发送给客户端

MessageSetSend (fetch result)

    total length     : 4 bytes
    error code       : 2 bytes
    message 1        : x bytes
    ...
    message n        : x bytes

MultiMessageSetSend (multiFetch result)

    total length       : 4 bytes
    error code         : 2 bytes
    messageSetSend 1
    ...
    messageSetSend n


参考:

1.  kafka官网:http://kafka.apache.org/documentation/#majordesignelements

2.  http://blog.csdn.net/jewes/article/details/42970799

3.  http://tech.meituan.com/kafka-fs-design-theory.html

4.  page cache 机制:http://blog.csdn.net/thewayma/article/details/4287170



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值