上一篇归集了一下Kafka的基本概念,这篇会详细介绍一下生产者和消费者两个核心;
生产者核心作用就是将业务数据即消息按照特定的格式和序列化方式通过某种算法发布到对应的主题分区上面。
一、发送方式
1、发送并忘记:把消息发送给服务器,不关心是否到达;
2、异步发送:send方法发送,返回一个Feature对象,调用Feature对象的get方法进行等待,从而知道是否成功;
3、异步发送:send方法发送,并且指定回调函数,服务器在响应的时候调用;
ps:先记住有这三种发送方式,一会通过kafka的源码来看一下;
二、属性配置
1、acks:指定了必须多少个分区副本收到消息,生产者才认为消息是写入成功的;其有3个值;
acks=0:表示不需要服务器(主分区和从分区)的响应,只要认为发出去了就算成功的;这样能够达到最高的吞吐率;但是不能保证消息的丢失;
acks=1;表示只要服务器的主分区收到消息,就认为消息是发送成功的;
acks=all;表示所有参与复制的节点全部都收到消息,才会认为消息发送成功;
2、buffer.memory:设置生产者内存缓冲区大小,用来缓冲要发到服务器的消息。kafka并不是一个消息一个消息的进行发送的,通过设置;
3、compression.type:指定消息被发送给broker之前使用哪一种压缩算法进行压缩。
snappy:google发明,占用较少的CPU,提供较好的性能和压缩比
gzip:占用cpu较多,但是压缩比更高。
lz4:占坑;待补充=================================================
4、retries:默认重试之间是100ms,可以通过retry.backoff.ms设置重试的时间间隔;
5、batch.size:当有多个消息需要被发送到同一个分区时,生产者会把它们放入到同一个批次里。
6、linger.ms:指定生产者在发送批次之前等待更多消息加入批次的时间。
7、client.id:任意字符串,用来识别消息的来源
8、max.in.flight.requests.per.connection:指定生产者在收到服务器响应之前可以发送多少个消息。
timeout.ms:指定了broker等待同步服务返回消息确认的时间
request.timeout.ms :生产者在发送数据时等待服务器响应时间
metadata.fetch.timeout.ms:获取元数据时等待服务器响应时间
max.block.ms:最大阻塞时间;
max.request.size:最大请求的大小;
receive.buffer.bytes和send.buffer.bytes:TCP socket接收和发送数据包的缓冲区大小,设置为-1,使用操作系统的默认值
三、序列化器
1、原生序列化:
a、字符串序列化
b、整型序列化;
c、字节数组序列化;
2、自定义序列化:
3、AVRO序列化:
四、源码分析(基于0.9.0.1版本解读)
1、基础的JavaBean
ProducerRecord:生产要使用到的记录消息,生产者是序列化到broker中;
属性:
topic:
partition:
key:
value:
ConsumerRecord:消费者从broker中获取的消息记录,
属性:
private final Map<TopicPartition, List<ConsumerRecord<K, V>>> records;
RecordMetaData:记录数据的原信息;
属性:
offset:提交的偏移量
TopicPartion:topic是否指定了发送到哪个分区;
TopicPartion:分区实体
属性:
hash:
partition:
topic:
ProducerConfig:生产者配置文件
Metadata:kafka配置的各种元数据信息Bean
属性:
private final long refreshBackoffMs;
private final long metadataExpireMs;
private int version;
private long lastRefreshMs;
private long lastSuccessfulRefreshMs;
private Cluster cluster;
private boolean needUpdate;
private final Set<String> topics;
private final List<Listener> listeners;
private boolean needMetadataForAllTopics;
SubscriptionState:订阅相关的配置信息
属性:
订阅符合某个正则表达式的topic
private Pattern subscribedPattern;
已经订阅的topic主题集合
private final Set<String> subscription;
该组已经订阅的主题列表
private final Set<String> groupSubscription;
用户请求的分区列表
private final Set<TopicPartition> userAssignment;
获取当前已经分配的分区
private final Map<TopicPartition, TopicPartitionState> assignment;
是否需要从协调器请求分区分配??
private boolean needsPartitionAssignment;
是否需要从协调器拉取最新提交的偏移量
private boolean needsFetchCommittedOffsets;
偏移量重置策略
private final OffsetResetStrategy defaultResetStrategy;
分配发生更改时调用的监听器
private ConsumerRebalanceListener listener;
Metrics:监控数据
属性:
private final ConcurrentMap<MetricName, KafkaMetric> metrics;
private final ConcurrentMap<String, Sensor> sensors;
private final ConcurrentMap<Sensor, List<Sensor>> childrenSensors;
private final List<MetricsReporter> reporters;
private final Time time;
private final ScheduledThreadPoolExecutor metricsScheduler;
private static final Logger log = LoggerFactory.getLogger(Metrics.class);
2、生产者Producer:生产者的抽象接口
A、接口方法:
发送kafka消息到broker:
Future<RecordMetadata> send(ProducerRecord<K,V> record),返回一个Future,通过future的get方法获取执行结果,因此是异步;
Futrue<RecordMetadata> send (ProducerRecord<K,V> recor,Callback callback),返回一个Future,在broker收到数据时候,执行回调函数;
清理Kafka数据:
void Flush
查询Kafka的分区:
List<PartitionInfo> parttionFor(String topic)
关闭到kafka的连接:
close();
close(long timeout,TimeUnit unit);
B、实现类:KafkaProducer
构造函数:
a、传递Map<String,Object>,实质是将Map转换为Properties,以及是否传递序列化的key,序列化的value;
b、传递Properties来进行构造,以及是否传递序列化的key和序列化的value;
c、传递ProducerConfig 和序列化的key和序列化的value;
解析配置:
a、int parseAcks(String acksString);
acks配置有三个:
0:记录发送出去,不管主分区和从分区是否收到,都认为是成功的;
1:记录发送出去,只要主分区确认收到,就认为是成功的;
all:记录分送出去,只有主分区和复制分区都收到,才认为是成功的;
return acksString.trim().toLowerCase().equals("all") ? -1 : Integer.parseInt(acksString.trim());
b、获取一个topic的元数据信息
long waitOnMetadata(String topic,long maxWaitMs)
c、检查kafka消息记录的大小,单条kafka消息不能超过设置的最大值;
ensureValidRecordSize(int size)
d、计算消息所在的分区
private int partition(ProducerRecord<K, V> record, byte[] serializedKey , byte[] serializedValue, Cluster cluster)
//如果ProducerRecord指定了要放入哪个分区,直接返回对应的分区
//如果没有指定分区,要进行计算:对keyBytes(topic+key)做murmur hash,然后取模;
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 = counter.getAndIncrement();
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = DefaultPartitioner.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// no partitions are available, give a non-available partition
return DefaultPartitioner.toPositive(nextValue) % numPartitions;
}
} else {
// hash the keyBytes to choose a partition
return DefaultPartitioner.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
发送消息:
a、返回Future,不传递回调函数:
public Future<RecordMetadata> send(ProducerRecord<K, V> record) {
return send(record, null);
}
b、返回Future,传递回调函数;
public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback)
关闭&清理:
flush():
close():
close(long timeout,TimeUnit timeunit, boolean swallowException)