概要
说到 kafka 源码理解
可能大家听到的kafka对多的是kafka
顺序追加?
0拷贝?
高并发?
线程安全?
....
但是这些到底为什么呢 ?其实看看kafka的源码就非常的清晰了
优秀的点非常的多,我这里重点讲我认为设计的非常优秀美观的地方.
核心优势
kafka的优秀的点.
分段加锁
double check
读写分离
内存池的设计
高性能的设计
跳表设计
稀疏索引设计
零拷贝..
源码浅析
kafka producer
请看下面 kafka producer主要干了啥
那我们就从producer.send的开始吧,此处引用的是kafka2.7的版本,可能各版本有细节的差异,比如可以观测下面frre.dealocate的位置和前面的版本有所差异哦
doSend()主要分为 以下步骤
1.同步等待拉取元数据
// first make sure the metadata for the topic is available
long nowMs = time.milliseconds();
ClusterAndWaitTime clusterAndWaitTime;
try {
clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), nowMs, maxBlockTimeMs);
} catch (KafkaException e) {
if (metadata.isClosed())
throw new KafkaException("Producer closed while send in progress", e);
throw e;
}
nowMs += clusterAndWaitTime.waitedOnMetadataMs;
long remainingWaitMs = Math.max(0, maxBlockTimeMs - clusterAndWaitTime.waitedOnMetadataMs);
Cluster cluster = clusterAndWaitTime.cluster;
2.对消息的key value进行序列化
3.计算出应该发到哪个分区(如果有自定义,走自定义)
4.确认消息的序列化之后有没有超过1M(此时这个参数可以设置.) max.request.size
5.给消息绑定相应的回调函数
6.最核心,最关键的.就是上图说的accumulaor.append,看看优秀的kafka是怎样完美的实现了 高并发读写,并且还能线程安全的
将消息放入一个 32M的内存,然后有个accumulator.apend 封装成一个批次,一个批次的发出去
7.唤醒sender的线程,将将准备好的批次数据开始批量的发送
RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey)
(1)获取队列,如果有就获取,没有就创建
(2)线程1第一次进来 check1(这一步一定是高频操作,不断的往去追加数据到空闲的recordBatch.并排成队列等待处理.在高并发下保证线程安全,不然数据的顺序性,不然回滚等一致性操作全部乱了, 所以此处加了 锁
写的时候,如果有这个队列中有个ProducerBatch, 那么直接获取最后一条.如果没有,就新生产一个bath
队列中buffersize 预估内存,如果不够,还要去内存池中申请.这里的一个batchSize是16k.如果不够,将会调用free.allocate去内存池里申请内存.
申请内存为耗时操作.
你们有没有惊奇的发现,相同的tryAppend又来了,难道是源码写错了?
当然不是啦,源码设计精巧之处就在在这里. 如果线程1申请到空间但是他并没有使用不是吗?
1.倘若线程2又跳到这里,是不是刚好可以用, 而且各自都是按照latest的方式去追加在末尾. 并在同一批次中准备待命.
2.将申请空间的代码抽离出来,追加数据的代码用锁的方式封装起来, 实现了真正的高并发.其实细节很多的,鉴于技术有限,实在是描述不清楚,做个了解就行.
3.真正实现了读写分离,不管是读请求,还是写请求,都会非常的快速
4.最后会有一个free.deallocate(buffer) ,用完的空间有回归出去,为下一个批次做准备.
我也快下班了.今天就写到这里.下次我们还可以聊聊
跳表设计
稀疏索引设计
零拷贝..