1、Kafka架构
Producer
通过push模式向消息队列Kafka版的Kafka Broker发送消息。发送的消息可以是网站的页面访问、服务器日志,也可以是CPU和内存相关的系统资源信息。
Kafka Broker
用于存储消息的服务器。Kafka Broker支持水平扩展。Kafka Broker节点的数量越多,消息队列Kafka版集群的吞吐率越高。
Group(Consumer消费者)
通过pull模式从消息队列Kafka版Broker订阅并消费消息。
Zookeeper
管理集群的配置、选举leader分区,并且在Group发生变化时,进行负载均衡。
注意:Zookeeper中保存Broker id和消费者offsets等信息,但是没有生产者信息。
Kafka框架
ali-kafka配置设置
topic:100;group:200;partitions:1200;
最大消息大小:1M;消息保留时长:72h(3d)
消费位点保留时间:10080分钟(7d)
2、Kafka的机器数量
Kafka机器数量 = 2 *(峰值生产速度 * 副本数 / 100)+ 1
3、副本数设定
一般我们设置成2个或3个,很多企业设置为2个。
副本的优势:提高可靠性;副本劣势:增加了网络IO传输
4、Kafka压测
Kafka官方自带压力测试脚本(kafka-consumer-perf-test.sh、kafka-producer-perf-test.sh)。Kafka压测时,可以查看到哪个地方出现了瓶颈(CPU,内存,网络IO)。一般都是网络IO达到瓶颈。
5、Kafka日志保存时间
默认保存7天;生产环境建议3天
6、Kafka中数据量计算
日志信息(两张表,一张:80-90g,一张:20-30g):
每天总数据量100-120g,每天产生10亿条日志,10000w/24/60/60=1.1w条/每秒钟
平均每秒钟:11000条
低谷每秒钟:9000条
高峰每秒钟:13900条
每条日志大小:0.8-1.8kb(1.3kb)
每秒多少数据量:8.8MB-12.8MB(10.6MB)
实时写入小时表:小时分区总数据量 3300w-5000w(保留1month)
t+1合并(join 其他维度数据,打宽表)为d表(保留180d)
实时订单:
每天总订单量大概60-70w, 60w/24/60/60=7条/每秒钟
平均每秒钟:7条
低谷每秒钟:1-2条
高峰每秒钟:7条 *(10 -15倍)=70条-120条
hadoop查询表大小 hadoop fs -du
hadoop fs -du -h -s /user/hive/warehouse/xx_ods.db/表名/(分区)
-s:表示汇总值(其实就是查询这个表数据总的文件大小,反之查的是表每个文件的大小)
-h:单位为k,m,g,便于阅读
7、Kafka的硬盘大小
每天的数据量110g * 2个副本 * 3天 / 70%
8、Kafka监控
开源的监控器:KafkaManager、KafkaMonitor、KafkaEagle
直接通过python脚本直接Request(集群)kafka中的消费情况,根据给定的阈值来判断是否告警(钉钉)。
9、Kafka分区数
1)创建一个只有1个分区的topic
2)测试这个topic的producer吞吐量和consumer吞吐量。
3)假设他们的值分别是Tp和Tc,单位可以是MB/s。
4)然后假设总的目标吞吐量是Tt,那么分区数=Tt / min(Tp,Tc)
例如:producer吞吐量=20m/s;consumer吞吐量=50m/s,期望吞吐量100m/s;
分区数=100 / 20 = 5分区
如何根据数据量确定Kafka分区个数、Kafka的分区是不是越多越好、Kafak生产者分发策略,消费者负载均衡 09_啊策策大数据社区-CSDN博客
分区数一般设置为:3-10个(ali-默认分区个数是12)
10、Kafka多少个Topic
通常情况:多少个日志类型就多少个Topic。也有对日志类型进行合并的。
11、Kafka的ISR副本同步队列
ISR(In-Sync Replicas),副本同步队列。ISR中包括Leader和Follower。如果Leader进程挂掉,会在ISR队列中选择一个服务作为新的Leader。有replica.lag.max.messages(延迟条数)和replica.lag.time.max.ms(延迟时间)两个参数决定一台服务是否可以加入ISR副本队列,在0.10版本移除了replica.lag.max.messages参数,防止服务频繁的进去队列。
任意一个维度超过阈值都会把Follower剔除出ISR,存入OSR(Outof-Sync Replicas)列表,新加入的Follower也会先存放在OSR中。
分区分配策略
在 Kafka内部存在两种默认的分区分配策略:Range和 RoundRobin。
Range是默认策略。Range是对每个Topic而言的(即一个Topic一个Topic分),首先对同一个Topic里面的分区按照序号进行排序,并对消费者按照字母顺序进行排序。然后用Partitions分区的个数除以消费者线程的总数来决定每个消费者线程消费几个分区。如果除不尽,那么前面几个消费者线程将会多消费一个分区。
例如:我们有10个分区,两个消费者(C1,C2),3个消费者线程,10 / 3 = 3而且除不尽。
C1-0 将消费 0, 1, 2, 3 分区
C2-0 将消费 4, 5, 6 分区
C2-1 将消费 7, 8, 9 分区
第一步:将所有主题分区组成TopicAndPartition列表,然后对TopicAndPartition列表按照hashCode进行排序,最后按照轮询的方式发给每一个消费线程。
13、Kafka挂掉
1)Flume记录
2)日志有记录
3)短期没事
14、Kafka丢不丢数据
Ack = 0,相当于异步发送,消息发送完毕即offset增加,继续生产。
Ack = 1,leader收到leader replica 对一个消息的接受ack才增加offset,然后继续生产。
Ack = -1,leader收到所有replica 对一个消息的接受ack才增加offset,然后继续生产。
15、Kafka数据重复
幂等性 + ack-1 + 事务
Kafka数据重复,可以再下一级:SparkStreaming、redis或者Hive中dwd层去重,去重的手段:分组、按照id开窗只取第一个值;
16、Kafka消息数据积压,Kafka消费能力不足怎么处理?
1)如果是Kafka消费能力不足,则可以考虑增加Topic的分区数,并且同时提升消费组的消费者数量,消费者数 = 分区数。(两者缺一不可)
2)如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间 < 生产速度),使处理的数据小于生产的数据,也会造成数据积压。
17、Kafka参数优化
1)Broker参数配置(server.properties)
(1)日志保留策略配置
# 保留三天,也可以更短 (log.cleaner.delete.retention.ms)
log.retention.hours=72
(2)Replica相关配置
default.replication.factor:1 默认副本1个
(3)网络通信延时
replica.socket.timeout.ms:30000 # 当集群之间网络不稳定时,调大该参数
replica.lag.time.max.ms= 600000 # 如果网络不好,或者kafka集群压力较大,会出现副本丢失,然后会频繁复制副本,导致集群压力更大,此时可以调大该参数
2)Producer优化(producer.properties)
compression.type:none gzip snappy lz4
# 默认发送不进行压缩,推荐配置一种适合的压缩算法,可以大幅度的减缓网络压力和Broker的存储压力。
3)Kafka内存调整(kafka-server-start.sh)
默认内存1个G,生产环境尽量不要超过6个G。
export KAFKA_HEAP_OPTS="-Xms4g -Xmx4g"
18、Kafka高效读写数据
1)Kafka本身是分布式集群,同时采用分区技术,并发度高。
2)顺序写磁盘
Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到600M/s,而随机写只有100K/s。
3)零复制技术
19、Kafka单条日志传输大小
Kafka对于消息体的大小默认为单条最大值是1M,但是在我们应用场景中,常常会出现一条消息大于1M,如果不对Kafka进行配置。则会出现生产者无法将消息推送到Kafka或消费者无法去消费Kafka里面的数据,这时我们就要对Kafka进行以下配置:server.properties
replica.fetch.max.bytes: 1048576 broker可复制的消息的最大字节数, 默认为1M
message.max.bytes: 1000012 kafka 会接收单个消息size的最大限制, 默认为1M左右
注意:message.max.bytes必须小于等于replica.fetch.max.bytes,否则就会导致replica之间数据同步失败。
20、Kafka过期数据清理
保证数据没有被引用(没人消费他)
日志清理保存的策略只有delete和compact两种
log.cleanup.policy = delete启用删除策略
log.cleanup.policy = compact启用压缩策略
21、Kafka可以按照时间消费数据
Map<TopicPartition, OffsetAndTimestamp> startOffsetMap = KafkaUtil.fetchOffsetsWithTimestamp(topic, sTime, kafkaProp);
22、Kafka消费者角度考虑是拉取数据还是推送数据
拉取数据
23、Kafka中的数据是有序的吗
单分区内有序;多分区,分区与分区间无序;
扩展:
kafka producer发送消息的时候,可以指定key:
/**
* Creates a record with a specified timestamp to be sent to a specified topic and partition
*
* @param topic The topic the record will be appended to
* @param partition The partition to which the record should be sent
* @param timestamp The timestamp of the record
* @param key The key that will be included in the record
* @param value The record contents
*/
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value) {
if (topic == null)
throw new IllegalArgumentException("Topic cannot be null.");
if (timestamp != null && timestamp < 0)
throw new IllegalArgumentException(
String.format("Invalid timestamp: %d. Timestamp should always be non-negative or null.", timestamp));
if (partition != null && partition < 0)
throw new IllegalArgumentException(
String.format("Invalid partition: %d. Partition number should always be non-negative or null.", partition));
this.topic = topic;
this.partition = partition;
this.key = key;
this.value = value;
this.timestamp = timestamp;
}
这个key的作用是为消息选择存储分区,key可以为空,当指定key且不为空的时候,Kafka是根据key的hash值与分区数取模来决定数据存储到那个分区。
/**
* Compute the partition for the given record.
*
* @param topic The topic name
* @param key The key to partition on (or null if no key)
* @param keyBytes serialized key to partition on (or null if no key)
* @param value The value to partition on or null
* @param valueBytes serialized value to partition on or null
* @param cluster The current cluster metadata
*/
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
if (keyBytes == null) {
int nextValue = nextValue(topic);
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// no partitions are available, give a non-available partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
有序解决方案:同一张表的数据 放到 同一个 分区
=> ProducerRecord里传入key,会根据key取hash算出分区号
=> key使用表名,如果有库名,拼接上库名