use std::collections::hash_map::{HashMap, Entry};
use std::collections::VecDeque;
use std::slice;
use client::{self, KafkaClient, FetchPartition, CommitOffset, FetchGroupOffset};
use client::metadata::Topics;
use error::{Error, KafkaCode, Result};
use client::fetch;
pub use client::fetch::Message;
pub use client::FetchOffset;
pub const DEFAULT_RETRY_MAX_BYTES_LIMIT: i32 = 0;
pub struct Consumer {
client: KafkaClient,
state: State,
config: Config,
}
#[derive(Debug)]
struct FetchState {
offset: i64,
max_bytes: i32,
}
#[derive(Debug)]
struct State {
fetch_offsets: HashMap<i32, FetchState>,
retry_partitions: VecDeque<i32>,
consumed_offsets: HashMap<i32, i64>,
consumed_offsets_dirty: bool,
}
struct Config {
group: String,
topic: String,
partitions: Vec<i32>,
fallback_offset: Option<FetchOffset>,
retry_max_bytes_limit: i32,
}
impl Consumer {
pub fn from_client(client: KafkaClient, group: String, topic: String) -> Builder {
Builder::new(Some(client), Vec::new())
.with_group(group)
.with_topic(topic)
}
pub fn from_hosts(hosts: Vec<String>, group: String, topic: String) -> Builder {
Builder::new(None, hosts)
.with_group(group)
.with_topic(topic)
}
pub fn client(self) -> KafkaClient {
self.client
}
pub fn poll(&mut self) -> Result<MessageSets> {
let (n, resps) = self.fetch_messages();
self.process_fetch_responses(n, try!(resps))
}
pub fn single_partition_consumer(&self) -> bool {
self.state.fetch_offsets.len() == 1
}
fn fetch_messages(&mut self) -> (u32, Result<Vec<fetch::Response>>) {
let topic = &self.config.topic;
let fetch_offsets = &self.state.fetch_offsets;
match self.state.retry_partitions.pop_front() {
Some(partition) => {
let s = match fetch_offsets.get(&partition) {
Some(fstate) => fstate,
None => return (1, Err(Error::Kafka(KafkaCode::UnknownTopicOrPartition))),
};
debug!("fetching messages: (topic: {} / fetch-offset: {{{}: {:?}}})",
topic, partition, s);
(1, self.client.fetch_messages_for_partition(
&FetchPartition::new(topic, partition, s.offset)
.with_max_bytes(s.max_bytes)))
}
None => {
debug!("fetching messages: (topic: {} / fetch-offsets: {:?})",
topic, fetch_offsets);
let reqs = fetch_offsets.iter()
.map(|(&p, s)| {
FetchPartition::new(topic, p, s.offset)
.with_max_bytes(s.max_bytes)
});
(fetch_offsets.len() as u32, self.client.fetch_messages(reqs))
}
}
}
fn process_fetch_responses(&mut self, num_partitions_queried: u32, resps: Vec<fetch::Response>)
-> Result<MessageSets>
{
let single_partition_consumer = self.single_partition_consumer();
let mut empty = true;
let mut retry_partitions = &mut self.state.retry_partitions;
for resp in &resps {
for t in resp.topics() {
for p in t.partitions() {
let partition = p.partition();
let data = match p.data() {
&Err(ref e) => return Err(e.clone()),
&Ok(ref data) => data,
};
let mut fetch_state = self.state.fetch_offsets
.get_mut(&partition)
.expect("non-requested partition");
unsafe {
data.forget_before_offset(fetch_state.offset);
}
if let Some(last_msg) = data.messages().last() {
fetch_state.offset = last_msg.offset + 1;
empty = false;
if fetch_state.max_bytes != self.client.fetch_max_bytes_per_partition() {
let prev_max_bytes = fetch_state.max_bytes;
fetch_state.max_bytes = self.client.fetch_max_bytes_per_partition();
debug!("reset max_bytes for {}:{} from {} to {}",
t.topic(), partition, prev_max_bytes, fetch_state.max_bytes);
}
} else {
debug!("no data received for {}:{} (max_bytes: {} / fetch_offset: {} / highwatermark_offset: {})",
t.topic(), partition, fetch_state.max_bytes, fetch_state.offset, data.highwatermark_offset());
if fetch_state.offset < data.highwatermark_offset() {
if fetch_state.max_bytes < self.config.retry_max_bytes_limit {
let prev_max_bytes = fetch_state.max_bytes;
let incr_max_bytes = prev_max_bytes + prev_max_bytes;
if incr_max_bytes > self.config.retry_max_bytes_limit {
fetch_state.max_bytes = self.config.retry_max_bytes_limit;
} else {
fetch_state.max_bytes = incr_max_bytes;
}
debug!("increased max_bytes for {}:{} from {} to {}",
t.topic(), partition, prev_max_bytes, fetch_state.max_bytes);
} else if num_partitions_queried == 1 {
return Err(Error::Kafka(KafkaCode::MessageSizeTooLarge));
}
if !single_partition_consumer {
debug!("rescheduled for retry: {}:{}", t.topic(), partition);
retry_partitions.push_back(partition)
}
}
}
}
}
}
Ok(MessageSets{ responses: resps, empty: empty })
}
pub fn last_consumed_message(&self, partition: i32) -> Option<i64> {
self.state.consumed_offsets.get(&partition).cloned()
}
pub fn consume_message(&mut self, partition: i32, offset: i64) {
match self.state.consumed_offsets.entry(partition) {
Entry::Vacant(v) => {
v.insert(offset);
self.state.consumed_offsets_dirty = true;
}
Entry::Occupied(mut v) => {
let o = v.get_mut();
if offset > *o {
*o = offset;
self.state.consumed_offsets_dirty = true;
}
}
}
}
pub fn consume_messageset<'a>(&mut self, msgs: MessageSet<'a>) {
debug_assert_eq!(msgs.topic, self.config.topic);
if !msgs.messages.is_empty() {
self.consume_message(
msgs.partition, msgs.messages.last().unwrap().offset);
}
}
pub fn commit_consumed(&mut self) -> Result<()> {
if !self.state.consumed_offsets_dirty {
debug!("no new consumed offsets to commit.");
return Ok(());
}
let client = &mut self.client;
let (group, topic) = (&self.config.group, &self.config.topic);
let offsets = &self.state.consumed_offsets;
debug!("commiting consumed offsets (topic: {} / group: {} / offsets: {:?}",
topic, group, offsets);
try!(client.commit_offsets(
group,
offsets.iter().map(|(&p, &o)| CommitOffset::new(topic, p, o))));
self.state.consumed_offsets_dirty = false;
Ok(())
}
}
impl State {
fn new(client: &mut KafkaClient, config: &Config) -> Result<State> {
let partitions = try!(determine_partitions(config, client.topics()));
let consumed_offsets = try!(load_consumed_offsets(config, client, &partitions));
let fetch_offsets = try!(load_fetch_states(config, client, &partitions, &consumed_offsets));
Ok(State {
fetch_offsets: fetch_offsets,
retry_partitions: VecDeque::new(),
consumed_offsets: consumed_offsets,
consumed_offsets_dirty: false,
})
}
}
fn determine_partitions(config: &Config, metadata: Topics) -> Result<Vec<i32>> {
let avail_partitions = match metadata.partitions(&config.topic) {
None => {
debug!("no such topic: {} (all metadata: {:?})", config.topic, metadata);
return Err(Error::Kafka(KafkaCode::UnknownTopicOrPartition));
}
Some(tp) => tp,
};
let ps = if config.partitions.is_empty() {
avail_partitions.iter().map(|p| p.id()).collect()
} else {
let mut ps: Vec<i32> = Vec::with_capacity(config.partitions.len());
for &p in config.partitions.iter() {
match avail_partitions.partition(p) {
None => {
debug!("no such partition: {} (all metadata for {}: {:?})",
p, config.topic, avail_partitions);
return Err(Error::Kafka(KafkaCode::UnknownTopicOrPartition));
}
Some(_) => ps.push(p),
};
}
ps.sort();
ps.dedup();
ps
};
Ok(ps)
}
fn load_consumed_offsets(config: &Config, client: &mut KafkaClient, partitions: &[i32])
-> Result<HashMap<i32, i64>>
{
assert!(!partitions.is_empty());
let topic = &config.topic;
let tpos = try!(client.fetch_group_offsets(
&config.group,
partitions.iter().map(|&p_id | FetchGroupOffset::new(topic, p_id))));
let mut offs = HashMap::with_capacity(partitions.len());
for tpo in tpos {
match tpo.offset {
Ok(o) if o != -1 => {
offs.insert(tpo.partition, o);
}
_ => {}
}
}
Ok(offs)
}
fn load_fetch_states(config: &Config,
client: &mut KafkaClient,
partitions: &[i32],
consumed_offsets: &HashMap<i32, i64>)
-> Result<HashMap<i32, FetchState>>
{
fn load_partition_offsets(client: &mut KafkaClient, topic: &str, offset: FetchOffset)
-> Result<HashMap<i32, i64>>
{
let offs = try!(client.fetch_topic_offsets(topic, offset));
let mut m = HashMap::with_capacity(offs.len());
for off in offs {
m.insert(off.partition, off.offset.unwrap_or(-1));
}
Ok(m)
}
let max_bytes = client.fetch_max_bytes_per_partition();
let latest = try!(load_partition_offsets(client, &config.topic, FetchOffset::Latest));
let earliest = try!(load_partition_offsets(client, &config.topic, FetchOffset::Earliest));
let mut fetch_offsets = HashMap::new();
for p in partitions {
let (&l_off, &e_off) = (latest.get(p).unwrap_or(&-1), earliest.get(p).unwrap_or(&-1));
let offset = match consumed_offsets.get(p) {
Some(&co) if co >= e_off && co < l_off => co + 1,
_ => {
match config.fallback_offset {
Some(FetchOffset::Latest) => l_off,
Some(FetchOffset::Earliest) => e_off,
_ => {
debug!("cannot determine fetch offset (group: {} / topic: {} / partition: {})",
&config.group, &config.topic, p);
return Err(Error::Kafka(KafkaCode::Unknown));
}
}
}
};
fetch_offsets.insert(*p, FetchState{ offset: offset, max_bytes: max_bytes });
}
Ok(fetch_offsets)
}
pub struct MessageSets {
responses: Vec<fetch::Response>,
empty: bool
}
impl MessageSets {
pub fn is_empty(&self) -> bool {
self.empty
}
pub fn iter(&self) -> MessageSetsIter {
let mut responses = self.responses.iter();
let mut topics = responses.next().map(|r| r.topics().iter());
let (curr_topic, partitions) =
topics.as_mut()
.and_then(|t| t.next())
.map_or((None, None), |t| (Some(t.topic()), Some(t.partitions().iter())));
MessageSetsIter {
responses: responses,
topics: topics,
curr_topic: curr_topic.unwrap_or(""),
partitions: partitions,
}
}
}
pub struct MessageSet<'a> {
topic: &'a str,
partition: i32,
messages: &'a [Message<'a>],
}
impl<'a> MessageSet<'a> {
#[inline]
pub fn topic(&self) -> &'a str {
self.topic
}
#[inline]
pub fn partition(&self) -> i32 {
self.partition
}
#[inline]
pub fn messages(&self) -> &'a [Message<'a>] {
self.messages
}
}
pub struct MessageSetsIter<'a> {
responses: slice::Iter<'a, fetch::Response>,
topics: Option<slice::Iter<'a, fetch::Topic<'a>>>,
curr_topic: &'a str,
partitions: Option<slice::Iter<'a, fetch::Partition<'a>>>,
}
impl<'a> Iterator for MessageSetsIter<'a> {
type Item = MessageSet<'a>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if let Some(p) = self.partitions.as_mut().and_then(|p| p.next()) {
match p.data() {
&Err(_) => {
continue;
}
&Ok(ref pdata) => {
let msgs = pdata.messages();
if msgs.is_empty() {
continue;
} else {
return Some(MessageSet {
topic: self.curr_topic,
partition: p.partition(),
messages: msgs,
});
}
}
}
}
if let Some(t) = self.topics.as_mut().and_then(|t| t.next()) {
self.curr_topic = t.topic();
self.partitions = Some(t.partitions().iter());
continue;
}
if let Some(r) = self.responses.next() {
self.curr_topic = "";
self.topics = Some(r.topics().iter());
continue;
}
return None;
}
}
}
#[derive(Debug)]
pub struct Builder {
client: Option<KafkaClient>,
hosts: Vec<String>,
group: String,
topic: String,
partitions: Vec<i32>,
fallback_offset: Option<FetchOffset>,
fetch_max_wait_time: i32,
fetch_min_bytes: i32,
fetch_max_bytes_per_partition: i32,
retry_max_bytes_limit: i32,
}
impl Builder {
fn new(client: Option<KafkaClient>, hosts: Vec<String>) -> Builder {
let mut b = Builder {
client: client,
hosts: hosts,
fetch_max_wait_time: client::DEFAULT_FETCH_MAX_WAIT_TIME,
fetch_min_bytes: client::DEFAULT_FETCH_MIN_BYTES,
fetch_max_bytes_per_partition: client::DEFAULT_FETCH_MAX_BYTES_PER_PARTITION,
retry_max_bytes_limit: DEFAULT_RETRY_MAX_BYTES_LIMIT,
group: "".to_owned(),
topic: "".to_owned(),
partitions: Vec::new(),
fallback_offset: None,
};
if let Some(ref c) = b.client {
b.fetch_max_wait_time = c.fetch_max_wait_time();
b.fetch_min_bytes = c.fetch_min_bytes();
b.fetch_max_bytes_per_partition = c.fetch_max_bytes_per_partition();
}
b
}
fn with_group(mut self, group: String) -> Builder {
self.group = group;
self
}
fn with_topic(mut self, topic: String) -> Builder {
self.topic = topic;
self
}
pub fn with_partitions(mut self, partitions: &[i32]) -> Builder {
self.partitions.extend_from_slice(partitions);
self
}
pub fn with_fallback_offset(mut self, fallback_offset_time: FetchOffset) -> Builder {
self.fallback_offset = Some(fallback_offset_time);
self
}
pub fn with_fetch_max_wait_time(mut self, max_wait_time: i32) -> Builder {
self.fetch_max_wait_time = max_wait_time;
self
}
pub fn with_fetch_min_bytes(mut self, min_bytes: i32) -> Builder {
self.fetch_min_bytes = min_bytes;
self
}
pub fn with_fetch_max_bytes_per_partition(mut self, max_bytes_per_partition: i32) -> Builder {
self.fetch_max_bytes_per_partition = max_bytes_per_partition;
self
}
pub fn with_retry_max_bytes_limit(mut self, limit: i32) -> Builder {
self.retry_max_bytes_limit = limit;
self
}
pub fn create(self) -> Result<Consumer> {
let mut client = match self.client {
Some(client) => client,
None => {
let mut client = KafkaClient::new(self.hosts);
try!(client.load_metadata_all());
client
}
};
client.set_fetch_max_wait_time(self.fetch_max_wait_time);
client.set_fetch_min_bytes(self.fetch_min_bytes);
client.set_fetch_max_bytes_per_partition(self.fetch_max_bytes_per_partition);
let config = Config {
group: self.group,
topic: self.topic,
partitions: self.partitions,
fallback_offset: self.fallback_offset,
retry_max_bytes_limit: self.retry_max_bytes_limit,
};
let state = try!(State::new(&mut client, &config));
debug!("initialized: (topic: {} / group: {} / state: {:?})",
config.topic, config.group, state);
Ok(Consumer{ client: client, state: state, config: config })
}
}