kafka 的心跳是 kafka consumer 和 broker 之间的健康检查,只有当 broker coordinator 正常时,consumer 才会发送心跳。

consumer 和 rebalance 相关的 2 个配置参数:

参数名                --> MemberMetadata 字段
session.timeout.ms   --> MemberMetadata.sessionTimeoutMs
max.poll.interval.ms --> MemberMetadata.rebalanceTimeoutMs

 

broker 端,sessionTimeoutMs 参数

broker 处理心跳的逻辑在 GroupCoordinator 类中:如果心跳超期, broker coordinator 会把消费者从 group 中移除,并触发 rebalance。

 1   private def completeAndScheduleNextHeartbeatExpiration(group: GroupMetadata, member: MemberMetadata) {
 2     // complete current heartbeat expectation
 3     member.latestHeartbeat = time.milliseconds()
 4     val memberKey = MemberKey(member.groupId, member.memberId)
 5     heartbeatPurgatory.checkAndComplete(memberKey)
 6 
 7     // reschedule the next heartbeat expiration deadline
 8     // 计算心跳截止时刻
 9     val newHeartbeatDeadline = member.latestHeartbeat + member.sessionTimeoutMs
10     val delayedHeartbeat = new DelayedHeartbeat(this, group, member, newHeartbeatDeadline, member.sessionTimeoutMs)
11     heartbeatPurgatory.tryCompleteElseWatch(delayedHeartbeat, Seq(memberKey))
12   } 
13   
14   // 心跳过期
15   def onExpireHeartbeat(group: GroupMetadata, member: MemberMetadata, heartbeatDeadline: Long) {
16     group.inLock {
17       if (!shouldKeepMemberAlive(member, heartbeatDeadline)) {
18         info(s"Member ${member.memberId} in group ${group.groupId} has failed, removing it from the group")
19         removeMemberAndUpdateGroup(group, member)
20       }
21     }
22   }
23   
24   private def shouldKeepMemberAlive(member: MemberMetadata, heartbeatDeadline: Long) =
25     member.awaitingJoinCallback != null ||
26       member.awaitingSyncCallback != null ||
27       member.latestHeartbeat + member.sessionTimeoutMs > heartbeatDeadline

 

consumer 端:sessionTimeoutMs,rebalanceTimeoutMs 参数 

如果客户端发现心跳超期,客户端会标记 coordinator 为不可用,并阻塞心跳线程;如果 poll 消息的间隔超过了 rebalanceTimeoutMs,则 consumer 告知 broker 主动离开消费组,也会触发 rebalance

org.apache.kafka.clients.consumer.internals.AbstractCoordinator.HeartbeatThread 代码片段:

if (coordinatorUnknown()) {
    if (findCoordinatorFuture != null || lookupCoordinator().failed())
        // the immediate future check ensures that we backoff properly in the case that no
        // brokers are available to connect to.
        AbstractCoordinator.this.wait(retryBackoffMs);
} else if (heartbeat.sessionTimeoutExpired(now)) {
    // the session timeout has expired without seeing a successful heartbeat, so we should
    // probably make sure the coordinator is still healthy.
    markCoordinatorUnknown();
} else if (heartbeat.pollTimeoutExpired(now)) {
    // the poll timeout has expired, which means that the foreground thread has stalled
    // in between calls to poll(), so we explicitly leave the group.
    maybeLeaveGroup();
} else if (!heartbeat.shouldHeartbeat(now)) {
    // poll again after waiting for the retry backoff in case the heartbeat failed or the
    // coordinator disconnected
    AbstractCoordinator.this.wait(retryBackoffMs);
} else {
    heartbeat.sentHeartbeat(now);

    sendHeartbeatRequest().addListener(new RequestFutureListener<Void>() {
        @Override
        public void onSuccess(Void value) {
            synchronized (AbstractCoordinator.this) {
                heartbeat.receiveHeartbeat(time.milliseconds());
            }
        }

        @Override
        public void onFailure(RuntimeException e) {
            synchronized (AbstractCoordinator.this) {
                if (e instanceof RebalanceInProgressException) {
                    // it is valid to continue heartbeating while the group is rebalancing. This
                    // ensures that the coordinator keeps the member in the group for as long
                    // as the duration of the rebalance timeout. If we stop sending heartbeats,
                    // however, then the session timeout may expire before we can rejoin.
                    heartbeat.receiveHeartbeat(time.milliseconds());
                } else {
                    heartbeat.failHeartbeat();

                    // wake up the thread if it's sleeping to reschedule the heartbeat
                    AbstractCoordinator.this.notify();
                }
            }
        }
    });
}
 1 /**
 2  * A helper class for managing the heartbeat to the coordinator
 3  */
 4 public final class Heartbeat {
 5     private final long sessionTimeout;
 6     private final long heartbeatInterval;
 7     private final long maxPollInterval;
 8     private final long retryBackoffMs;
 9 
10     private volatile long lastHeartbeatSend; // volatile since it is read by metrics
11     private long lastHeartbeatReceive;
12     private long lastSessionReset;
13     private long lastPoll;
14     private boolean heartbeatFailed;
15 
16     public Heartbeat(long sessionTimeout,
17                      long heartbeatInterval,
18                      long maxPollInterval,
19                      long retryBackoffMs) {
20         if (heartbeatInterval >= sessionTimeout)
21             throw new IllegalArgumentException("Heartbeat must be set lower than the session timeout");
22 
23         this.sessionTimeout = sessionTimeout;
24         this.heartbeatInterval = heartbeatInterval;
25         this.maxPollInterval = maxPollInterval;
26         this.retryBackoffMs = retryBackoffMs;
27     }
28 
29     public void poll(long now) {
30         this.lastPoll = now;
31     }
32 
33     public void sentHeartbeat(long now) {
34         this.lastHeartbeatSend = now;
35         this.heartbeatFailed = false;
36     }
37 
38     public void failHeartbeat() {
39         this.heartbeatFailed = true;
40     }
41 
42     public void receiveHeartbeat(long now) {
43         this.lastHeartbeatReceive = now;
44     }
45 
46     public boolean shouldHeartbeat(long now) {
47         return timeToNextHeartbeat(now) == 0;
48     }
49     
50     public long lastHeartbeatSend() {
51         return this.lastHeartbeatSend;
52     }
53 
54     public long timeToNextHeartbeat(long now) {
55         long timeSinceLastHeartbeat = now - Math.max(lastHeartbeatSend, lastSessionReset);
56         final long delayToNextHeartbeat;
57         if (heartbeatFailed)
58             delayToNextHeartbeat = retryBackoffMs;
59         else
60             delayToNextHeartbeat = heartbeatInterval;
61 
62         if (timeSinceLastHeartbeat > delayToNextHeartbeat)
63             return 0;
64         else
65             return delayToNextHeartbeat - timeSinceLastHeartbeat;
66     }
67 
68     public boolean sessionTimeoutExpired(long now) {
69         return now - Math.max(lastSessionReset, lastHeartbeatReceive) > sessionTimeout;
70     }
71 
72     public long interval() {
73         return heartbeatInterval;
74     }
75 
76     public void resetTimeouts(long now) {
77         this.lastSessionReset = now;
78         this.lastPoll = now;
79         this.heartbeatFailed = false;
80     }
81 
82     public boolean pollTimeoutExpired(long now) {
83         return now - lastPoll > maxPollInterval;
84     }
85 
86 }
View Code

 

join group 的处理逻辑:kafka.coordinator.group.GroupCoordinator#onCompleteJoin

posted on 2019-01-17 14:16  偶尔发呆  阅读(5731)  评论(1编辑  收藏  举报