producer发送消息的过程
producerRecord先指定想要写入的topic,或者指定一个key或者partition(若没有指定partition,会根据key来自动选择partition),当发送这个record,producer会先serialize这个record为一个byteArrays.当知道要写入哪一个topic的哪一个partition后,这个record会被加入属于这个topic的partition的batch中,里面包含所有即将发送到同一个topic的partition的record,然后会由另一个线程负责根据一定策略发送这个batch records。
一个producer最起码包含三个东西,你想要写入的kafka cluster的host:port,key serializer,value serializer
你可以通过增加线程去使用同一个producer来增加throughput。
发送的三种模式
fire-and-forget:发送record并不关心它是否成功到达,大部分会成功到达,且未成功到达的会尝试重新发送,但扔会有部分数据的丢失。
synchronous send:通过send()发送,返回一个future,通过get()来等待返回的future来查看是否发送成功。producer.send(record).get();
asynchronous send:通过send()发送,并传入一个callback函数,这个callback函数会在收到kafka broker的response后调用。一般都是用这种,不需要等response,可以同时发送很多数据,然后通过加入的callback函数来抛出异常,记录错误或者是把record写入error file以便随后分析。
producer的两种错误
一种是retriable error:比如“no leader”错误,当一个新的leader被选出来了,这个问题就解决了,比如“connection error”,可能过段时间connection会自动重新连接,这类错误通过重新发送是可以解决的。
一种是通过重新发送也无法解决的错误:比如“message size too large”,这种producer不会尝试重新发送,而会直接抛出异常。
acks
acks参数用于决定当有多少partition replicas 接收到record后即可认为该record写入成功,这决定了数据的丢失策略。acks参数有三个选项。
acks=0:producer不会等到broker将record写入成功,这意味着如果broker没有收到record,数据便丢失了,但这种方式可以使得发送数据的throughput达到网络支持的最快速度。
acks=1:leader收到record后便会返回一个success response,若leader写入失败,会返回错误并重试,这种模式在一种情况下扔会丢失数据,就是leader挂掉了,然后一个没有收到这个record的replica被选为了新的leader。这种模式的throughput取决于发送的策略是sync还是async,以及async模式下的in-flight messages能决定在收到回复前能够发送的数据量。
acks=all:当所有的in-sync 的replicas收到数据后,便会返回success response,这是最安全的模式。但延迟也是最高的。
RETIRES
决定了当遇到可重试的错误时的重试次数,若还是失败,会提醒client该失败。还可设置retry.backoff.ms来决定等待多久后重试,建议参考broker挂掉后重新选主等一系列因素的时间来决定这个参数。
我们编程时只需考虑nonretriable errors或者retry attempts were exhausted。
LINGER.MS
控制数据batch发送间隔时间,default:producer会在当sender可发送时立马发送,设置该参数使得producer等待该时间然后发送,这会增加latency但也会增加throughput。
MAX.IN.FLIGHT.REQUESTS.PER.CONNECTION
这个控制了producer在没有收到response的情况下发送数据的数量,设置的值为1时会保证数据写入broker的顺序,即使retries occur。
ORDERING GUARANTEES
kafka会保留同一个partition的顺序,若对消息的顺序性要求高,则应将相应事件写入同一partition。
将retires参数设为nonzero,将max.in.flight.requests.per.connection设为more than one.意味着可能broker会在第一次写入batch of message时失败,但在第二次(already in-flight)写入成功,然后再重试第一次,从而导致了顺序颠倒。
若要保证顺序,设置in.flight.requests.per.session=1,来保证当retry时不会有新的message发送。但这会牺牲throughput来保证order。
SERIALIZER&SCHEMA
常用的有比如json,Apache Avor,统一的模式来记录record,可以使用schema registry。
PARTITION
若record设置了partition,则会将该record发到相应的topic partition所属的batch中发送。
若key设为null,则会根据round robin算法来balance the message among the partitions。
若key存在且partition没设置(设为default),则会根据key来hash到一个指定的partition,同一个key会写入同一个partition.(但若是有新的partition加入的话,就不一样了,所以建议创建topics时固定partition数量并永不改变)
你也可以实现自己的分区策略。
总体源码的流程图如下
kafkaProducer:
accumulater.append():将数据插入topic partition相对应的ConcurrentMap<TP,Deque<producerBatch>>中,每次取最后一个batch,若最后一个数据满了则新建一个batch,判断是否唤醒sender的两个判据是batchIsFull和newBatchCreated,然后从第一个batch开始发送(Deque是FIFO的双端队列)。
sender:
数据流是accumulator的batch--client.send(clientRequest)--selector.send(send)--channel.setSend(send)--channel.write()
selector:
select():调用javaNIO的selectNow()和select(timeeoutMs),查看是否有准备好的key。
pollSelectionKeys(readyKeys):给每个准备好的key创建对应的channel,然后进行channel的读写操作(将channel.setSend(send)设置好的send写入channel)。