Kafka系列文章:
Kafka系列 —— 入门及应用场景 & 部署 & 简单测试
Kafka系列 —— Kafka核心概念
Kafka系列 —— Kafka常用命令
Kafka系列 —— Kafka监控
Kafka系列 —— Kafka消费语义分析&分区策略解读
本篇文章中将结合生产的案例来简述Kafka的一些生产实践
在实际生产中通常会使用Flume + Kafka + SparkStreaming去打造通用的流式处理平台,在Kafka与SparkStreaming的对接过程中,有许多有意思的点值得思考
Kafka消息量的相关解读
引入kafka的目的
避免在业务高峰的时候,数据量大,随之而来的就是计算压力也会很大,因此会引入kafka
但是当作为一个小白去设计的时候,一开始肯定不会考虑的很周全:
- Spark Streaming去消费kafka的时候,是一窝蜂的去消费,并没有定义一套规则:比如在每个批次指定消费多少记录
这个指定消费多少记录,是可以通过Kafka的几组参数进行控制的
每个批次的最大消息量的参数及公式
先来看一个图:
2500 records = 1 * 2 * 1250
- 1:topic的partition个数
- 2:spark streaming代码中batch时间
- 1250:spark.streaming.kafka.maxRatePerPartition=1250
每个partition最大的速率,在这样的一个规定下向Kafka去拿数据,最大只能拿1250条/s
注意点:
并不是说一个批次中据量越大越好
从Spark Web UI上我们可以发现有个Scheduling Delay的概念
假设1个批次有10W条记录,我们的batch时间设置的为2s,除了读取数据之外,如果含有业务逻辑的操作:比如写数据到数据库中,那么这2s能处理完吗?相当于在这2s内需要拿数据、进行业务逻辑的处理、将处理结果写入数据库;如果处理不完,那么就意味着会出现消息堆积的情况,Scheduling Delay(延迟时间)会加大
因此,我们需要去做压测,从而去寻找符合公司实际业务场景的、最大的数据量
如果这个曲线图一直在上升,那么就说明数据堆积了
Spark Streaming生产参数解读
spark.streaming.backpressure.enabled
场景:假设目前还剩7500条消息没有进行消费
- 设置参数spark.streaming.backpressure.enabled=true,默认为false
7500条不是两个批次直接消费完,而是慢慢的进行消费 - 设置参数spark.streaming.backpressure.enabled=false
7500条是两个批次直接消费完
问题:上述这2种场景该怎么去取舍呢?
- spark.streaming.backpressure.enabled=true,生产常用,kafka没有堆积的情况,慢慢去消费即可(因为数据是比较正常过来的)
- spark.streaming.backpressure.enabled=false,Kafka中有消息堆积,需要去快速的消费完可能会出现这种场景,比如:Kafka中已经堆积了1亿条数据,如果还是设置为true去慢慢消费的话,会出现问题,因此需要将该参数设置为false
这种场景比较特殊;当出现这种情况的时候,需要我们去思考为什么会产生数据的堆积?
曾经遇到过这种情况,不过是由于人为造成的
spark.streaming.stopGracefullyOnShutdown
- 默认为false
默认为false情况下的一个场景:
手工kill掉了Spark Streaming进程,当前进程就挂了;同时,也不会去做任何操作,比如写offset啊这种,都不会去做,那么重启之后怎么办?
这里就引入了断批还原的概念,由于涉及到公司项目的相关生产场景,不方便在博文中进行文字表述,大家可以仔细思考这种场景下的解决方案 - 如果设置为true,那么在接收到kill的命令之后,会等待下一个批次处理完,然后再kill,比较优雅;生产上建议设置为true
offset管理
官网:http://spark.apache.org/docs/2.2.0/streaming-kafka-0-10-integration.html#obtaining-offsets
断批还原的关键点在于对于offset的维护管理,因为我们的offset是一个批次一个批次去写的,offset的3种维护方式:
-
checkpoint
维护在HDFS上的;是每隔一段时间将内容存放到HDFS上去的,会有许多问题:
1.小文件,会对HDFS集群造成很大的压力
2.会丢数据
checkpoint配合kafka能够在特定环境下保证不丢不重,注意为什么要加上特定环境呢,这里有一些坑,checkpoint是对sparkstreaming运行过程中的元数据和每次rdd的数据状态保存到一个持久化系统中,当然这里面也包含了offset,一般是HDFS,S3,如果程序挂了,或者集群挂了,下次启动仍然能够从checkpoint中恢复,从而做到生产环境的7*24高可用
但是checkpoint的最大的弊端在于,一旦你的流式程序代码或配置改变了,或者更新迭代新功能了,这个时候,你先停旧的sparkstreaming程序,然后新的程序打包编译后执行运行,会发现两种情况:
(1)启动报错,反序列化异常
(2)启动正常,但是运行的代码仍然是上一次的程序的代码。
为什么会出现上面的两种情况,这是因为checkpoint第一次持久化的时候会把整个相关的jar给序列化成一个二进制文件,每次重启都会从里面恢复,但是当你新的程序打包之后序列化加载的仍然是旧的序列化文件,这就会导致报错或者依旧执行旧代码。有的同学可能会说,既然如此,直接把上次的checkpoint删除了,不就能启动了吗?确实是能启动,但是一旦你删除了旧的checkpoint,新启动的程序,只能从kafka的smallest或者largest的偏移量消费,默认是从最新的,如果是最新的,而不是上一次程序停止的那个偏移量,就会导致有数据丢失,如果是老的,那么就会导致数据重复。不管怎么样搞,都有问题 -
Kafka自身
官网:http://spark.apache.org/docs/2.2.0/streaming-kafka-0-10-integration.html#kafka-itself
enable.auto.commit=true,自动提交,默认是每隔5s去提交一次
自身维护是:提交到kafka的当前topic的内嵌的topic的:_consumer中
enable.auto.commit=false,官方给的默认值为false
false情况下,我们需要在业务处理后,手工提交offset,即手工使用官方API异步提交offset(生产)
官网给的代码:
-
其它存储
早期版本的Kafka offset是维护在zk里的
可选的存储有:ZK、HBase、Redis
Github:https://github.com/wangliangbd/SparkStreaming_Store_KafkaTopicOffset_To_HBase
Kafka与SparkStreaming对接的2种方式
[待更新…]
Kafka调优参数详解
Kafka的调优,从三个方面入手去考虑:Producer、Broker、Consumer
Producer(Flume/Maxwell/…)
生产上使用的Kafka是0.10.x版本,因此对应查看相应官网:
http://kafka.apache.org/0100/documentation.html#producerconfigs
-
acks
生产上设置为all,默认为1
1个topic有3个partition,每个partition的副本数为3;其中,只有作为leader的那个副本才具备读写的功能,其余2个不作为leader的副本都不对外提供读和写的,仅仅只用于复制,如下图:
因此,当一条数据过来,我们复制三份,才能保证是最安全的
all代表:三个副本都写完了,而且各自都返回了1个ack的确认值,这样producer才觉得消息是完完全全的发送过去了
This is the strongest available guarantee 官网的描述,说白了就是数据不会丢 -
buffer.memory
比如说flume向kafka发送数据,当发送的过快时,我们需要将这份数据先缓存起来再发(类似于MapReduce的shuffle过程,并不是直接写到磁盘里面,而是先写到内存的buffer缓冲池,达到阈值之后再刷到磁盘上去) -
compression.type
-
retries
生产上设置为100,默认为0
如果发送消息失败的话,要重试多少次;如果不去重试,就很有可能引起消息的发送失败
注意:
需要设置参数max.in.flight.requests.per.connection = 1
如果没有设置这个参数,将会改变发送的消息的顺序关系
举例:
现在有2条消息都发送到partition0,第1条发送失败了,第2条发送成功了;现在第1条发送失败的要重试了,原本的顺序关系是1、2;
而现在由于第1条发送失败了,第2条成功,第1条需要进行重试,这个时候就会引起发送消息的顺序问题,因此必须设置这个参数
在上文中我们提到过:单分区内是要保证有序的,而对于多分区是无法保证有序的
面试的时候尽量不要自己主动提排序的问题,因为很可能会给自己挖坑,但是大致的也是需要懂的 -
batch.size
对于数据可以是来一个批次,将该批次内的数据都给发送过去;这样做可以降低请求数 -
linger.ms
在工作中用的不是很多,默认值为0
比如flume往kafka发送消息,发送的过程中会去延迟一会、等待一会
第1个批次和第2个批次的间隔时间给拉大一点,每个批次里的数据堆积的多点了再发送,这样做能够降低负载,但是会增加延迟
Setting linger.ms=5, for example, would have the effect of reducing the number of requests sent but would add up to 5ms of latency to records sent in the absense of load.
官网对于该参数的一个案例解释:如果设置5ms,可以降低请求,但是我们会添加5ms的延迟
对于一般的交易系统来说,默认这个参数是不用的 -
max.request.size
指的是请求最大的字节,该参数用于控制生产者发送的请求大小;它可以指能发送的单个消息的最大值,也可以指单个请求里所有消息总的大小 -
request.timeout.ms
需要大于replica.lag.time.max.ms(Broker中的参数)
指定了生产者在发送数据时等待服务器返回响应的时间 -
metadata.fetch.timeout.ms
指定了生产者在获取元数据(比如目标分区的首领是谁)时等待服务器返回响应的时间;如果等待响应超时,那么生产者要么重试发送数据,要么返回一个错误 (抛出异常或执行回调) -
max.block.ms
该参数指定了在调用send()方法或使用parttitionFor()方能获取元数据时生产者的阻塞 时间;当生产者的发送缓冲区已满,或者没有可用的元数据时,这些方屈就会阻塞在阻塞时间达到max.block.ms时,生产者会抛出超时异常 -
timeout.ms
该参数是针对acks的,默认才30s,一旦遇到网络抖动肯定是不够的,因此需要调大
指定了broker等待同步副本返回消息确认的时间,与asks的配置相匹配一一如果在指定时间内没有收到同步副本的确认,那么broker就会返回 一个错误
注意: 对于调优,带有timeout的参数肯定需要先进行全局搜索一下(不管是什么框架)
Broker(CDH)
- message.max.bytes
1条消息的大小 - zookeeper.session.timeout.ms
- replica.fetch.max.bytes
需要大于message.max.bytes - num.replica.fetchers
提高复制的进程数,但是会影响到IO - replica.lag.max.messages
最多可以复制的消息大小 - replica.lag.time.max.ms
- log.flush.interval.messages
- log.flush.interval.ms
Consumer(Spark Streaming)
涉及到的调优参数可以参考这个issue:https://issues.apache.org/jira/browse/SPARK-22968