Kafka——消息的发送流程

前言

本文将介绍kafka的一条消息的发送流程,从消息的发送到服务端的存储。上文说到kafak分为客户端与服务端,要发送消息就涉及到了网络通讯,kafka采用TCP协议进行客户端与服务端的通讯协议。

案例

消息发送

下面看一个简单的生产者案例

        Properties props = new Properties();
        props.put("bootstrap.servers", "broker1:9092,broker1:9092");
        props.put("acks", "all");
        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        Producer<String, String> producer = new KafkaProducer<>(props);
        for (int i = 0; i < 100; i++){
            producer.send(
                    new ProducerRecord<>("my-topic", Integer.toString(i), Integer.toString(i)));
        }
        producer.close();
  • bootstrap.servers:指定生产者启动时连接服务端的地址,客户端将从连接的服务端拿到整个集群的信息,建议配置3-4个,如果只用一个broker,万一连接不上导致无法拿到整个集群的信息导致启动失败,配置数量也不需要太多,因为生产者启动时候会与配置的broker都建立TCP连接;
  • key.serializer:指定key的序列化规则;
  • acks:指定必须要有多少个分区副本接受到消息,生产者才会认为消息写入成功。1. acks=0时生成者写入消息不会等待服务器的响应,acks=1,只要leader节点接收到消息,生产者就会收到一个服务器的成功响应,acks=all,只要当所有参与复制的节点全部收到消息时,生产者才会收到服务器的成功响应。
  • value.serializer: 指定value的序列化规则。
  • ProducerRecord:指定发送的topic,key与value,key可以为null;
  • send:创建完生产者与消息之后就可以发送了,发送消息分为三种:
  1. 发送并忘记(send and forget):producer.send(),默认为异步发送,并不关心消息是否达到服务端,会存在消息丢失的问题。
  2. 同步:producer.send()返回一个Future对象,调用get()方法变回进行同步等待,就知道消息是否发送成功。
  3. 异步发送:如果消息都进行同步发送,要发送这次的消息需要等到上次的消息成功发送到服务端,这样整个消息发送的效率就很低了。kafka支持producer.send()传入一个回调函数,消息不管成功或者失败都会调用这个回调函数,这样就算是异步发送,我们也知道消息的发送情况,然后再回调函数中选择记录日志还是重试都取决于调用方。Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback);

指定分区

kafka会将一个topic划分成n个分区,那么在生产者发送消息的时候是怎么知道要发给哪个分区的呢。上面说过生产者会拿到整个集群的信息,所以生产者知道每个topic下面有一个分区,基于此可以有些常见的消息分区策略:

  • 轮询:依次将消息发送该topic下的所有分区,如果在创建消息的时候key为null,kafka默认采用这种策略。
  • key指定分区:在创建消息是key不为空,并且使用默认分区器,kafka会将key进行hash,然后根据hash值隐射到指定的分区上。这样的好处是key相同的消息会在一个分区下,kafka并不能保证全局有序,但是在每个分区下的消息是有序的,按照顺序存储,按照顺序消费。在保证同一个key的消息是有序的,这样基本能满足消息的顺序性的需求。
  • 自定义策略:实现Partitioner接口就能自定义分区策略。

客户端

上面介绍了怎么发送消息和一些分区策略,接下来介绍生产者是如何发送消息的。


消息发送

上图是生产者消息发送的一个流程图。

  1. 创建一个消息,topic、value必填,key、partition可以选填;
  2. 调用send()方法,根据指定的key.serializer、value.serializer将key与value进行序列化,将对象序列化为字节数组,才能在网络上进行传输。
  3. 如果ProducerRecord指定了分区,分区器不会做任何事情,否则会按照指定的分区策略选择一个分区。
  4. 之前提到过,生产者发送消息是按照批次来发,所以消息选择好了分区之后,生产者并不会立即发送消息到服务端,而是消息添加到本地的消息批次里,这个批次的所有消息的topic和分区都相同。
  5. 生产者有个后台的Sender线程,负责将消息发送到服务端。服务端在接收到消息之后会返回一个响应,它包含主题和分区信息以及消息在分区中的偏移量。如果消息写入失败会返回一个错误。生产者接收到错误之后会根据配置的retries,尝试重复发送消息,如果超过retries配置的重试次数,还是发送失败,就会返回错误。

以上就是生产者发送消息的一个主要流程,当然还有很多细节没有涉及到,这里只是一个简易的主流程。

服务端

请求处理

客户端发送消息之后,服务端需要请求作出相应,kafka使用的是TCP作为服务端与客户端通讯协议。Kafak的请求的处理使用的是Reactor模式,关于Reactor模式我前面有文章进行介绍,这里不详细展开。简单来说Reactor模式利用底层的多路IO复用,采用事件驱动的形式,从传统阻塞IO,转为非阻塞的IO。Reactor常见的模型是一个mainReactor接受网络连接,然后由subReactor处理网络请求。Kafka也是这样设计的,服务端在它监听的端口上运行一个Acceptor线程,该线程会创建一个连接然后转交给Processor(网络线程)线程处理。

极客时间《Kafka核心技术与实战》

上图是服务端处理请求的流程图,并非只是处理发送消息的请求,获取元数据、读取消息也是这样的流程。这里就拿消息发送的请求举例。
由Acceptor接受请求,然后以轮询的方式均匀的将请求分给网络线程(数量可以通过num.network.threads配置),然后网络线程将请求放到一个共享请求队列中。Broker 端还有个 IO 线程池,负责从该队列中取出请求,执行真正的处理。如果是写请求,将会把消息写入本地磁盘。在Linux系统上,并不会立即写入磁盘,而是写入文件系统的缓存中,kafka并不会等待数据写入到磁盘中。
当 IO 线程处理完请求后,会将生成的响应发送到网络线程池的响应队列中,然后由对应的网络线程负责将 Response 返还给客户端。
Purgarory它是用来缓存延时请求,所谓延时请求,就是那些一时未满足条件不能立刻处理的请求。比一个生产消息当acks=all时,这时候请求就需要等待其他参与复制的副本响应成功之后,才能回应客户端写入成功的消息。当请求不能立刻处理时,它就会暂存在 Purgatory 中。稍后一旦满足了完成条件,IO 线程会继续处理该请求,并将 Response 放入对应网络线程的响应队列中。

副本

这里再介绍一下kafka的副本机制。备份日志文件是分布式数据系统最基础的要素之一,实现方法也有很多种。最常见的做法就是,一个leader副本和若干个follower副本,由leader接受写请求,然后将日志同步给follower。正常情况下leader正常工作,没有任何的问题,如果leader崩溃就需要从follower副本中选择一个当下一个leader副本。此时就有一个问题存在,因为网络传输存在延迟,在follower副本中的数据并没有和leader副本保持一致。所以 我们必须确保我们leader的候选者们是一个数据同步最新的follower节点。
如果选择写入时候需要保证一定数量的副本写入成功,读取时需要保证读取一定数量的副本,读取和写入之间有重叠。这样的读写机制称为 Quorum。
常见的方式是过半策略,在写入的时候保证集群中过半节点写入成功,才算一次写操作成功,zookeeper、raft采用的就是这种策略。这样能够保证follower中有能与leader数据保持一致的节点。缺点在于,写操作的成本过大,需要保证过半的节点写入成功,而且容错的数量有限,如果5个几点的集群只能支持2个节点挂掉。
Kafka 不是用大多数投票选择 leader 。Kafka动态维护了一个同步状态的备份的集合 (a set of in-sync replicas), 简称 ISR ,在这个集合中的节点都是和 leader 保持高度一致的,只有这个集合的成员才有资格被选举为 leader,一条消息必须被这个集合所有节点读取并追加到日志中了,这条消息才能视为提交。
ISR中的副本首先eader副本是在其中的,如何判断follower节点是否在ISR中呢?
通过Broker 端参数 replica.lag.time.max.ms,这个参数的含义是Follower 副本能够落后 Leader 副本的最长时间间隔,当前默认值是 10 秒。这就是说,只要一个 Follower 副本落后 Leader 副本的时间不连续超过 10 秒,那么 Kafka 就认为该 Follower 副本与 Leader 是同步的,将会将该副本踢出ISR,如果后续该副本后面追上Leader的进度,也会被加入到ISR中。采用ISR的方式能够提高系统的可用性,容错的节点也变多了,只有ISR中有一个副本也能保证正常的处理请求。
当然如果ISR一个副本都没有了就不能继续对外提供服务了,kafka提供unclean leader 选举机制,有以下两种方式,在一致性与可用性之间做出选择。

  • 等待一个 ISR 的副本重新恢复正常服务,并选择这个副本作为领 leader (它有极大可能拥有全部数据)。
  • 选择第一个重新恢复正常服务的副本(不一定是 ISR 中的)作为leader。

关于kafka副本机制,这里就不说太多了,可以看出kafka在解决分布式的可用性、一致性问题做出的思考和解决方案。

总结

本文介绍了生成者如何发送一个请求和服务端如何处理一个请求,之前写过关于netty的文章。可以看出在处理网络请求的方式上,Reactor模式依然是主流的选择。
还有就是kafka在日志复制上的设计通过ISR提高系统的可用性的同时保证数据的一致性,与unclean leader机制在一致性与可用性的选择,当然kafka还有很多很多精妙的设计,就不一一探讨了,本文分析到此结束。

参考

《Kafka权威指南》
kafka官网
极客时间《Kafka核心技术与实战》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,783评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,360评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,942评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,507评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,324评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,299评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,685评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,358评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,652评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,704评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,465评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,318评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,711评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,991评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,265评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,661评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,864评论 2 335