From ccfc94a041ec427ca4c470a05a98dd6abbbf13e7 Mon Sep 17 00:00:00 2001 From: farhan Date: Mon, 20 Nov 2023 15:54:30 +0700 Subject: [PATCH] feat: add page apache kafka golang --- content/C-apache-kafka.md | 293 +++++++++++++++++++++ content/images/C-apache-kafka_consumer.png | Bin 0 -> 10461 bytes content/images/C-apache-kafka_producer.png | Bin 0 -> 4447 bytes 3 files changed, 293 insertions(+) create mode 100644 content/C-apache-kafka.md create mode 100644 content/images/C-apache-kafka_consumer.png create mode 100644 content/images/C-apache-kafka_producer.png diff --git a/content/C-apache-kafka.md b/content/C-apache-kafka.md new file mode 100644 index 000000000..beeea1922 --- /dev/null +++ b/content/C-apache-kafka.md @@ -0,0 +1,293 @@ +# C.39 Message Broker Apache Kafka +Pada chapter kali ini kita akan belajar bagaimana cara menggunakan apache kafka di golang secara sederhana, dari membuat koneksi apache kafka, membuat service worker consumer, dan cara komunikasi antara worker dan consumer. +## C.39.1 Apa itu Apache Kafka ? +Apache kafka adalah sebuah platform publish-subscribe streaming yang digunakan untuk mengatasi masalah pengolahan data secara real-time. Pada dasarnya Kafka bertujuan untuk mengirim pesan dari producer ke consumer yang akan diterima pesan dari producer tersebut. + +Kelebihanan Kafka daripada platform lain seperti RabbitMQ adalah kecepatannya dalam mengirim data. Di dalam Kafka, setiap pesan hanya bisa ditambahkan saja jadi saat pesan sudah dikirim tidak dapat di edit atau dihapus. Selain itu juga, kelebihan dari Kafka menawarkan durabilitas, dan memiliki fitur toleransi kesalahan. Ini juga membantu untuk memastikan data stream secara skala yang besar dapat di manajemen secara efisien dan dengan latensi yang kecil. + +## C.39.2 Persiapan +Kita siapkan dulu apache kafka di lokal komputer kita, untuk instalasi Kafka kalian juga bisa langsung install di sistem operasi komputer masing-masing.Namun dalam praktek ini kita menggunakan docker untuk install apache kafka. +### • Instalasi Apache Kafka +Jalankan command ini untuk mengambil dockerfile Kafka: +``` +curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-kafka/master/docker-compose.yml > docker-compose.yml +``` +Setelah dockerfile sudah ditambahkan, kita akan mengubah salah satu code ini. + +Sebelum: +``` +KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://:9092 +``` +Setelah: +``` +KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092 +``` +Jalankan command ini untuk membuat kontainer baru dan menjalankan apache kafka: +```bash +docker-compose up -d +``` + +## C.39.3 Praktek +Apache kafka sudah berjalan di komputer kita, setelah itu kita buat struktur folder seperti berikut ini: +```bash +. +│ # Folder connection berisi file konfigurasi dari Apache Kafka. +├── connection +│ └── ... +│ # Folder consumer berisi proses aplikasi digunakan untuk subcription ke satu atau lebih topik dan mengolah data-data dari topik tersebut. +├── consumer +│ └── ... +│ # Folder producer berisi proses atau sistem yang dapat mempublikasikan data ke suatu topik. +├── producer +│ └── ... +│ # File berisi depedencies seperti library kafka. +└── go.mod +``` +Setelah struktur sudah dibuat, kita akan mengunduh library Apache Kafka golang. +``` +go get github.com/IBM/sarama +``` +### C.39.3.1 Koneksi Consumer dan Worker +Kita membuat 1 file bernama ```connection.go```, didalam file ini berisi konfigurasi koneksi untuk apache kafka. Tambahkan 1 fungsi koneksi Consumer di folder ```connection``` yang berfungsi untuk menerima data store(```Topic```) dari worker. +- #### File connection.go +```go +package connection + +import "github.com/IBM/sarama" + +func ConnectToConsumer(brokersUrl []string) (sarama.Consumer, error) { + config := sarama.NewConfig() + config.Consumer.Return.Errors = true + + conn, err := sarama.NewConsumer(brokersUrl, config) + if err != nil { + return nil, err + } + return conn, nil +} + +func ConnectToProducer(urls []string) (sarama.SyncProducer, error) { + config := sarama.NewConfig() + config.Producer.Return.Successes = true + config.Producer.RequiredAcks = sarama.WaitForAll + config.Producer.Retry.Max = 6 + + conn, err := sarama.NewSyncProducer(urls, config) + if err != nil { + return nil, err + } + + return conn, err +} +``` +Seperti yang terlihat di atas, kita menambahkan 1 fungsi lagi untuk koneksi Worker digunakan mengirimkan sebuah ``` Topic ``` yang berisi data ke Consumer. +### C.39.3.2 Mempersiapkan Consumer Kafka +Sekarang kita membuat Consumer, buat 2 file ```consumer.go``` dan ```main.go``` didalam folder ```consumer```. +Tambahkan code seperti dibawah ini: +- #### File consumer.go +```go +package main + +import ( + "tutorial-go-kafka/connection" + + "github.com/IBM/sarama" +) + +func PullFromProducer(topic string) (sarama.Consumer, sarama.PartitionConsumer) { + + worker, err := connection.ConnectToConsumer([]string{"localhost:9092"}) + if err != nil { + panic(err) + } + + consumer, err := worker.ConsumePartition(topic, 0, sarama.OffsetOldest) + if err != nil { + panic(err) + } + + return worker, consumer +} +``` +File ```consumer.go``` secara sederhana berisi code untuk melakukan konsumsi data Message dari Worker. Di file ini juga kita menambahkan Topic yang bernama ```Pizza```, Topic ini nanti akan kita gunakan untuk menerima message berupa data. +pada code dibawah ini, berfungsi untuk meng-konsumsi data message dengan parameter nama Topic, partisi, dan offset consumer. +```go +consumer, err := worker.ConsumePartition(topic, 0, sarama.OffsetOldest) +``` +- #### File main.go +Buat main program untuk Consumer seperti dibawah ini: +```go +package main + +import ( + "fmt" + "log" + "os" + "os/signal" + "syscall" +) + +func main() { + // Nama Topic + topic := "Pizza" + + worker, consumer := PullFromProducer(topic) + + log.Println("Consumer started") + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + messageCount := 0 + + doneChan := make(chan struct{}) + go func() { + for { + select { + case err := <-consumer.Errors(): + fmt.Println(err) + case msg := <-consumer.Messages(): + messageCount++ + fmt.Printf("Received message Count %d: | Topic(%s) | Message(%s) \n", messageCount, string(msg.Topic), string(msg.Value)) + case <-sigChan: + fmt.Println("Interrupt is detected") + doneChan <- struct{}{} + } + } + }() + + <-doneChan + fmt.Println("Processed", messageCount, "messages") + + if err := worker.Close(); err != nil { + panic(err) + } +} +``` +Pada file ini berisi main program untuk menjalankan Consumer. Nantinya apabila ada data Message dikirim, maka code dibawah ini akan tampil log berisi total data Message dan data seperti apa yang dikirim. +```go +fmt.Printf("Received message Count %d: | Topic(%s) | Message(%s) \n", messageCount, string(msg.Topic), string(msg.Value)) + ``` + +### C.39.3.3 Mempersiapkan Worker Kafka +Kita buat 2 file ```producer.go``` dan ```main.go``` didalam folder ```producer```. Nantinya pada main program Worker akan bertindak sebagai pengirim data Message. +- #### File producer.go +```go +package main + +import ( + "fmt" + "tutorial-go-kafka/connection" + + "github.com/IBM/sarama" +) + +func PushPizzaQueue(topic string, message []byte) error { + urls := []string{"localhost:9092"} + producer, err := connection.ConnectToProducer(urls) + if err != nil { + return err + } + + defer producer.Close() + + msg := sarama.ProducerMessage{ + Topic: topic, + Value: sarama.StringEncoder(message), + } + + partition, offset, err := producer.SendMessage(&msg) + if err != nil { + return err + } + + fmt.Printf("Message is stored in topic(%s)/partition(%d)/offset(%d)\n", topic, partition, offset) + + return nil +} +``` +Pada file diatas code hampir sama dengan file ```consumer.go``` namun hanya saja disini berisi fungsi untuk mengirimkan data Message. +```go +msg := sarama.ProducerMessage{ + Topic: topic, + Value: sarama.StringEncoder(message), +} + +partition, offset, err := producer.SendMessage(&msg) +if err != nil { + return err +} +``` +Barisan code diatas berisi mengirimkan data dan juga mengirimkan sebuah Topic yang bernama ```Pizza```. Parameter message data menggunakan tipe data ```[]byte```. Pada praktek ini kita akan mengirimkan data berupa json. +- #### File main.go +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" +) + +type Pizza struct { + Slice int `form:"slice" json:"slice"` + Price int `form:"price" json:"price"` +} + +func main() { + http.HandleFunc("/sendpizza", func(w http.ResponseWriter, r *http.Request) { + pizza := new(Pizza) + dec := json.NewDecoder(r.Body) + + err := dec.Decode(&pizza) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Println("[ERROR]:" + err.Error()) + } + pizzaBytes, err := json.Marshal(pizza) + err = PushPizzaQueue("Pizza", pizzaBytes) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + log.Println("[ERROR]:" + err.Error()) + } + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(pizza) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + fmt.Println("server started at localhost:9000") + http.ListenAndServe(":9000", nil) +} +``` +Pada main program Worker kita membuat sebuah Http Server dengan url ```/sendpizza```. Url ini berisi request data berupa json dan dari data tersebut akan dikirimkan ke server apache Kafka local lalu akan diterima oleh Consumer. +## C.39.4 Testing Aplikasi +Jalankan service Consumer dan Worker di folder ```consumer``` dan ```producer``` sudah dibuat dengan command berikut: +``` +go run . +``` +Kita akan melakukan testing mengirim 1 data Message dari Worker dengan menembak api ```/sendpizza```. Buat 1 request payload dengan text dan value seperti dibawah ini: +```json +{ + "slice": 12, + "price": 1000 +} +``` +Maka apabila berhasil maka service Producer akan muncul log seperti ini: +![](images/producer.png) +Gambar diatas menandakan bahwa message sudah dikirim dengan 3 kali pengiriman karena sebelumnya penulis sudah testing dengan mengirim 2 buah message. + +Seperti yang kita lihat gambar dibawah ini adalah log dari service Consumer. Data message dari service Producer telah diterima oleh service Consumer. +![](images/consumer.png) + +--- + - [IBM Sarama](https://github.com/IBM/sarama), MIT license +--- + +--- + \ No newline at end of file diff --git a/content/images/C-apache-kafka_consumer.png b/content/images/C-apache-kafka_consumer.png new file mode 100644 index 0000000000000000000000000000000000000000..da8faf2270aa3e71ff51275d3c66d955b2ebe9aa GIT binary patch literal 10461 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 literal 0 HcmV?d00001 diff --git a/content/images/C-apache-kafka_producer.png b/content/images/C-apache-kafka_producer.png new file mode 100644 index 0000000000000000000000000000000000000000..b81786a7a338720c250227acf425a55f103e1a84 GIT binary patch literal 4447 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 literal 0 HcmV?d00001