Apache Kafka is a distributed stream processing middleware.
Data. Data streamed in topics
Each data record is a key value pair
Timestamps and additional headers supported
Topics split into partitions
Each data record stored in one partition
Record addressed by offset within partition
Configurable assignment of records to partitions
Brokers. Replicated broker cluster
Each broker stores data logs of some topic partitions
Data log retention period configurable
Partition replication configurable
Leader follower architecture
Topic access done on leader broker
Leader election in case of leader failure
Producer may require minimum number of in sync replicas
Clients. Producers
Can batch records when so configured
Can guarantee exactly once delivery semantics
Can wait for confirmation from zero, one or all in sync brokers
Consumers
Each consumer maintains own topic position
Consumer groups split topic partitions among themselves
Can update topic position together with output in transaction
Stream processors
public class KafkaProducer <K,V> implements Producer <K,V> { public KafkaProducer (Properties properties) { ... } public Future <RecordMetadata> send (ProducerRecord <K,V> record) { ... } public Future <RecordMetadata> send (ProducerRecord <K,V> record, Callback callback) { ... } public void flush () { ... } public void close () { ... } public void initTransactions () { ... } public void beginTransaction () { ... } public void abortTransaction () { ... } public void commitTransaction () { ... } // Introspection. public List <PartitionInfo> partitionsFor (String topic) { ... } ... } public class ProducerRecord <K,V> { public ProducerRecord (String topic, V value) { ... } public ProducerRecord (String topic, K key, V value) { ... } public ProducerRecord ( String topic, Integer partition, K key, V value) { ... } public ProducerRecord ( String topic, Integer partition, K key, V value, Iterable <Header> headers) { ... } public ProducerRecord ( String topic, Integer partition, Long timestamp, K key, V value, Iterable <Header> headers) { ... } public K key () { ... } public V value () { ... } public Headers headers () { ... } ... } public interface Header { String key (); byte [] value (); } public final class RecordMetadata { public boolean hasOffset () { ... } public long offset () { ... } public boolean hasTimestamp () { ... } public long timestamp () { ... } public String topic () { ... } public int partition () { ... } public int serializedKeySize () { ... } public int serializedValueSize () { ... } } public interface Callback { void onCompletion (RecordMetadata metadata, Exception exception); }
public class KafkaConsumer <K,V> implements Consumer <K,V> { public KafkaConsumer (Properties properties) { ... } // Statically assigned topics and partitions. public void assign (Collection <TopicPartition> partitions) { ... } public Set <TopicPartition> assignment () { ... } // Specific topics with dynamically assigned partitions. public void subscribe (Collection <String> topics) { ... } public void subscribe (Collection <String> topics, ConsumerRebalanceListener listener) { ... } // Regular expression topics with dynamically assigned partitions. public void subscribe (Pattern pattern) { ... } public void subscribe (Pattern pattern, ConsumerRebalanceListener listener) { ... } public void unsubscribe() { ... } public Set <String> subscription () { ... } // Poll for records. public ConsumerRecords <K,V> poll (final Duration timeout) { ... } // Seek and query position in topic partitions. public void seek (TopicPartition partition, long offset) { ... } public void seek (TopicPartition partition, OffsetAndMetadata offsetAndMetadata) { ... } public void seekToEnd (Collection <TopicPartition> partitions) { ... } public void seekToBeginning (Collection <TopicPartition> partitions) { ... } public long position (TopicPartition partition) { ... } public long position (TopicPartition partition, final Duration timeout) { ... } // Set and query committed position in topic partitions. public void commitSync () { ... } public void commitSync (Duration timeout) { ... } public void commitSync (final Map <TopicPartition, OffsetAndMetadata> offsets) { ... } public void commitSync (final Map <TopicPartition, OffsetAndMetadata> offsets, final Duration timeout) { ... } public void commitAsync () { ... } public void commitAsync (OffsetCommitCallback callback) { ... } public OffsetAndMetadata committed (TopicPartition partition) { ... } public OffsetAndMetadata committed (TopicPartition partition, final Duration timeout) { ... } public void pause (Collection<TopicPartition> partitions) { ... } public void resume (Collection<TopicPartition> partitions) { ... } public void close () { ... } // Introspection. public Map <String, List <PartitionInfo>> listTopics () { ... } public List <PartitionInfo> partitionsFor (String topic) { ... } ... } public class ConsumerRecord <K,V> { public String topic () { ... } public int partition () { ... } public long offset () { ... } public long timestamp () { ... } public K key () { ... } public V value () { ... } public Headers headers () { ... } public int serializedKeySize () { ... } public int serializedValueSize () { ... } ... }
By default, the consumer position is committed periodically,
as directed by the enable.auto.commit
and auto.commit.interval.ms
configuration settings.
Explicit position commit is also supported.
To achieve atomic message processing, the consumer position information is stored inside an internal system topic. Consumer position updates are thus in fact message publishing operations. A client whose inputs and outputs are messages can wrap the consumer position updates and the message publishing operations in a transaction.
Stream processors are arranged in a processor topology that is replicated for parallel processing of stream partitions. The replication can use a combination of multiple client processes and multiple threads within a client process.
public interface KStream <K,V> { // Filter stream by predicate. KStream <K,V> filter (Predicate <? super K, ? super V> predicate); KStream <K,V> filterNot (Predicate <? super K, ? super V> predicate); // Replace key with new key. <KR> KStream <KR,V> selectKey (KeyValueMapper <? super K, ? super V, ? extends KR> mapper); // Map entry to new entry. <KR,VR> KStream <KR,VR> map (KeyValueMapper < ? super K, ? super V, ? extends KeyValue <? extends KR, ? extends VR>> mapper); // Map value to new value. <VR> KStream <K,VR> mapValues (ValueMapper <? super V, ? extends VR> mapper); // Map entry to multiple new entries. <KR,VR> KStream <KR,VR> flatMap (KeyValueMapper < ? super K, ? super V, ? extends Iterable <? extends KeyValue <? extends KR, ? extends VR>> mapper); // Map value to multiple new values. <VR> KStream <K,VR> flatMapValues (ValueMapper <? super V, ? extends Iterable <? extends VR>> mapper); // Print entries. void print (Printed <K, V> printed); // Consume or peek at entries with action. void foreach (ForeachAction <? super K, ? super V> action); KStream <K,V> peek (ForeachAction <? super K, ? super V> action); // Split by predicate or merge a stream. KStream <K,V> [] branch (Predicate <? super K, ? super V> ... predicates); KStream <K,V> merge (KStream <K,V> stream); // Materialize a stream into a topic. KStream <K,V> through (String topic); void to (String topic); // Process a stream using a stateful processor. <KO,VO> KStream <KO,VO> process ( ProcessorSupplier <? super K, ? super V, KO, VO> processorSupplier, String... stateStoreNames); <VO> KStream <K,VO> processValues ( FixedKeyProcessorSupplier <? super K, ? super V, VO> processorSupplier, String... stateStoreNames); // Group entries in a stream. KGroupedStream <K,V> groupByKey (); <KR> KGroupedStream <KR,V> groupBy (KeyValueMapper <? super K, ? super V, KR> selector); // Join stream with another stream or table on key. // Operation on streams limited by join window. <VO,VR> KStream <K,VR> join ( KStream <K, VO> otherStream, ValueJoiner <? super V, ? super VO, ? extends VR> joiner, JoinWindows windows); <VO,VR> KStream <K,VR> leftJoin ( KStream <K, VO> otherStream, ValueJoiner <? super V, ? super VO, ? extends VR> joiner, JoinWindows windows); <VO,VR> KStream <K,VR> outerJoin ( KStream <K,VO> otherStream, ValueJoiner <? super V, ? super VO, ? extends VR> joiner, JoinWindows windows); <VT,VR> KStream <K,VR> join ( KTable <K,VT> table, ValueJoiner <? super V, ? super VT, ? extends VR> joiner, Joined <K, V, VT> joined); <VT,VR> KStream <K,VR> leftJoin ( KTable <K,VT> table, ValueJoiner <? super V, ? super VT, ? extends VR> joiner); ... }
public interface KGroupedStream <K,V> { KTable <K,Long> count (); KTable <K,V> reduce (Reducer <V> reducer); <VR> KTable <K,VR> aggregate ( Initializer <VR> initializer, Aggregator <? super K, ? super V, VR> aggregator); <W extends Window> TimeWindowedKStream <K,V> windowedBy (Windows<W> windows); ... }
public interface Processor <KIn, VIn, KOut, VOut> { // Lifecycle. default void init (final ProcessorContext <KOut, VOut> context) {} default void close () {} // Process individual records. void process (Record<KIn, VIn> record); } public interface ProcessorContext <KForward, VForward> extends ProcessingContext { // Forward record to all child processors. <K extends KForward, V extends VForward> void forward (Record <K, V> record); // Forward record to specified child processor. <K extends KForward, V extends VForward> void forward (Record <K, V> record, final String childName); }
public interface ReadOnlyKeyValueStore <K, V> { V get (K key); KeyValueIterator <K, V> range (K from, K to); default KeyValueIterator <K, V> reverseRange (K from, K to) { ... } KeyValueIterator <K, V> all (); default KeyValueIterator <K, V> reverseAll () { ... } default <PS extends Serializer <P>, P> KeyValueIterator <K, V> prefixScan (P prefix, PS prefixKeySerializer) { ... } long approximateNumEntries (); } public interface KeyValueStore <K, V> extends StateStore, ReadOnlyKeyValueStore <K, V> { void put (K key, V value); V putIfAbsent (K key, V value); void putAll (List <KeyValue <K, V>> entries); V delete (K key); }
persistent or transient store
can be backed by topic for fault tolerance
Correct stream processing requires that each partition is assigned to single client only. Client churn is handled by dynamically balancing partitions between clients.
In the KIP-848 version of the balancing protocol, the group coordinator computes a new partition assignment every time the group membership metadata changes. For each partition whose assignment has changed from one client to another, the coordinator first informs the former client about partition revocation, and, once the former client confirms, the latter client about partition assignment. The protocol therefore only disrupts those clients whose assignment has changed.
The Apache Kafka Project Home Page. https://kafka.apache.org
KIP-500: Replace ZooKeeper with a Self-Managed Metadata Quorum. https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum
KIP-848: The Next Generation Of The Consumer Rebalance Protocol. https://cwiki.apache.org/confluence/display/KAFKA/KIP-848%3A+The+Next+Generation+of+the+Consumer+Rebalance+Protocol