kafka笔记
kafka的定义:是一个分布式消息系统,由LinkedIn使用Scala编写,用作LinkedIn的活动流(Activity
Stream)和运营数据处理管道(Pipeline)的基础,具有高水平扩展和高吞吐量。
应用领域:已被多家不同类型的公司作为多种类型的数据管道和消息系统使用。如:
淘宝,支付宝,百度,twitter等
目前越来越多的开源分布式处理系统如Apache
flume、Apache
Storm、Spark,elasticsearch都支持与Kafka集成。
ActiveMQ
RabbitMQ
Kafka
所属社区/公司
Apache
Mozilla
Public LicenseApache/LinkedIn
开发语言
Java
Erlang
Java
支持的协议
OpenWire、STOMP、
REST、XMPP、AMQPAMQP
仿AMQP
事物
支持
不支持
不支持
集群
支持
支持
支持
负载均衡
支持
支持
支持
动态扩容
不支持
不支持
支持(zk)
ActiveMQ还是支持JMS的一种消息中间件
Kafka的动态扩容目前是通过zookeeper来完成的
阿里巴巴的metaq,rocketmq都有kafka的影子
AMQP协议
消费者(Consumer):从消息队列中请求消息的客户端应用程序;
生产者(Producer):向broker发布消息的客户端应用程序;
AMQP服务器端(broker):用来接收生产者发送的消息并将这些消息路由给服务器中的队列;
Kafka客户端支持当前大部分主流语言,包括:
C、C++、Erlang、Java、.net、perl、PHP、Python、Ruby、Go、Javascript。
可以使用以上任何一种语言和kafka服务器进行通信(即编写自己的consumer和producer程序)
kafka的架构
主题(Topic):一个主题类似新闻中的体育、娱乐、教育等分类概念,在实际工程中通常一个业务一个主题;
分区(Partition):一个topic中的消息数据按照多个分区组织,分区是kafka消息队列组织的最小单位,一个分区可以看做是一个FIFO的队列;
备份(Replication):为了保证分布式可靠性,kafka0.8开始对每个分区的数据进行备份(不同Broker上),防止其中一个Broker宕机造成分区数据不可用
zookeeper:一个提供分布式状态管理、分布式配置管理、分布式锁服务等的集群
Kafka编程实例
消费者编程模型
分区消费模型
一个分区 一个消费者实例
分区消费伪代码描述
Main()
分区模型消费的分区数是多少 即 获取分区的size
针对每个分区创建一个线程或进程 – 消费者实例
For
index = 0 to size
Create
thread(or process) consumer(index)
第index个线程(进程) –每个消费者实例的工作
Consumer(index)
创建到kafka
broker的连接,KafkaClient(host,port)
指定消费参数构建consumer,SimpleConsumer(topic,partitions)
设置消费offset:consumer.seek(offset,0)
While(true)
{
消费指定topic第index个分区的数据
处理
}
记录当前消息offset(可选)
提交当前offset(可选)(kafka集群默认做两个操作Java客户端)
组消费模型
组消费可以复制消费,kafka集群的一条消息会同时发送给各个组消费者,每组都会拿到一个全量的数据。
组消费伪代码:
Main()
设置需要创建的流数N
---每个consumer组里有多少个consumer实例
For
index = 0 to N
Create
thread comsumer(index)
第index个线程(进程) –每个消费者实例的工作
Consumer(index)
创建到kafka
broker的连接,KafkaClient(host,port)
指定消费参数构建consumer,SimpleConsumer(topic,partitions)
设置从头消费还是从最新的消费(smallest或largest)offset
While(true)
{
消费指定topic第index个流的数据
处理
}
(offset自动提交给zookeeper)
Consumer分配算法:
假设一个组消费者包含两个消费者实例,某一topic拥有4个分区,则如何进行consumer分配?
针对kafka集群下的某一topic的所有分区进行排序,part0.part1,part2,part3
针对客户端实例进行排序consumer1
consumer2
用partition数量除以consumer数量N
将I
* N to (i+1) * N传递给消费者consumer(i)
将当前分配关系注册到集群
For
each topic T that Ci subscribes to
let
PT be all partitions producing topic T
let
CG be all consumers in the same group as Ci that consume topic T
sort
PT (so partitions on the same broker are clustered together)
sort
CG
let
i be the index position of Ci in CG and let N = size(PT)/size(CG)
assign
partitions from i*N to (i+1)*N - 1 to consumer Ci
remove
current entries owned by Ci from the partition owner registry
add
newly assigned partitions to the partition owner registry
(we
may need to re-try this until the original partition owner releases
its
ownership)
消费模型对比
分区消费模型更加灵活,但需要自己处理各种异常情况
需要自己管理offset以实现消息传递的其他语义—至少一次(kafka)发送至少一次,不会丢;
至多一次,只会发送一次,可能丢消息,可能会重复
准确一次(保存偏移量):
组消费者模型简单但不灵活:
不需要自己处理异常情况,不需要自己管理offset
只能实现kafka默认的最少一次消息传递语义
Maven-assembly-plugin
Jar-with-dependencies
Kafka参数调优
FetchSize从服务器获取单包大小 –每次从服务器端获取的tcp大小
bufferSize
kafka客户端缓冲区大小 –一次最多获取多少数据再返回给用户 一个bufersize由多个fetchsize组成 异国传输带宽不一,尽可能设置大客户端缓冲区
group.id分组消费时分组名--分组消费时指定不同的id可以实现复制消费的目的
生产者编程模型
同步生产模型
Kafka客户端发送一条消息后处于阻塞状态,等待kafka服务器的确认消息,如果没有等到确认消息(等了一段时间),则生产线程sleep一段时间后,继续重新发送,直到到达最大发送次数,直接结束发送。
Kafka至少一次 否则程序会中止
异步生产模型
Kafka客户端发送一条消息,直接将该消息发送至客户端的缓冲队列中,直到达到客户端的缓冲队列消息数目达到预先配置的数目或者kafka消息队列的消息累计时间到了预先配置的时间,kafka生产者客户端的 消息队列打包一次性发送给kafka服务器,kafka客户端消息队列也是存在kafka集群,并不是kafka
Server. Kafka集群代码包括kafka
Server和kafka
Client,维持一个队列。
伪代码描述
Main()
创建到kafka
broker的连接,KafkaClient(host,port)
选择或者自定义生产者负载均衡算法partitioner
设置生产者参数
根据负责均衡算法(默认hash轮询 随机) 和 设置得生产者参数(缓冲队列长度累计时间等等)构造producer对象
While
true
getMessage从上游获得一条消息
按照kafka要求的消息格式构造(kafka自定义的)kafka消息
根据分区算法得到分区
发送消息
处理异常
两种生产模型对比
同步:
低消息丢失率
高消息重复率(等待时间较长,由于网络原因,确认长时间未收到,导致多次重发)
高延迟
异步生产模型:(每秒一个分区50万记录)
低延迟
高发送性能
高消息丢失率(无确认机制,发送端队列满时,无法传送消息。 队列发送kafka服务器期间,可能造成整个消息队列的丢失。)
生产者编程
req_acks:发送失败次数
ack_timeout:未接到确认,认为发送失败的时间
async:是否异步发送
batch_send_every_n:异步发送时,累计最大消息数
batch_send_every_t:异步发送时,累计最大时间
同步需要设置ack参数--默认为同步传输 不指定发送类型的情况下
在发送消息时需要制定topic
key(可能根据key进行partition)以及value
即便自己的分区算法未使用到仍然需要制定key不能将其设置为null或者“”“”
Proprs.put(“serializer.class”,”kafka.serializer.StringEncoder”)
//默认encoder
–字节序列化
Proprs.put(“partitioner.class”,”kafka.proceducer.partition.SimplePartitioner”)
//自己实现的分区算法 默认的是针对key进行hash
//
kafka.proceducer.partition.SimplePartitioner为自己实现的实现了Partitioner的接口
实现了partition方法,其参数为key和分区数(当前topic的分区数,系统调用时,自动传参)key来自于EventKey(传递的Message中的key)
Proprs.put(“request.required.acks”,”1”)
//
0绝不等确认,1:leader的一个副本收到这一消息,并发回确认-1:leader的所有副本收到这一消息,并发回确认
KeyedMessage
data = new KeyedMessage(eventTopic, eventKey,
eventvalue)
//
eventKey必须有(即便自己的分区算法用不到这个key,也不能为null或者””),否则自己的分区算法根本得不到调用,
异步不需要Proprs.put(“request.required.acks”,”1”)设置了也没有用 需要制定类型为异步
Props.put(“producer.type”,”async”)
// 1:async 2:sync
Java客户端参数调优
Message.send.max.retries发送失败重试次数 同步
Retry.backoff.ms:未接到确认,认为发送失败的时间 同步
Producer.type:同步发送或者异步发送
Batch.num.messages:异步发送时,累计最大消息数
Queue.buffering.max.ms:异步发送时,累计最大时间
Kafka消息组织原理
磁盘重认识
根据数据的局部性原理kafka
预读或者提前读
读取当前字节 读取下一块数据
合并写
多个逻辑上的写操作 合并成一个大的物理写操作
顺序读写 不需要寻道时间
很少旋转时间
一般而言 顺序写比 随机写 相差很多(这是kafka设计的关键考虑)速度相差万倍 线性每秒300M随机写每秒50k
Kafka特性:gree
Kafka消息的写入原理
一般的将数据从文件传到套接字的路径;
Os将数据从磁盘读取到内核空间中的页缓存
应用将数据从内核空间读到用户空间的缓存中
应用将数据写回到内存空间的套接字缓存中
Os将数据从套接字缓存写到网卡缓存中,以便数据经网络发出
以上效率很低,经历了四次拷贝(磁盘空间到内核空间,内核空间到用户空间,用户空间到套接字空间,套接字空间到网卡空间;)和两次系统调用(磁盘到内核空间;套接字到网卡空间)。
使用sendFile(linux系统调用)FileChannel.transferTo
api,两次拷贝可以被避免:允许os将数据直接从页缓存发送到网络上(保留1/4)。优化后,只需要最后一步将数据拷贝到网卡缓存中。
数据写入和读出的Byte
Zero Copy
生产:
网络------------------------
pagecache(生产一次消费多次)---------------------------磁盘
消费:
磁盘-------------------------网络
以上两种都没有使用用户空间
Kafka设计目标是生产一次消费多次 生产的时候写入pagecache消费者可以从pagecache中直接读取 不需要再磁盘读取(顺序读虽然快但还是比不上主存的读取)。
Pagecache满了或者存储的时间到了,写入进磁盘可以增加整体开发的性能
以上设计 就是数据写入和读出的零字节拷贝Byte
Zero Copy(不用拷贝到用户空间,所以称为零字节拷贝)
基于的原理:
顺序写比随机写快很多
数据的零字节拷贝
不用拷贝到用户空间中
Isr:1,0
处于同步中的broker
不在同步中broker:
假如当前有两个broker
broker0与broker1假设broker0挂了则broker0为不在同步中
假设slave的消费进度比leader的消费进度 快很多 超过了某个阈值 这个时候也是不在同步中slave中的数据不是最新的数据 假如leader挂了 数据不是最新的 不会选择该slave作为新的leader会默认选择正在同步中的broker为leader即数据达到了最新 数据一致性可以保证
Kafka消息文件存储
每个分区都是以index
log文件进行存储的
Kafka消息的删除原理
从最久的日志段
开始删除 (按日志段为单位进行删除),然后逐步向前推进,知道某个日志段不满足条件为止,删除条件:
满足给定条件:(kafka/config/server.properties) 一般默认保留7天
Predicate配置项:log.retention.{ms,minutes,hours}和log.retention.bytes指定
驻留时间 每条消息存放在kafka里的最长时间
消息的
最大字节数(某个分区最大存储空间) 某一分区非常大,
不能是当前激活日志段(尽管日期过了7天,但是仅有的当前日志段为当前激活日志段,不会被删除。)kafka保证topic当前有一个日志段
大小不能小于日志段的大小(log.segment.bytes配置,这种情况只存在于当前激活日志段,因为是一个日志段写满后 才会继续写入后续的日志段)
要删除的是否是所有日志段,如果是,直接调用roll方法进行切分,kafka至少保留一个日志段
Scheduler:
删除log.retention.check.interval.ms指定间隔
刷盘log.flush.scheduler.interval.ms
记录checkpoint
log.flush.offset.checkpoint.interval.ms
压缩(如果有)一直运行(log.cleaner.enable指定是否开启)
Kafka消息检索原理– 消费时快速定位
Kafka消息的segment
file的组成和物理结构
--(.log
file)
分区文件=
log文件+
index文件
Segment
file = Seq[Message]
Message
= 8 byte Offset (当前partition的第几条消息)+
4 byte message size(Meaasge大小)
+ 4 byte CRC32(CRC32校验)+
1 byte “magic” (本次发布kafka服务程序协议版本号)
+ 1 byte “attributes”(独立版本,或标识压缩类型或编码类型)+
4byte key length(key的长度,当key为-1时,K
byte key字段不填)+
K byte key(Key可选)+
value bytes length(实际消息数据)+
payload (实际的消息)
Partition
file存储方式
Segment
file结构
为加快kafka检索、消费以及生产速度,以log文件保留消息,并通过index文件保存,首先根据索引的offset(是当前分区的第ofset个消息),根据分段log文件的消息条数范围,进行二分查找,确定当前offset消息所在的分段log。然后根据index的索引(二分索引)(index的key,value中的key指的是当前分段的第key个消息,value为当前分段的第key个消息对应于当前分段的字节偏移量),进行二分查找,如果正好匹配,则直接读取对应字节偏移量的消息,否则,在相近的二分查找所找到的索引出的字节偏移量后续进行顺序读知道找到知道消息个数偏移量的消息。
查找第368776条消息(二分查找根据消息偏移量确定分段文件、二分查找根据分段索引确定相对于当前分段的字节偏移量)和第368774条消息(二分查找根据消息偏移量确定分段文件、二分查找+顺序读取 根据分段索引确定相对于当前分段的字节偏移量)
以读取offset=368776的message为例,需要通过下面2个步骤查找:
第一步查找segment
file;
以上图为例,其中00000000000000000000.index表示最开始的文件,起始偏移量(offset)为0.第二个文件00000000000000368769.index的消息量起始偏移量为368770
= 368769 +
1。只要根据offset二分查找文件列表,就可以快速定位到具体文件。当offset=368776时定位到00000000000000368769.index|log
第二步通过segment
file查找message;
算出368776-368770=6,取00000000000000368769.index文件第三项(6,1407),得出从00000000000000368769.log文件头偏移1407字节读取一条消息即可
Kafka消息的index
file的组成和物理结构(.index
file)
Kafka集群维护
增加一个topic,针对某个topic进行扩展分区,
实时获取kafka集群信息
Topic工具
列出集群当前所有可用的topic
Kafka-topic.sh
–list –zookeeper zookeeper_address
查看集群特定的topic
Kafka-topic.sh
–describe –zookeeper zookeeper_address –topic topic_name
Ctrl
+ R –
创建topic
Kafka_topic.sh
–create –zookeeper zookeeper_address –replication-factor 1
–partitions 1 –topic topic_name
Kafka-topics.sh
–zookeeper zookeeper_address –alter –topic topic_name
–partitions 4修改partitions数量
Kafka集群leader平衡机制
Leader下线 上线时不平衡
每个partition的所有replicas叫做“assigned
replicas”,“assigned
replicas”中第一个replicas叫做“preferred
replica”,刚创建的topic一般“preferred
replica”是leader。
集群leader手动平衡:
Kafka-preferred-replica-election.sh
–zookeeper zookeeper_address
自动平衡设置:
Auto.leader.balance.enable=true
Kafka集群分区日志迁移
主要有两种情况:
某个topic全部迁移一个机器移动到另外一个机器
某个topic的某个分区移动
迁移topic数据到其他broker,以下四步:
写json文件文件格式:cat
topics-to-move.json
{“topics”:[{“topic”:”foo1”},{“topic”,”foo2”}],”version”:1}记录迁移的topic列表
使用–generate生成迁移计划kafka-reassign-partitions.sh
–zookeeper zookeeper_address –topics-to-move-json-file
topics-to-move.json –broker-list “5,6” –generate仅仅是生成计划 没有执行数据迁移(会生成一个新的分区json文件可以写入expand-cluster-reassignment.json命名随意)
使用-execute执行计划kafka-reassign-partitions.sh
–zookeeper zookeeper_address –reassignment-json-file
expand-cluster-reassignment.json –execute执行前最好保存当前分配情况,以防出错回滚
使用-verify验证是否已经迁移完成kafka-reassign-partitions.sh
–zookeeper zookeeper_address –reassignment-json-file
expand-cluster-reassignment.json
–verify迁移某个topic的某些特定partition数据到其他broker,步骤同上,但json文件如下:cat
custom-reassignment.json
{“partitions”:[{“topic”:”foo1”,”partition”:0,”replicas”:[5,6]},{
“topic”:”foo2”,”partition”:1,”replicas”:[2,3]}]}
�可以指定到topic分区编号�
集群分区日志迁移和leader平衡机制在实际的kafka集群运维中非常重要
kafka-reassign-partitions.sh工具会复制磁盘上的日志文件,只有当完全复制完成,才会删除迁移前磁盘的日志文件。执行分区日志迁移需要注意:
kafka-reassign-partitions.sh粒度只能到broker(机器),不能到broker的目录(如果broker上面配置了多个目录,是按照磁盘上已驻留的分区数来均匀分配的),如果topic之间的数据或topic中partition之间的数据本身就不均匀,很有可能造成磁盘数据的不均匀。
对于分区数据较多的分区迁移数据会花大量时间(经过网络传输),topic数据较少或磁盘有效数据较少时进行数据迁移
进行分区迁移时,最好先保留一个分区在原来的磁盘上,这样不会影响正常的消费和生产,如果目的是将分区5(broker1,5)迁移到broker2,3.可以先将5迁移到2和1【1作为轴,没有变,确切是将5迁移到2】;再将2和1迁移到2和3【首先使用leader平衡工具,将2和1中的作为分区的leader,将数据从1迁移到broker3】.而不是一次将1和5迁移到2和3.
Kafka集群监控
Kafka
Offset Monitor –监控当前的kafka集群有哪些机器存活哪些topic队列生产者消费者积压了多少数据 监控一个集群
Kafka
Manager Yahoo开源的监控工具 监控多个集群
(另有JMX)
Kafka
Offset Monitor:
当前存活的broker集合
当前活动topic集合
消费者组列表
当前consumer按组消费的offset
lag数(当前topic当前分区目前有多少消息积压而没有及时消费)
部署kafka
offset Monitor github的一个KafkaOffsetMonitor的jar包
Java
–cp (class package )KafkaOffsetMonitor-assembly-0.2.0.jar
com.quantifind.kafka.offsetapp.OffsetGetterWeb –zk zk-01,zk-02
–refresh 5.minutes –retain 1.day & (刷新几次 驻留时间)引用gogle的api必须翻墙 一次即可
Kafka
Manager由雅虎开源:(安装要求能上网还要求能翻墙)
管理几个不同的集群
检查集群状态(topics,brokers,副本的分布,分区的分布)
选择副本进行查看
基于集群的当前状态产生分区分配
重新分配分区
安装sbt
scala-sbt配置环境变量
安装kafka-manager下载 编译
部署 修改配置文件
下载打包好的kafka-manager解压后修改配置
Kafka服务器模型和leader选举机制
Hadoop
netty redis mina服务器 常使用的reactor模型leader会使用zookeeper选举kafka不是使用zookeeper选举因为会存在一系列的问题
Kafka核心源码分析
设计和技巧
Kafka消费者源码
分区消费模式源码:
分区消费模式
服务器端源码:
通过findLeader创建PartitionMetadata从kafka服务器端获取消息,需要分区、leader的元数据,通过findLeader服务器端提供的函数,接收一个host、port,获取对应的分区。其分区、备份数据,构造一个PartitionMetadata。
通过PartitionMetadata创建一个SimpleConsumer,分区消费模式使用的是SimpleConsumer
SimpleConsumer创建完成之后,可以进行消费,构建FetchRequest对象--包括所要获取的topic、partition、offset等组成的数据结构,发送给服务器,服务器根据所要了解的这些key,获取返回的值,发送这些结果给client。
发送FetchRequest,如果没成功,可能是我们请求的那个broker挂了等原因,回到第一步创建MetaData,如果发送成功,服务器端回复一个响应
接收服务器端发送的FetchResponse响应,(message、offset)
迭代的处理消息(message,offset)
再去构造一个FetchRequest再进行消息的获取
服务器端发送的消息是以消息组或消息块的形式发送给我们,发送一个的消息后,会在块内进行迭代,迭代完成后,再构建FetchRequest。一次发送一个块,能够使用尽量少的交互获取尽量多的消息。
分区消费模式直接由客户端(任何高级语言编写)使用Kafka提供的协议向服务器发送RPC请求获取数据,服务器接收到客户端的RPC请求后,将数据构造成RPC响应,返回给客户端,客户端解析相应的RPC响应获取数据。
Kafka支持的协议:
获取消息的FetchRequest和FetchResponse发送请求 发回响应
获取offset的OffsetRequest和OffsetResponse单独发送一个请求返回某一个offse
提交offset的OffsetCommitRequest和OffsetCommitResponse
low-level的api中,提交某个offset,发送OffsetCommitRequest获取OffsetCommitResponse
获取Metadata的Metadata
Request和Metadata
Response metadata(topic的某个分区,leader是谁,还有多少分区,当前服务器还有多少broker)
生产消息的producerRequest和ProducerResponse生产消息时,服务器端会构造一个producerRequest,发送给broker,broker回复一个producerResponse,
组消费模式源码:
通过ConsumerConfig对象创建配置,通过ConsumerConfig传递配置参数,(zk地址,fetch取数据创建线程,buffer参数)
根据配置ConsumerConfig中的相关配置创建ConsumerConnector(实际上创建的是zkConsumerConnector)
通过ConsumerConnector,创建一个KafkaStream流,kafkaStream流封装了流式消息 ,类似于java的输入输出流
基于KafkaStream创建一个消费者迭代器ConsumerIterator获取消息进行消息的处理
如果服务器无新的消息就会进行阻塞,知道有新的消息进入处理消息。Iterator
next hasNext
通过配置创建ConsumerConfig
基于配置建立ZookeeperConsumerConnector连接,创建ZookeeperConsumerConnector对象
一下分为两方面,第一:
创建ConsumerFetchManager获取消息通过ConsumerFetchManager
ConsumerFetchManager创建ConsumerFetchThread可以创建多个线程可配置
ConsumerFetchThread发送FetchRequest获取消息
服务器端 将返回的消息填充至FetchDataChannel队列 将队列封装成FetchResponse响应 获取FetchResponse填充数据到KafkaStream获取流式数据,在流式数据上获取迭代器,通过迭代器获取消息
仍然通过发送FetchRequest
RPC请求获取消息,由服务器端帮忙处理,不需要客户端的操作 分区消费模式需要我们自己发送FetchRequest
组消费模式也会自动提交offset
另一方面:
创建Scheduler,定式调度的任务,调度的频率可通过参数配置
定期调用autoCommit
Offset向Zk提交offet把当前用户消费过的offset更新在zk对应的节点并返回 下一次获取的时候读取zk的配置即可
两种消费模式服务器端源码对比:
分区消费模式:
指定消费topic、partition、Offset通过向服务器发送RPC请求进行消费
需要自己提交offset发送offset
commit Request提交offset;将offset写到zk对应的目录
需要自己处理各种错误,如leader切换错误
自己处理消费者负载均衡策略在FetchRequest中指定需要消费的topic和对应分区需要用户、客户端自己处理负载均衡
组消费模式:
通过向服务器发送RPC请求完成的 和分区消费模式一样
组消费模式由kafka客户端处理各种错误(如leader切换错误),然后将消息放入队列再封装为迭代器(队列为FetchedDataChunk对象),客户端只需要在迭代器上迭代取出消息。
由kafka服务器端周期性的通过scheduler提交当前消费的offset,无需客户端负责。分区消费模式则需要自己来管理和提交
Kafka服务器端处理消费者负载均衡策略
监控工具Kafka
Offset Monitor和KafkaManager均是基于组消费模式
尽可能使用组消费模式,除非需要:
自己管理offset,(为了实现消息投递的其他语义 至多一次 准确一次)
自己处理各种错误(根据业务需求) 分区消费模式和low-level
api
Kafka生产者源码介绍:
同步发送模式源码:
创建producer对象 传递一些配置 如brokerlist
ack等等
创建发送者线程
根据brokerId和partition选择SyncPool发送消息在服务器端维护一个线程池 从线程池中选择一个线程
选择好一个发送线程后 构造ProducerRequest请求 发送实质的消息
按照用户执行的序列化函数序列化消息
用户传递的消息可能是字符串或其他的形式,需要将其序列化为二进制
序列化函数可为用户定义 默认两种:字符串序列化为字符数组
对象序列化为字节数组
发送ProducerRequest请求 检查发送是否出错(所选择broker
leader切换)
如果无错误接受ProducerResponse响应
按照client配置发送ack信息
如果有错误
是否超过最大出错次数 如果是发送出错 如果没有继续重试
异步发送源码:
Producer异步发送至客户端段的一个队列中,队列中的消息没有达到一定数量或超过一定时间,producer消息一直存放在该队列中,达到一定数量后或者积累时间超过配置,
会启动一个发送线程,发送线程会通过轮询读取消息,并将这些消息批量发送至Kafka
Cluster
在发送消息之前会在Producer
Pool中根据partition寻找一个SyncProducer
创建producer
创建发送者线程(同样是从创建的同步发送池中获取的同步发送线程)
消息放入队列
消息数达到或者时间达到?
是的话
根据brokerId和Partition选择SyncPool(kafka消息都是通过leader进行读写的)
按照用户执行的序列化函数序列化消息
构造ProducerRequest请求RPC请求 同步会一条一条发送消息和ack异步是批量发送数据给broker
发送ProducerRequest请求
接收ProducerReponse响应
发送成功 –继续接收producer发送的消息
不管是同步还是异步都是取出一个同步发送线程,最后都是同步过程,只是异步的时候会先将消息发送至队列里,当消息数达到一定数量和时间达到配置时发送。所以同步发送和异步发送后半部分都是一致的。
同步发送和异步发送最后都是通过构造RPC请求进行发送的,
同步发送服务器端会向客户端发送一个确认,异步发送服务器端不会发送ACK。
两种模式服务器端源码介绍:
同步发送模式具有几下特点:
同步的向服务器发送RPC请求进行生产,(所谓同步就是每条消息一个请求一个ack)
发送错误可以重试
可以向客户端发送ack(可以配置不发送)
异步:
最终也是通过向服务器发送RPC请求完成的(和同步发送模式一样),异步发送的是消息段批量发送
异步发送模式先将一定量消息放入队列中,待达到一定数量后再一起发送。
异步发送模式不支持发送ack,但是Client可以调用回调函数获取发送结果。
性能高用异步,准确性用同步。
Kafka
Server Reactor设计模式
大量的连接数 高并发Reactor模型基于Java
NIO进行设计,是对Linux
epoll模型进行改造,
Reactor模型:
Java
NIO:
Java
1.4引入的,NIO比较成熟,Java1.3之前使用的是原生态的打开文件、读取文件的方式(比较慢,不方便)
New
io全新的方式读取文件 套接字
Java
NIO:
Channels:java中的流
Buffers:缓冲区 存放二进制的缓冲区 类似于数组
Selectors:解决并发网络套接字
Channel通道和Java中的Stream一样,用于传输数据的数据流,数据可以从Channel读到buffer中,也可以从buffer写到Channel中。
Selector允许单线程处理多个Channel。使用Selector,首先得向Selector注册Channel,然后调用它的select方法。此方法会一直阻塞到某个注册的Channel有时间就绪。一旦这个方法返回,线程可以处理这些事件,事件的例子如新连接进来,数据接收等。
Linux
epoll模型:
Epoll是一种IO多路复用技术,在Linux内核中广泛使用。常见的三种IO多路复用技术为select模型、poll模型和epoll模型。
Select模型:轮询所有的套接字查看是否有时间发生(套接字需要注册在select上,select向注册的套接字进行轮询,查看是否有事件发生)
缺点:套接字最大1024个主动轮询效率低(对所有的套接字不断地轮询) 事件发生后需要将套接字从内存空间拷贝到用户空间效率低
Poll模型和select模型类似,修正了select模型最大套接字限制
Epoll模型:修改主动轮询为被动轮询,当有事件发生时,被动接收通知。所以,epoll模型注册套接字后,主程序可以做其他事情,当事件发生时,接收到通知后再去处理。修正了select模型的三个缺点,(第三点使用共享内存修正)共享内存:内核空间和用户空间共同使用的内存空间。
Epoll模型为Linux系统作为服务器提供很大的支持。
Java
nio叫做select模型,底层使用的是epoll模型。
Java
nio叫做select模型指的是选择哪一个Channel的意思。
Kafka
Server Reactor模型:处理大量连接 大量套接字 不同套接字 不同的行为
Kafka
SocketServer是基于Java
NIO开发的,采用Reactor的模式(已被大量实践证明非常高效,在Netty和Mina中广泛使用 网络开发的框架 大量使用Reactor模型处理高并发大量连接)。Kafka
Reactor的模式包括三种角色:
Acceptor:接收注册Channel
Procesor:接收从Select传回的Channel然后将Channel注册到Handler
Handler:Handler处理用户逻辑
Kafka
Reactor包含了一个Acceptor负责接受客户端请求,N个Processor线程负责读写数据(为每个Connection创建出一个Processor去单独处理,每个Processor中均引用独立的Selector—Processor下也有Selector需要注册Channel),M个handler来处理业务逻辑。在Acceptor和Processor,Processor和Handler之间都有队列(缓冲、解耦)来缓冲请求。
Kafka
Server Reactor模型:(高并发 的核心Hbase
Storm Hadoop Redis服务器端
大量使用) (大量的连接 每个连接处理大量的事件)
客户端发起连接
KafkaSocketServer接收连接 通过Acceptor接收连接注册Selector创建N个Processor处理业务 每个Processor对应一个客户端连接 为Processor注册一个Selector
Selector注册一个Channel,查看对应的连接上是否有读数据和写数据
Acceptor注册Selector给Processor
Processor注册Selector给Handler
Acceptor主要职责是监听客户端的连接请求,并建立和客户端的数据传输通道,然后为客户端指定一个Processor,它的工作到此结束。接着响应下一个客户端的连接请求:
Processor主要职责是负责从客户端读取数据和将响应返回给客户端,本身不处理业务逻辑,每个Processor都有一个Selector,用来监听多个客户端,可以非阻塞地处理多个客户端的读写请求,Processor将数据放入RequestChannel的RequestQueue和从ReponseQueue读取响应。
Handler(kafka.server.KafkaRequestHandler
kafka.server.KafkaApis)的职责从RequestChannel中的RequestQueue取出Request,处理以后再将Response添加到RequestChannel中的ResponseQueue
Kafka
Partition Leader选主机制
大数据常用的选主机制:
Leader选举算法很多
Zab:Zookeeper使用 (选主 同步队列 复制锁)
他们都是Paxos算法的变种--大数据领域非常著名的算法
Zab协议四个阶段:
Leader
election:leader选举
Discovery(epoch
establish):发现 版本建立
Synchronize(sync
with followers):从leader同步数据和状态
Broadcast:leader广播状态和数据到follower
Zk和raft
posx都会首先选择自己为leader然后进行同步协商;另一个特点:半数以上的服务器认为是leader,则同一其为leader。(少数服从多数)
先选自己为leader,然后选择一个编号大的为leader,然后半数赞同为leader,则选举其唯leader。
一般来说第二个启动的节点就是leader
Zk是奇数的,负载不是很高,处理元数据。
Raft:
在Raft中,任何时候一个服务器可以扮演下面角色之一:
Leader:处理所有客户端交互,日志复制等,一般只有一个Leader
Follower:类似选民,完全被动
Candidate:候选人可以被选为新领导者
Zk中每个人都可以成为leader
raft必须从候选人里选择,候选人是由系统管理员配置的
启动时集群中制定一些机器为候选人candidate,然后candidate开始向其他机器(尤其是Follower)拉票,当某一个Candidate的票数超过半数,它就成为leader。
Kafka集群将元数据存储在zk
Kafka集群依赖zk集群,所有follower都在zookeeper上设置一个watch,一旦leader宕机,其对应的ephemeral
znode临时节点会自动删除,此时所有的Follower都尝试创建该节点,创建成功者(Zookeeper保证只有一个能创建成功)即是新的leader,其它replica即成为follower
Split-brain(脑裂):由zookeeper特性引起,虽然zookeeper能保证所有watch顺序触发,但并不能保证同一时刻所有repilca“看”到的状态是一样的(可能由于网络原因),可能造成replica的响应不一致 可能会产生多个leader
Herd
effect(羊群效应):如果宕机的那个broker(leader)上的partition比较多,会造成多个watch(follower)被触发,造成集群内大量的调整。
Zookeeper负载过重:每个replica都要为此在zookeeper注册一个watch,当集群规模增加到几千个partition时zookeeper负载会过重
主要用来处理数据量比较小的,比如HDFS的HA切换HA的选举,用于通信量比较小 数据量比较小的选举
像kafka大数据量的传输HDFS大数据量的传输不使用zk
raft进行选举 大量通信的开销kafka和HDFS的大量数据的传输 很容易造成元数据通信消息的消失 网络丢包等缺点
Kafka使用自己的选主机制:
Kafka的Leader
Election解决以上问题,在所有broker中选出一个controller,所有partition的leader选举都由controller决定。Controller会将leader的改变直接通过rpc的方式(比zookeeper
queue方式更高效)通知需要为此做出相应的broker。
controller的选举过程:以broker为单位使用zookeeper进行选举而不是之前那种以partition为单位使用zookeeper进行选举量太大了
kafka
partition leader选举过程有controller执行:从zk中读取当前分区的所有ISR(in-sync
replicas)集合(心跳机制保持连接且同步数据中数据记录相差不超过10000) 调用配置的分区选择算法选择分区的leader(基本都会选择ISr中的第一个即Prefer分区作为leader) 当前包括五种选择算法
离线处理:
Flume
- kafka - storm、spark
– mongodb(数据入库)/hbase/redis
Elasticsearch
drill impala kylin
常用选主机制的缺点:
Kafka
Partition选主机制:
Kafka生产者源码
Kafka
Server Reactor设计模型
KafkaPartition
Leader选举机制
Kafka回顾:
Kafka业务场景
接触耦合:
增加冗余:规避数据丢失风险
提高可扩展性:解耦了处理过程
增大消息入队和处理的频率 – 额外增加处理的过程即可
Buffering:任何一个重要的系统需要处理不同的时间元素 消息队列通过缓冲层帮助任务高效执行 写入队列的处理会尽可能的快速,该缓冲有助于控制和优化数据流经系统的速度
异步通信:异步的非阻塞发送对于扩展消息系统来说
是一个基本的特性
应用场景:
Push
Message:消息推送 使用kafka作为消息系统的核心中间件完成消息的生产和消费
Website
Tracking:网站跟踪
日志收集中心:kafka的push
pull适合异构集群,适合批量提交消息,对于生产端来说,性能方面没有消费。消费段;Hadoop离线分析storm实时分析
实时统计平台搭建注意事项:
HA特性:分布式计算分布式存储kafka有HA特性
核心文件配置:
启动步骤:先启动zookeeper再启动kafka
Kafka
Project Process
Data
Collection -> Data Access -> Stream Computing ->
Dataoutput
Flume
kafka storm Redis/Mysql(持久化)
Kafka
Producer:Flume
cluster ------ Sink ---------》 (producer)KafkaCluster
Kafka
Consumer:Kafka
Cluster ----------KafkaSpout ------------》Storm
Cluster
Kafka工程准备:kafka监控工具stormui管理界面
基础环境准备:
Producer:服务器数据------
flume agent代理节点-------Sink到Kafka集群
Consumer:kafka集群-------通过kafkaSpout输送到Storm集群----Storm集群--------通过Storm集群进行实时计算,并将结果持久化到DB库中 (MySQl、Redis)