关于Kafka producer管理TCP连接的讨论
在Kafka中,TCP连接的管理交由底层的Selector类(org.apache.kafka.common.network)来维护。Selector类定义了很多数据结构,其中最核心的当属java.nio.channels.Selector实例,故所有的IO事件实际上是使用Java的Selector来完成的。本文我们探讨一下producer与Kafka集群进行交互时TCP连接的管理与维护。
一、何时创建TCP连接
Producer端在创建KafkaProducer实例时就会创建与broker的TCP连接——这个表述严格来说不是很准确,应当这么说:在创建KafkaProducer实例时会创建并启动Sender线程实例。Sender线程开始运行时首先就会创建与broker的TCP连接,如下面这段日志所示:
[2018-12-09 09:35:45,620] DEBUG [Producer clientId=producer-1] Initialize connection to node localhost:9093 (id: -2 rack: null) for sending metadata request (org.apache.kafka.clients.NetworkClient:1084)
[2018-12-09 09:35:45,622] DEBUG [Producer clientId=producer-1] Initiating connection to node localhost:9093 (id: -2 rack: null) using address localhost/127.0.0.1 (org.apache.kafka.clients.NetworkClient:914)[2018-12-09 09:35:45,814] DEBUG [Producer clientId=producer-1] Initialize connection to node localhost:9092 (id: -1 rack: null) for sending metadata request (org.apache.kafka.clients.NetworkClient:1084)
[2018-12-09 09:35:45,815] DEBUG [Producer clientId=producer-1] Initiating connection to node localhost:9092 (id: -1 rack: null) using address localhost/127.0.0.1 (org.apache.kafka.clients.NetworkClient:914)[2018-12-09 09:35:45,828] DEBUG [Producer clientId=producer-1] Sending metadata request (type=MetadataRequest, topics=) to node localhost:9093 (id: -2 rack: null) (org.apache.kafka.clients.NetworkClient:1068)
在我的样例代码中,bootstrap.servers指定了"localhost:9092, localhost:9093"。由上面的日志可以看到KafkaProducer实例创建后(此时尚未开始发送消息)producer会创建与这两台broker的TCP连接。特别注意我标红的broker id——这里的id都是负值,我会在后文详细说说这里面的事情。另外,上述日志中最后一行表明producer选择了向localhost:9093的broker发送METADATA请求去获取集群的元数据信息——实际上producer选择的是当前负载最少的broker。这里的负载指的是未处理完的网络请求数。
总的来说,TCP连接是在Sender线程运行过程中创建的,所以即使producer不发送任何消息(即显式调用producer.send),底层的TCP连接也是会被创建出来的。
在转到下一个话题之前,我想聊聊针对这种设计的一些自己的理解:如社区文档所说,KafkaProducer类是线程安全的。我虽然没有详尽地去验证过是否真的thread-safe,但根据浏览源码大致可以得出这样的结论:producer主线程和Sender线程共享的可变数据结构大概就只有RecordAccumulator类,因此维护RecordAccumulator类的线程安全也就实现了KafkaProducer的线程安全,而RecordAccumulator类中主要的数据结构是ConcurrentMap<TopicPartition, Deque<ProducerBatch>>,而且凡是用到Deque的地方基本上都由Java monitor lock来保护,所以基本上可以认定RecordAccumulator的线程安全性。
我这里真正想说的是,即使KafkaProducer类是线程安全的,我其实也不太赞同创建KafkaProducer实例时立即启动Sender线程的做法。Brian Goetz大神著作《Java Concurrency in Practice》中明确给出了这样做的风险:在对象构造器中启动线程会造成this指针的逃逸——理论上Sender线程完全能够看到一个未构造完整的KafkaProducer实例。当然在构造KafkaProducer实例时创建Sender线程实例本身没有任何问题,但最好不要启动它。
二、创建多少个TCP连接
我们还是结合日志来看。这次producer开始发送消息,日志如下:
[2018-12-09 10:06:46,761] DEBUG [Producer clientId=producer-1] 开始发送消息...
[2018-12-09 10:06:46,762] DEBUG [Producer clientId=producer-1] Initialize connection to node localhost:9092 (id: 0 rack: null) for sending metadata request (org.apache.kafka.clients.NetworkClient:1084)
[2018-12-09 10:06:46,762] DEBUG [Producer clientId=producer-1] Initiating connection to node localhost:9092 (id: 0 rack: null) using address localhost/127.0.0.1 (org.apache.kafka.clients.NetworkClient:914)[2018-12-09 10:06:46,765] DEBUG [Producer clientId=producer-1] Initialize connection to node localhost:9093 (id: 1 rack: null) for sending metadata request (org.apache.kafka.clients.NetworkClient:1084)
[2018-12-09 10:06:46,766] DEBUG [Producer clientId=producer-1] Initiating connection to node localhost:9093 (id: 1 rack: null) using address localhost/127.0.0.1 (org.apache.kafka.clients.NetworkClient:914)[2018-12-09 10:06:46,770] DEBUG [Producer clientId=producer-1] Sending metadata request (type=MetadataRequest, topics=test) to node localhost:9092 (id: 0 rack: null) (org.apache.kafka.clients.NetworkClient:1068)
日志告诉我们,producer又创建了与localhost:9092、localhost:9093的TCP连接。加上最开始创建的两个TCP连接,目前producer总共创建了4个TCP连接,连向localhost:9092和localhost:9093各有两个。再次注意标红的broker id——此时id不再是负值了,或者说此时它们是真正的broker id了(即在server.properties中broker.id指定的值)。这个结论告诉了我们一个有意思的事实:当前版本下(2.1.0),Kafka producer会为bootstrap.servers中指定的每个broker都创建两个TCP连接:第一个TCP连接用于首次获取元数据信息;第二个TCP连接用于消息发送以及之后元数据信息的获取。注意,第一个TCP连接中broker id是假的;第二个TCP连接中broker id才是真实的broker id。
另外,注意上面日志的最后一行。当producer再次发送METADATA请求时它使用的是新创建的TCP连接,而非最开始的那个TCP连接。这点非常关键!这揭示了一个事实:最开始创建的TCP连接将不再被使用,或者说完全被废弃掉了。
三、何时关闭TCP连接
Producer端关闭TCP连接的方式有两种:一种是用户主动关闭;一种是Kafka自动关闭。我们先说第一种,这里的主动关闭实际上是广义的主动关闭,甚至包括用户调用kill -9主动“杀掉”producer应用。当然最推荐的方式还是调用producer.close方法来关闭。第二种则是Kafka帮你关闭,这与producer端参数connections.max.idle.ms的值有关。默认情况下该参数值是9分钟,即如果在9分钟内没有任何请求“流过”该某个TCP连接,那么Kafka会主动帮你把该TCP连接关闭。用户可以在producer端设置connections.max.idle.ms=-1禁掉这种机制。一旦被设置成-1,TCP连接将成为永久长连接。当然这只是软件层面的“长连接”机制,由于Kafka创建的这些Socket连接都开启了keepalive,因此keepalive探活机制还是会遵守的。
四、可能的问题?
显然,这种机制存在一个问题:假设你的producer指定了connections.max.idle.ms = -1(因为TCP连接的关闭与创建也是有开销的,故很多时候我们确实想要禁掉自动关闭机制)而且bootstrap.servers指定了集群所有的broker连接信息。我们假设你的broker数量是N,那么producer启动后它会创建2 * N个TCP连接,而其中的N个TCP连接在producer正常工作之后再也不会被使用且不会被关闭。实际上,producer只需要N个TCP连接即可与N个broker进行通讯。为了请求元数据而创建的N个TCP连接完全是浪费——我个人倾向于认为Kafka producer应该重用最开始创建的那N个连接,因此我觉得这是一个bug。
造成重复创建TCP连接的根本原因在于broker id的记录。就像之前说到的,最开始producer请求元数据信息时它肯定不知道broker的id信息,故它做了一个假的id(从-1开始,然后是-2, -3。。。。),同时它将这个id保存起来以判断是否存在与这个broker的TCP连接。Broker端返回元数据信息后producer获知了真正的broker id,于是它拿着这个broker id去判断是否存在与该broker的TCP连接——自然是不存在,因此它重新创建了一个新的Socket连接。这里的问题就在于我们不能仅仅依靠broker id来判断是否存在连接。实际上使用host:port对来判断可能是更好的方法。也许社区可以考虑在后续修正这个问题。
五、总结
简单总结一下当前的结论,针对最新版本Kafka(2.1.0)而言,Java producer端管理TCP连接的方式是:
1. KafkaProducer实例创建时启动Sender线程,从而创建与bootstrap.servers中所有broker的TCP连接
2. KafkaProducer实例拿到元数据信息之后还会再次创建与bootstrap.servers中所有broker的TCP连接
3. 步骤1中创建的TCP连接只用于首次获取元数据信息(实际上也只是会用到其中的一个连接,其他的N - 1个甚至完全不会被用到)
4. 如果设置producer端connections.max.idle.ms参数大于0,则步骤1中创建的TCP连接会被自动关闭;如果设置该参数=-1,那么步骤1中创建的TCP连接将成为“僵尸”连接
5. 当前producer判断是否存在与某broker的TCP连接依靠的是broker id,这是有问题的,依靠<host, port>对可能是更好的方式
关于Kafka producer管理TCP连接的讨论的更多相关文章
- 关于Kafka java consumer管理TCP连接的讨论
本篇是<关于Kafka producer管理TCP连接的讨论>的续篇,主要讨论Kafka java consumer是如何管理TCP连接.实际上,这两篇大部分的内容是相同的,即consum ...
- golang 网络编程之如何正确关闭tcp连接以及管理它的生命周期
欢迎访问我的个人网站获取更佳阅读排版 golang 网络编程之如何正确关闭tcp连接以及管理它的生命周期 | yoko blog (https://pengrl.com/p/47401/) 本篇文章部 ...
- TCP/IP 笔记 - TCP连接管理
TCP是一种面向连接的单播协议,在发送数据之前,通信双方必须在彼此建立一条连接:这与UDP的无连接不同,UDP无需通信双方发送数据之前建立连接.所有TCP需要处理多种TCP状态时需要面对的问题,比如连 ...
- TCP连接管理(TCP Connection Management)
在最近的求职面试过程中,关于"建立TCP连接的三次握手"不止一次被问到了,虽然我以前用同样的问题面试过别人,但感觉还是不能给面试官一个很清晰的回答.本文算是对整个TCP连接管理做一 ...
- TCP系列07—连接管理—6、TCP连接管理的状态机
经过前面对TCP连接管理的介绍,我们本小节通过TCP连接管理的状态机来总结一下看看TCP连接的状态变化 一.TCP状态机整体状态转换图(截取自第二版TCPIP详解) 二.TCP连接建立 ...
- TCP系列05—连接管理—4、TCP连接的ISN、连接建立超时及TCP的长短连接
一.TCP连接的ISN 之前我们说过初始建立TCP连接的时候的系列号(ISN)是随机选择的,那么这个系列号为什么不采用一个固定的值呢?主要有两方面的原因 防止同一个连接的不同实例(di ...
- 多图详解 TCP 连接管理,太全了!!!
TCP 是一种面向连接的单播协议,在 TCP 中,并不存在多播.广播的这种行为,因为 TCP 报文段中能明确发送方和接受方的 IP 地址. 在发送数据前,相互通信的双方(即发送方和接受方)需要建立一条 ...
- tcp连接管理
[root@ok etc]# cat /proc/sys/net/core/netdev_max_backlog 每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目 ...
- TCP连接状态管理
tcp 连接过程 tcp 状态机
随机推荐
- 安装pytorch0.4.0
参考了官网https://pytorch.org/previous-versions/中的说明 (jj1env) [ji@dev down_python0.4.0]$ pip install http ...
- modelform的操作以及验证
1,model的两个功能 1,数据库操作 2,验证只有一个clean方法作为钩子来操作,方法比较少 2,form(专门用来做验证的) 根据form里面写的类,类里面的字段,这些字段里有内置的的正则表达 ...
- 避免 Deepin 15.4 系统 界面卡顿、假死等现象:隐藏自带的“任务栏”,安装轻量级的“任务栏tint2”
使用 Deepin 过程中,发现当点击“任务栏”上面的按钮,尤其是右键单击,选择菜单时,界面很容易卡顿,再也动弹不了. 好吧,,,就不使用自带的“任务栏”了,换成一个轻量级的“任务栏tint2”, 1 ...
- [Asp.net core]使用ssh命令发布asp.net core项目
命令 # 移除之前发布的包 rm -rf ./.Publish rm -rf ./Wolfy.Blog.tar.gz # 编译并发布 将发布包打包在.Publish目录下 -o "../.P ...
- 什么是crf
什么是crf 利用crf++进行实体识别的流程 确定标签体系: 确定特征模板文件: 处理训练数据文件: 模型训练. 确定标签体系 大部分情况下,标签体系越复杂准确度也越高,但相应的训练时间也会增加.因 ...
- 完善mui的tap及longtap点击效果
以下为原版效果: 以下为我们改版的效果: 希望你们能看出差别,很细小,但更适合我们的需要.就是选中背景色的问题,官方的是点一下背景色就没有了,我们的是选中一直存在.
- CentOS7+Hadoop2.7.2(HA高可用+Federation联邦)+Hive1.2.1+Spark2.1.0 完全分布式集群安装
1 2 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 2.9.1 2.9.2 2.9.2.1 2.9.2.2 2.9.3 2.9.3.1 2.9.3.2 2.9.3.3 2. ...
- 将 Smart 构件发布到 Maven 中央仓库
https://my.oschina.net/huangyong/blog/226738
- mysql sql执行慢 分析过程
摘自: https://blog.csdn.net/zhuzaijava/article/details/77935200 为了验证select 1 与 select 1 from tableName ...
- Android ListView滚动到指定的位置
这篇文章主要给大家介绍了Android中的ListView如何滚动到指定的位置,文章给出了两种解决的方法,并给出详细的示例代码,相信会对大家的理解和学习很有帮助,有需要的朋友们下面来一起看看吧. 本文 ...