Kafka 核心技术与实战
客户端实践及原理剖析
21 | Java 消费者是如何管理TCP连接的?
和生产者不同,构建 KafkaConsumer 实例时是不会创建任何 TCP 连接的,而是在调用 KafkaConsumer.poll 方法时被创建的。 在 poll 方法内部有 3 个时机可以创建 TCP 连接。
1. 发起 FindCoordinator 请求时。
当消费者程序首次启动调用 poll 方法时,它需要向 Kafka 集群发送一个名为 FindCoordinator 的请求,希望 Kafka 集群告诉它哪个 Broker 是管理它的协调者。
消费者应该向哪个 Broker 发送这类请求呢?
理论上任何一个 Broker 都能回答这个问题,但在这个问题上,社区做了一点点优化:消费者程序会向集群中当前负载最小的那台 Broker 发送请求(刚开始的时候类似于随机选broker,但后面慢慢积累了一些数据之后这个小优化才会起一些作用)。
2.连接协调者时。
Broker 处理完上一步发送的 FindCoordinator 请求之后,会返还对应的响应结果(Response),显式地告诉消费者哪个 Broker 是真正的协调者,因此在这一步,消费者知晓了真正的协调者后,会创建连向该 Broker 的 Socket 连接。只有成功连入协调者,协调者才能开启正常的组协调操作,比如加入组、等待组分配方案、心跳请求处理、位移获取、位移提交等。
3.消费数据时。
消费者会为每个要消费的分区创建与该分区领导者副本所在 Broker 连接的 TCP。
创建多少个 TCP 连接?
消费者程序创建的第一个连接,此时消费者对于要连接的 Kafka 集群一无所知,因此它连接的 Broker 节点的 ID 是 -1,表示消费者根本不知道要连接的 Kafka Broker 的任何信息。
消费者程序开始发送 FindCoordinator 请求给第一步中连接的 Broker,即 nodeId 等于 -1 的那个,成功地获悉消费者程序协调者所在的 Broker 信息。
消费者就已经知道协调者 Broker 的连接信息了,因此将发起了第二个 Socket 连接,连接协调者所在的 broker。此时 id 值可能类似于 2147483645 这样一个很大的值 ,它是由 Integer.MAX_VALUE 减去协调者所在 Broker 的真实 ID 计算得来的,目的就是要让组协调请求和真正的数据获取请求使用不同的 Socket 连接。
最后,消费者又分别创建了新的 TCP 连接,主要用于实际的消息获取。
何时关闭 TCP 连接?
和生产者类似,消费者关闭 Socket 也分为主动关闭和自动关闭。主动关闭是指显式地调用消费者 API 的方法去关闭消费者,具体方式就是手动调用 KafkaConsumer.close() 方法,或者是执行 Kill 命令,不论是 Kill -2 还是 Kill -9;而自动关闭是由消费者端参数 connection.max.idle.ms 控制的,该参数现在的默认值是 9 分钟,即如果某个 Socket 连接上连续 9 分钟都没有任何请求“过境”的话,那么消费者会强行“杀掉”这个 Socket 连接。
当第三类 TCP 连接成功创建后,消费者程序就会废弃第一类 TCP 连接,之后在定期请求元数据时,它会改为使用第三类 TCP 连接。也就是说,第一类 TCP 连接会在后台被默默地关闭掉。对一个运行了一段时间的消费者程序来说,只会有后面两类 TCP 连接存在。
第一类 TCP 连接仅仅是为了首次获取元数据而创建的,后面就会被废弃掉。最根本的原因是,消费者在启动时还不知道 Kafka 集群的信息,只能使用一个“假”的 ID 去注册,即使消费者获取了真实的 Broker ID,它依旧无法区分这个“假”ID 对应的是哪台 Broker,因此也就无法重用这个 Socket 连接,只能再重新创建一个新的连接。为什么会出现这种情况呢?主要是因为目前 Kafka 仅仅使用 ID 这一个维度的数据来表征 Socket 连接信息。