Welcome back. In our previous lesson, we implemented a robust producer-consumer system using RabbitMQ, focusing on its "smart broker" model for distributing discrete tasks. You learned how publisher confirms and manual acknowledgments are crucial for building resilient, decoupled services.
Today, we shift our focus to Apache Kafka, the other major player in the asynchronous messaging space. We'll implement the same producer-consumer pattern, again using Go, but this time against Kafka's distributed log. The goal is to give you direct, hands-on experience with both systems. This will solidify your understanding of their fundamental differences and equip you to make informed architectural decisions—a critical skill for designing the scalable systems you're aiming to build.
This lesson will address how Kafka's log-centric design, where consumers pull data and manage their own progress via offsets, results in a different programming model and a different set of trade-offs compared to RabbitMQ.
1. The Kafka Model: A Quick Refresher
Before we dive into code, let's revisit the core components of Kafka from a developer's perspective. The Kcode channel's video provides a great, concise overview of the terminology.
Building Kafka Producer & Consumer with Go (Part 1)
This video, "Go Kafka: Building Producer & Consumer with Go", starts with a clear, animated explanation of Kafka's core concepts.
Watch the introductory segment from the high-level overview. As you watch, focus on understanding these key terms: Producer & Consumer: The client applications. Note the "dumb producer, smart consumer" philosophy. Broker: A single Kafka server. A cluster is composed of multiple brokers. Topic: A named stream of records, analogous to a table in a database. Offset: A unique, sequential ID for each message within a topic partition. This is the "smart" part of the consumer—it tracks which offset it has processed up to.
The key takeaway is that consumers are in control. They "pull" messages from the broker and are responsible for tracking their position (offset) in the log. This is a fundamental shift from the RabbitMQ model, where the broker "pushes" messages to consumers.

2. Setting Up Your Kafka Environment
Just like with RabbitMQ, the simplest way to get a Kafka instance running for local development is with Docker. We will use the segmentio/kafka-go library, a popular pure-Go client that doesn't require any C library dependencies (CGO).
The following article provides a modern Docker Compose setup that uses KRaft mode, which eliminates the need for a separate Zookeeper instance.
How to Use Kafka in Go with segmentio/kafka-go
This article from OneUptime provides an excellent setup guide and code examples using the segmentio/kafka-go library.
Focus on the Installation and Setup section. Install the library into your Go project. Use the Docker Compose file provided to start a local Kafka cluster. You can save this as docker-compose.yml and run docker-compose up -d.
With your Kafka broker running and the library installed, you're ready to write some code.
3. Building the Kafka Producer
In segmentio/kafka-go, the high-level API for producing messages is the Writer. It efficiently handles connection management, batching, and routing messages to the correct topic partitions.
Let's look at a basic implementation.
How to Use Kafka in Go with segmentio/kafka-go
The "Basic Producer Implementation" section in the OneUptime article gives a clean and complete example.
Read through the code under the heading "Simple Producer with Writer". Pay close attention to these parts: &kafka.Writer{...}: The configuration struct for the producer. You provide the broker address (Addr) and the target Topic. RequiredAcks: kafka.RequireAll: This is the Kafka equivalent of RabbitMQ's publisher confirms. It dictates the level of acknowledgment the producer requires from the brokers before considering a write successful. RequireAll is the most durable setting. writer.WriteMessages(ctx, messages...): The method used to send one or more messages. The library handles batching them under the hood for efficiency.
This producer is synchronous (Async: false), meaning WriteMessages will block until the required acknowledgments are received. This is conceptually similar to the confirmation.WaitContext(ctx) call in our RabbitMQ producer.
4. Building the Kafka Consumer and Consumer Groups
On the consumer side, things are more interesting. While you can consume from a specific topic partition, the real power of Kafka for scalability lies in Consumer Groups.
A consumer group is a set of consumers that cooperate to consume data from a topic. Kafka automatically distributes the topic's partitions among the members of the group. If a new consumer joins, Kafka rebalances the partitions. If a consumer leaves (or crashes), its partitions are assigned to the remaining members. This is Kafka's mechanism for horizontal scaling and fault tolerance on the consumer side.
How to Use Kafka in Go with segmentio/kafka-go
Now, let's study the consumer group implementation from the same OneUptime article.
Read the section "Consumer Groups for Scalability" carefully. This is a production-grade example that includes graceful shutdown. Focus on: kafka.NewReader(kafka.ReaderConfig{...}): The consumer configuration. GroupID: groupID: This is the most important setting. All consumers that share the same GroupID will be part of the same group. reader.FetchMessage(ctx): This blocks until a message is fetched from the broker. This is the "pull" model in action. reader.CommitMessages(ctx, msg): This is the Kafka equivalent of delivery.Ack() in RabbitMQ. It tells the broker that this consumer group has successfully processed this message, and its offset should be updated. If the consumer crashes before this, the message will be re-read by another consumer in the group after a rebalance.
The flow of FetchMessage -> Process -> CommitMessages is the core loop for reliable message processing in Kafka, directly paralleling the manual acknowledgment flow you implemented with RabbitMQ.
5. Tuning for Performance and Reliability
The default settings for Kafka clients are optimized for high throughput, often at the expense of latency. This means the clients will buffer messages on both the producer and consumer side to create larger batches, reducing network overhead. The sohamkamani.com article provides an excellent breakdown of these tuning parameters.
Implementing a Kafka Producer and Consumer In Golang (With Full ...
This article provides clear explanations of key configuration options for both producers and consumers.
Read the section Tuning Kafka Client Configuration. You don't need to implement these right now, but it's crucial to understand what these levers do: Consumer: Minimum Buffered Bytes (MinBytes): Controls how much data the broker should wait for before sending it to a consumer. Max Wait Time (MaxWait): The maximum time the consumer will wait, preventing messages from being stuck indefinitely if MinBytes isn't met. Start Offset (StartOffset): Determines if a new consumer group starts from the FirstOffset (beginning) or LastOffset (end) of a topic. Producer: Message Batching (BatchSize, BatchTimeout): Controls how many messages (or for how long) the producer buffers before sending a batch. Required Acknowledgements (RequiredAcks): As discussed, this controls durability. The article explains the different levels (-1, 1, 0).
Understanding these parameters is key to moving from a basic implementation to one that is tailored for the specific needs of your application, whether it prioritizes low latency, high throughput, or maximum durability.
To see all the pieces working together, the Kcode video includes a full debugging and execution session.
Building Kafka Producer & Consumer with Go (Part 1)
The final segment of the video shows the producer and consumer applications running, with messages being sent, delivery being confirmed, and messages being received by the consumer.
Watch the live demo from "now we are producing". You'll see the logs showing message was delivered from the producer's confirmation handler and received message from the consumer, demonstrating the end-to-end flow.
Conclusion
In this lesson, you've built a foundational Kafka producer and consumer in Go, providing a direct point of comparison to your earlier work with RabbitMQ. You now have practical experience with the two dominant technologies for asynchronous communication in modern backend systems.
Key Takeaways:
- Kafka producers and consumers are implemented in Go using a client library like
segmentio/kafka-go, withWriterandReaderobjects. - Consumer Groups are Kafka's core mechanism for providing scalability and fault tolerance to consumers.
- Consumers pull messages from brokers and track their progress using offsets. Committing an offset is the equivalent of acknowledging a message.
- The developer experience is shaped by Kafka's log-based architecture, emphasizing batching and consumer-side logic.
- Configuration parameters like
RequiredAcks,BatchSize, andStartOffsetare critical levers for tuning the trade-offs between throughput, latency, and durability.
In our next lesson, we will formalize the reliability concepts you've encountered. We will dive deep into message delivery semantics—at-most-once, at-least-once, and exactly-once—and see how the configurations you've just learned about are used to achieve these guarantees in both RabbitMQ and Kafka.