DEV Community

Mohamad Hasan
Mohamad Hasan

Posted on

Ketika Golang Dipilih untuk Kirim Pesan Yang Sibuk

Image description

Bayangkan sebuah kota metropolitan di zaman belum ada telepon dan internet. Ribuan surat datang setiap hari ke kantor pos pusat, dan harus dibagikan ke rumah-rumah atau kantor di seluruh penjuru kota. Jika hanya satu tukang pos yang bertugas, tentu surat akan menumpuk, pengiriman jadi lambat, dan banyak pesan penting yang terlambat sampai. Tapi bagaimana jika ada puluhan, bahkan ratusan tukang pos yang bisa bekerja bersama, saling membantu, dan semuanya bisa berkoordinasi dengan baik? Inilah gambaran sederhana mengapa sistem pengiriman pesan yang canggih membutuhkan teknologi yang tepat.

Mengapa Golang? Karena Golang seperti pasukan tukang pos super cepat yang bisa bergerak serempak tanpa saling tabrakan. Dalam dunia komputer, ini disebut concurrency—kemampuan melakukan banyak hal sekaligus. Golang punya alat khusus bernama goroutine, seperti tukang pos kecil yang bisa diutus kapan saja untuk mengantarkan surat. Tidak hanya itu, mereka bisa saling mengirim kabar lewat channels, semacam kode-kode atau pesan singkat agar tidak ada yang salah paham atau tersesat di jalan.


1. Concurrency dengan Goroutines dan Channels

Ketika kota harus mengirim banyak surat ke berbagai penjuru sekaligus, satu-satunya cara agar pesan tidak menumpuk adalah dengan membagi tugas ke banyak tukang pos. Di Golang, setiap "tukang pos" ini disebut goroutine. Mereka bisa diaktifkan kapan saja, jumlahnya bisa sangat banyak, dan mereka tidak saling mengganggu.

Misalnya, saat kantor pos menerima surat, Golang bisa langsung mengutus goroutine untuk mengantarkannya tanpa harus menunggu tugas lain selesai. Ini seperti mengirim surat ke banyak alamat sekaligus, bukan satu per satu. Dengan channel, setiap goroutine bisa memberi kabar, “Surat sudah sampai!” atau “Ada masalah di jalan!” sehingga koordinasi tetap terjaga.

Lihat dokumentasi goroutine

// MessageProcessor menangani pesan secara concurrent
type MessageProcessor struct {
    incomingMessages chan Message
    workers          int
}

func (mp *MessageProcessor) Start() {
    // Jalankan multiple workers
    for i := 0; i < mp.workers; i++ {
        go mp.processMessages()
    }
}

func (mp *MessageProcessor) processMessages() {
    for msg := range mp.incomingMessages {
        // Proses setiap pesan dalam goroutine terpisah
        go mp.handleMessage(msg)
    }
}
Enter fullscreen mode Exit fullscreen mode
type MessageQueue struct {
    messages    chan Message
    errors      chan error
    shutdown    chan struct{}
}

func (mq *MessageQueue) Listen() {
    for {
        select {
        case msg := <-mq.messages:
            // Proses pesan baru
            log.Printf("Processing message ID: %s", msg.ID)
        case err := <-mq.errors:
            // Tangani error
            log.Printf("Error processing message: %v", err)
        case <-mq.shutdown:
            // Terima sinyal shutdown
            log.Println("Shutting down message processor")
            return
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Tapi, bagaimana jika ada dua tukang pos yang ingin menghitung berapa banyak surat yang sudah dikirim? Kalau tidak hati-hati, catatan bisa jadi berantakan. Di sinilah atomic operation berperan, memastikan setiap penambahan atau pengurangan dilakukan dengan rapi, tanpa rebutan.

Lihat dokumentasi sync/atomic

type MessageStats struct {
    processed atomic.Int64
    failed    atomic.Int64
}

func (ms *MessageStats) RecordSuccess() {
    ms.processed.Add(1)
}

func (ms *MessageStats) RecordFailure() {
    ms.failed.Add(1)
}

func (ms *MessageStats) GetStats() (processed, failed int64) {
    return ms.processed.Load(), ms.failed.Load()
}
Enter fullscreen mode Exit fullscreen mode

2. Message Queue dengan RabbitMQ

Sekarang bayangkan ada lumbung surat besar di tengah kota. Semua surat dari luar kota ditaruh di sana dulu, lalu tukang pos mengambil satu per satu sesuai rute dan giliran. RabbitMQ adalah lumbung surat digital yang memastikan tidak ada surat yang hilang, dan setiap pesan diproses dengan urutan yang benar.

Dengan bantuan goroutine, surat-surat ini bisa diambil dan diantarkan secara bersamaan, pekerjaan jadi jauh lebih cepat. Setiap pesan yang masuk ke antrian akan diantar oleh tukang pos (worker) yang berjalan dalam goroutine terpisah, memastikan tidak ada penumpukan di satu titik.

type Consumer struct {
    queue    string
    messages <-chan amqp.Delivery
    done     chan struct{}
}

func (c *Consumer) Start(ctx context.Context) error {
    for i := 0; i < c.workerCount; i++ {
        go func() {
            for {
                select {
                case msg := <-c.messages:
                    if err := c.processMessage(msg); err != nil {
                        msg.Nack(false, true) // Kirim kembali ke queue
                        continue
                    }
                    msg.Ack(false)
                case <-ctx.Done():
                    return
                }
            }
        }()
    }
    return nil
}
Enter fullscreen mode Exit fullscreen mode

Manajemen perjalanan tukang pos menjadi sangat penting. Sistem harus bisa menambah, menghentikan, dan memantau setiap tukang pos secara dinamis. Heartbeat dan error channel digunakan untuk memastikan setiap tukang pos tetap aktif dan bisa digantikan jika ada yang kelelahan atau tersesat.

type ConsumerHealth struct {
    lastHeartbeat atomic.Value
    status        atomic.Value
    done          chan struct{}
}

func (h *ConsumerHealth) Monitor(interval time.Duration) {
    ticker := time.NewTicker(interval)
    go func() {
        for {
            select {
            case <-ticker.C:
                h.updateHealth()
                if !h.isHealthy() {
                    h.status.Store("unhealthy")
                    h.reconnect()
                }
            case <-h.done:
                ticker.Stop()
                return
            }
        }
    }()
}
Enter fullscreen mode Exit fullscreen mode

3. Graceful Shutdown

Sistem juga harus tahu kapan harus berhenti bekerja. Ketika hari mulai gelap dan kota harus beristirahat, semua tukang pos harus memastikan tidak ada surat yang tertinggal sebelum pulang. Inilah yang disebut graceful shutdown. Dengan WaitGroup, Golang memastikan semua tugas benar-benar selesai sebelum sistem dimatikan. Tidak ada pesan yang terlewat, tidak ada pengiriman yang ditinggalkan begitu saja.

Lihat dokumentasi context, sync.WaitGroup, dan goroutine

type MessageServer struct {
    consumers map[string]*Consumer
    wg        sync.WaitGroup
}

func (s *MessageServer) Shutdown(ctx context.Context) error {
    // Kirim sinyal shutdown ke semua consumer
    for id, consumer := range s.consumers {
        s.wg.Add(1)
        go func(id string, c *Consumer) {
            defer s.wg.Done()
            if err := c.Shutdown(ctx); err != nil {
                log.Printf("Error shutting down consumer %s: %v", id, err)
            }
        }(id, consumer)
    }

    // Tunggu semua consumer selesai atau context timeout
    done := make(chan struct{})
    go func() {
        s.wg.Wait()
        close(done)
    }()

    select {
    case <-done:
        return nil
    case <-ctx.Done():
        return ctx.Err()
    }
}
Enter fullscreen mode Exit fullscreen mode

Dalam implementasi nyata, graceful shutdown diintegrasikan ke seluruh komponen. Shutdown manager akan menangkap sinyal sistem (seperti SIGINT atau SIGTERM), membatalkan context, menutup channel, dan menunggu semua proses selesai dengan WaitGroup. Hasilnya, sistem dapat berhenti dengan mulus tanpa kehilangan data atau meninggalkan proses zombie.


Penutup: Apa Pelajaran Besarnya?

Mengapa semua ini penting? Karena dalam dunia nyata, sistem seperti ini harus bisa diandalkan. Tidak boleh ada pesan yang hilang, tidak boleh ada pengiriman yang macet, dan semuanya harus bisa berjalan cepat tanpa saling mengganggu. Golang menyediakan semua alat itu secara sederhana, efisien, dan mudah dipahami.

Pelajaran terbesar dari perjalanan ini sederhana: teknologi terbaik adalah yang mampu membuat hal rumit menjadi mudah, dan yang mampu bekerja sama seperti tim yang solid—bahkan jika tim itu terdiri dari jutaan “petugas surat” digital yang bekerja tanpa henti di balik layar.

Top comments (0)

OSZAR »