Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This moves the heartbeat functionality into a separate GenServer so that even if the worker startup takes a long time, such as over high latency connections, the broker won't remove the consumer from the group. This seems to solve issues we've had where we were getting :unknown_member_id errors back. The heartbeat notifies the manager of a need for rebalance by exiting with a reason of `:rebalance`. The Manager starts a rebalance upon receiving that message.
- Loading branch information
1 parent
1e0f080
commit d8580e4
Showing
3 changed files
with
115 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"], | ||
line_length: 80 | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
defmodule KafkaEx.ConsumerGroup.Heartbeat do | ||
@moduledoc false | ||
# GenServer to send heartbeats to the broker | ||
# | ||
# A `HeartbeatRequest` is sent periodically by each active group member (after | ||
# completing the join/sync phase) to inform the broker that the member is | ||
# still alive and participating in the group. If a group member fails to send | ||
# a heartbeat before the group's session timeout expires, the coordinator | ||
# removes that member from the group and initiates a rebalance. | ||
# | ||
# `HeartbeatResponse` allows the coordinating broker to communicate the | ||
# group's status to each member: | ||
# | ||
# * `:no_error` indicates that the group is up to date and no action is | ||
# needed. | ||
# * `:rebalance_in_progress` a rebalance has been initiated, so each member | ||
# should re-join. | ||
# * `:unknown_member_id` means that this heartbeat was from a previous dead | ||
# generation. | ||
# | ||
# For either of the error conditions, the heartbeat process exits, which is | ||
# trapped by the KafkaEx.ConsumerGroup.Manager and handled by re-joining the | ||
# consumer group. (see KafkaEx.ConsumerGroup.Manager.join/1) | ||
|
||
require Logger | ||
alias KafkaEx.Protocol.Heartbeat.Request, as: HeartbeatRequest | ||
alias KafkaEx.Protocol.Heartbeat.Response, as: HeartbeatResponse | ||
|
||
defmodule State do | ||
defstruct [ | ||
:worker_name, | ||
:heartbeat_request, | ||
:heartbeat_interval | ||
] | ||
end | ||
|
||
def start_link(options) do | ||
GenServer.start_link(__MODULE__, options) | ||
end | ||
|
||
def init(%{group_name: group_name, member_id: member_id, generation_id: generation_id, worker_name: worker_name, heartbeat_interval: heartbeat_interval}) do | ||
heartbeat_request = %HeartbeatRequest{ | ||
group_name: group_name, | ||
member_id: member_id, | ||
generation_id: generation_id | ||
} | ||
|
||
state = %State{ | ||
worker_name: worker_name, | ||
heartbeat_request: heartbeat_request, | ||
heartbeat_interval: heartbeat_interval | ||
} | ||
|
||
{:ok, state, state.heartbeat_interval} | ||
end | ||
|
||
def handle_info( | ||
:timeout, | ||
%State{ | ||
worker_name: worker_name, | ||
heartbeat_request: heartbeat_request, | ||
heartbeat_interval: heartbeat_interval | ||
} = state | ||
) do | ||
case KafkaEx.heartbeat(heartbeat_request, worker_name: worker_name) do | ||
%HeartbeatResponse{error_code: :no_error} -> | ||
Logger.debug("XXX: HB OK") | ||
{:noreply, state, heartbeat_interval} | ||
|
||
%HeartbeatResponse{error_code: :rebalance_in_progress} -> | ||
Logger.debug("XXX: HB REBALANCE") | ||
{:stop, :rebalance, state} | ||
|
||
%HeartbeatResponse{error_code: :unknown_member_id} -> | ||
{:stop, :rebalance, state} | ||
|
||
%HeartbeatResponse{error_code: error_code} -> | ||
Logger.warn("Heartbeat failed, got error code #{error_code}") | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters