使用 Kafka 和 Debezium 排程數百萬條訊息 - Yotpo

banq發表於2021-12-21

Yotpo使用Apache Kafka和Debezium為每分鐘數百萬條訊息實施了高度可擴充套件且可靠的預定訊息解決方案:
實現大規模分散式系統並不容易,因為傳統的資料庫排程無法擴充套件。此外,在使用微服務架構時,它變得更加困難,因為您繼承了所有分散式系統問題,例如資料不一致、雙重寫入 和 域邊界問題。
在本文中,我將分享我在 Yotpo 如何使用 Apache Kafka 和 Debezium CDC 為每分鐘數百萬條訊息實施高度可擴充套件且可靠的預定訊息解決方案的過程。
在接下來的幾章中,我將深入探討選擇 Debezium CDC 來將訊息排程到 Apache Kafka。
 

動機
作為 Yotpo 業務的一部分,我們需要向終端使用者傳送電子郵件。不僅如此,它可以在未來某個日期以每天數百萬的規模進行,在高峰時間甚至可以在幾分鐘內達到數百萬。因此,挑戰在於安排以這種規模傳送的電子郵件。
 

傳統排程解決方案之旅
鑑於上述要求,我開始研究用於安排電子郵件請求的傳統解決方案,並很快發現它們不符合要求。我們如何儲存所有這些電子郵件,並在這種規模的確切交付日期安排它們——確保所有電子郵件都在給定的交付日期執行?讓我們仔細看看旅程以及我在此過程中解決的問題。

  • RabbitMQ——延遲訊息交換

起初我們以為,這正是我們所需要的!但是在閱讀外掛 README 後不久,我們發現它無法擴充套件。
此外,這個解決方案意味著我們不會在我們的系統中儲存任何訊息,從而導致缺乏可見性,並且無法監控和分析這些資料。
  • Quartz 作業排程器

使用 Quartz 作為排程程式和作業執行工具不會擴充套件到數以萬計,而且看起來很複雜,尤其是當您的任務/作業很短時。
  • 資料庫作為 Apache Kafka 訊息的延遲訊息儲存

  1. 將預定訊息插入資料庫
  2. 當排程器定時器啟動時,它需要查詢資料庫中未交付的條目並獲取這些條目的鎖,以確保這些條目只會產生一次
  3. 生產到卡夫卡
  4. 更新並提交到資料庫
  5. 消費者服務消費訊息併傳送電子郵件

缺點
  1. 雙寫問題——我們試圖寫入兩個不同的系統,即資料庫和Kafka,導致資料不一致
  2. 兩階段提交不是一種選擇,因為它不可擴充套件

 

Debezium CDC 來救援
Debezium 是一個用於變更資料捕獲的開源分散式平臺。啟動它,將它指向您的資料庫,您的應用程式就可以開始響應其他應用程式提交到您的資料庫的所有插入、更新和刪除操作。
利用 Debezium 將預定訊息從資料庫流式傳輸到 Kafka:
我們使用 Apache Kafka 作為我們的流媒體平臺和微服務之間的通訊。您可能知道,Apache Kafka 是一個高度可擴充套件的分散式平臺,能夠處理大量訊息,但它沒有提供延遲訊息的解決方案。因此,我們必須找到一種解決方案來儲存所有延遲訊息,直到它們準備好進行處理(即排程)。
既然 Debezium 在 Yotpo 被廣泛用作變更資料捕獲的基礎設施,為什麼不使用它來解決普通 CDC 以外的問題呢?讓我解釋一下我們是如何做到的……
如果我們將訊息儲存在資料庫中,我們只需要在這些訊息準備好進行處理時將它們流式傳輸回 Kafka。由於這是 Debezium 所擅長的,更重要的是在大規模方面 - 我們發現它是完美的選擇!

  1. HTTP 伺服器將訊息插入到資料庫中,並帶有交付時間
  2. 一個單獨的應用程式將作為一個簡單的 cron 排程程式,當交付時間 < NOW 並且更新準備好時更新延遲的訊息- 標記為 true 以在下一個 cron 週期中被忽略
  3. Debezium 將透過 CDC 捕獲那些更新的條目(僅那些特定的更新,稍後會詳細介紹),並將這些訊息流式傳輸到 Apache Kafka 以供執行消費者使用

Debezium 聯結器:

{
  "name": "delayed-email-message",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",
    "tasks.max": "1",
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "root",
    "database.password": "rootpass",
    "database.server.id": "184054",
    "database.server.name": "dbserver1",
    "database.whitelist": "emails",
    "database.history.kafka.bootstrap.servers": "kafka:9093",
    "database.history.kafka.topic": "delayed.emails.history",
    "key.converter": "org.apache.kafka.connect.json.JsonConverter",
    "value.converter": "org.apache.kafka.connect.json.JsonConverter",
    "key.converter.schemas.enable": "false",
    "value.converter.schemas.enable": "false",
    "table.whitelist": "emails.delayed_messages",
    "transforms": "Reroute, filter, unwrap",
    "transforms.unwrap.type": "io.debezium.transforms.ExtractNewRecordState",
    "transforms.unwrap.drop.tombstones": "true",
    "transforms.Reroute.type": "io.debezium.transforms.ByLogicalTableRouter",
    "transforms.Reroute.topic.regex": "(.*)(delayed_messages)$",
    "transforms.Reroute.topic.replacement": "email.execution",
    "transforms.filter.type": "io.debezium.transforms.Filter",
    "transforms.filter.language": "jsr223.groovy",
    "transforms.filter.topic.regex": "email.execution",
    "transforms.filter.condition": "value.op == \"u\" && value.before.is_ready == false && value.after.is_ready == true"
  }
}

聯結器在delay_messages表(第 19 行)上配置,並且只會響應將is_ready=false更改為is_ready=true的更新操作。
讓我們仔細看看轉換部分:
  • ExtractNewRecordState — 用於訊息扁平化
  • 重新路由——將預設的 Debezium Kafka 主題更改為“email.execution”
  • 過濾器 — 使用更新操作過濾訊息,將其 is_ready 標誌從 false 更改為 true。這可確保 Debezium 聯結器僅為此特定更新操作生成訊息,而忽略任何其他操作

 

執行消費者
一個簡單的 Kafka 消費者,它使用來自“email.execution”主題的電子郵件訊息,並簡單地傳送電子郵件。此外,您需要根據您的規模最佳化消費者配置和 Kafka 主題分割槽。

好處

  • 分離排程和執行之間的關注點,以提高可擴充套件性
  • 排程程式執行不會影響資料庫效能
  • 資料一致性——沒有雙寫問題
  • 容錯
  • 靈活性
  • 在資料庫中儲存您需要的任何資料
  • 出於分析/管理目的查詢此資料
  • 獲得 Apache Kafka 的好處
  • 水平刻度
  • 訊息排序
  • 批次消費以提高效能
  • 流媒體功能,以及更多……

缺點
  • 複雜性——我們有 2 個額外的基礎設施需要監控和維護

結果
系統在生產中執行了一段時間,在高峰期,同時排程了50K封郵件,結果如下:
  • 排程程式查詢耗時 1.25 秒
  • 大約 250 毫秒的延遲,直到 debezium 從資料庫中捕獲所有 50k 封電子郵件並生成到 Kafka 主題——這是從排程程式應用程式和電子郵件消費者應用程式的日誌中收集的

排程非常快,根據結果,我們每分鐘可以收到大約 240 萬條訊息!這是透過對 Debezium 和 Mysql 使用預設配置。
 

相關文章