一、kafka简介
Apache Kafka是分布式发布-订阅消息系统。它最初由LinkedIn公司于2010年12月份开源,之后成为Apache项目的一部分。Kafka是一种快速、可扩展的、设计内在就是分布式的,分区的和可复制的提交日志服务。
参考:https://blog.csdn.net/suifeng3051/article/details/48053965
1.应用场景
日志收集:一个公司可以用Kafka可以收集各种服务的log,通过kafka以统一接口服务的方式开放给各种consumer,例如hadoop、Hbase、Solr等。
用户活动跟踪:Kafka经常被用来记录web用户或者app用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到kafka的topic中,然后订阅者通过订阅这些topic来做实时的监控分析,或者装载到hadoop、数据仓库中做离线分析和挖掘。
运营指标:Kafka也经常用来记录运营监控数据。包括收集各种分布式应用的数据,生产各种操作的集中反馈,比如报警和报告。
流式处理:比如接入到spark streaming和storm中。2.特点
– 消息持久化:通过O(1)的磁盘数据结构提供数据的持久化,并且支持数据备份防止数据丢失。其快速读写的性能得益于预读和后写两个特性。【 预读:提前把下条记录读到内存中。后写:一次写入多个操作,要求必须是顺序读写,这种操作系统级别的,对磁盘的一个顺序访问要比对内存随机访问还要快】
– 高吞吐量:每秒百万级的消息读写,每秒可以处理上百兆的数据
– 分布式:扩展能力强
– 多客户端支持:java、 php、 python、 c++ ……
– 实时性:生产者生产的message立即被消费者可见(message是kafka的基本单位)
– 高并发:支持数千个客户端同时读写
二、kafka架构组件
Kafka中发布订阅的对象是topic。我们可以为每类数据创建一个topic,把向topic发布消息的客户端称作producer,从topic订阅消息的客户端称作consumer。Producers和consumers可以同时从多个topic读写数据。一个kafka集群由一个或多个broker服务器组成,它负责持久化和备份具体的kafka消息。
• Broker:每一台机器叫一个Broker,broker数量越多,吞吐越强。
• Producer:消息生产者,用来写数据
• Consumer:消息消费者,用来读数据
• Topic:不同消费者去指定的Topic中读,不同的生产者往不同的Topic中写,topic是个逻辑概念,partition是topic的物理实现,一个topic由一个或者多个partition组成,可以提高并行能力。
• Partition:partition物理上由多个segment组成,每个partition内部message有顺序,但是全局没法保证顺序。每个Partition代表一个并行单元,也就是说partition的存在是为了负载均衡,提高并行度。
下面具体讲解这些概念。
1.架构特点
Kafka同时为发布和订阅提供高吞吐量,每秒可以生产约25万消息(约50 MB),每秒处理55万消息(约110 MB),生产和消费同时进行。可进行数据持久化操作,将消息持久化到磁盘,可用于批量消费,例如ETL,以及实时应用程序。通过将数据持久化到硬盘以及replication防止数据丢失。
Kafka可构建分布式系统,易扩展。所有的producer、broker和consumer都会有多个,均为分布式。无需停机即可扩展集群规模。消息被处理的状态是在consumer端维护,而不是由server端维护。Kafka需要zookeeper支撑,在集群中Producer不依赖zookeeper,consumer依赖zookeeper。
2.Topic和Partition
• 消息发送时都被发送到一个topic,其本质就是一个目录。topic是个虚拟的概念,topic实际由一些Partition Logs(分区日志)组成,其组织结构如下图所示:
我们可以看到,每个Partition中的消息都是有序的,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值。
Kafka集群会保存所有的消息,不管消息有没有被消费;我们可以设定消息的过期时间(默认七天),只有过期的数据才会被自动清除以释放磁盘空间。
Kafka需要维持的元数据只有一个–消费消息在Partition中的offset值,通过offset值能够知道信息具体在哪个位置。
Consumer每消费一个消息,就会自动移动到下一个信息上,也就是自己记录的offset值在不断增大。其实消息的消费完全是由Consumer控制的,Consumer可以跟踪和重设这个offset值,这样的话Consumer就可以读取任意位置的消息。这个偏移量kafka不会维护,如果让kafka在去记录每一个consumer的offset,因为每一个Consumer读的offset是不一样,再让kafka去存储这个信息,势必使系统更加复杂,也就是说kafka把更多的控制权给了consumer。
把消息日志以Partition的形式存放有多重考虑,第一,方便在集群中扩展,每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的数据了;第二就是可以提高并发,因为可以以Partition为单位读写了。
Kafka中的topic是以partition的形式存放的,每一个topic都可以设置它的partition数量,Partition的数量决定了组成topic的log的数量。Producer在生产数据时,会按照一定规则(这个规则是可以自定义的)把消息发布到topic的各个partition中。topic可以有多个副本,副本都是以partition为单位的,不过只有一个partition的副本会被选举成leader作为读写用。
关于如何设置partition值需要考虑的因素。一个partition只能被一个消费者消费(一个消费者可以同时消费多个partition),因此,如果设置的partition的数量小于consumer的数量,就会有消费者消费不到数据。所以,推荐partition的数量一定要大于同时运行的consumer的数量。另外一方面,建议partition的数量大于集群broker的数量,这样leader partition就可以均匀的分布在各个broker中,最终使得集群负载均衡。在Cloudera,每个topic都有上百个partition。需要注意的是,kafka需要为每个partition分配一些内存来缓存消息数据,如果partition数量越大,就要为kafka分配更大的heap space。
3.message
message是kafka的基本单位,Kafka 消息由一个定长的header和变长的字节数组组成。因为kafka消息支持字节数组,也就使得kafka可以支持任何用户自定义的序列号格式或者其它已有的格式如Apache Avro、protobuf等。Kafka没有限定单个消息的大小,但我们推荐消息大小不要超过1MB,通常一般消息大小都在1~10kB之间。
• message format:转化成二进制
– message length : 4 bytes(value: 1+4+n)
– "magic" value: 1 byte
– crc : 4 bytes检验码
– payload : n bytes
4.Producer
Producers直接发送消息到broker上的leader partition,不需要经过任何中介一系列的路由转发。为了实现这个特性,kafka集群中的每个broker都可以响应producer的请求,并返回topic的一些元信息,这些元信息包括哪些机器是存活的,topic的leader partition都在哪,现阶段哪些leader partition是可以直接被访问的。
Producer客户端自己控制着消息被推送到哪些partition。实现的方式可以是随机分配、实现一类随机负载均衡算法,或者指定一些分区算法。Kafka提供了接口供用户实现自定义的分区,用户可以为每个消息指定一个partitionKey,通过这个key来实现一些hash分区算法。比如,把userid作为partition key的话,相同userid的消息将会被推送到同一个分区。
以Batch的方式推送数据可以极大的提高处理效率,kafka Producer 可以将消息在内存中累计到一定数量后作为一个batch发送请求。Batch的数量大小可以通过Producer的参数控制,参数值可以设置为累计的消息的数量(如500条)、累计的时间间隔(如100ms)或者累计的数据大小(64KB)。通过增加batch的大小,可以减少网络请求和磁盘IO的次数,当然具体参数设置需要在效率和时效性方面做一个权衡。
Producers可以异步的并行的向kafka发送消息,但是通常producer在发送完消息之后会得到一个future响应,返回的是offset值或者发送过程中遇到的错误。这其中有个非常重要的参数“acks”,这个参数决定了producer要求leader partition 收到确认的副本个数,如果acks设置数量为0,表示producer不会等待broker的响应,所以,producer无法知道消息是否发送成功,这样有可能会导致数据丢失,但同时,acks值为0会得到最大的系统吞吐量。若acks设置为1,表示producer会在leader partition收到消息时得到broker的一个确认,这样会有更好的可靠性,因为客户端会等待直到broker确认收到消息。若设置为-1,producer会在所有备份的partition收到消息时得到broker的确认,这个设置可以得到最高的可靠性保证。
5.Consumer
consumer是消费数据的客户端,对于同一个topic,可以有多个消费者,比如spark,storm等等。Kafka提供了两套consumer api,分为high-level api和sample-api。Sample-api是一个底层的API,它维持了一个和单一broker的连接,并且这个API是完全无状态的,每次请求都需要指定offset值,因此,这套API也是最灵活的。
在kafka中,当前读到消息的offset值是由consumer来维护的,因此,consumer可以自己决定如何读取kafka中的数据。比如,consumer可以通过重设offset值来重新消费已消费过的数据。不管有没有被消费,kafka会保存数据一段时间,这个时间周期是可配置的,只有到了过期时间,kafka才会删除这些数据。 【offset之前存在zookeeper上,现在存在kafka的topic上】
High-level API封装了对集群中一系列broker的访问,可以透明的消费一个topic。它自己维持了已消费消息的状态,即每次消费的都是下一个消息。High-level API还支持以组的形式(group)消费topic,如果consumers有同一个组名,那么kafka就相当于一个队列消息服务,而各个consumer均衡地消费相应partition中的数据,整个group消费的partition加起来就是完整的topic信息。若consumers有不同的组名,那么此时kafka就相当与一个广播服务,会把topic中的所有消息广播到每个consumer。每一个partition只能同一时间服务于一个Consumer。但是consumer可以同时消费多个partition。
6.Replication
kafka中的数据是持久化的并且能够容错的。Kafka允许用户为每个topic设置副本数量,副本数量决定了有几个broker来存放写入的数据。如果你的副本数量设置为3,那么一份数据就会被存放在3台不同的机器上,那么就允许有2个机器失败。一般推荐副本数量至少为2,这样就可以保证增减、重启机器时不会影响到数据消费。如果对数据持久化有更高的要求,可以把副本数量设置为3或者更多。
三、Kafka核心特性
1.压缩
Kafka支持以集合(batch)为单位发送消息,在此基础上,Kafka还支持对消息集合进行压缩,Producer端可以通过GZIP或Snappy格式对消息集合进行压缩。Producer端进行压缩之后,在Consumer端需进行解压。压缩的好处就是减少传输的数据量,减轻对网络传输的压力,在对大数据处理上,瓶颈往往体现在网络上而不是CPU(压缩和解压会耗掉部分CPU资源)。
那么如何区分消息是压缩的还是未压缩的呢,Kafka在消息头部添加了一个描述压缩属性字节,这个字节的后两位表示消息的压缩采用的编码,如果后两位为0,则表示消息未被压缩。
2.信息可靠性
在消息系统中,保证消息在生产和消费过程中的可靠性是十分重要的,在实际消息传递过程中,可能会出现如下三中情况:• 一个消息发送失败
• 一个消息被发送多次
• 最理想的情况:exactly-once ,一个消息发送成功且仅发送了一次
有许多系统声称它们实现了exactly-once,但是它们其实忽略了生产者或消费者在生产和消费过程中有可能失败的情况。比如虽然一个Producer成功发送一个消息,但是消息在发送途中丢失,或者成功发送到broker,也被consumer成功取走,但是这个consumer在处理取过来的消息时失败了。
从Producer端看:Kafka是这么处理的,当一个消息被发送后,Producer会等待broker成功接收到消息的反馈(可通过参数控制等待时间),如果消息在途中丢失或是其中一个broker挂掉,Producer会重新发送(我们知道Kafka有备份机制,可以通过参数控制是否等待所有备份节点都收到消息)。
从Consumer端看:前面讲到过partition,broker端记录了partition中的一个offset值,这个值指向Consumer下一个即将消费message。当Consumer收到了消息,但却在处理过程中挂掉,此时Consumer可以通过这个offset值重新找到上一个消息再进行处理。Consumer还有权限控制这个offset值,对持久化到broker端的消息做任意处理。
3.备份机制
备份机制是Kafka0.8版本的新特性,备份机制的出现大大提高了Kafka集群的可靠性、稳定性。有了备份机制后,Kafka允许集群中的节点挂掉后而不影响整个集群工作。一个备份数量为n的集群允许n-1个节点失败。在所有备份节点中,有一个节点作为lead节点,这个节点保存了其它备份节点列表,并维持各个备份间的状体同步。下面这幅图解释了Kafka的备份机制:
• kafka将日志(数据)复制到指定多个服务器上。
• 复本的单元是partition。在正常情况下,每个partition有一个leader和0到多个follower。
• leader处理对应partition上所有的读写请求。partition可以多于broker数,leader也是分布式的。
• follower的日志和leader的日志是相同的, follower被动的复制leader。如果leader挂了,其中一个follower会自动变成新的leader。【ISR:是个集合,只有在ISR集合里的follower才有机会被选为leader】
• kafka需要两个条件保证是“活着”
– 节点在zookeeper注册的session还在且可维护(基于zookeeper心跳机制)
– 如果是slave则能够紧随leader的更新不至于落得太远。
• kafka采用in sync来实现“活着”机制
– 如果follower挂掉或卡住或落得很远,则leader会移除同步列表中的in sync。至于落了多远才叫远,由replica.lag.max.messages配置,而表示复本“卡住”由replica.lag.time.max.ms配置。
• 所谓一条消息是“提交”的,意味着所有in sync的复本也持久化到了他们的log中。这意味着消费者无需担心leader挂掉导致数据丢失。另一方面,生产者可以选择是否等待消息“提交”。
• kafka动态的维护了一组in-sync(ISR)的复本,表示已追上了leader,只有处于该状态的成员组才是能被选择为leader。这些ISR组会在发生变化时被持久化到zookeeper中。通过ISR模型和n复本,可以让kafka的topic支持最多n-1个节点挂掉而不会导致提交的数据丢失。
4.kafka高效性相关设计
1)消息持久化
Kafka高度依赖文件系统来存储和缓存消息,一般的人认为磁盘是缓慢的,这导致人们对持久化结构具有竞争性持怀疑态度。其实,磁盘远比你想象的要快或者慢,这决定于我们如何使用磁盘。
一个和磁盘性能有关的关键事实是:磁盘驱动器的吞吐量跟寻到延迟是相背离的,也就是所,线性写的速度远远大于随机写。比如:在一个67200rpm SATA RAID-5 的磁盘阵列上线性写的速度大概是600M/秒,但是随机写的速度只有100K/秒,两者相差将近6000倍。线性读写在大多数应用场景下是可以预测的,因此,操作系统利用read-ahead和write-behind技术来从大的数据块中预取数据,或者将多个逻辑上的写操作组合成一个大的物理写操作中。对磁盘的线性读在有些情况下可以比内存的随机访问要快一些。 为了补偿这个性能上的分歧,现代操作系统都会把空闲的内存用作磁盘缓存,尽管在内存回收的时候会有一点性能上的代价。所有的磁盘读写操作会在这个统一的缓存上进行。
此外,如果我们是在JVM的基础上构建的,熟悉java内存应用管理的人应该清楚以下两件事情:
• 一个对象的内存消耗是非常高的,经常是所存数据的两倍或者更多。
• 随着堆内数据的增多,Java的垃圾回收会变得非常昂贵。
基于这些事实,利用文件系统并且依靠页缓存比维护一个内存缓存或者其他结构要好——我们至少要使得可用的缓存加倍,通过自动访问可用内存,并且通过存储更紧凑的字节结构而不是一个对象,这将有可能再次加倍。这么做的结果就是在一台32GB的机器上,如果不考虑GC惩罚,将最多有28-30GB的缓存。此外,这些缓存将会一直存在即使服务重启,然而进程内缓存需要在内存中重构(10GB缓存需要花费10分钟)或者它需要一个完全冷缓存启动(非常差的初始化性能)。它同时也简化了代码,因为现在所有的维护缓存和文件系统之间内聚的逻辑都在操作系统内部了,这使得这样做比one-off in-process attempts更加高效与准确。如果你的磁盘应用更加倾向于顺序读取,那么read-ahead在每次磁盘读取中实际上获取到这人缓存中的有用数据。
以上这些建议了一个简单的设计:不同于维护尽可能多的内存缓存并且在需要的时候刷新到文件系统中,我们换一种思路。所有的数据不需要调用刷新程序,而是立刻将它写到一个持久化的日志中。事实上,这仅仅意味着,数据将被传输到内核页缓存中并稍后被刷新。我们可以增加一个配置项以让系统的用户来控制数据在什么时候被刷新到物理硬盘上。
2)常数时间性能保证
消息系统中持久化数据结构的设计通常是维护者一个和消费队列有关的B树或者其它能够随机存取结构的元数据信息。
B树是一个很好的结构,可以用在事务型与非事务型的语义中。但是它需要一个很高的花费,尽管B树的操作需要O(logN)。通常情况下,这被认为与常数时间等价,但这对磁盘操作来说是不对的。磁盘寻道一次需要10ms,并且一次只能寻一个,因此并行化是受限的。
直觉上来讲,一个持久化的队列可以构建在对一个文件的读和追加上,就像一般情况下的日志解决方案。尽管和B树相比,这种结构不能支持丰富的语义,但是它有一个优点,所有的操作都是常数时间,并且读写之间不会相互阻塞。这种设计具有极大的性能优势:最终系统性能和数据大小完全无关,服务器可以充分利用廉价的硬盘来提供高效的消息服务。
事实上还有一点,磁盘空间的无限增大而不影响性能这点,意味着我们可以提供一般消息系统无法提供的特性。比如说,消息被消费后不是立马被删除,我们可以将这些消息保留一段相对比较长的时间(比如一个星期)。
3)进一步提高效率
我们已经为效率做了非常多的努力。但是有一种非常主要的应用场景是:处理Web活动数据,它的特点是数据量非常大,每一次的网页浏览都会产生大量的写操作。更进一步,我们假设每一个被发布的消息都会被至少一个consumer消费,因此我们更要让消费变得更廉价。
通过上面的介绍,我们已经解决了磁盘方面的效率问题,除此之外,在此类系统中还有两类比较低效的场景:
• 太多小的I/O操作• 过多的字节拷贝
为了减少大量小I/O操作的问题,kafka的协议是围绕消息集合构建的。Producer一次网络请求可以发送一个消息集合,而不是每一次只发一条消息。在server端是以消息块的形式追加消息到log中的,consumer在查询的时候也是一次查询大量的线性数据块。消息集合即MessageSet,实现本身是一个非常简单的API,它将一个字节数组或者文件进行打包。所以对消息的处理,这里没有分开的序列化和反序列化的上步骤,消息的字段可以按需反序列化(如果没有需要,可以不用反序列化)。
另一个影响效率的问题就是字节拷贝。为了解决字节拷贝的问题,kafka设计了一种“标准字节消息”,Producer、Broker、Consumer共享这一种消息格式。Kakfa的message log在broker端就是一些目录文件,这些日志文件都是MessageSet按照这种“标准字节消息”格式写入到磁盘的。
维持这种通用的格式对这些操作的优化尤为重要:持久化log 块的网络传输。流行的unix操作系统提供了一种非常高效的途径来实现页面缓存和socket之间的数据传递。在Linux操作系统中,这种方式被称作:sendfile system call(Java提供了访问这个系统调用的方法:FileChannel.transferTo api)。为了理解sendfile的影响,需要理解一般的将数据从文件传到socket的路径:
数据先拷贝到系统级别的一个Readbuffer里面去,再从内核(kemelcontext),就是系统缓存里面,读到应用层(Application context)的缓存,数据再进行输出到外面的磁盘上或者是通过往IO的形式进行向外传输,也得通过一个应用层(Application buffer)把这个数据写回到系统层(Socket buffer),然后系统层再对接到外部的设备上(NIC buffer),很耗时间和性能。
这种操作方式明显是非常低效的,这里有四次拷贝,两次系统调用。如果使用sendfile,就可以避免两次拷贝:操作系统将数据直接从页缓存发送到网络上。所以在这个优化的路径中,只有最后一步将数据拷贝到网卡缓存中是需要的。
• Kafka层采用无缓存设计,而是依赖于底层的文件系统页缓存。这有助于避免双重缓存,即消息只缓存了一份在页缓存中。同时这在kafka重启后保持缓存warm也有额外的优势。因kafka根本不缓存消息在进程中,故gc开销也就很小。
• zero-copy:kafka为了减少字节拷贝,采用了大多数系统都会提供的sendfile系统调用。【性能提升60%】
在系统级别层进行直接读入和读出,kafka有预读和后写功能,预读会把没有读到的数据提前放到缓存里,那这个缓存其实就是Read buffer。
利用上述的zero-copy,数据只被拷贝到页缓存一次,然后就可以在每次消费时被重得利用,而不需要将数据存在内存中,然后在每次读的时候拷贝到内核空间中。这使得消息消费速度可以达到网络连接的速度。这样以来,通过页面缓存和sendfile的结合使用,整个kafka集群几乎都已以缓存的方式提供服务,而且即使下游的consumer很多,也不会对整个集群服务造成压力。
5.segment
• Kafka存储布局简单:Topic的每个Partition对应一个逻辑日志(一个日志为相同大小的一组分段文件),由实际的segment files组成。由这些segment files文件指向实际的partition。
• 每次生产者发布消息到一个分区,代理就将消息追加到最后一个段文件中。当发布的消息数量达到设定值或者经过一定的时间后,段文件真正flush磁盘中。写入完成后,消息公开给消费者。
• 与传统的消息系统不同,Kafka系统中存储的消息没有明确的消息Id。
• 消息通过日志中的逻辑偏移量offset来公开。
在设计中,一个partition对应多个segment文件,当要去segment中查找数据时,由于大segment拆分为多个小的segment,通过二分法(查messageID号)定位到小的segment,再通过offset从前往后查找数据。
6.无状态的Broker
• Kafka代理是无状态的:意味着消费者必须维护已消费的状态信息。这些信息由消费者自己维护,代理完全不管。这种设计非常微妙,它本身包含了创新。
– 从代理删除消息变得很棘手,因为代理并不知道消费者是否已经使用了该消息。 Kafka创新性地解决了这个问题,它将一个简单的基于时间的SLA应用于保留策略。当消息在代理中超过一定时间后,将会被自动删除。
– 这种创新设计有很大的好处,消费者可以故意倒回到老的偏移量再次消费数据。这违反了队列的常见约定,但被证明是许多消费者的基本特征。
7.交付保证
• Kafka默认采用at least once的消息投递策略。即在消费者端的处理顺序是获得消息->处理消息->保存位置。这可能导致一旦客户端挂掉,新的客户端接管时处理前面客户端已处理过的消息。
• 三种保证策略:
– At most once 消息可能会丢,但绝不会重复传输
– At least one 消息绝不会丢,但可能会重复传输
– Exactly once 每条消息肯定会被传输一次且仅传输一次
8.分布式协调
• 由于kafka中一个topic中的不同分区只能被消费组中的一个消费者消费,就避免了多个消费者消费相同的分区时会导致额外的开销(如:要协调哪个消费者消费哪个消息,还有锁及状态的开销)。 kafka中消费进程只需要在代理和同组消费者有变化时时进行一次协调(这种协调不是经常性的,故可以忽略开销)。
四、Zookeeper的kafka信息
参考:https://cwiki.apache.org/confluence/display/KAFKA/Kafka+data+structures+in+Zookeeper
kafka使用zookeeper做以下事情:
– 探测broker和consumer的添加或移除
– 维护消费关系和追踪消费者在分区消费的消息的offset。
1.broker
• Broker NodeRegistry
• /brokers/ids/[0...N] --> host:port (ephemeral node)
– broker启动时在/brokers/ids下创建一个znode,把broker id写进去。
– 因为broker把自己注册到zookeeper中实用的是瞬时节点,所以这个注册是动态的,如果broker宕机或者没有响应该节点就会被删除。
• BrokerTopic Registry
• /brokers/topics/[topic]/[0...N] --> nPartions (ephemeral node)
– 每个broker把自己存储和维护的partion信息注册到该路径下
2.consumer
• Consumersand Consumer Groups
– consumers也把它们自己注册到zookeeper上,用以保持消费负载平衡和offset记录。
– group id相同的多个consumer构成一个消费租,共同消费一个topic,同一个组的consumer会尽量均匀的消费,其中的一个consumer只会消费一个partion的数据。
• Consumer IdRegistry
• /consumers/[group_id]/ids/[consumer_id] --> {"topic1":#streams, ...,
"topicN": #streams} (ephemeralnode)
– 每个consumer在/consumers/[group_id]/ids下创建一个瞬时的唯一的consumer_id,用来描述当前该group下有哪些consumer是alive的,如果消费进程挂掉对应的consumer_id就会从该节点删除。
• ConsumerOffset Tracking
• /consumers/[group_id]/offsets/[topic]/[partition_id] -->offset_counter_value((persistent node)
– consumer把每个partition的消费offset记录保存在该节点下。
3.partition
• PartitionOwner registry
•/consumers/[group_id]/owners/[topic]/[broker_id-partition_id]-->consumer_node_id (ephemeral node)
– 该节点维护着partion与consumer之间的对应关系。
基本看到这就晕的差不多了,现在进入实践部分消化kafka的理论知识。
五、搭建基于Kafka消息队列系统
1.安装和配置
下载安装kafka,将kafka命令写入系统环境变量。
export KAFKA_HOME=/usr/local/src/kafka_2.11-0.10.2.1 export PATH=$KAFKA_HOME/bin:$PATH
1)server.properties
broker.id=0#不同机子配置不同的id delete.topic.enable=true listeners=PLAINTEXT://master:9092 log.dirs=/usr/local/src/kafka_2.11-0.10.2.1/kafka-logs zookeeper.connect=masteractive:2181,slave1:2181,slave2:2181 注意:如果要想让其他机子远程访问你的kafka,加上一条配置: advertised.host.name=192.168.101.101 #提供访问的地址
2)zookeeper.properties
由于使用自己安装的zookeeper,所以这里不再配置。如果要用kafka自带的zookeeper,可以在配置后,使用:
./bin/zookeeper-server-start.sh config/zookeeper.properties &
2.单机模式
• 启动Zookeeper: – ]# ./bin/zookeeper-server-start.sh • 启动server: – ]#./bin/kafka-server-start.sh config/server.properties • 创建topic: – ]# bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test • 查看主题: – ]# bin/kafka-topics.sh --list --zookeeper localhost:2181 • 查看主题详情: – ]# bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic test • 删除主题: – ]# bin/kafka-topics.sh --zookeeper localhost:2181 --delete --topic test – 注意:如果kafaka启动时加载的配置文件中server.properties没有配置delete.topic.enable=true,那么此时的删除并不是真正的删除,而是把topic标记为:marked for deletion –此时你若想真正删除它,可以登录zookeeper客户端,进入终端后,删除相应节点 • 模拟Producer发消息: – ]# ./bin/kafka-console-producer.sh --broker-list master:9092--topic test • 模拟Consumer读消息: – ]# ./kafka-console-consumer.sh --zookeeper master:2181 --topic test --from beginning • segment存在/kafka-logs的topic中。 00000000000000000000.index:msgid->offsetid,查到了msgid就可以根据offsetid定位到message。
3.集群模式
1)单机模拟多节点
# cp server.properties server-1.properties # cp server.properties server-2.properties # cp server.properties server-3.properties
再分别修改,以server-3.properties为例,下面id参数要不一样:
broker.id=3 listeners=PLAINTEXT://:9095 log.dirs=/usr/local/src/tmp/kafka-logs-3
在节点上分别启动:
kafka-server-start.sh -daemon config/server-1.properties & kafka-server-start.sh -daemon config/server-2.properties & kafka-server-start.sh -daemon config/server-3.properties &
验证:
# jps -m 25009 QuorumPeerMain/usr/local/src/zookeeper-3.4.5/bin/../conf/zoo.cfg 117321 Jps -m 117096 Kafka config/server-1.properties 117192 Kafka config/server-2.properties 117246 Kafka config/server-3.properties # kafka-topics.sh --describe --zookeeper localhost:2181 #假设已经创建了test1 # kafka-console-producer.sh --broker-list master:9093,master:9094,master:9095, --topic test1 #写到master的不同端口上 # kafka-console-consumer.sh --zookeeper master:2181--topic test1 --from beginning
2)多机
刚刚使用的是在1台机子模拟集群,在真正的分布式系统中,配置的思路是相同的,只是在不同机器上面启动而已,这里要注意的是,zookeeper的myid一定要先设置好,不然多台机器没法在zookeeper上创建节点,那就别谈能够成集群工作了。
4.consumer.py
可以用Python编写自己的consumer代码,首先安装kafka模块。
pip install kafka
from kafka import KafkaConsumer def main(): consumer = KafkaConsumer(b"test", bootstrap_servers=["master:9092"], auto_offset_reset='earliest') for message in consumer: print(message) if __name__=="__main__": main()
启动kafka,再启动producer,最后运行consumer.py,可以进行消息的消费。
[root@master kafka_2.11-0.9.0.0]# python consumer.py ConsumerRecord(topic=u'test', partition=0, offset=1106, timestamp=None, timestamp_type=None, key=None, value='dd', checksum=795512426, serialized_key_size=-1, serialized_value_size=2) ConsumerRecord(topic=u'test', partition=0, offset=1107, timestamp=None, timestamp_type=None, key=None, value='jfdsajsfda;', checksum=-32431402, serialized_key_size=-1, serialized_value_size=11) ConsumerRecord(topic=u'test', partition=0, offset=1108, timestamp=None, timestamp_type=None, key=None, value='dsa', checksum=-121585800, serialized_key_size=-1, serialized_value_size=3)
5.producer.py
可以编写producer,模拟发送数据。
# -*- coding: UTF-8 -*- import time from kafka import SimpleProducer, KafkaClient from kafka.common import LeaderNotAvailableError def print_response(response=None): if response: print('error:{0}'.format(response[0].error)) print('offset:{0}'.format(response[0].error)) def main(): kafka = KafkaClient("master:9092") producer = SimpleProducer(kafka) topic = b'test' msg = b'dragon is me' try: print_response(producer.send_messages(topic,msg)) except LeaderNotAvailableError: time.sleep(1) print_response(producer.send_messages(topic,msg)) kafka.close() if __name__=="__main__": main()
6.groupid.py
这里模拟创建1个group。
from kafka import KafkaConsumer def main(): consumer = KafkaConsumer(b"test", group_id =b"dragonid",bootstrap_servers=["master:9092",slave1:9092],auto_offset_reset='earliest') for message in consumer: print(message) if __name__=="__main__": main()
master和slave1上的consumer将会一起消费topic为test的消息,producer发送多条数据,master和slave1将会一起消费,二者消费的总和将会等于producer总共发出的数据。
7.指定offset消费
当给topic设置了多个partition后,consumer可以指定具体消费哪个partition,并且从哪个offset开始。
from kafka import KafkaConsumer from kafka.structs import TopicPartition consumer = KafkaConsumer("test", bootstrap_servers=["master:9092"], auto_offset_reset='earliest') consumer.seek(TopicPartition(topic=u'test',partition=2),21) #消费第二个partition,且偏移量是21 print consumer.partitions_for_topic("test") print consumer.topics() print consumer.assignment() print consumer.begining_offsets(consumer.assignment()) for message in consumer: print("%s:%d:%d: key=%s value=%s" %(message.topic,message.partition,message.offset,message.key,message.value))
六、Java编程(IDEA)
1.producer
package com.imooc.spark.kafka; import kafka.Kafka; import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; import java.util.Properties; public class kafkaproducer extends Thread{ private Stringtopic; private Producer<Integer,String>producer; public kafkaproducer(String topic){ this.topic=topic; Properties properties = new Properties(); properties.put("metadata.broker.list",kafkaproperties.BROKER_LIST); properties.put("serializer.class","kafka.serializer.StringEncoder"); properties.put("request.required.acks","1"); producer = new Producer<Integer,String>(new ProducerConfig(properties)); } @Override public void run() { int messageNo= 1; while(true){ String message = "message" +messageNo; producer.send(new KeyedMessage<Integer, String>(topic,message)); //System.out.println("send:"+message); messageNo++; try { Thread.sleep(2000); }catch(Exception e){ e.printStackTrace(); } } } }
2.consumer
package com.imooc.spark.kafka; import kafka.consumer.Consumer; import kafka.consumer.ConsumerConfig; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; import kafka.javaapi.consumer.ConsumerConnector; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; public class KafkaConsumer extends Thread{ private String topic; public KafkaConsumer(String topic){ this.topic=topic; } private ConsumerConnector createConnector(){ Properties properties = new Properties(); properties.put("zookeeper.connect",kafkaproperties.ZK); properties.put("group.id",kafkaproperties.GROUP_ID); properties.put("zookeeper.session.timeout.ms", "15000"); return Consumer.createJavaConsumerConnector(new ConsumerConfig(properties)); } @Override public void run() { ConsumerConnector consumer = createConnector(); Map<String,Integer> topicCountMap = new HashMap<String, Integer>(); topicCountMap.put(topic,1); Map<String, List<KafkaStream<byte[], byte[]>>> messagestream = consumer.createMessageStreams(topicCountMap); KafkaStream<byte[], byte[]> stream = messagestream.get(topic).get(0); ConsumerIterator<byte[], byte[]> iterator = stream.iterator(); while (iterator.hasNext()){ String message = new String(iterator.next().message()); System.out.println("rec:"+message); } } }
3.properties
package com.imooc.spark.kafka; public class kafkaproperties { public static final String ZK= "192.168.101.10:2181"; public static final String TOPIC = "test1"; public static final String BROKER_LIST = "192.168.101.10:9092"; public static final String GROUP_ID = "test1"; }
4.test
package com.imooc.spark.kafka; public class KafkaTestApp { public static void main(String[] args) { new kafkaproducer(kafkaproperties.TOPIC).start(); new KafkaConsumer(kafkaproperties.TOPIC).start(); } }
5.具体步骤
1.创建maven工程,使用intellijidea
2.创建需要的依赖包,在pom.xml添加:
<properties> <scala.version>2.11.8</scala.version> <spark.version>2.2.0</spark.version> <hadoop.version>2.6.0-cdh5.7.0</hadoop.version> <hbase.version>1.2.0-cdh5.7.0</hbase.version> <kafka.version>0.9.0.0</kafka.version> </properties> <repositories> <repository> <id>cloudera</id> <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>${hadoop.version}</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>${hbase.version}</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-server</artifactId> <version>${hbase.version}</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>${spark.version}</version> </dependency> <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka_2.11</artifactId> <version>${kafka.version}</version> </dependency> </dependencies>
3.打开Linux上的kafka,确保正常运行kafka。运行代码,可以自己产生数据,并用自己编写的consumer来处理具体的业务。