Current section
Files
Jump to
Current section
Files
src/kafe_consumer_fetcher.erl
% @hidden
-module(kafe_consumer_fetcher).
-compile([{parse_transform, lager_transform}]).
-behaviour(gen_server).
-include("../include/kafe.hrl").
%% API.
-export([start_link/14]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {
topic = undefined,
partition = undefined,
server = undefined,
fetch_interval = undefined,
timer = undefined,
offset = -1,
group_id = undefined,
generation_id = undefined,
member_id = undefined,
fetch_size = ?DEFAULT_CONSUMER_FETCH_SIZE,
autocommit = ?DEFAULT_CONSUMER_AUTOCOMMIT,
min_bytes = ?DEFAULT_FETCH_MIN_BYTES,
max_bytes = ?DEFAULT_FETCH_MAX_BYTES,
max_wait_time = ?DEFAULT_FETCH_MAX_WAIT_TIME,
callback = undefined,
processing = ?DEFAULT_CONSUMER_PROCESSING
}).
start_link(Topic, Partition, Srv, FetchInterval,
GroupID, GenerationID, MemberID,
FetchSize, Autocommit,
MinBytes, MaxBytes, MaxWaitTime,
Callback, Processing) ->
gen_server:start_link(?MODULE, [
Topic
, Partition
, Srv
, FetchInterval
, GroupID
, GenerationID
, MemberID
, FetchSize
, Autocommit
, MinBytes
, MaxBytes
, MaxWaitTime
, Callback
, Processing
], []).
%% gen_server.
init([Topic, Partition, Srv, FetchInterval,
GroupID, GenerationID, MemberID,
FetchSize, Autocommit,
MinBytes, MaxBytes, MaxWaitTime, Callback,
Processing]) ->
NoError = kafe_error:code(0),
case kafe:offset_fetch(GroupID, [{Topic, [Partition]}]) of
{ok, [#{name := Topic,
partitions_offset := [#{error_code := NoError,
offset := Offset,
partition := Partition}]}]} ->
lager:info("Start fetcher for ~p#~p with offset ~p", [Topic, Partition, Offset]),
{ok, #state{
topic = Topic,
partition = Partition,
server = Srv,
fetch_interval = FetchInterval,
timer = erlang:send_after(FetchInterval, self(), fetch),
offset = Offset,
group_id = GroupID,
generation_id = GenerationID,
member_id = MemberID,
fetch_size = FetchSize,
autocommit = Autocommit,
min_bytes = MinBytes,
max_bytes = MaxBytes,
max_wait_time = MaxWaitTime,
callback = Callback,
processing = Processing
}};
_ ->
lager:debug("Failed to fetch offset for ~p:~p in group ~p", [Topic, Partition, GroupID]),
{stop, fetch_offset_faild}
end.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(fetch, State) ->
{noreply, fetch(State)};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
lager:debug("Stop fetcher ~p", [_State]),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
fetch(#state{fetch_interval = FetchInterval,
topic = Topic,
partition = Partition,
server = Srv,
offset = OffsetFetch,
fetch_size = FetchSize,
autocommit = Autocommit,
processing = Processing,
min_bytes = MinBytes,
max_bytes = MaxBytes,
max_wait_time = MaxWaitTime,
callback = Callback} = State) ->
OffsetFetch1 = case kafe:offset([{Topic, [{Partition, -1, 1}]}]) of
{ok, [#{name := Topic,
partitions := [#{error_code := none,
id := Partition,
offsets := [Offset]}]}]} ->
if
OffsetFetch + 1 =< Offset - 1 ->
Offsets = lists:sublist(lists:seq(OffsetFetch + 1, Offset - 1), FetchSize),
lager:debug("~p#~p Fetch ~p", [Topic, Partition, Offsets]),
case perform_fetch(Offsets, [], Topic, Partition,
Autocommit, Processing, Srv, Callback,
MinBytes, MaxBytes, MaxWaitTime) of
[] ->
lists:max(Offsets);
Fetched ->
lists:max(Fetched)
end;
true ->
OffsetFetch
end;
{ok, [#{name := Topic,
partitions := [#{error_code := Error}]}]} ->
lager:error("Get offset for ~p#~p error : ~p", [Topic, Partition, Error]),
OffsetFetch;
{error, Error} ->
lager:error("Get offset for ~p#~p error : ~p", [Topic, Partition, Error]),
OffsetFetch
end,
State#state{timer = erlang:send_after(FetchInterval, self(), fetch),
offset = OffsetFetch1}.
perform_fetch([], Acc, _, _, _, _, _, _, _, _, _) ->
Acc;
perform_fetch([Offset|Offsets], Acc,
Topic, Partition, Autocommit, Processing, Srv, Callback,
MinBytes, MaxBytes, MaxWaitTime) ->
case gen_server:call(Srv, can_fetch) of
true ->
NoError = kafe_error:code(0),
OffsetOutOfRange = kafe_error:code(1),
case kafe:fetch(-1, Topic, #{partition => Partition,
offset => Offset,
max_bytes => MaxBytes,
min_bytes => MinBytes,
max_wait_time => MaxWaitTime}) of
{ok, #{topics :=
[#{name := Topic,
partitions :=
[#{error_code := NoError,
message := #{key := Key,
offset := Offset,
value := Value},
partition := Partition}]}]}} ->
CommitRef = gen_server:call(Srv, {store_for_commit, Topic, Partition, Offset}, infinity),
case commit(CommitRef, Autocommit, Processing, at_most_once) of
{error, Reason} ->
lager:error("[~p] Commit error for offset ~p of ~p#~p : ~p", [Processing, Offset, Topic, Partition, Reason]),
Acc;
_ ->
lager:debug("Fetch offset ~p for ~p#~p with commit ref ~p", [Offset, Topic, Partition, CommitRef]),
case try
erlang:apply(Callback, [CommitRef, Topic, Partition, Offset, Key, Value])
catch
Class:Reason0 ->
lager:error(
"Callback for message #~p of ~p#~p crash:~s",
[Offset, Topic, Partition, lager:pr_stacktrace(erlang:get_stacktrace(), {Class, Reason0})]),
callback_exception
end of
ok ->
case commit(CommitRef, Autocommit, Processing, at_least_once) of
{error, Reason} ->
lager:error("[~p] Commit error for offset ~p of ~p#~p : ~p", [Processing, Offset, Topic, Partition, Reason]),
Acc;
_ ->
perform_fetch(Offsets, [Offset|Acc], Topic, Partition, Autocommit, Processing, Srv, Callback,
MinBytes, MaxBytes, MaxWaitTime)
end;
callback_exception ->
Acc;
{error, Error} ->
lager:error("Callback for message #~p of ~p#~p return error : ~p", [Offset, Topic, Partition, Error]),
Acc
end
end;
{ok, #{topics :=
[#{name := Topic,
partitions :=
[#{error_code := OffsetOutOfRange,
partition := Partition}]}]}} ->
lager:debug("Offset #~p topic ~p:~p : out of range", [Offset, Topic, Partition]),
perform_fetch(Offsets, Acc, Topic, Partition, Autocommit, Processing, Srv, Callback,
MinBytes, MaxBytes, MaxWaitTime);
Error ->
lager:error("Failed to fetch message #~p topic ~p:~p : ~p", [Offset, Topic, Partition, Error]),
Acc
end;
false ->
Acc
end.
commit(CommitRef, true, Processing, Processing) when Processing == at_least_once;
Processing == at_most_once ->
kafe_consumer:commit(CommitRef, #{retry => 3, delay => 1000});
commit(_, _, Processing, _) when Processing == at_least_once;
Processing == at_most_once ->
ok.