Kafka深度解析.docx

上传人:b****5 文档编号:8294472 上传时间:2023-01-30 格式:DOCX 页数:29 大小:1.91MB
下载 相关 举报
Kafka深度解析.docx_第1页
第1页 / 共29页
Kafka深度解析.docx_第2页
第2页 / 共29页
Kafka深度解析.docx_第3页
第3页 / 共29页
Kafka深度解析.docx_第4页
第4页 / 共29页
Kafka深度解析.docx_第5页
第5页 / 共29页
点击查看更多>>
下载资源
资源描述

Kafka深度解析.docx

《Kafka深度解析.docx》由会员分享,可在线阅读,更多相关《Kafka深度解析.docx(29页珍藏版)》请在冰豆网上搜索。

Kafka深度解析.docx

Kafka深度解析

背景介绍

Kafka简介

Kafka是一种分布式的,基于发布/订阅的消息系统。

主要设计目标如下:

∙以时间复杂度为O

(1)的方式提供消息持久化能力,并保证即使对TB级以上数据也能保证常数时间的访问性能

∙高吞吐率。

即使在非常廉价的商用机器上也能做到单机支持每秒100K条消息的传输

∙支持KafkaServer间的消息分区,及分布式消息消费,同时保证每个partition内的消息顺序传输

∙同时支持离线数据处理和实时数据处理

为什么要用MessageQueue

∙解耦 

在项目启动之初来预测将来项目会碰到什么需求,是极其困难的。

消息队列在处理过程中间插入了一个隐含的、基于数据的接口层,两边的处理过程都要实现这一接口。

这允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束

∙冗余 

有时在处理数据的时候处理过程会失败。

除非数据被持久化,否则将永远丢失。

消息队列把数据进行持久化直到它们已经被完全处理,通过这一方式规避了数据丢失风险。

在被许多消息队列所采用的”插入-获取-删除”范式中,在把一个消息从队列中删除之前,需要你的处理过程明确的指出该消息已经被处理完毕,确保你的数据被安全的保存直到你使用完毕。

∙扩展性 

因为消息队列解耦了你的处理过程,所以增大消息入队和处理的频率是很容易的;只要另外增加处理过程即可。

不需要改变代码、不需要调节参数。

扩展就像调大电力按钮一样简单。

∙灵活性&峰值处理能力 

在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见;如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。

使用消息队列能够使关键组件顶住增长的访问压力,而不是因为超出负荷的请求而完全崩溃。

∙可恢复性 

当体系的一部分组件失效,不会影响到整个系统。

消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。

而这种允许重试或者延后处理请求的能力通常是造就一个略感不便的用户和一个沮丧透顶的用户之间的区别。

∙送达保证 

消息队列提供的冗余机制保证了消息能被实际的处理,只要一个进程读取了该队列即可。

在此基础上,IronMQ提供了一个”只送达一次”保证。

无论有多少进程在从队列中领取数据,每一个消息只能被处理一次。

这之所以成为可能,是因为获取一个消息只是”预定”了这个消息,暂时把它移出了队列。

除非客户端明确的表示已经处理完了这个消息,否则这个消息会被放回队列中去,在一段可配置的时间之后可再次被处理。

∙顺序保证 

在许多情况下,数据处理的顺序都很重要。

消息队列本来就是排序的,并且能保证数据会按照特定的顺序来处理。

IronMO保证消息浆糊通过FIFO(先进先出)的顺序来处理,因此消息在队列中的位置就是从队列中检索他们的位置。

∙缓冲 

在任何重要的系统中,都会有需要不同的处理时间的元素。

例如,加载一张图片比应用过滤器花费更少的时间。

消息队列通过一个缓冲层来帮助任务最高效率的执行—写入队列的处理会尽可能的快速,而不受从队列读的预备处理的约束。

该缓冲有助于控制和优化数据流经过系统的速度。

∙理解数据流 

在一个分布式系统里,要得到一个关于用户操作会用多长时间及其原因的总体印象,是个巨大的挑战。

消息系列通过消息被处理的频率,来方便的辅助确定那些表现不佳的处理过程或领域,这些地方的数据流都不够优化。

∙异步通信 

很多时候,你不想也不需要立即处理消息。

消息队列提供了异步处理机制,允许你把一个消息放入队列,但并不立即处理它。

你想向队列中放入多少消息就放多少,然后在你乐意的时候再去处理它们。

常用MessageQueue对比

∙RabbitMQ 

RabbitMQ是使用Erlang编写的一个开源的消息队列,本身支持很多的协议:

AMQP,XMPP,SMTP,STOMP,也正因如此,它非常重量级,更适合于企业级的开发。

同时实现了Broker构架,这意味着消息在发送给客户端时先在中心队列排队。

对路由,负载均衡或者数据持久化都有很好的支持。

∙Redis 

Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。

虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。

对于RabbitMQ和Redis的入队和出队操作,各执行100万次,每10万次记录一次执行时间。

测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。

实验表明:

入队时,当数据比较小时Redis的性能要高于RabbitMQ,而如果数据大小超过了10K,Redis则慢的无法忍受;出队时,无论数据大小,Redis都表现出非常好的性能,而RabbitMQ的出队性能则远低于Redis。

∙ZeroMQ 

ZeroMQ号称最快的消息队列系统,尤其针对大吞吐量的需求场景。

ZMQ能够实现RabbitMQ不擅长的高级/复杂的队列,但是开发人员需要自己组合多种技术框架,技术上的复杂度是对这MQ能够应用成功的挑战。

ZeroMQ具有一个独特的非中间件的模式,你不需要安装和运行一个消息服务器或中间件,因为你的应用程序将扮演了这个服务角色。

你只需要简单的引用ZeroMQ程序库,可以使用NuGet安装,然后你就可以愉快的在应用程序之间发送消息了。

但是ZeroMQ仅提供非持久性的队列,也就是说如果down机,数据将会丢失。

其中,Twitter的Storm中默认使用ZeroMQ作为数据流的传输。

∙ActiveMQ 

ActiveMQ是Apache下的一个子项目。

类似于ZeroMQ,它能够以代理人和点对点的技术实现队列。

同时类似于RabbitMQ,它少量代码就可以高效地实现高级应用场景。

∙Kafka/Jafka 

Kafka是Apache下的一个子项目,是一个高性能跨语言分布式Publish/Subscribe消息队列系统,而Jafka是在Kafka之上孵化而来的,即Kafka的一个升级版。

具有以下特性:

快速持久化,可以在O

(1)的系统开销下进行消息持久化;高吞吐,在一台普通的服务器上既可以达到10W/s的吞吐速率;完全的分布式系统,Broker、Producer、Consumer都原生自动支持分布式,自动实现复杂均衡;支持Hadoop数据并行加载,对于像Hadoop的一样的日志数据和离线分析系统,但又要求实时处理的限制,这是一个可行的解决方案。

Kafka通过Hadoop的并行加载机制来统一了在线和离线的消息处理,这一点也是本课题所研究系统所看重的。

ApacheKafka相对于ActiveMQ是一个非常轻量级的消息系统,除了性能非常好之外,还是一个工作良好的分布式系统。

Kafka解析

Terminology

∙Broker 

Kafka集群包含一个或多个服务器,这种服务器被称为broker

∙Topic 

每条发布到Kafka集群的消息都有一个类别,这个类别被称为topic。

(物理上不同topic的消息分开存储,逻辑上一个topic的消息虽然保存于一个或多个broker上但用户只需指定消息的topic即可生产或消费数据而不必关心数据存于何处)

∙Partition 

parition是物理上的概念,每个topic包含一个或多个partition,创建topic时可指定parition数量。

每个partition对应于一个文件夹,该文件夹下存储该partition的数据和索引文件

∙Producer 

负责发布消息到Kafkabroker

∙Consumer 

消费消息。

每个consumer属于一个特定的consuergroup(可为每个consumer指定groupname,若不指定groupname则属于默认的group)。

使用consumerhighlevelAPI时,同一topic的一条消息只能被同一个consumergroup内的一个consumer消费,但多个consumergroup可同时消费这一消息。

Kafka架构

如上图所示,一个典型的kafka集群中包含若干producer(可以是web前端产生的pageview,或者是服务器日志,系统CPU、memory等),若干broker(Kafka支持水平扩展,一般broker数量越多,集群吞吐率越高),若干consumergroup,以及一个 Zookeeper 集群。

Kafka通过Zookeeper管理集群配置,选举leader,以及在consumergroup发生变化时进行rebalance。

producer使用push模式将消息发布到broker,consumer使用pull模式从broker订阅并消费消息。

Pushvs.Pull

作为一个messagingsystem,Kafka遵循了传统的方式,选择由producer向brokerpush消息并由consumer从brokerpull消息。

一些logging-centricsystem,比如Facebook的 Scribe 和Cloudera的 Flume ,采用非常不同的push模式。

事实上,push模式和pull模式各有优劣。

push模式很难适应消费速率不同的消费者,因为消息发送速率是由broker决定的。

push模式的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。

而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

Topic&Partition

Topic在逻辑上可以被认为是一个在的queue,每条消费都必须指定它的topic,可以简单理解为必须指明把这条消息放进哪个queue里。

为了使得Kafka的吞吐率可以水平扩展,物理上把topic分成一个或多个partition,每个partition在物理上对应一个文件夹,该文件夹下存储这个partition的所有消息和索引文件。

每个日志文件都是“logentries”序列,每一个 logentry 包含一个4字节整型数(值为N),其后跟N个字节的消息体。

每条消息都有一个当前partition下唯一的64字节的offset,它指明了这条消息的起始位置。

磁盘上存储的消费格式如下:

messagelength:

4bytes(value:

1+4+n)

“magic”value:

1byte

crc:

4bytes

payload:

nbytes

这个“logentries”并非由一个文件构成,而是分成多个segment,每个segment名为该segment第一条消息的offset和“.kafka”组成。

另外会有一个索引文件,它标明了每个segment下包含的 logentry 的offset范围,如下图所示。

因为每条消息都被append到该partition中,是顺序写磁盘,因此效率非常高(经验证,顺序写磁盘效率比随机写内存还要高,这是Kafka高吞吐率的一个很重要的保证)。

每一条消息被发送到broker时,会根据paritition规则选择被存储到哪一个partition。

如果partition规则设置的合理,所有消息可以均匀分布到不同的partition里,这样就实现了水平扩展。

(如果一个topic对应一个文件,那这个文件所在的机器I/O将会成为这个topic的性能瓶颈,而partition解决了这个问题)。

在创建topic时可以在 $KAFKA_HOME/config/server.properties 中指定这个partition的数量(如下所示),当然也可以在topic创建之后去修改parition数量。

#Thedefaultnumberoflogpartitionspertopic.Morepartitionsallowgreater#parallelismforconsumption,butthiswillalsoresultinmorefilesacross#thebrokers.

num.partitions=3

在发送一条消息时,可以指定这条消息的key,producer根据这个key和partition机制来判断将这条消息发送到哪个parition。

paritition机制可以通过指定producer的paritition.class这一参数来指定,该class必须实现 kafka.producer.Partitioner 接口。

本例中如果key可以被解析为整数则将对应的整数与partition总数取余,该消息会被发送到该数对应的partition。

(每个parition都会有个序号)

importkafka.producer.Partitioner;

importkafka.utils.VerifiableProperties;

publicclassJasonPartitionerimplementsPartitioner{

publicJasonPartitioner(VerifiablePropertiesverifiableProperties){}

@Override

publicintpartition(Objectkey,intnumPartitions){

try{

intpartitionNum=Integer.parseInt((String)key);

returnMath.abs(Integer.parseInt((String)key)%numPartitions);

}catch(Exceptione){

returnMath.abs(key.hashCode()%numPartitions);

}

}

}

如果将上例中的class作为partition.class,并通过如下代码发送20条消息(key分别为0,1,2,3)至topic2(包含4个partition)。

publicvoidsendMessage()throwsInterruptedException{

  for(inti=1;i<=5;i++){

  ListmessageList=newArrayList>();

  for(intj=0;j<4;j++){

  messageList.add(newKeyedMessage("topic2",j+"","The"+i+"messageforkey"+j));

  }

  producer.send(messageList);

}

  producer.close();

}

则key相同的消息会被发送并存储到同一个partition里,而且key的序号正好和partition序号相同。

(partition序号从0开始,本例中的key也正好从0开始)。

如下图所示。

对于传统的messagequeue而言,一般会删除已经被消费的消息,而Kafka集群会保留所有的消息,无论其被消费与否。

当然,因为磁盘限制,不可能永久保留所有数据(实际上也没必要),因此Kafka提供两种策略去删除旧数据。

一是基于时间,二是基于partition文件大小。

例如可以通过配置 $KAFKA_HOME/config/server.properties ,让Kafka删除一周前的数据,也可通过配置让Kafka在partition文件超过1GB时删除旧数据,如下所示。

  #############################LogRetentionPolicy#############################

#Thefollowingconfigurationscontrolthedisposaloflogsegments.Thepolicycan#besettodeletesegmentsafteraperiodoftime,orafteragivensizehasaccumulated.#Asegmentwillbedeletedwhenever*either*ofthesecriteriaaremet.Deletionalwayshappens#fromtheendofthelog.

#Theminimumageofalogfiletobeeligiblefordeletion

log.retention.hours=168

#Asize-basedretentionpolicyforlogs.Segmentsareprunedfromthelogaslongastheremaining#segmentsdon'tdropbelowlog.retention.bytes.#log.retention.bytes=1073741824

#Themaximumsizeofalogsegmentfile.Whenthissizeisreachedanewlogsegmentwillbecreated.

log.segment.bytes=1073741824

#Theintervalatwhichlogsegmentsarecheckedtoseeiftheycanbedeletedaccording#totheretentionpolicies

log.retention.check.interval.ms=300000

#Bydefaultthelogcleanerisdisabledandthelogretentionpolicywilldefaultto#justdeletesegmentsaftertheirretentionexpires.#Iflog.cleaner.enable=trueissetthecleanerwillbeenabledandindividuallogs#canthenbemarkedforlogcompaction.

log.cleaner.enable=false

这里要注意,因为Kafka读取特定消息的时间复杂度为O

(1),即与文件大小无关,所以这里删除文件与Kafka性能无关,选择怎样的删除策略只与磁盘以及具体的需求有关。

另外,Kafka会为每一个consumergroup保留一些metadata信息—当前消费的消息的position,也即offset。

这个offset由consumer控制。

正常情况下consumer会在消费完一条消息后线性增加这个offset。

当然,consumer也可将offset设成一个较小的值,重新消费一些消息。

因为offet由consumer控制,所以Kafkabroker是无状态的,它不需要标记哪些消息被哪些consumer过,不需要通过broker去保证同一个consumergroup只有一个consumer能消费某一条消息,因此也就不需要锁机制,这也为Kafka的高吞吐率提供了有力保障。

Replication&Leaderelection

Kafka从0.8开始提供partition级别的replication,replication的数量可在 $KAFKA_HOME/config/server.properties 中配置。

default.replication.factor=1

该Replication与leaderelection配合提供了自动的failover机制。

replication对Kafka的吞吐率是有一定影响的,但极大的增强了可用性。

默认情况下,Kafka的replication数量为1。

每个partition都有一个唯一的leader,所有的读写操作都在leader上完成,leader批量从leader上pull数据。

一般情况下partition的数量大于等于broker的数量,并且所有partition的leader均匀分布在broker上。

follower上的日志和其leader上的完全一样。

和大部分分布式系统一样,Kakfa处理失败需要明确定义一个broker是否alive。

对于Kafka而言,Kafka存活包含两个条件,一是它必须维护与Zookeeper的session(这个通过Zookeeper的heartbeat机制来实现)。

二是follower必须能够及时将leader的writing复制过来,不能“落后太多”。

leader会track“insync”的nodelist。

如果一个follower宕机,或者落后太多,leader将把它从”insync”list中移除。

这里所描述的“落后太多”指follower复制的消息落后于leader后的条数超过预定值,该值可在 $KAFKA_HOME/config/server.properties 中配置

#Ifareplicafallsmorethanthismanymessagesbehindtheleader,theleaderwillremovethefollowerfromISRandtreatitasdead

replica.lag.max.messages=4000

#Ifafollowerhasn'tsentanyfetchrequestsforthiswindowoftime,theleaderwillremovethefollowerfromISR(in-syncreplicas)andtreatitasdead

replica.lag.time.max.ms=10000

需要说明的是,Kafka只解决”fail/recover”,不处理“Byzantine”(“拜占庭”)问题。

一条消息只有被“insync”list里的所有follower都从leader复制过去才会被认为已提交。

这样就避免了部分数据被写进了leader,还没来得及被任何follower复制就宕机了,而造成数据丢失(consumer无法消费这些数据)。

而对于producer而言,它可以选择是否等待消息commit,这可以通过request.required.acks 来设置。

这种机制确保了只要“insync”list有一个或以上的flollower,一条被commit的消息就不会丢失。

这里的复制机制即不是同步复制,也不是单纯的异步复制。

事实上,同步复制要求“活着的”follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率(高吞吐率是Kafka非常重要的一个特性)。

而异步复制方式下,follower异步的从leader复制数据,数据只要被leader写入log就被认为已经commit,这种情况下如果follwer都落后于leader,而leader突然宕机,则会丢失数据。

而Kafka的这种使用“insync”list的方式则很好的均衡了确保数据不丢失以及吞吐率。

fol

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 成人教育 > 专升本

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1