Einführung in Debezium — CDC für Apache Kafka

Einführung in Debezium — CDC für Apache Kafka

In meiner Arbeit stoße ich häufig auf neue technische Lösungen und Softwareprodukte, über die es im deutschsprachigen Internet nur wenig Informationen gibt. Mit diesem Artikel möchte ich eine solche Lücke schließen und ein Beispiel aus meiner jüngsten Praxis vorstellen, als es darum ging, die Übertragung von CDC-Ereignissen aus zwei beliebten Datenbanksystemen (PostgreSQL und MongoDB) an ein Kafka-Cluster mittels Debezium einzurichten. Ich hoffe, dass dieser Überblick auf der Basis der durchgeführten Arbeiten auch anderen hilfreich sein wird.

Was ist Debezium und was bedeutet CDC?

Debezium ist ein Vertreter der Kategorie CDC-Software (Capture Data Change), genauer gesagt, handelt es sich um eine Sammlung von Connectoren für verschiedene Datenbanksysteme, die mit dem Apache Kafka Connect-Framework kompatibel sind.

Das Ein Open Source-Projekt, das unter der Apache License v2.0 lizenziert ist und von Red Hat gesponsert wird. Die Entwicklung begann im Jahr 2016, und derzeit wird die Unterstützung für folgende Datenbanksysteme offiziell angeboten: MySQL, PostgreSQL, MongoDB, SQL Server. Es gibt auch Connectoren für Cassandra und Oracle, jedoch befinden sich diese derzeit im Status „Early Access“, und neue Versionen garantieren keine Rückwärtskompatibilität.

Im Vergleich zu traditionellen Ansätzen, bei denen Anwendungen direkt aus Datenbanken lesen, bietet CDC wesentliche Vorteile wie das Streamen von Datenänderungen auf Zeilenebene mit niedriger Latenz sowie hohe Zuverlässigkeit und Verfügbarkeit. Letztere beiden Punkte werden durch die Verwendung eines Kafka-Clusters als Speicher für CDC-Ereignisse erreicht.

Ein weiterer Vorteil ist, dass eine einheitliche Modellierung für die Speicherung von Ereignissen verwendet wird. Dadurch muss sich die Endanwendung nicht um die spezifischen Anforderungen verschiedener Datenbanksysteme kümmern.

Dank des Einsatzes eines Nachrichtenbrokers eröffnet sich die Möglichkeit zur horizontalen Skalierung von Anwendungen, die Datenänderungen verfolgen. Dabei wird der Einfluss auf die Datenquelle minimiert, da die Daten nicht direkt aus der Datenbank, sondern aus dem Kafka-Cluster abgerufen werden.

Über die Architektur von Debezium

Die Verwendung von Debezium lässt sich einfach wie folgt beschreiben:

Datenbank (als Datenquelle) → Connector in Kafka Connect → Apache Kafka → Consumer

Zur Veranschaulichung folgt hier ein Diagramm von der Projektseite:

Einführung in Debezium — CDC für Apache Kafka

Allerdings gefällt mir dieses Schema nicht besonders, da der Eindruck entsteht, dass nur der Einsatz eines Sink-Connectors möglich ist.

In Wirklichkeit sieht die Situation jedoch anders aus: Das Befüllen Ihres Data Lakes (das letzte Glied im obigen Schema) ist nicht die einzige Anwendungsmöglichkeit von Debezium. Die in Apache Kafka gesendeten Ereignisse können von Ihren Anwendungen genutzt werden, um verschiedene Situationen zu lösen. Beispielsweise:

  • Entfernung nicht mehr relevanter Daten aus dem Cache;
  • Versand von Benachrichtigungen;
  • Aktualisierungen von Suchindizes;
  • ein gewisses Äquivalent zu Audit-Logs;

Falls Sie eine Anwendung in Java haben und nicht die Notwendigkeit/Möglichkeit besteht, einen Kafka-Cluster zu nutzen, gibt es auch die Möglichkeit, über den Embedded-Connectorzu arbeiten. Der offensichtliche Vorteil besteht darin, dass man auf zusätzliche Infrastruktur (in Form eines Connectors und Kafka) verzichten kann. Diese Lösung wurde jedoch mit Version 1.1 als veraltet erklärt und wird nicht mehr empfohlen (in zukünftigen Releases könnte die Unterstützung eingestellt werden).

In diesem Artikel wird die von den Entwicklern empfohlene Architektur behandelt, die Ausfallsicherheit und Skalierbarkeit bietet.

Konfiguration des Connectors

Um die Veränderungen des wertvollsten Guts – der Daten – zu verfolgen, benötigen wir:

  1. eine Datenquelle, die MySQL ab Version 5.7, PostgreSQL 9.6+ oder MongoDB 3.2+ sein kann (vollständige Liste);
  2. einen Apache Kafka-Cluster;
  3. eine Kafka Connect-Instanz (Versionen 1.x, 2.x);
  4. einen konfigurierten Debezium-Connector.

Die ersten beiden Punkte, d.h. der Installationsprozess für die DBMS und Apache Kafka, sind nicht Teil dieses Artikels. Für diejenigen, die alles in einer Sandbox bereitstellen möchten, gibt es im offiziellen Repository mit Beispielen eine fertige docker-compose.yaml.

Hier konzentrieren wir uns ausführlicher auf die letzten beiden Punkte.

0. Kafka Connect

Im Folgenden werden alle Konfigurationsbeispiele im Kontext des von den Debezium-Entwicklern veröffentlichten Docker-Images betrachtet. Es enthält alle notwendigen Plugin-Dateien (Connectoren) und ermöglicht die Konfiguration von Kafka Connect über Umgebungsvariablen.

Sollte Kafka Connect von Confluent verwendet werden, müssen die Plugins der erforderlichen Connectoren manuell in das Verzeichnis hinzugefügt werden, das in plugin.path oder über die Umgebungsvariable CLASSPATH. Die Einstellungen für den Kafka Connect Worker und die Connectoren werden über Konfigurationsdateien definiert, die als Argumente an den Startbefehl des Workers übergeben werden. Weitere Informationen finden Sie in Dokumentation..

Der gesamte Prozess zur Einrichtung von Debezium mit einem Connector umfasst zwei Schritte. Lassen Sie uns jeden von ihnen betrachten:

1. Konfiguration des Kafka Connect Frameworks

Für das Streaming von Daten in ein Apache Kafka-Cluster im Kafka Connect Framework werden spezifische Parameter festgelegt, wie zum Beispiel:

  • Verbindungsparameter für das Cluster,
  • Themen, in denen die Konfiguration des Connectors gespeichert wird,
  • der Gruppenname, unter dem der Connector läuft (im Falle der Verwendung des verteilten Modus).

Das offizielle Docker-Image des Projekts unterstützt die Konfiguration über Umgebungsvariablen - und das werden wir auch nutzen. Also laden wir das Image herunter:

docker pull debezium/connect

Der minimale Satz an Umgebungsvariablen, der benötigt wird, um den Connector zu starten, sieht folgendermaßen aus:

  • BOOTSTRAP_SERVERS=kafka-1:9092,kafka-2:9092,kafka-3:9092 – die initiale Liste der Kafka-Cluster-Server, um die vollständige Liste der Cluster-Mitglieder zu erhalten;
  • OFFSET_STORAGE_TOPIC=connector-offsets – das Thema zur Speicherung der Positionen, an denen sich der Connector momentan befindet;
  • CONNECT_STATUS_STORAGE_TOPIC=connector-status — Thema zur Speicherung des Status des Connectors und seiner Aufgaben;
  • CONFIG_STORAGE_TOPIC=connector-config — Thema zur Speicherung der Konfigurationsdaten des Connectors und seiner Aufgaben;
  • GROUP_ID=1 — Identifikator der Worker-Gruppe, auf der die Aufgabe des Connectors ausgeführt werden kann; erforderlich bei Verwendung des verteilten (distributed) Betriebs.

Wir starten den Container mit diesen Variablen:

docker run 
  -e BOOTSTRAP_SERVERS='kafka-1:9092,kafka-2:9092,kafka-3:9092' 
  -e GROUP_ID=1 
  -e CONFIG_STORAGE_TOPIC=my_connect_configs 
  -e OFFSET_STORAGE_TOPIC=my_connect_offsets 
  -e STATUS_STORAGE_TOPIC=my_connect_statuses debezium/connect:1.2

Hinweis zu Avro

Standardmäßig schreibt Debezium Daten im JSON-Format, was für Sandbox-Umgebungen und kleine Datenmengen akzeptabel ist, jedoch in stark belasteten Datenbanken problematisch werden kann. Eine Alternative zum JSON-Konverter ist die Serialisierung von Nachrichten mithilfe von Avro in ein binäres Format, was die Belastung des I/O-Subsystems in Apache Kafka verringert.

Für die Nutzung von Avro ist die Bereitstellung eines separaten schema-registry (zur Speicherung von Schemata) erforderlich. Die Variablen für den Konverter sehen wie folgt aus:

name: CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL
value: http://kafka-registry-01:8081/
name: CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL
value: http://kafka-registry-01:8081/
name: VALUE_CONVERTER
value: io.confluent.connect.avro.AvroConverter

Die Details zur Verwendung von Avro und dessen Registrierung gehen über den Rahmen dieses Artikels hinaus — zur Veranschaulichung werden wir weiter unten JSON verwenden.

2. Konfiguration des Connectors

Jetzt können wir direkt zur Konfiguration des Connectors übergehen, der Daten aus der Quelle lesen wird.

Lassen Sie uns am Beispiel von zwei Datenbanksystemen: PostgreSQL und MongoDB betrachten — für die ich Erfahrung habe und bei denen es Unterschiede gibt (auch wenn diese geringfügig sind, sind sie in manchen Fällen doch erheblich!).

Die Konfiguration wird in JSON-Notation beschrieben und über einen POST-Request in Kafka Connect geladen.

2.1. PostgreSQL

Beispiel für die Konfiguration des Connectors für PostgreSQL:

{
  "name": "pg-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "plugin.name": "pgoutput",
    "database.hostname": "127.0.0.1",
    "database.port": "5432",
    "database.user": "debezium",
    "database.password": "definitelynotpassword",
    "database.dbname" : "dbname",
    "database.server.name": "pg-dev",
    "table.include.list": "public.(.*)",
    "heartbeat.interval.ms": "5000",
    "slot.name": "dbname_debezium",
    "publication.name": "dbname_publication",
    "transforms": "AddPrefix",
    "transforms.AddPrefix.type": "org.apache.kafka.connect.transforms.RegexRouter",
    "transforms.AddPrefix.regex": "pg-dev.public.(.*)",
    "transforms.AddPrefix.replacement": "data.cdc.dbname"
  }
}

Die Funktionsweise des Connectors nach dieser Konfiguration ist recht einfach:

  • Bei der ersten Ausführung verbindet sich der Connector mit der in der Konfiguration angegebenen Datenbank und startet im initialen Snapshot, dabei sendet er einen anfänglichen Datensatz in Kafka, der durch die Bedingung SELECT * FROM table_name.
  • erhalten wurde. Nachdem die Initialisierung abgeschlossen ist, wechselt der Connector in den Modus, in dem Änderungen aus den WAL-Dateien von PostgreSQL gelesen werden.

Zu den verwendeten Optionen:

  • name — der Name des Connectors, für den die unten beschriebene Konfiguration verwendet wird; dieser Name wird später verwendet, um mit dem Connector zu arbeiten (d.h. Status zu überprüfen/neu zu starten/die Konfiguration über die REST API von Kafka Connect zu aktualisieren);
  • connector.class — Connector-Klasse für das DBMS, die vom konfigurierbaren Connector verwendet wird;
  • plugin.name — Name des Plugins zur logischen Dekodierung von Daten aus WAL-Dateien. Folgende Optionen stehen zur Auswahl: wal2json, decoderbuffs und pgoutput. Die ersten beiden erfordern die Installation der entsprechenden Erweiterungen im DBMS, während pgoutput für PostgreSQL Version 10 und höher keine zusätzlichen Maßnahmen erforderlich sind;
  • database.* — Optionen zur Verbindung mit der DB, wobei database.server.name — Name der PostgreSQL-Instanz, die zur Bildung des Thementitels im Kafka-Cluster verwendet wird;
  • table.include.list — Liste der Tabellen, deren Änderungen wir verfolgen möchten; formatiert als schema.table_name; kann nicht zusammen mit table.exclude.list;
  • heartbeat.interval.ms — Intervall (in Millisekunden), in dem der Connector Heartbeat-Nachrichten an ein spezielles Thema sendet;
  • heartbeat.action.query — Abfrage, die bei jedem Versand einer Heartbeat-Nachricht ausgeführt wird (Option eingeführt ab Version 1.1);
  • slot.name — Name des Replikationsslots, der vom Connector verwendet wird;
  • publication.name — Name Veröffentlichung in PostgreSQL, die vom Connector verwendet wird. Falls sie nicht existiert, wird Debezium versuchen, sie zu erstellen. Wenn der Benutzer, unter dem die Verbindung erfolgt, dafür nicht über ausreichende Rechte verfügt, wird der Connector mit einem Fehler abbrechen;
  • sinks bestimmt, wie der Name des Zielthemen geändert werden soll:
    • transforms.AddPrefix.type gibt an, dass wir reguläre Ausdrücke verwenden werden;
    • transforms.AddPrefix.regex — das Muster, nach dem der Name des Zielthemen überschrieben wird;
    • transforms.AddPrefix.replacement — das, worauf wir umbenennen.

Weitere Informationen zu heartbeat und transforms

Standardmäßig sendet der Connector Daten an Kafka bei jeder bestätigten Transaktion und protokolliert deren LSN (Log Sequence Number) in einem Systemthema offset. Aber was passiert, wenn der Connector so konfiguriert ist, dass er nicht die gesamte Datenbank, sondern nur Teile ihrer Tabellen liest (in denen die Datenaktualisierung nicht häufig stattfindet)?

  • Der Connector wird WAL-Dateien lesen und keine bestätigten Transaktionen in den Tabellen finden, die er überwacht.
  • Daher wird er seine aktuelle Position weder im Thema noch im Replikationsslot aktualisieren.
  • Dies führt wiederum dazu, dass WAL-Dateien auf der Festplatte „festgehalten“ werden, was möglicherweise den gesamten Speicherplatz erschöpfen kann.

Hier kommen die Optionen ins Spiel, heartbeat.interval.ms und heartbeat.action.query. Die Verwendung dieser Optionen in Kombination ermöglicht es, bei jeder Sendung einer Heartbeat-Nachricht einen Änderungsantrag in einer separaten Tabelle durchzuführen. Damit wird das LSN, an dem sich der Connector (im Replikationsslot) gerade befindet, ständig aktualisiert. Dies ermöglicht es der Datenbank, WAL-Dateien zu löschen, die nicht mehr benötigt werden. Weitere Informationen zur Funktionsweise der Optionen finden Sie in Dokumentation..

Eine andere Option, die mehr Aufmerksamkeit verdient, ist sinks. Auch wenn es eher um Benutzerfreundlichkeit und Ästhetik geht…

Standardmäßig erstellt Debezium Themen gemäß folgender Namenspolitik: serverName.schemaName.tableName. Dies ist nicht immer praktisch. Mit den Optionen sinks können Sie mithilfe von regulären Ausdrücken die Liste der Tabellen definieren, deren Events in ein Thema mit einem bestimmten Namen geroutet werden sollen.

In unserer Konfiguration geschieht Folgendes: sinks Alle CDC-Ereignisse aus der überwachten Datenbank werden in das Thema mit dem Namen data.cdc.dbname. Andernfalls (ohne diese Einstellungen) würde Debezium standardmäßig für jedes Tabellenobjekt ein Topic erstellen: pg-dev.public.

.

Einschränkungen des Connectors

Abschließend zur Beschreibung der Connector-Konfiguration für PostgreSQL sollten einige Merkmale/Einschränkungen seiner Funktionsweise erwähnt werden:

  1. Die Funktionalität des Connectors für PostgreSQL basiert auf dem Konzept der logischen Dekodierung. Daher verfolgt er keine Anfragen zur Änderung der Datenbankstruktur (DDL) – folglich werden in diesen Daten keine Topics erstellt.
  2. Da Replikations-Slots verwendet werden, kann der Connector nur mit dem führenden Systemdatenbank-Instanz verbunden werden.
  3. Wenn dem Benutzer, unter dem der Connector mit der Datenbank verbunden ist, nur Lesezugriffsrechte erteilt wurden, muss vor dem ersten Start manuell ein Replikations-Slot und eine Veröffentlichung in der Datenbank erstellt werden.

Anwendung der Konfiguration

Lassen Sie uns also unsere Konfiguration in den Connector laden:

curl -i -X POST -H "Accept:application/json" 
  -H  "Content-Type:application/json"  http://localhost:8083/connectors/ 
  -d @pg-con.json

Wir überprüfen, ob das Laden erfolgreich war und der Connector gestartet wurde:

$ curl -i http://localhost:8083/connectors/pg-connector/status 
HTTP/1.1 200 OK
Date: Thu, 17 Sep 2020 20:19:40 GMT
Content-Type: application/json
Content-Length: 175
Server: Jetty(9.4.20.v20190813)

{"name":"pg-connector","connector":{"state":"RUNNING","worker_id":"172.24.0.5:8083"},"tasks":[{"id":0,"state":"RUNNING","worker_id":"172.24.0.5:8083"}],"type":"source"}

Ausgezeichnet: Er ist konfiguriert und bereit für den Einsatz. Lassen Sie uns jetzt als Consumer auftreten und eine Verbindung zu Kafka herstellen, um dann einen Datensatz in der Tabelle hinzuzufügen und zu ändern:

$ kafka/bin/kafka-console-consumer.sh 
  --bootstrap-server kafka:9092 
  --from-beginning 
  --property print.key=true 
  --topic data.cdc.dbname

postgres=# insert into customers (id, first_name, last_name, email) values (1005, 'foo', 'bar', 'foo@bar.com');
INSERT 0 1
postgres=# update customers set first_name = 'egg' where id = 1005;
UPDATE 1

In unserem Thema wird dies wie folgt dargestellt:

Ein sehr langes JSON mit unseren Änderungen

{
  "schema":{
    "type":"struct",
    "fields":[
      {
        "type":"int32",
        "optional":false,
        "field":"id"
      }
    ],
    "optional":false,
    "name":"data.cdc.dbname.Key"
  },
  "payload":{
    "id":1005
  }
}{
  "schema":{
    "type":"struct",
    "fields":[
      {
        "type":"struct",
        "fields":[
          {
            "type":"int32",
            "optional":false,
            "field":"id"
          },
          {
            "type":"string",
            "optional":false,
            "field":"first_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"last_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"email"
          }
        ],
        "optional":true,
        "name":"data.cdc.dbname.Value",
        "field":"before"
      },
      {
        "type":"struct",
        "fields":[
          {
            "type":"int32",
            "optional":false,
            "field":"id"
          },
          {
            "type":"string",
            "optional":false,
            "field":"first_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"last_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"email"
          }
        ],
        "optional":true,
        "name":"data.cdc.dbname.Value",
        "field":"after"
      },
      {
        "type":"struct",
        "fields":[
          {
            "type":"string",
            "optional":false,
            "field":"version"
          },
          {
            "type":"string",
            "optional":false,
            "field":"connector"
          },
          {
            "type":"string",
            "optional":false,
            "field":"name"
          },
          {
            "type":"int64",
            "optional":false,
            "field":"ts_ms"
          },
          {
            "type":"string",
            "optional":true,
            "name":"io.debezium.data.Enum",
            "version":1,
            "parameters":{
              "allowed":"true,last,false"
            },
            "default":"false",
            "field":"snapshot"
          },
          {
            "type":"string",
            "optional":false,
            "field":"db"
          },
          {
            "type":"string",
            "optional":false,
            "field":"schema"
          },
          {
            "type":"string",
            "optional":false,
            "field":"table"
          },
          {
            "type":"int64",
            "optional":true,
            "field":"txId"
          },
          {
            "type":"int64",
            "optional":true,
            "field":"lsn"
          },
          {
            "type":"int64",
            "optional":true,
            "field":"xmin"
          }
        ],
        "optional":false,
        "name":"io.debezium.connector.postgresql.Source",
        "field":"source"
      },
      {
        "type":"string",
        "optional":false,
        "field":"op"
      },
      {
        "type":"int64",
        "optional":true,
        "field":"ts_ms"
      },
      {
        "type":"struct",
        "fields":[
          {
            "type":"string",
            "optional":false,
            "field":"id"
          },
          {
            "type":"int64",
            "optional":false,
            "field":"total_order"
          },
          {
            "type":"int64",
            "optional":false,
            "field":"data_collection_order"
          }
        ],
        "optional":true,
        "field":"transaction"
      }
    ],
    "optional":false,
    "name":"data.cdc.dbname.Envelope"
  },
  "payload":{
    "before":null,
    "after":{
      "id":1005,
      "first_name":"foo",
      "last_name":"bar",
      "email":"foo@bar.com"
    },
    "source":{
      "version":"1.2.3.Final",
      "connector":"postgresql",
      "name":"dbserver1",
      "ts_ms":1600374991648,
      "snapshot":"false",
      "db":"postgres",
      "schema":"public",
      "table":"customers",
      "txId":602,
      "lsn":34088472,
      "xmin":null
    },
    "op":"c",
    "ts_ms":1600374991762,
    "transaction":null
  }
}{
  "schema":{
    "type":"struct",
    "fields":[
      {
        "type":"int32",
        "optional":false,
        "field":"id"
      }
    ],
    "optional":false,
    "name":"data.cdc.dbname.Key"
  },
  "payload":{
    "id":1005
  }
}{
  "schema":{
    "type":"struct",
    "fields":[
      {
        "type":"struct",
        "fields":[
          {
            "type":"int32",
            "optional":false,
            "field":"id"
          },
          {
            "type":"string",
            "optional":false,
            "field":"first_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"last_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"email"
          }
        ],
        "optional":true,
        "name":"data.cdc.dbname.Value",
        "field":"before"
      },
      {
        "type":"struct",
        "fields":[
          {
            "type":"int32",
            "optional":false,
            "field":"id"
          },
          {
            "type":"string",
            "optional":false,
            "field":"first_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"last_name"
          },
          {
            "type":"string",
            "optional":false,
            "field":"email"
          }
        ],
        "optional":true,
        "name":"data.cdc.dbname.Value",
        "field":"after"
      },
      {
        "type":"struct",
        "fields":[
          {
            "type":"string",
            "optional":false,
            "field":"version"
          },
          {
            "type":"string",
            "optional":false,
            "field":"connector"
          },
          {
            "type":"string",
            "optional":false,
            "field":"name"
          },
          {
            "type":"int64",
            "optional":false,
            "field":"ts_ms"
          },
          {
            "type":"string",
            "optional":true,
            "name":"io.debezium.data.Enum",
            "version":1,
            "parameters":{
              "allowed":"true,last,false"
            },
            "default":"false",
            "field":"snapshot"
          },
          {
            "type":"string",
            "optional":false,
            "field":"db"
          },
          {
            "type":"string",
            "optional":false,
            "field":"schema"
          },
          {
            "type":"string",
            "optional":false,
            "field":"table"
          },
          {
            "type":"int64",
            "optional":true,
            "field":"txId"
          },
          {
            "type":"int64",
            "optional":true,
            "field":"lsn"
          },
          {
            "type":"int64",
            "optional":true,
            "field":"xmin"
          }
        ],
        "optional":false,
        "name":"io.debezium.connector.postgresql.Source",
        "field":"source"
      },
      {
        "type":"string",
        "optional":false,
        "field":"op"
      },
      {
        "type":"int64",
        "optional":true,
        "field":"ts_ms"
      },
      {
        "type":"struct",
        "fields":[
          {
            "type":"string",
            "optional":false,
            "field":"id"
          },
          {
            "type":"int64",
            "optional":false,
            "field":"total_order"
          },
          {
            "type":"int64",
            "optional":false,
            "field":"data_collection_order"
          }
        ],
        "optional":true,
        "field":"transaction"
      }
    ],
    "optional":false,
    "name":"data.cdc.dbname.Envelope"
  },
  "payload":{
    "before":{
      "id":1005,
      "first_name":"foo",
      "last_name":"bar",
      "email":"foo@bar.com"
    },
    "after":{
      "id":1005,
      "first_name":"egg",
      "last_name":"bar",
      "email":"foo@bar.com"
    },
    "source":{
      "version":"1.2.3.Final",
      "connector":"postgresql",
      "name":"dbserver1",
      "ts_ms":1600375609365,
      "snapshot":"false",
      "db":"postgres",
      "schema":"public",
      "table":"customers",
      "txId":603,
      "lsn":34089688,
      "xmin":null
    },
    "op":"u",
    "ts_ms":1600375609778,
    "transaction":null
  }
}

In beiden Fällen bestehen die Aufzeichnungen aus dem Schlüssel (PK) des Datensatzes, der geändert wurde, und dem Inhalt der Änderungen: Wie der Datensatz vorher war und wie er danach aussieht.

  • Im Fall von INSERT: Der Wert vor (before) ist gleich null, und nachher ist es die eingefügte Zeichenfolge.
  • Im Fall von UPDATE: In payload.before wird der vorherige Zustand der Zeichenfolge angezeigt, und in payload.after – der neue mit dem Inhalt der Änderungen.

2.2 MongoDB

Dieser Connector verwendet den standardmäßigen Replikationsmechanismus von MongoDB, indem er Informationen aus dem oplog des primären DB-Knotens liest.

Ähnlich wie beim bereits beschriebenen Connector für PgSQL wird hier beim ersten Start auch ein primärer Snapshot der Daten erstellt, danach wechselt der Connector in den Lesemodus des oplogs.

Beispielkonfiguration:

{
  "name": "mp-k8s-mongo-connector",
  "config": {
        "connector.class": "io.debezium.connector.mongodb.MongoDbConnector",
        "tasks.max": "1",
        "mongodb.hosts": "MainRepSet/mongo:27017",
        "mongodb.name": "mongo",
        "mongodb.user": "debezium",
        "mongodb.password": "dbname",
        "database.whitelist": "db_1,db_2",
        "transforms": "AddPrefix",
        "transforms.AddPrefix.type": "org.apache.kafka.connect.transforms.RegexRouter",
        "transforms.AddPrefix.regex": "mongo.([a-zA-Z_0-9]*).([a-zA-Z_0-9]*)",
        "transforms.AddPrefix.replacement": "data.cdc.mongo_$1"
        }
  }

Wie bereits erwähnt, gibt es hier im Vergleich zum vorherigen Beispiel keine neuen Optionen, aber die Anzahl der Optionen, die sich auf die Verbindung zur Datenbank beziehen, und deren Präfixe wurde reduziert.

Einstellungen sinks In diesem Fall wird Folgendes gemacht: Der Name des Zielthemas wird aus dem Schema umgewandelt. .. in data.cdc.mongo_.

Ausfallsicherheit

Die Frage der Fehlertoleranz und Hochverfügbarkeit ist heutzutage so relevant wie nie – insbesondere wenn wir von Daten und Transaktionen sprechen, und das Tracking von Datenänderungen ist hierbei nicht unwichtig. Lassen Sie uns betrachten, was prinzipiell schiefgehen kann und was in jedem dieser Fälle mit Debezium passieren wird.

Es gibt drei Ausfallvarianten:

  1. Ausfall von Kafka Connect.Wenn Connect für den Einsatz im verteilten Modus konfiguriert ist, müssen mehreren Workern dieselbe group.id zugewiesen werden. Wenn einer von ihnen ausfällt, wird der Connector auf einem anderen Worker neu gestartet und liest von der letzten bestätigten Position im Thema in Kafka weiter.
  2. Verlust der Konnektivität mit dem Kafka-Cluster.Der Connector stoppt einfach das Lesen an der Position, die nicht an Kafka gesendet werden konnte, und wird in regelmäßigen Abständen versuchen, sie erneut zu senden, bis der Versuch erfolgreich ist.
  3. Datenquelle nicht verfügbar. Der Connector wird versuchen, sich entsprechend der Konfiguration wieder mit der Quelle zu verbinden. Standardmäßig sind dies 16 Versuche mit exponential backoff. Nach dem 16. fehlgeschlagenen Versuch wird der Task als fehlgeschlagen markiert und muss manuell über die REST-Schnittstelle von Kafka Connect neu gestartet werden.
    • Im Fall von PostgreSQL Die Daten gehen nicht verloren, da die Verwendung von Replikationsslots verhindert, dass WAL-Dateien gelöscht werden, die noch nicht vom Connector gelesen wurden. In diesem Fall gibt es jedoch auch eine Kehrseite: Wenn die Netzwerkverbindung zwischen dem Connector und der DBMS über längere Zeit unterbrochen ist, besteht die Gefahr, dass der Speicherplatz auf der Festplatte ausgeht, was zu einem vollständigen Ausfall der DBMS führen kann.
    • Im Fall von MySQL Die Binärprotokolldateien können von der DBMS früher archiviert werden, als die Konnektivität wiederhergestellt wird. Dies führt dazu, dass der Connector in den Zustand 'fehlgeschlagen' wechselt, und um den normalen Betrieb wiederherzustellen, ist ein Neustart im Modus 'initial snapshot' erforderlich, um das Lesen der Binärprotokolle fortzusetzen.
    • Über MongoDB. Die Dokumentation besagt, dass das Verhalten des Connectors, wenn die Logdateien/oLog gelöscht wurden und der Connector nicht an der Stelle, an der er aufgehört hat, weiterlesen kann, für alle DBMS gleich ist. Es liegt darin, dass der Connector in den Zustand wechselt fehlgeschlagen und einen Neustart im Modus anfordert initialen Snapshot.

      Es gibt jedoch Ausnahmen. Wenn der Connector längere Zeit offline war (oder nicht mit einer MongoDB-Instanz kommunizieren konnte) und der oLog während dieser Zeit rotiert wurde, wird der Connector beim Wiederherstellen der Verbindung ruhig von der ersten verfügbaren Position aus weiter lesen, wodurch Teile der Daten in Kafka nicht gelangen.

Fazit

Debezium ist meine erste Erfahrung mit CDC-Systemen und insgesamt ziemlich positiv. Das Projekt überzeugt mit der Unterstützung der gängigen DBMS, der einfachen Konfiguration, der Clusterunterstützung und einer aktiven Community. Allen, die sich für die Praxis interessieren, empfehle ich, sich die Anleitungen für Kafka Connect anzusehen. und Debezium.

Im Gegensatz zum JDBC-Connector für Kafka Connect bietet Debezium den Vorteil, dass Änderungen aus den Datenbankprotokollen gelesen werden, was eine Datenübertragung mit minimaler Verzögerung ermöglicht. Der JDBC-Connector (aus der Kafka Connect-Bibliothek) führt Anfragen an die überwachte Tabelle in festen Intervallen aus und generiert (aus diesem Grund) keine Nachrichten bei der Löschung von Daten (wie kann man Daten anfordern, die nicht vorhanden sind?).

Für vergleichbare Aufgaben können folgende Lösungen in Betracht gezogen werden (neben Debezium):

P.S.

Lesen Sie auch in unserem Blog:

Quelle: habr.com

Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen 🔥 Zuverlässiges Webhosting mit DDoS-Schutz, VPS- und VDS-Server kaufen | ProHoster