目录
一、kafka概述
1、定义
1)开源的分布式事件流平台(Event Streaming Platform)
2)发布信息分为不同的类别,订阅者只消费感兴趣的信息
2、消息队列
1)java:ActiveMQ、RabbitMQ、RocketMQ
2)大数据:Kafka
3、应用场景
3.1、缓存
控制和优化数据流经过系统的速度,解决生产信息和消费信息处理速度不一致的情况
3.2、解耦
数据源数据存入消息队列,消费者去拉取自己对应的数据,解决数据源直接对接目的地的问题
3.3、异步通信
优化步骤,将消息存入队列,并不立即处理,在需要的时候去处理
4、模式
4.1、点对点
消费者主动拉取数据,拉取之后回应,并且队列删除消息
4.2、发布/订阅模式
多个topic、消费者消费信息后不删除数据、消费者相互独立,可重复消费数据
5、架构
说明:
kafka2.8之后可以不配置zookeeper
1、producer:消息生产者
2、consumer:消息消费者
3、consumer Group(CG):消费者之间消费不同分区的数据,一个分区只能由一个组内消费者消费,消费者组逻辑上是一个订阅者
4、broker:一台kafka服务器代表一个broker、一个broker包容多个topic
5、topic:队列,生产和消费者面向都是一个topic
6、parition:过大的topic分布到多个broker,并且是有序的队列
7、replica:副本,一个leader多个follower
8、leader:副本的主,发送的数据已经消费数据都是在主
9、follower:同步数据,故障转移
二、安装
1)官方下载地址: http://kafka.apache.org/downloads.html
2)解压文件 tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module/ mv kafka_2.12-3.0.0/ kafka
1、配置文件
1)修改config目录下server.properties文件
vim server.properties broker.id #每台kafka唯一标识不可重复!!!
log.dirs #日志数据存放路径,默认为Tmp需要修改
zookeeper.connect #向zookeeper集群注册信息,2.8版本之后可以不配置
2、环境变量
1)配置kafka配置环境变量
vim /etc/profile.d/my_env.sh
#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka
export PATH=$PATH:$KAFKA_HOME/bin
3、启动脚本
vim /root/bin/kf.sh
#!/bin/bash
case $1 in
"start"){
for i in hadoop102 hadoop103 hadoop104
do
echo " --------启动 $i Kafka-------"
ssh $i "/opt/module/kafka/bin/kafka-server-start.sh -daemon /opt/module/kafka/config/server.properties"
done
};;
"stop"){
for i in hadoop102 hadoop103 hadoop104
do
echo " --------停止 $i Kafka-------"
ssh $i "/opt/module/kafka/bin/kafka-server-stop.sh "
done
};;
esacchmod 777 /root/bin/kf.sh
注意:先停止kafka在停止zookeeper
4、命令行操作
--bootstrap-server <String: server toconnect to> 连接的 Kafka Broker 主机名称和端口号。
--topic <String: topic> 操作的 topic 名称。
--create 创建主题。
--delete 删除主题。
--alter 修改主题。
--list 查看所有主题。
--describe 查看主题详细描述。
--partitions <Integer: # of partitions> 设置分区数。
--replication-factor<Integer: replication factor> 设置分区副本。
--config <String: name=value> 更新系统默认的配置。
4.1、查看topic
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --list
4.2、创建topic
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --partitions 2 --replication-factor 2 --topic first
4.3、查看topic详情
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --describe --topic <topicName>
4.4、修改分区数--只增不能减
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --alter --topic first --partitions 3
4.5、删除topic
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --delete --topic first
4.6、生产者命令
bin/kafka-console-producer.sh --bootstrap-server hadoop102:9092 --topic first
4.7、消费者命令
1、最新位置消费 bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic first
2、历史消息消费 bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --from-beginning --topic first
3、指定时间消费 bin/kafka-consumer-groups.sh --bootstrap-server 127.0.0.1:9092 --group group_test --topic test_topic --reset-offsets --to-datetime 2022-07-02T12:00:00.000 -execute
三、kafka生产者
1、架构图
主要两个线程:
1)main线程:创建双端队列RecordAccumulator 缓冲区(32m,单批数据为16k)
2)sender线程:从RecordAccumulator 缓冲区拉取信息发送到kafka broker重要参数:
1、bootstrap.servers:生产者连接集群所需的broker地址
2、key和value.serializer:序列化类型,一定要写全类名
3、buffer.memory:RecordAccumulator缓冲区大小,默认32m
4、batch.size:一批数据的最大值,默认16k,增大即可增加吞吐量,过大会增加延迟性
5、linger.ms:发送的数据速率,默认0ms
6、acks:
1)0:生产者发送过来的数据,不需要等数据落盘应答。
2)1:生产者发送过来的数据,Leader 收到数据后应答。
3)-1(all):生产者发送过来的数据,Leader+和 isr 队列里面的所有节点收齐数据后应答。默认值是-1,-1 和all 是等价的。
7、max.in.flight.requests.per.connection:允许最多没有返回ack的次数,默认为5
8、retries:重试次数,默认为int的最大值
9、retry.backoff.ms:重试间隔,默认100ms
10、enable.idempotence:默认开启幂等性
11、compression.type:支持压缩方式none、gzip、snappy、lz4、zstd
2、异步发送
2.1、普通异步发送无回调
package com.xzz.kafka.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
/**
* @author 徐正洲
* @date 2022/3/6-20:18
*/
public class CustomProducer {
public static void main(String[] args) {
// 创建生产者配置信息
Properties properties = new Properties();
// 创建生产者连接的broker
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 创建key,value全类名
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 创建生产者
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// 创建send方法发送信息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first","ocean"+i));
}
// 关闭资源
kafkaProducer.close();
}
}
2.2、异步发送带回调
回调函数在生产者收到ack时调用,有两个参数元数据 RecordMetadata和异常信息Exception 注意:信息发送失败自动会重试
package com.xzz.kafka.producer;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
/**
* @author 徐正洲
* @date 2022/3/6-20:18
*/
public class CustomProducerCallback {
public static void main(String[] args) {
// 创建生产者配置信息
Properties properties = new Properties();
// 创建生产者连接的broker
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 创建key,value全类名
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 创建生产者
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// 创建send方法发送信息
for (int i = 0; i < 5; i++) {
kafkaProducer.send(new ProducerRecord<String, String>("first3", "ocean" + i), new Callback() {
// 该方法在 Producer 收到 ack 时调用,为异步调用
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
if (e ==null){
System.out.println("主题:" + recordMetadata.topic() + "->" + recordMetadata.partition());
}else {
e.printStackTrace();
System.out.println("发送信息失败");
}
}
});
}
// 关闭资源
kafkaProducer.close();
}
}
2.3、同步发送
注意:只需要在异步发送的基础上调用一下get方法
package com.xzz.kafka.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
/**
* @author 徐正洲
* @date 2022/3/6-20:18
*/
public class CustomProducerSync {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建生产者配置信息
Properties properties = new Properties();
// 创建生产者连接的broker
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 创建key,value全类名
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 创建生产者
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// 创建send方法发送信息
for (int i = 0; i < 5; i++) {
// 同步发送
kafkaProducer.send(new ProducerRecord<String, String>("first","ocean"+i)).get();
}
// 关闭资源
kafkaProducer.close();
}
}
3、分区
1)把海量的数据按照分区切割成一块一块数据存储在多台Broker上。合理控制分区的任务,可以实现负载均衡的效果。
2)提高并行度,生产者可以以分区为单位发送数据;消费者可以以分区为单位进行消费数据。
3.1、架构图
3.2、分区原则
1)指明分区情况下:直接所有数据写入指定的分区内
2)没有指定分区但是有key的情况下:讲key的hash值与topic的分区数取余得到写入分区的值
3)既没有分区也没有key的情况下,采用黏性分区器,随机分配一个分区,如果该分区batch(16k)存储满了则会换一个不同的分区进行存储
4、提高吞吐量
1)batch.size:默认16k,可提高
2)linger.ms:默认0 可修改为5-100ms
3)compression.type:可使用snappy压缩
4)RecordAccumlator:缓冲区大小默认32m,可提高为64m
5、数据可靠性
5.1、ack原理
0:生产者发送过来的数据,不需要等数据落盘应答---导致丢数
1:生产者发送过来的数据,等leader收到数据落盘后应答---应答成功后,但还没同步副本,leader挂了,新的leader没有收到上次发送的数据导致丢数。
-1(all):生产者发送过来的数据,等leader和ISR队列收到数据后落盘答应
ISR:与leader保持通信的leader和follower的集合,当follower超过30秒没有与leader发送通信或者同步数据,则被退出ISR队列。
数据完全可靠条件==ACK级别设置为-1 + 分区副本大于等于2 + ISR里应答的最小副本数量大于等于2
5.2、数据重复分析:
ack=-1(all)时仍然存在数据重复可能,当生产者发送的数据leader和ISR队列落盘之后,leader回应的时候挂了,生产者默认认为数据发送失败,将会重新发送数据导致数据重复
6、幂等性
1、原理:无论broker发送多少重复数据,都会持久化成为一条
2、至少一次:ack=-1,会导致数据重复,但是不丢数据。
最多一次:ack=0,会导致丢数据,但是数据不重复。
精确一次:幂等性 + 至少一次( ack=-1 + 分区副本数>=2 + ISR最小副本数量>=2)。3、判断逻辑:具有<PID,Partition,SeqNumber>相同主键的消息提交时,Broker只会持久化一条。其中PID是Kafka每次重启都会分配一个新的;
Partition 表示分区号;Sequence Number是单调自增的。所以幂等性只能保证的是在单分区单会话内不重复。如果kafka挂掉重启生成新的PID导致数据重复
enable.idempotence 默认为true开启状态,
7、事务
解决kafka挂掉重启生成新的PID导致数据重复,手动定义事务Id持久化到主题中,底层还是存储到磁盘,即使kafka挂了重启还能继续处理未完成的事务
1、生产者向事务协调器申请事务ID给幂等性
2、获取到事务ID后,发送信息给leader
3、发送完后,持久化提交请求到topic中
4、成功后事务协调器发送请求给leader判断信息是否持久化磁盘,成功后持久化事务成功信息到topic磁盘底层
package com.xzz.kafka.producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
/**
* @author 徐正洲
* @date 2022/3/6-20:18
*/
public class CustomProducerTransaction {
public static void main(String[] args) {
// 创建生产者配置信息
Properties properties = new Properties();
// 创建生产者连接的broker
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// 创建key,value全类名
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,StringSerializer.class.getName());
// 定义事务ID
properties.put(ProducerConfig.TRANSACTIONAL_ID_CONFIG,"transaction_id");
// 创建生产者
KafkaProducer<String, String> kafkaProducer = new KafkaProducer<String, String>(properties);
// 1)初始化事务
kafkaProducer.initTransactions();
// 2)开启事务
kafkaProducer.beginTransaction();
try {
for (int i = 0; i < 5; i++) {
// 创建send方法发送信息
kafkaProducer.send(new ProducerRecord<String, String>("first","ocean"+i));
}
// 4)提交事务
kafkaProducer.commitTransaction();
}catch (Exception e){
kafkaProducer.abortTransaction();
}finally {
// 关闭资源
kafkaProducer.close();
}
}
}
8、数据有序
1、单分区内,有序,多分区,分区与分区间无序。
2、乱序(重点):
1)1.x版本之前保证数据单分区有序
max.in.flight.requests.per.connection=1(不需要考虑是否开启幂等性)--一个请求发送完之后在发送第二个
2)kafka在1.x及以后版本保证数据单分区有序,
开启幂等性max.in.flight.requests.per.connection需要设置小于等于5。
未开启幂等性max.in.flight.requests.per.connection需要设置为1。
原因说明:因为在kafka1.x以后,启用幂等后,kafka服务端会缓存producer发来的最近5个request的元数据,故无论如何,都可以保证最近5个request的数据都是有序的,原因是使用幂等性中seq单调递增的原理,当生产者有请求发送失败时,默认后面请求继续发送,但是不落盘,等请求全部发送完开始排序落盘。
四、kafka Broker
1、kafka在zk中存储的信息
2、工作流程
1、broker启动向zk注册
2、哪台broker中的controller先注册,则辅助leader选举
3、controller监听broker节点变化,按照ISR存活、AR顺序原则选举分区的leader
4、选举完之后将节点信息上传至zk,其他controller从zk 中进行同步
5、如果有broker节点挂了,controller监听到变化,重新获取isr信息,分配leader。并且更新leader和isr,其他节点从zk继续同步信息
3、重要参数
1)replica.lag.time.max.ms
ISR中,如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值,默认30s。
2)auto.leader.rebalance.enable
默认是true,自动Leader Partition 平衡。
3)leader.imbalance.per.broker.percentage
默认是10%。每个broker允许的不平衡的leader的比率。如果每个broker超过了这个值,控制器会触发leader的平衡。
4)leader.imbalance.check.interval.seconds
默认值300秒。检查leader负载是否平衡的间隔时间。
5)log.segment.bytes
Kafka中log日志是分成一块块存储的,此配置是指log日志划分 成块的大小,默认值1G。
6)log.index.interval.bytes
默认4kb,kafka里面每当写入了4kb大小的日志(.log),然后就往index文件里面记录一个索引。
7)log.retention.hours
Kafka中数据保存的时间,默认7天。
8)log.retention.bytes
默认等于-1,表示无穷大。超过设置的所有日志总大小,删除最早的segment。
9)log.cleanup.policy
默认是delete,表示所有数据启用删除策略;
如果设置值为compact,表示所有数据启用压缩策略。
10)num.io.threads
默认是8。负责写磁盘的线程数。整个参数值要占总核数的50%。
3、节点服役退役
3.1、服役
1)创建一个要均衡的主题。
vim topics-to-move.json
{
"topics": [
{"topic":"first"}
],
"version": 1
}
2)生成一个负载均衡的计划
bin/kafka-reassign-partitions.sh --bootstrap-server --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2,3" --generate hadoop102:9092##均衡后的执行计划
Proposedpartition reassignment configuration
{"version":1,"partitions":[{"topic":"first","partition":0,"replicas":[2,3,0],"log_dirs":["any","any","any"]},{"topic":"first","partition":1,"replicas":[3,0,1],"log_dirs":["any","any","any"]},{"topic":"first","partition":2,"replicas":[0,1,2],"log_dirs":["any","any","any"]}]}3)创建副本存储计划(所有副本存储在broker0、broker1、broker2、broker3中)。
vim increase-replication-factor.json
将Proposedpartition reassignment configuration内容填入4)执行副本存储计划。
bin/kafka-reassign-partitions.sh--bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json--execute5)验证副本存储计划。
bin/kafka-reassign-partitions.sh--bootstrap-server hadoop102:9092 --reassignment-json-fileincrease-replication-factor.json --verify
3.2、退役
1)先按照退役一台节点,生成执行计划,然后按照服役时操作流程执行负载均衡。
2)创建执行计划,服役、退役关键点在于--broker-list 。
bin/kafka-reassign-partitions.sh--bootstrap-server hadoop102:9092 --topics-to-move-json-file topics-to-move.json --broker-list "0,1,2" --generate
4、副本
4.1、基本信息
1)Kafka副本作用:提高数据可靠性。
2)Kafka默认副本1个,生产环境一般配置为2个,保证数据可靠性;太多副本会增加磁盘存储空间,增加网络上数据传输,降低效率。
3)Kafka中副本分为:Leader和Follower。Kafka生产者只会把数据发往Leader,然后Follower找Leader进行同步数据。
4)Kafka分区中的所有副本统称为AR(Assigned Repllicas)。
AR = ISR + OSR ISR = 表示和Leader保持同步的Follower集合。如果Follower长时间未向Leader发送通信请求或同步数据,则该Follower将被踢出ISR。该时间阈值由replica.lag.time.max.ms参数设定,默认30s。Leader发生故障之后,就会从ISR中选举新的Leader。
OSR = 表示Follower与Leader副本同步时,延迟过多的副本。
4.2、副本leader选举
Kafka集群中有一个broker的Controller会被选举为Controller Leader,负责管理集群broker的上下线,所有topic的分区副本分配和Leader选举等工作。Controller的信息同步工作是依赖于Zookeeper的。
4.3、故障处理
Follow故障
LEO:每个副本最后一个offset+1
HW:所有副本中最小的LEO
1、follow出现故障退出ISR
2、期间leader和存活follower正常接收数据和同步数据
3、故障的follower恢复后,读取本地磁盘记录上次的HW位置,将log文件中高于HW的部分截取掉,从HW开始向Leader进行同步
4、当follow的LEO大于等于分区的HW,则可以重新加入ISR
Leader故障
1、leader宕机后,退出ISR
2、follower成为leader之后,其它follower将各自log文件中高于HW的部分截取掉,与leader保持一致
3、只能保证数据一致性,不能保证数据不重复和数据不丢失
4.4、副本分配
1、将副本平均分布在所有的 Broker 上;
2、partition 的多个副本应该分配在不同的 Broker 上;
3、如果所有的 Broker 有机架信息的话, partition 的副本应该分配到不同的机架上。
4、无机架方式分配
1)从 broker.list 随机选择一个 Broker,使用 round-robin 算法分配每个 partition 的第一个副本;
2)对于这个 partition 的其他副本,逐渐增加 Broker.id 来选择 replica 的分配。
3)对于副本分配来说,每经历一次Broker的遍历,则第一个副本跟后面的副本直接的间隔+1;
4.5、手动调整副本
1、创建副本存储计划(所有副本都指定存储在broker0、broker1中)。
vim increase-replication-factor.json
输入如下内容:
{
"version":1,
"partitions":[{"topic":"three","partition":0,"replicas":[0,1]},
{"topic":"three","partition":1,"replicas":[0,1]},
{"topic":"three","partition":2,"replicas":[1,0]},
{"topic":"three","partition":3,"replicas":[1,0]}]
}
2、执行副本存储计划。
bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --execute
3、验证副本存储计划。
bin/kafka-reassign-partitions.sh --bootstrap-server hadoop102:9092 --reassignment-json-file increase-replication-factor.json --verify
4.6、leader分区负载平衡
正常情况下,Kafka本身会自动把LeaderPartition均匀分散在各个机器上,来保证每台机器的读写吞吐量都是均匀的。但是如果某些broker宕机,会导致Leader Partition过于集中在其他少部分几台broker上,这会导致少数几台broker的读写请求压力过高,其他宕机的broker重启之后都是follower partition,读写请求很低,造成集群负载不均衡。
1、auto.leader.rebalance.enable
默认是true。 自动Leader Partition 平衡。生产环境中,leader重选举的代价比较大,可能会带来性能影响,建议设置为false关闭。
2、leader.imbalance.per.broker.percentage
默认是10%。每个broker允许的不平衡的leader的比率。如果每个broker超过了这个值,控制器会触发leader的平衡。
3、leader.imbalance.check.interval.seconds
默认值300秒。检查leader负载是否平衡的间隔时间。
4.7、增加副本
1、创建副本存储计划(所有副本都指定存储在broker0、broker1、broker2中)。
vim increase-replication-factor.json
输入如下内容:
{"version":1,"partitions":[{"topic":"four","partition":0,"replicas":[0,1,2]},{"topic":"four","partition":1,"replicas":[0,1,2]},{"topic":"four","partition":2,"replicas":[0,1,2]}]}
2、执行副本存储计划。
bin/kafka-reassign-partitions.sh--bootstrap-server hadoop102:9092 --reassignment-json-fileincrease-replication-factor.json --execute
5、文件存储
5.1、存储机制
1、topic、log、segment为逻辑上的概念,而partition为物理上的概念。
2、segment包含:.log、.index、.timeindex等文件,以第一条segment信息命名。
3、kafka采用稀疏索引,log文件每写入4kb时,向index文件记录一条索引信息。
4、index文件采用相对offset+position组成kafka读取数据流程:
1.根据目标offset定位Segment文件
2.找到小于等于目标offset的最大offse对应的索引项
3.定位到Iog文件
4.向下遍历找到目标Record参数:
1、log.segment.bytes
Kafka中log日志是分成一块块存储的,此配置是指log日志划分 成块的大小,默认值1G。
2、log.index.interval.bytes
默认4kb,kafka里面每当写入了4kb大小的日志(.log),然后就往index文件里面记录一个索引。 稀疏索引。
5.2、清理策略
1、Kafka中默认的日志保存时间为7天,可以通过调整如下参数修改保存时间。
log.retention.hours,最低优先级小时,默认7天。
log.retention.minutes,分钟。
log.retention.ms,最高优先级毫秒。
log.retention.check.interval.ms,负责设置检查周期,默认5分钟。
那么日志一旦超过了设置的时间,怎么处理呢?
Kafka中提供的日志清理策略有delete和compact两种。2、delete日志删除:将过期数据删除。log.cleanup.policy = delete 所有数据启用删除策略
1)基于时间:默认打开。以segment中所有记录中的最大时间戳作为该文件时间戳,当前segment最大时间戳的超过7天,则删除该segment的数据。
2)基于大小:默认关闭。超过设置的所有日志总大小,删除最早的segment。
log.retention.bytes,默认等于-1,表示无穷大。3、compact日志压缩:对于相同key不同value,只保留最后一个value,适用于map使用的场景。
6、高效读写数据
1)Kafka本身是分布式集群,可以采用分区技术,并行度高
2)读数据采用稀疏索引,可以快速定位要消费的数据
3)顺序写磁盘,Kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。
4)页缓存 + 零拷贝技术
零拷贝:Kafka的数据加工处理操作交由Kafka生产者和Kafka消费者处理。Broker应用层不关心存储的数据,所以就不用Kafka走应用层,传输效率高
PageCache页缓存:Kafka重度依赖底层操作系统提供的PageCache功能。当上层有写操作时,操作系统只是将数据写入PageCache。当读操作发生时,先从PageCache中查找,如果找不到,再去磁盘中读取。实际上PageCache是把尽可能多的空闲内存都当做了磁盘缓存来使用。
log.flush.interval.messages
强制页缓存刷写到磁盘的条数,默认是long的最大值,9223372036854775807。一般不建议修改,交给系统自己管理。
log.flush.interval.ms
每隔多久,刷数据到磁盘,默认是null。一般不建议修改,交给系统自己管理。
五、kafka消费者
1、消费方式
1.1、拉模式
根据消费者消费速率决定拉取的速度,缺点:如果没有数据,一直返回空数据。
2、工作流程
1)一个消费者可以消费单个或多个分区
2)消费者之间独立,可以重复消费
3)每个分区只能由消费者组中一个消费者消费信息,防止重复数据
4)每个消费者的offset由消费者提交到系统主题保存,记录当前消费的分区数位置 0.9版本offset存储在zookeeper中
3、消费者组原理
1)多个组ID相同的消费者组成
2)每个分区只能由消费者组中一个消费者消费信息,防止重复数据
3)当消费者大于分区数,则有消费者空闲
4、消费者组初始化流程
每个消费者都与coordinator保存心跳(3s)超过45s 就会被移除,如果消费时间超过5分钟也会被移除
1)coordinator 辅助消费者组的初始化和分区的分配
2)coordinator节点选择=组id的hashcode值%50取余的分区,在那台broker上,就由那台的coordinator成为辅助消费者组
3)所有消费者向coordinator发送Join组请求
4)coordinator选举一个消费者成为leader,leader消费者把消费的主题情况发送到coordinator
5)coordinator把消费方案下发给各个消费者
5、消费者组详细消费流程
1)消费者组通过消费者网络客户端与kafka集群保持拉取数据
2)每批次最小抓取为1字节,最小值未达到的超时时间500ms 最大的抓取大小为50m
3)通过send方法发送请求,之后通过onsuccess方法回调将数据放入消息队列
4)消费者从消息队列中一次拉取默认500条,过程经过反序列化->拦截器->处理数据
6、消费组API
6.1、独立消费者(订阅主题)
package com.xzz.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
/**
* @author 徐正洲
* @date 2022/3/10-18:16
*/
public class CustomConsumer {
public static void main(String[] args) {
// 配置
Properties properties = new Properties();
// 连接kafka
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// key、value反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 创建消费组Id必须手动设置
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"ocean");
// 创建消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
// 订阅信息
ArrayList<String> topics = new ArrayList<String>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 拉取数据打印
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
6.2、独立消费者案例(订阅分区)
package com.xzz.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
/**
* @author 徐正洲
* @date 2022/3/10-18:16
*/
public class CustomConsumerPartition {
public static void main(String[] args) {
// 配置
Properties properties = new Properties();
// 连接kafka
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// key、value反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 创建消费组Id必须手动设置
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"ocean");
// 创建消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
// 订阅主题的某分区
ArrayList<TopicPartition> topicPartitions = new ArrayList<>();
topicPartitions.add(new TopicPartition("first",0));
kafkaConsumer.assign(topicPartitions);
// 拉取数据打印
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
7、分区的分配以及再平衡
默认是Range+CooperativeSticky,通过partition.assignment.strategy修改分配策略
7.1、range+再平衡
1、Range 是对每个 topic 而言的。
2、通过 partitions数/consumer数 来决定每个消费者应该消费几个分区。如果除不尽,那么前面几个消费者将会多消费 1 个分区。
3、如果单个topic影响不大、但是多个topic而言,会造成数据倾斜。再平衡:
1、当某个消费者异常退出后,45s内没有触发再平衡,在发送数据,异常消费者消费的分区数据会全部发送给下一个消费者。
2、45s后触发在平衡,消费策略按照range实现。
7.2、RoundRobin+再平衡
1、Range 是对所有 topic 而言的。
2、通过轮询算法来决定每个消费者应该消费几个分区。再平衡:
1、当某个消费者异常退出后,45s内没有触发再平衡,在发送数据,异常消费者消费的分区数据会轮询发送给消费者。
2、45s后触发在平衡,消费策略按照RoundRobin实现。
7.3、Sticky+在平衡
1、均匀随机地将分区分配给消费者
再平衡:
1、当某个消费者异常退出后,45s内没有触发再平衡,在发送数据,异常消费者消费的分区数据会均匀随机发送给消费者。
2、45s后触发在平衡,消费策略按照Sticky实现。
8、offset
8.1、说明:
1)0.9版本之后消费者将offset存储到系统主题中(_consumer_offsets)
2)系统主题以key、value形式存储只保留最新数据(采用compact,相同key只保留最新value) key:groupid+topic+分区号 value:当前的offset的值
3)默认为true不能查看系统主题值 exclude.iternal.topics=true
8.2、查看
vim consumer.properties exclude.internal.topics=false
8.3、创建新的topic测试
bin/kafka-topics.sh --bootstrap-server hadoop102:9092 --create --topic atguigu --partitions 2 --replication-factor 2
8.4、向offset主题发送数据,并且消费数据
bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic atguigu group oceandatum
8.5查看消费者消费主题_consumer_offsets
bin/kafka-console-consumer.sh --topic __consumer_offsets --bootstrap-server master1:9092 --consumer.config config/consumer.properties --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --from-beginning
9、自动提交offset
为了用户专注业务,但是可能导致重复消费和漏消费
enable.auto.commit:是否开启自动提交offset功能,默认是true
auto.commit.interval.ms:自动提交offset的时间间隔,默认是5
10、手动提交offset
10.1、同步与异步提交
相同点:两者都会将本次一批数据的最高偏移量提交
不同:同步会阻塞当前线程,异步没有失败重试,可能提交失败
2、同步:拉取完数据,等都提交offset在消费下一批数据 kafkaConsumer.commitSync();
3、异步:拉取完数据,直接消费下一批数据 kafkaConsumer.commitAsync();
11、指定Offset消费
auto.offset.reset = earliest | latest | none 默认为latest
1)earliest:自动将偏移量重置为最早的偏移量,--from-beginning 从头开始消费
2)latest(默认值):自动将偏移量重置为最新偏移量--从启动的时刻开始消费,历史数据不消费 3)none:如果未找到消费者组的先前偏移量,则向消费者抛出异常。
package com.xzz.kafka.consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Properties;
import java.util.Set;
/**
* @author 徐正洲
* @date 2022/3/10-18:16
*/
public class CustomConsumerSeek {
public static void main(String[] args) {
// 配置
Properties properties = new Properties();
// 连接kafka
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// key、value反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 创建消费组Id必须手动设置
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"ocean4");
// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,"org.apache.kafka.clients.consumer.StickyAssignor");
// 创建消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
// 订阅主题
ArrayList<String> topics = new ArrayList<String>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 指定offset位置开始消费
// 1) 通过assignment 获取分区信息
Set<TopicPartition> assignment = kafkaConsumer.assignment();
// 保证分区信息方案分配完毕
while (assignment.size() == 0){
kafkaConsumer.poll(Duration.ofSeconds(1));
// 分区值更新
assignment = kafkaConsumer.assignment();
}
// 2)指定位置
for (TopicPartition topicPartition : assignment) {
kafkaConsumer.seek(topicPartition,1572);
}
// 拉取数据打印
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
12、指定时间消费
package com.xzz.kafka.consumer;
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.*;
/**
* @author 徐正洲
* @date 2022/3/10-18:16
*/
public class CustomConsumerSeekTime {
public static void main(String[] args) {
// 配置
Properties properties = new Properties();
// 连接kafka
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop102:9092,hadoop103:9092");
// key、value反序列化
properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG,StringDeserializer.class.getName());
// 创建消费组Id必须手动设置
properties.put(ConsumerConfig.GROUP_ID_CONFIG,"ocean4");
// 修改分区分配策略
properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG,"org.apache.kafka.clients.consumer.StickyAssignor");
// 创建消费者
KafkaConsumer<String, String> kafkaConsumer = new KafkaConsumer<String, String>(properties);
// 订阅主题
ArrayList<String> topics = new ArrayList<String>();
topics.add("first");
kafkaConsumer.subscribe(topics);
// 指定offset位置开始消费
// 1) 通过assignment 获取分区信息
Set<TopicPartition> assignment = kafkaConsumer.assignment();
// 保证分区信息方案分配完毕
while (assignment.size() == 0){
kafkaConsumer.poll(Duration.ofSeconds(1));
// 分区值更新
assignment = kafkaConsumer.assignment();
}
// 将时间转换为offset
HashMap<TopicPartition, Long> topicPartitionLongHashMap = new HashMap<>();
// 封装对应集合
for (TopicPartition topicPartition : assignment) {
topicPartitionLongHashMap.put(topicPartition,System.currentTimeMillis() - 1 * 24 * 3600 * 1000);
}
Map<TopicPartition, OffsetAndTimestamp> topicPartitionOffsetAndTimestampMap = kafkaConsumer.offsetsForTimes(topicPartitionLongHashMap);
// 2)指定位置
for (TopicPartition topicPartition : assignment) {
OffsetAndTimestamp offsetAndTimestamp = topicPartitionOffsetAndTimestampMap.get(topicPartition);
kafkaConsumer.seek(topicPartition,offsetAndTimestamp.offset());
}
// 拉取数据打印
while (true){
ConsumerRecords<String, String> consumerRecords = kafkaConsumer.poll(Duration.ofSeconds(1));
// 打印消费到的数据
for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
System.out.println(consumerRecord);
}
}
}
}
13、漏消费和重复消费
1)重复:消费了数据,但是offset没提交或提交失败--自动提交
2)漏消费:先提交offset后消费---手动提交
14、数据积压
1)增加主题的分区数,且分区数=消费者
2)每批次拉取数据量-->1000条 3)批次总量大小50m-->100m
六、Eagle监控
1、环境准备
1)关闭集群
kf.sh stop
2)修改kafka-server-start.sh命令
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-server -Xms2G-Xmx2G -XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200-XX:ParallelGCThreads=8 -XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70"
export JMX_PORT="9999"
#exportKAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi
3)分发节点
xsync kafka-server-start.sh
2、安装
1)解压
tar -zxvf efak-web-2.0.8-bin.tar.gz -C /opt/module/
2)修改配置文件efak/conf/system-config.properties
##zk连接存储信息
efak.zk.cluster.alias=cluster1
cluster1.zk.list=hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka
##offset保存在kafka
cluster1.efak.offset.storage=kafka
##配置mysql连接
efak.driver=com.mysql.jdbc.Driver
efak.url=jdbc:mysql://hadoop102:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
efak.username=root
efak.password=000000
3)配置环境变量
vim /etc/profile.d/my_env.sh
# kafkaEFAK
exportKE_HOME=/opt/module/efak
exportPATH=$PATH:$KE_HOME/bin
4)启动
注意:启动之前需要先启动ZK以及KAFKA。
bin/ke.sh start
5)登录http://192.168.10.102:8048/,账号密码默认:admin/123456
七、kraft模式
1、架构
1)2.8之前kafka元数据存储在zk中,运行时选举controller,由controller进行集群管理
2)2.8之后kafka不在依赖zk,由三台controller代替zk,元数据存储在controller中
2、优点
1)kafka不依赖外部框架,独立运行,减少了大量与zk的交互时间。
2)不受zk读写权限控制。
3)controller不用动态选举(谁先注册的节点的controller为辅助选举)根据配置文件规定,加强了controller节点的配置
3、部署
3.1、配置文件
vim /opt/module/kafka_kraft/config/kraft/server.properties
#kafka的角色(controller相当于主机、broker节点相当于从机,主机类似zk功能)
process.roles=broker, controller
#节点ID,需要唯一。
node.id=2
#controller服务协议别名
controller.listener.names=CONTROLLER
#全Controller列表
controller.quorum.voters=2@hadoop102:9093,3@hadoop103:9093,4@hadoop104:9093
#不同服务器绑定的端口
listeners=PLAINTEXT://:9092,CONTROLLER://:9093
#broker服务协议别名
inter.broker.listener.name=PLAINTEXT
#broker对外暴露的地址
advertised.Listeners=PLAINTEXT://hadoop102:9092
#协议别名到安全协议的映射
listener.security.protocol.map=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL
#kafka数据存储目录
log.dirs=/opt/module/kafka2/data
3.2、初始化集群数据目录
1)首先生成存储目录唯一ID。
bin/kafka-storage.shrandom-uuid
J7s9e8PPTKOO47PxzI39VA
2)用该ID格式化kafka存储目录(三台节点)。
bin/kafka-storage.sh format -t J7s9e8PPTKOO47PxzI39VA-c /opt/module/kafka2/config/kraft/server.properties
4、启动脚本
vim kf2.sh
#! /bin/bash
case $1 in
"start"){
for i in hadoop102hadoop103 hadoop104
do
echo " --------启动 $iKafka2-------"
ssh $i"/opt/module/kafka2/bin/kafka-server-start.sh -daemon /opt/module/kafka2/config/kraft/server.properties"
done
};;
"stop"){
for i in hadoop102hadoop103 hadoop104
do
echo " --------停止 $iKafka2-------"
ssh $i"/opt/module/kafka2/bin/kafka-server-stop.sh "
done
};;
esacchmod +x kf2.sh
八、生产调优
1、硬件配置选择
1.1、生产环境:
100W用户 * 每天100条信息 = 1E条--规模(中型公司)
处理日志速度: 1E/24*3600 = 1150条
1条日志=(0.5k-2k 1k)
1150条*1k/s =1m
高峰值(中午小高峰 )生产速率: 1m/s * 20倍 =20m/s --40m/s
1.2、服务器条数
台数 = 2 * (生产者峰值生产速率 * 副本数 /100)+ 1
1.3、磁盘选择
kafka按照顺序读写 机械硬盘与固态硬盘 顺序读写速度差不多
1E条 *1k =100g
100g * 2 *3/0.7 =1T
建议三台服务器总的磁盘大小大于1T
1.4、内存选择
kafka 内存= 堆内存(kafka 内部配置JVM) + 页缓存(服务器内存)
1)JVM生产环境:10-15G ---kafka-server-start.sh
2)查看GC进程:jstat -gc 进程号 1s 10
3)查看内存使用率:jmap -heap 进程号
4)页缓存 seg(1g) 分区数leader * 1g * 25%/3 =1g ---效率就比较高了
5)一台服务器: 11G
1.5、CPU选择
建议:32核
1)num.io.threads = 8 占C 50%
2)num.replica.fetchers = 1 占总C 50%的1/3
3)num.network.threads = 3 占总C 50%的2/3
1.6、网络选择
1)网络带宽 = 峰值 = 20M/s 选择千兆网卡
2、kafka生产者
1、 生产者发送的数据发送到缓冲区每批数据先从内存池拉取内存进行分配,发送完数据释放完内存
2、缓冲区--64m
3、批次数据--32k
4、linger.ms--5-100ms
5、ack-- all
6、request + 幂等性 ==数据有序 没有幂等性 request设置为1
7、snappy
3、broker
replica.lag.time.max.ms ISR 中,如果 Follower 长时间未向 Leader 发送通信请求或同步数据,则该 Follower 将被踢出 ISR。时间阈值,默认 30s。
auto.leader.rebalance.enable 默认是 true。 自动 Leader Partition 平衡。建议关闭。
leader.imbalance.per.broker.percentage 默认是 10%。每个 broker 允许的不平衡的 leader的比率。如果每个 broker 超过了这个值,控制器会触发 leader 的平衡。
leader.imbalance.check.interval.seconds 默认值 300 秒。检查 leader 负载是否平衡的间隔时间。
log.segment.bytes Kafka 中 log 日志是分成一块块存储的,此配置是指 log 日志划分 成块的大小,默认值 1G。
log.index.interval.bytes 默认 4kb,kafka 里面每当写入了 4kb 大小的日志(.log),然后就往 index 文件里面记录一个索引。
log.retention.hours Kafka 中数据保存的时间,默认 7 天。
log.retention.minutes Kafka 中数据保存的时间,分钟级别,默认关闭。
log.retention.ms Kafka 中数据保存的时间,毫秒级别,默认关闭。
log.retention.check.interval.ms 检查数据是否保存超时的间隔,默认是 5 分钟。
log.retention.bytes 默认等于-1,表示无穷大。超过设置的所有日志总大小,删除最早的 segment。
log.cleanup.policy 默认是 delete,表示所有数据启用删除策略;如果设置值为 compact,表示所有数据启用压缩策略。
num.io.threads 默认是 8。负责写磁盘的线程数。整个参数值要占总核数的 50%。
num.replica.fetchers 默认是 1。副本拉取线程数,这个参数占总核数的 50%的 1/3
num.network.threads 默认是 3。数据传输线程数,这个参数占总核数的 50%的 2/3 。
log.flush.interval.messages 强制页缓存刷写到磁盘的条数,默认是 long 的最大值,9223372036854775807。一般不建议修改,交给系统自己管理。
log.flush.interval.ms 每隔多久,刷数据到磁盘,默认是 null。一般不建议修改,交给系统自己管理。
生产环境建议 auto.create.topics.enable 设置为false,默认为ture。
4、总体调优(建议)
4.1、吞吐量
1)生产者提升吞吐量
1、buffer.memory:32m--->64m
2、batch.size:16k--->32k
3、linger.ms:0--->5,100ms
4、compression.type:none--->snappy
2)增加分区
3)消费者提高吞吐量
1、fetch.max.bytes:50m--->100m
2、max.poll.records:500---->1000
4)增加下游消费者处理能力
4.2、数据准确
1)生产者角度 ack=-1(all) + 幂等性 +事务
2)broker服务端 分区副本大于等于2 + ISR 副本数量大于等于2
3)消费者 事务 + 手动提交offset 消费者输出目的地必须支持事务
4.3、合理分区数
1)设置为3-10个
4.4、单条日志大于1m
message.max.bytes 默认 1m,broker 端接收每个批次消息最大值。
max.request.size 默认 1m,生产者发往 broker 每个请求消息最大值。针对 topic级别设置消息体的大小。
replica.fetch.max.bytes 默认 1m,副本同步数据,每个批次消息最大值。
fetch.max.bytes 默认 Default: 52428800(50 m)。消费者获取服务器端一批消息最大的字节数。如果服务器端一批次的数据大于该值(50m)仍然可以拉取回来这批数据,因此,这不是一个绝对最大值。一批次的大小受 message.max.bytes (broker config)or max.message.bytes (topic config)影响。
4.5、服务器宕机
1)增加内存、增加 CPU、网络带宽。
2)副本数大于等于 2,可以按照服役新节点的方式重新服役一个新节点,并执行负载均衡。