全部产品
Search
文档中心

ApsaraMQ for RocketMQ:Idempotensi konsumsi

更新时间:Mar 11, 2026

ApsaraMQ for RocketMQ menerapkan pengiriman pesan minimal sekali (at least once). Artinya, konsumen mungkin menerima pesan yang sama lebih dari satu kali. Jika logika bisnis Anda sensitif terhadap duplikat—seperti pemotongan pembayaran, penyesuaian inventaris, atau pembuatan pesanan—implementasikan konsumsi idempoten. Konsumsi idempoten memastikan bahwa pemrosesan berulang atas pesan yang sama menghasilkan efek yang identik dengan pemrosesan tunggal.

Sebagai contoh, sebuah konsumen memproses pesan pemotongan pembayaran untuk pesanan sebesar USD 100. Karena gangguan jaringan, pesan tersebut dikirim dua kali. Dengan konsumsi idempoten, pemotongan pembayaran hanya dilakukan sekali, dan hanya satu catatan pemotongan sebesar USD 100 yang dihasilkan untuk pesanan tersebut.

Mengapa pesan duplikat terjadi

Pesan duplikat dapat muncul dalam tiga skenario.

Retry produsen

Sebuah produsen mengirim pesan, dan broker ApsaraMQ for RocketMQ menyimpannya secara persisten. Namun, broker mungkin gagal mengirimkan acknowledgment ke produsen karena gangguan jaringan sementara atau crash pada produsen. Produsen menganggap pengiriman tersebut gagal dan melakukan retry. Akibatnya, konsumen menerima dua pesan dengan konten yang sama tetapi ID pesan yang berbeda.

Redelivery oleh broker

Konsumen menerima dan memproses sebuah pesan, tetapi acknowledgment kembali ke broker gagal karena gangguan jaringan sementara. Karena broker tidak dapat memastikan apakah pesan telah dikonsumsi, broker mengirim ulang pesan tersebut setelah jaringan pulih demi memenuhi jaminan at-least-once. Konsumen kemudian menerima dua pesan dengan konten yang sama dan ID pesan yang sama.

Load balancing

Peristiwa seperti fluktuasi jaringan, restart broker, atau restart aplikasi konsumen dapat memicu proses load balancing. Selama penyeimbangan ulang, konsumen mungkin menerima pesan yang sebelumnya sudah dikirim.

Gunakan kunci bisnis, bukan ID pesan

Insting pertama yang wajar adalah melakukan deduplikasi berdasarkan ID pesan, tetapi pendekatan ini tidak andal. Seperti dijelaskan dalam skenario retry produsen, pesan logis yang sama dapat tiba dengan dua ID pesan berbeda ketika produsen melakukan retry pengiriman. Deduplikasi berdasarkan ID pesan akan sepenuhnya melewatkan duplikat semacam ini.

Sebagai gantinya, tetapkan pengidentifikasi bisnis unik sebagai kunci pesan. Misalnya, gunakan ID pesanan, ID transaksi pembayaran, atau nilai apa pun yang secara unik mengidentifikasi operasi bisnis tersebut. Kunci ini tetap konsisten terlepas dari berapa kali pesan dikirim atau dikirim ulang. Kunci ini berasal dari logika bisnis Anda, bukan dihasilkan oleh sistem messaging.

Pendekatan ini memberikan pengulangan yang dapat diprediksi. Jika terjadi kegagalan dan pesan dikirim ulang, konteks bisnis yang sama selalu menghasilkan kunci yang sama, sehingga deduplikasi menjadi andal.

Implementasikan konsumsi idempoten

Langkah 1: Tetapkan kunci pesan di sisi produsen

Lampirkan pengidentifikasi bisnis unik sebagai kunci pesan saat mengirim pesan.

Message message = new Message();
message.setKey("ORDERID_100");
SendResult sendResult = producer.send(message);

Ganti ORDERID_100 dengan pengidentifikasi bisnis unik yang sebenarnya, seperti ID pesanan atau ID transaksi.

Langkah 2: Ambil kunci pesan di sisi konsumen

Ambil kunci pesan dalam callback konsumen dan gunakan untuk menerapkan pemrosesan idempoten.

consumer.subscribe("ons_test", "*", new MessageListener() {
    public Action consume(Message message, ConsumeContext context) {
        String key = message.getKey()
        // Lakukan pemrosesan idempoten berdasarkan kunci pesan yang secara unik mengidentifikasi operasi bisnis Anda.
    }
});

Langkah 3: Terapkan idempotensi dengan penyimpanan deduplikasi

Kunci pesan saja tidak mencegah pemrosesan duplikat—aplikasi Anda harus memeriksa apakah kunci tersebut sudah pernah diproses. Pola umum menggunakan database relasional dengan batasan unik:

  1. Sebelum memproses, masukkan kunci pesan ke dalam tabel deduplikasi dengan batasan unik pada kolom kunci.

  2. Jika operasi insert berhasil, proses pesannya.

  3. Jika operasi insert gagal karena pelanggaran batasan primary key atau unique constraint, berarti pesan tersebut sudah pernah diproses. Lewati saja.

Gunakan pola insert-then-check, bukan check-then-insert. Dengan check-then-insert, dua thread bisa lolos pemeriksaan sebelum salah satunya melakukan insert, sehingga mengakibatkan pemrosesan duplikat. Mengandalkan pelanggaran batasan unik dari database bersifat atomik dan aman dari race condition.

Contoh tabel deduplikasi (MySQL):

CREATE TABLE message_dedup (
    message_key VARCHAR(255) NOT NULL,
    created_at  TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (message_key)
);

Contoh logika konsumen idempoten:

consumer.subscribe("ons_test", "*", new MessageListener() {
    public Action consume(Message message, ConsumeContext context) {
        String key = message.getKey();

        try {
            // Coba masukkan kunci pesan. Gagal jika sudah pernah diproses.
            insertMessageKey(key);
        } catch (DuplicateKeyException e) {
            // Sudah diproses. Lewati.
            return Action.CommitMessage;
        }

        // Proses logika bisnis.
        processOrder(key);

        return Action.CommitMessage;
    }
});

Ganti insertMessageKey dan processOrder dengan metode database dan logika bisnis Anda yang sebenarnya.

Untuk skenario throughput tinggi di mana database relasional berpotensi menjadi bottleneck, gunakan pendekatan berbasis Redis dengan SETNX (SET if Not eXists) sebagai gantinya.

Praktik terbaik

PraktikDetail
Bungkus deduplikasi dan logika bisnis dalam satu transaksiJika logika bisnis gagal setelah catatan deduplikasi dimasukkan, catatan deduplikasi tersebut akan di-rollback, sehingga memungkinkan pesan dicoba ulang pada pengiriman berikutnya. Tanpa transaksi, operasi bisnis yang gagal akan meninggalkan catatan deduplikasi tetap ada, dan pesan tidak akan pernah diproses ulang.
Bersihkan penyimpanan deduplikasi secara berkalaTabel deduplikasi terus bertambah seiring waktu. Tetapkan periode retensi (misalnya, 7 hari) dan hapus catatan lama secara batch untuk mencegah pertumbuhan penyimpanan yang tidak terbatas.
Pilih penyimpanan deduplikasi yang sesuai dengan throughput AndaGunakan database relasional dengan batasan unik untuk throughput moderat. Untuk skenario throughput tinggi, gunakan Redis SETNX dengan TTL yang sesuai dengan periode retensi Anda, yang juga menangani pembersihan otomatis.