- 硬盘在线性读写的情况下性能优异
- 减少JVM的GC触发
- 写
Kafka在读方面使用了sendfile这个高级系统函数,也即zero-copy技术,感兴趣的同学可以去阅读IBM的文章。这项技术通过减少系统拷贝次数,极大地提高了数据传输的效率。
为了理解sendfile所带来的效果,重要的是要理解将数据从文件传输到socket的数据路径:
- 操作系统将数据从磁盘中读取到内核空间里的页面缓存
- 应用程序将数据从内核空间读入到用户空间的缓冲区
- 应用程序将读到的数据写回内核空间并放入socke的缓冲区
- 操作系统将数据从socket的缓冲区拷贝到NIC(网络借口卡,即网卡)的缓冲区,自此数据才能通过网络发送出去
这样效率显然很低,因为里面涉及4次拷贝,2次系统调用。使用sendfile就可以避免这些重复的拷贝操作,让OS直接将数据从页面缓存发送到网络中,其中只需最后一步中的将数据拷贝到NIC的缓冲区。
我们预期的一种常见的用例是一个话题拥有多个消息使用者。采用前文所述的零拷贝优化方案,数据只需拷贝到页面缓存中一次,然后每次发送给使用者时都对它进行重复使用即可,而无须先保存到内存中,然后在阅读该消息时每次都需要将其拷贝到内核空间中。如此一来,消息使用的速度就能接近网络连接的极限。
要得到Java中对send'file和零拷贝的支持方面的更多背景知识,请参考IBM developerworks上的这篇文章。
- 读
多数情况下系统的瓶颈是网络而不是CPU。这一点对于需要将消息在个数据中心间进行传输的数据管道来说,尤其如此。当然,无需来自Kafka的支持,用户总是可以自行将消息压缩后进行传输,但这么做的压缩率会非常低,因为不同的消息里都有很多重复性的内容(比如JSON里的字段名、web日志中的用户代理或者常用的字符串)。高效压缩需要将多条消息一起进行压缩而不是分别压缩每条消息。理想情况下,以端到端的方式这么做是行得通的 —— 也即,数据在消息生产者发送之前先压缩一下,然后在服务器上一直保存压缩状态,只有到最终的消息使用者那里才需要将其解压缩。
通过运行递归消息集,Kafka对这种压缩方式提供了支持。一批消息可以打包到一起进行压缩,然后以这种形式发送给服务器。这批消息都会被发送给同一个消息使用者,并会在到达使用者那里之前一直保持为被压缩的形式。
Kafka支持GZIP和Snappy压缩协议。关于压缩的更多更详细的信息,请参见这里。
- 最多一次—这种用于处理前段文字所述的第一种情况。消息在发出后立即标示为已使用,因此消息不会被发出去两次,但这在许多故障中都会导致消息丢失。
- 至少一次—这种用于处理前文所述的第二种情况,系统保证每条消息至少会发送一次,但在有故障的情况下可能会导致重复发送。
- 仅仅一次—这种是人们实际想要的,每条消息只会而且仅会发送一次。
- 代理(broker)将数据流划分为一组互相独立的分区。这些分区的语义由生产者(producer)定义,由生产者来指定每条消息属于哪个分区。一个分区内的消息以到达代理的时间为准进行排序,将来按此顺序将消息发送给使用者。这么一来,就用不着为每一条消息保存一条元数据(比如说,将消息标示为已使用)了,我们只需为使用者、话题和分区的每种组合记录一个“最高水位标记”(high water mark)即可。因此,标示使用者状态所需的元数据总量实际上特别小。在Kafka中,我们将该最高水位标记称为“偏移量”(offset)。
- 在Kafka中,使用者(consumer)将消息状态信息保存到它们的消息处理节点的那个数据存储(datastore)中。这样就消除了分布式的部分,从而解决了分布式中的一致性问题!这在非事务性系统中也有类似的技巧可用。搜索系统可用将使用者状态信息同它的索引段(index segment)存储到一起。尽管这么做可能无法保证数据的持久性(durability),但却可用让索引同使用者状态信息保存同步:如果由于宕机造成有一些没有刷新到磁盘的索引段信息丢了,我们总是可用从上次建立检查点(checkpoint)的偏移量处继续对索引进行处理。与此类似,Hadoop的加载作业(load job)从Kafka中并行加载,也有相同的技巧可用。每个Mapper在map任务结束前,将它使用的最后一个消息的偏移量存入HDFS。这个决策还带来一个额外的好处。使用者可用故意回退(rewind)到以前的偏移量处,再次使用一遍以前使用过的数据。虽然这么做违背了队列的一般协约(contract),但对很多使用者来讲却是个很基本的功能。举个例子,如果使用者的代码里有个Bug,而且是在它处理完一些消息之后才被发现的,那么当把Bug改正后,使用者还有机会重新处理一遍那些消息。
- broker的部署是一种no central master的概念,并且每个节点都是同等的,节点的增加和减少都不需要改变任何配置。
- producer和consumer通过zookeeper去发现topic,并且通过zookeeper来协调生产和消费的过程。
- producer、consumer和broker均采用TCP连接,通信基于NIO实现。Producer和consumer能自动检测broker的增加和减少。
具有伸缩性的持久化方案使得Kafka可支持批量数据装载,能够周期性将快照数据载入进行批量处理的离线系统。我们利用这个功能将数据载入我们的数据仓库(data warehouse)和Hadoop集群。
批量处理始于数据载入阶段,然后进入非循环图(acyclic graph)处理过程以及输出阶段(支持情况在这里)。支持这种处理模型的一个重要特性是,要有重新装载从某个时间点开始的数据的能力(以防处理中有任何错误发生)。对于Hadoop,我们通过在单个的map任务之上分割装载任务对数据的装载进行了并行化处理,分割时,所有节点/话题/分区的每种组合都要分出一个来。Hadoop提供了任务管理,失败的任务可以重头再来,不存在数据被重复的危险。
- Topic:一个Topic可以认为是一类消息,每个topic将被分成多个partition(区),每个partition在存储层面是append log文件.任何发布到此partition的消息都会直接追加到log文件的尾部,每条消息在文件中的位置称为offset(偏移量),offset为一个long型数字,它唯一的标记一条消息.kafka并没有提供其他额外的索引机制来存储offset,因为在kafka中几乎不允许对消息进行"随机读-写"。不同的数据可以按照不同的topic存储。
- Message:由消息生产者(producer)发布关于某话题(topic)的消息。在kafka中,消息是被发布到broker的topic中。而consumer也是从相应的topic中拿数据。也就是说,message是按topic存储的。
- Partition:同一个topic下可以设置多个partition,目的是为了提高并行处理的能力。可以将同一个topic下的message存储到不同的partition下。
- Offset:kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka。
- Producers:将消息发布到指定的Topic中,同时Producer也能决定将此消息归属于哪个partition;比如基于"round-robin"方式或者通过其他的一些算法等。
- Consumer Group:生产者(producer)、消费者(consumer)和代理(broker)都可以运行在作为一个逻辑单位的、进行相互协作的集群中不同的机器上。对于代理和生产者,这么做非常自然,但使用者却需要一些特殊的支持。每个消费者进程都属于一个使用者小组(consumer group)
。准确地讲,每条消息都只会发送给每个使用者小组中的一个进程。因此,使用者小组使得许多进程或多台机器在逻辑上作为一个单个的使用者出现。使用者小组这个概念非常强大,可以用来支持JMS中队列(queue)或者话题(topic)这两种语义。 -
- 如果所有的consumer都具有相同的group,这种情况和P2P模式的Queue模式很像;消息将会在consumers之间负载均衡。
- 如果所有的consumer都具有不同的group, 这种情况和Pub/Sub模式的Topic模式很像
;消息将会广播给所有的消费者。
- Distribution:
一个Topic的多个partitions,被分布在kafka集群中的多个server上;每个server(kafka实例)负责partitions中消息的读写操作;此外kafka还可以配置partitions需要备份的个数(replicas),每个partition将会被备份到多台机器上,以提高可用性。 基于replicated方案,那么就意味着需要对多个备份进行调度;每个partition都有一个server为"leader";leader负责所有的读写操作,如果leader失效,那么将会有其他follower来接管(成为新的leader);follower只是单调的和leader跟进,同步消息即可..由此可见作为leader的server承载了全部的请求压力,因此从集群的整体考虑,有多少个partitions就意味着有多少个"leader",kafka会将"leader"均衡的分散在每个实例上,来确保整体的性能稳定。
- Server-1 broker其实就是kafka的server,因为producer和consumer都要去连它。Broker主要还是做存储用。
- Server-2是zookeeper的server端,zookeeper负责集群调度,它维持了一张表,记录了各个节点的IP、端口等信息(以后还会讲到,它里面还存了kafka的相关信息)。
- Server-3、4、5他们的共同之处就是都配置了zkClient,更明确的说,就是运行前必须配置zookeeper的地址,道理也很简单,这之间的连接都是需要zookeeper来进行分发的。
- Server-1和Server-2的关系,他们可以放在一台机器上,也可以分开放,zookeeper也可以配集群。目的是防止某一台挂了。
- 启动zookeeper的server
- 启动kafka的server
- Producer如果生产了数据,会先通过zookeeper找到broker,然后将数据存放进broker
- Consumer如果要消费数据,会先通过zookeeper找对应的broker,然后消费。
- 在Kafka中,一个Topci可以分成若干个Partition,并尝试均匀的存储在可用的Broker集群中。可以对每个Broker和每个Topic设置 Partition数量
,每个Partition都会分配一个ID。每个Partition又可以分成若干个Replication备份在其他可用的Broker集群中,但每个Broker只能为每个Partition存放一个Replication,Replication的ID就是该宿主Broker的ID。例如一个Topic的Partition=4,Replica=3,那么Kafka将会为每个Partition创建3个Replica,一共12个,每个Partition的Replica都会选择一个Broker作为Leader。 - 默认情况下,生产消息和消费消息的模式为Sync同步模式,这样生产者使用send()方法提交写入blocks将会等到在所有可用的Broker中完成Replication,这样可以保证消息不会丢失,生产架构推荐
Replica=3。如果对延迟敏感的生产者,那么可以调整写入blocks会Async异步模式,或者只写入Broker Leader,那么这样做的后果就是放弃消息可靠性,因为消费者只能消费他们提交的消息,而看不见备份的消息。 - 对于高容量的Topic,每个Broker都会配置超过1个Partition。
Partition的增加将会增加Write写操作的I/O并非,同时也会增加消费者Consumer的并发度,因为Partition是分布式消息的处理单元。也就是说越多的Partition将会增加:a)产生越多的文件和打开越多的文件句柄;b)更多的偏移量offset需要被Consumer确认,从而增加Zookeeper的负载;
- 为Broker配置auto.create.topics.enable,当Broker接收到该Topic的第一个消息时,将通过num.partitions和default.replication.factor创建Topic
- 使用bin/kafka-create-topic.sh和bin/kafka-list-topic.sh命令创建和查看Topic
- public void send(KeyedMessage message);
向单个主题Topic发送消息,通过Key键分区,同时支持同步、异步生产者 - public void send(KeyedMessage message);
向多个主题Topic发送消息 - public void close();
向所有的Kafka Brokers关闭生产者Producer连接池
- kafka.consumer.Consumer
-
- public static kafka.javaapi.consumer.ConsumerConnector createJavaConsumerConnec
tor(config: ConsumerConfig); 创建一个ConsumerConnector
- public static kafka.javaapi.consumer.ConsumerConnector createJavaConsumerConnec
-
kafka.javaapi.consumer.ConsumerConnector
- public Map>>
createMessageStreams(Map topicCountMap, Decoder keyDecoder, Decoder valueDecoder); 根据泛型,为每个主题Topic创建一个消息流集合 - public Map>> createMessageStreams(Map
Integer> topicCountMap); 根据默认编码decoder,为每个主题Topic创建一个默认的消息流集合 - public List>
createMessageStreamsByFi lter(TopicFilter topicFilter, int numStreams, Decoder - keyDecoder, Decoder valueDecoder); 根据泛型,以及所匹配的通配符和返回的消息流数目,
返回对应的消息流集合 - public List> createMessageStreamsByFi
lter(TopicFiltertopicFilter, int numStreams); 根据默认编码decoder,以及所匹配的通配符和返回的消息流数目,返回对应的消息流集合 - public List> createMessageStreamsByFi
lter(TopicFiltertopicFilter); 根据默认编码decoder,以及所匹配的通配符,返回默认的消息流集合 - public void commitOffsets(); 向该connector所连接的所有主题Topic或者分区Partition提交偏移量
Offset - public void shutdown(); 关闭该connector
- kafka.javaapi.consumer.SimpleConsumer
-
- public FetchResponse fetch(request: kafka.javaapi.FetchRequest); 从一个主题Topic获取消息
- public kafka.javaapi.TopicMetadataResponse send(request: kafka.javaapi.TopicMetadataRequest); 获取一组主题Topic的元数据
- public kafka.javaapi.OffsetResponse getOffsetsBefore(request: OffsetRequest); 根据给定时间,获取有效的偏移量
- public void close(); 关闭该SimpleConsumer
- public FetchResponse fetch(request: kafka.javaapi.FetchRequest); 从一个主题Topic获取消息
- 安装
- 启动broker,前提zookeeper已启动
- 创建主题
- 查看主题:
- partition:该topic的分区
- leader:该partition的leader,负责处理所有的读、写操作,每个broker节点都会成为随机分配partition的leader
- replicas:该partition的副本所在节点列表,无论该节点是否可用
- isr:该partition的副本同步的节点列表,这是副本节点列表的子集,显示了目前跟随leader还可用的节点信息
- 创建消息
- 接收消息
- 测试容错
- 删除主题
c)删除zookeeper数据
d)重启Kafka集群
- 设置controlled.shutdown.enable=true,那么Broker在shundown之前,会根据controlled.shutdown.max.retries
和controlled.shutdown.retry.backoff.ms的配置情况,尝试至多交权次数和尝试交权时间间隔 - 使用命令行工具./kafka-run-class.sh kafka.admin.ShutdownBroker --zookeeper 172.16.38.220:2181/ --broker 2 --num.retries 3 --retry.interval.ms 60。注意:命令行工具并不会终结Broker进程,而只是将其所负责的Partition交由其他Broker管理。但由于
Controller 已经Shutdown,因此虽然Kafka的Broker进程还在,依旧无法正常使用,必须收到
- Borker:
- Consumer:
- Producer: