Introduktion till Debezium – CDC för Apache Kafka

Introduktion till Debezium – CDC för Apache Kafka

I mitt arbete stöter jag ofta pÄ nya tekniska lösningar/programprodukter, och informationen om dem pÄ svensksprÄkiga internet Àr ganska knapp. I den hÀr artikeln försöker jag fylla en sÄdan lucka med ett exempel frÄn min senaste praktik, dÀr jag behövde konfigurera utskick av CDC-hÀndelser frÄn tvÄ populÀra databashanterare (PostgreSQL och MongoDB) till ett Kafka-kluster med hjÀlp av Debezium. Jag hoppas att denna översiktsartikel, som kommit till efter genomförd verksamhet, kommer att vara till nytta Àven för andra.

Vad Àr Debezium och vad Àr CDC?

Debezium Àr en representant för kategorin CDC-programvara (Capture Data Change), och mer exakt, det Àr en uppsÀttning anslutare för olika databashanterare som Àr kompatibla med ramverket Apache Kafka Connect.

Detta Ett Open Source-projekt, som anvÀnder licensen Apache License v2.0 och sponsras av företaget Red Hat. Utvecklingen pÄbörjades 2016 och i nulÀget erbjuds officiellt stöd för följande databashanterare: MySQL, PostgreSQL, MongoDB, SQL Server. Det finns ocksÄ anslutare för Cassandra och Oracle, men dessa Àr för nÀrvarande i statusen "early access", och nya versioner garanterar inte bakÄtkompatibilitet.

JÀmfört med den traditionella metoden (dÀr applikationen lÀser data direkt frÄn databashanteraren) Àr dess frÀmsta fördelar en realisering av strömmande datÀndringar pÄ radnivÄ med lÄg latens, hög tillförlitlighet och tillgÀnglighet. De tvÄ sista punkterna uppnÄs genom att anvÀnda ett Kafka-kluster som lagringslösning för CDC-hÀndelser.

En annan fördel Àr att en enhetlig modell anvÀnds för lagring av hÀndelser, vilket innebÀr att den slutgiltiga applikationen inte behöver oroa sig för nyanserna av olika databashanterare.

Slutligen öppnar anvÀndningen av en meddelandebroker möjligheter för horisontell skalning av applikationer som spÄrar datÀndringar. Samtidigt minimeras pÄverkan pÄ datakÀllan, eftersom datainhÀmtning sker inte direkt frÄn databashanteraren, utan frÄn Kafka-klustret.

Om Debeziums arkitektur

AnvÀndningen av Debezium kan sammanfattas i ett sÄ enkelt schema:

Databashanterare (som datakĂ€lla) → anslutare i Kafka Connect → Apache Kafka → konsument

Som illustration ger jag schemat frÄn projektets webbplats:

Introduktion till Debezium – CDC för Apache Kafka

Men detta schema tilltalar mig inte sÀrskilt mycket, eftersom det ger intryck av att endast sink-connectorn Àr möjlig att anvÀnda.

I sjĂ€lva verket Ă€r situationen en annan: fyllningen av din Data Lake (den sista lĂ€nken i schemat ovan) ­— det Ă€r inte det enda sĂ€ttet att anvĂ€nda Debezium. HĂ€ndelser som skickas till Apache Kafka kan anvĂ€ndas av dina applikationer för att lösa olika situationer. Till exempel:

  • borttagning av irrelevanta data frĂ„n cachen;
  • skickande av meddelanden;
  • uppdateringar av sökindex;
  • nĂ„gon form av revisionsloggar;
  • 


Om du har en Java-applikation och inte har behov/kan anvÀnda ett Kafka-kluster, finns det Àven möjlighet att arbeta genom embedded-connector. Den uppenbara fördelen Àr att du kan avstÄ frÄn ytterligare infrastruktur (i form av connector och Kafka). Men denna lösning har deklarerats som förÄldrad (deprecated) sedan version 1.1 och rekommenderas inte lÀngre för anvÀndning (i framtida releaser kan stödet tas bort).

Denna artikel kommer att ta upp den rekommenderade arkitekturen frÄn utvecklarna, som erbjuder redundans och skalbarhet.

Connector-konfiguration

För att börja övervaka förĂ€ndringar av den mest vĂ€rdefulla tillgĂ„ngen — datan — behöver vi:

  1. en datakÀlla, som kan vara MySQL frÄn version 5.7, PostgreSQL 9.6+, MongoDB 3.2+ (fullstÀndig lista);
  2. Apache Kafka-kluster;
  3. Kafka Connect-instans (versioner 1.x, 2.x);
  4. konfigurerad Debezium-connector.

Arbetet med de första tvÄ punkterna, d.v.s. installationsprocessen av DBMS och Apache Kafka, ligger utanför ramarna för artikeln. Men för dem som vill stÀlla upp allt i en sandbox finns det en fÀrdig docker-compose.yaml.

Vi kommer istÀllet att gÄ djupare in pÄ de tvÄ sista punkterna.

0. Kafka Connect

HÀr och lÀngre fram i artikeln behandlas alla konfigurations exempel i kontexten av Docker-image, som distribueras av Debezium-utvecklarna. Den innehÄller alla nödvÀndiga plugin-filer (connectors) och möjliggör konfiguration av Kafka Connect med hjÀlp av miljövariabler.

Om det antas att Kafka Connect frÄn Confluent ska anvÀndas, krÀvs det att du sjÀlv lÀgger till de nödvÀndiga connectorns plugins i katalogen som anges i plugin.path eller som anges via miljövariabeln CLASSPATHInstÀllningar för Kafka Connect-arbetare och anslutningar definieras genom konfigurationsfiler som skickas som argument till kommandot för att starta arbetaren. LÀs mer i dokumentationen.

Hela processen för att konfigurera Debezium med en anslutning utförs i tvÄ steg. LÄt oss titta pÄ varje steg:

1. Konfigurera Kafka Connect-ramverket

För att strömma data till Apache Kafka-klustret i Kafka Connect-ramverket anges specifika parametrar, sÄsom:

  • anslutningsparametrar till klustret,
  • namn pĂ„ de Ă€mnen dĂ€r sjĂ€lva konfigurationen av anslutningen kommer att lagras,
  • namnet pĂ„ gruppen dĂ€r anslutningen körs (i fallet med distribuerat lĂ€ge).

Den officiella Docker-bilden för projektet stöder konfiguration via miljövariabler — och det Ă€r vad vi kommer att anvĂ€nda. SĂ„, ladda ner bilden:

docker pull debezium/connect

Minimala uppsÀttningar av miljövariabler som krÀvs för att starta anslutningen ser ut som följer:

  • BOOTSTRAP_SERVERS=kafka-1:9092,kafka-2:9092,kafka-3:9092 — den initiala listan över Kafka-klusterservrar för att fĂ„ den fullstĂ€ndiga listan över klustermedlemmar;
  • OFFSET_STORAGE_TOPIC=connector-offsets — Ă€mnet för att lagra positionerna för var anslutningen för nĂ€rvarande Ă€r;
  • CONNECT_STATUS_STORAGE_TOPIC=connector-status — Ă€mnet för att lagra statusen för anslutningen och dess uppgifter;
  • CONFIG_STORAGE_TOPIC=connector-config — Ă€mnet för att lagra data om konfigurationen för anslutningen och dess uppgifter;
  • GROUP_ID=1 — identifierare för gruppen arbetare dĂ€r uppgiften för anslutningen kan köras; nödvĂ€ndig vid anvĂ€ndning av distribuerat (distributed) lĂ€ge.

Starta containern med dessa variabler:

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

Obs för Avro

Som standard skriver Debezium data i JSON-format, vilket Àr acceptabelt för sandbox-miljöer och smÄ datamÀngder, men kan bli ett problem i högbelastade databaser. Ett alternativ till JSON-konvertern Àr serialisering av meddelanden med hjÀlp av Avro i binÀrt format, vilket minskar belastningen pÄ I/O-systemet i Apache Kafka.

För att anvÀnda Avro krÀvs det att en separat schema-registry (för att lagra scheman). Variablerna för konvertern kommer att se ut som följer:

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

Detaljer om anvĂ€ndning av Avro och konfiguration av registry ligger utanför artikelns Ă€mne — för tydlighetens skull kommer vi att anvĂ€nda JSON som exempel.

2. Konfigurera sjÀlva connectorn

Nu kan vi gÄ vidare till konfigurationen av sjÀlva connectorn som kommer att lÀsa data frÄn kÀllan.

Vi kommer att titta pÄ exempel pÄ connectors för tvÄ databashanteringssystem: PostgreSQL och MongoDB, för vilka jag har erfarenhet och dÀr det finns skillnader (Àven om de Àr smÄ, sÄ kan de i vissa fall vara betydelsefulla!).

Konfigurationen beskrivs i JSON-notation och laddas upp till Kafka Connect via en POST-förfrÄgan.

2.1. PostgreSQL

Exempel pÄ konfiguration för connectorn 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"
  }
}

Principen för hur connectorn fungerar efter denna konfiguration Àr ganska enkel:

  • Vid första starten ansluter den till databasen som anges i konfigurationen och startar i lĂ€get initial snapshot, och skickar en initial uppsĂ€ttning data till Kafka, hĂ€mtade med hjĂ€lp av en SELECT * FROM table_name.
  • NĂ€r initieringen Ă€r klar gĂ„r connectorn över till att lĂ€sa Ă€ndringar frĂ„n WAL-filerna i PostgreSQL.

Om de anvÀnda alternativen:

  • name — namnet pĂ„ connectorn som konfigurationen gĂ€ller för; detta namn anvĂ€nds senare för att arbeta med connectorn (dvs. se status/omstart/uppdatera konfigurationen) via Kafka Connects REST API;
  • connector.class — klassen för databashanteringssystemet (DBMS) connectorn ska anvĂ€nda;
  • plugin.name — namnet pĂ„ pluginet för logisk avkodning av data frĂ„n WAL-filer. TillgĂ€ngliga alternativ inkluderar wal2json, decoderbuffs och pgoutput. De tvĂ„ första krĂ€ver installation av motsvarande tillĂ€gg i DBMS, medan pgoutput för PostgreSQL version 10 och senare inte krĂ€ver nĂ„gra ytterligare Ă„tgĂ€rder;
  • database.* — alternativ för att ansluta till databasen, dĂ€r database.server.name — namnet pĂ„ PostgreSQL-instansen som anvĂ€nds för att bilda namnet pĂ„ Ă€mnet i Kafka-klustret;
  • table.include.list — lista över tabeller dĂ€r vi vill spĂ„ra Ă€ndringar; anges i formatet schema.table_name; kan inte anvĂ€ndas tillsammans med table.exclude.list;
  • heartbeat.interval.ms — intervallet (i millisekunder) med vilket adaptern skickar heartbeat-meddelanden till ett speciellt Ă€mne;
  • heartbeat.action.query — frĂ„gan som kommer att utföras vid varje skickat heartbeat-meddelande (alternativet introducerades med version 1.1);
  • slot.name — namnet pĂ„ replikeringsslotet som ska anvĂ€ndas av adaptern;
  • publication.name — namnet publikation i PostgreSQL som adaptern anvĂ€nder. Om det inte finns kommer Debezium att försöka skapa det. Om anvĂ€ndaren, som adapteranslutningen görs med, inte har tillrĂ€ckligt med rĂ€ttigheter för detta kommer adaptern att avsluta med ett fel;
  • transforms definierar hur mĂ„lets Ă€mnesnamn ska Ă€ndras:
    • transforms.AddPrefix.type anger att vi kommer att anvĂ€nda reguljĂ€ra uttryck;
    • transforms.AddPrefix.regex — masken vars namn pĂ„ mĂ„lets Ă€mne kommer att Ă„sidosĂ€ttas;
    • transforms.AddPrefix.replacement — det som vi faktiskt Ă„sidosĂ€tter.

Mer om heartbeat och transforms

Som standard skickar adaptern data till Kafka för varje begÄngen transaktion, och dess LSN (Log Sequence Number) skrivs till en tjÀnstÀmne offset. Men vad kommer att hÀnda om adaptern Àr instÀlld pÄ att lÀsa inte hela databasen utan bara delar av dess tabeller (dÀr datauppdateringar inte sker sÄ ofta)?

  • Adaptern kommer att lĂ€sa WAL-filer och inte upptĂ€cka nĂ„gra begĂ„ngna transaktioner i de tabeller som den övervakar.
  • DĂ€rför kommer den inte att uppdatera sin nuvarande position varken i Ă€mnet eller i replikeringsslotet.
  • Detta kommer i sin tur att leda till "hĂ„llande" av WAL-filer pĂ„ disken och potentiellt utnyttjande av allt diskutrymme.

Och hÀr kommer alternativen till hjÀlp. heartbeat.interval.ms och heartbeat.action.query. Genom att anvÀnda dessa alternativ tillsammans gör det möjligt att vid varje sÀndning av ett heartbeat-meddelande köra en frÄga för att Àndra data i en separat tabell. PÄ sÄ sÀtt hÄlls LSN, dÀr adaptern just nu befinner sig (i replikeringsslotet), stÀndigt aktuellt. Detta gör att databashanteraren kan ta bort WAL-filer som inte lÀngre behövs. Mer information om hur alternativen fungerar kan fÄs i dokumentationen.

Ett annat alternativ som Ă€r vĂ€rt mer uppmĂ€rksamhet Ă€r transforms. Även om det snarare handlar om bekvĂ€mlighet och estetik...

Som standard skapar Debezium Àmnen baserat pÄ följande namngivningspolicy: serverName.schemaName.tableName. Detta kanske inte alltid Àr praktiskt. Alternativen transforms kan anvÀnda reguljÀra uttryck för att definiera en lista med tabeller vars hÀndelser behöver routas till ett Àmne med ett specifikt namn.

I vÄr konfiguration tack vare transforms sker följande: alla CDC-hÀndelser frÄn den övervakade databasen kommer att hamna i ett Àmne med namnet data.cdc.dbname. Annars (utan dessa instÀllningar) skulle Debezium som standard skapa ett Àmne för varje tabell liknande: pg-dev.public.

.

BegrÀnsningar för anslutningen

Avslutningsvis i beskrivningen av anslutningskonfigurationen för PostgreSQL Àr det vÀrt att nÀmna följande funktioner/begrÀnsningar:

  1. Funktionaliteten för anslutningen till PostgreSQL bygger pĂ„ konceptet logisk dekodning. DĂ€rför spĂ„rar den inte frĂ„gor för att Ă€ndra databasens struktur (DDL) – dĂ€rför kommer dessa data inte att finnas i Ă€mnena.
  2. Eftersom replikeringsslott anvÀnds, Àr anslutningen till anslutningen möjlig bara till den ledande instansen av DBMS.
  3. Om anvÀndaren under vilket anslutningen kopplar upp sig mot databasen bara har lÀsbehörighet, mÄste man manuellt skapa ett replikeringsslott och en publik i databasen före den första körningen.

TillÀmpning av konfigurationen

SÄ, lÄt oss ladda vÄr konfiguration till anslutningen:

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

Vi kontrollerar att laddningen lyckades och att anslutningen startade:

$ 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"}

UtmÀrkt: den Àr instÀlld och klar för arbete. Nu spelar vi konsument och kopplar upp oss mot Kafka, varefter vi lÀgger till och Àndrar en post i tabellen:

$ 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

I vÄrt Àmne kommer detta att visas pÄ följande sÀtt:

Mycket lÄng JSON med vÄra Àndringar

{
  "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
  }
}

I bÄda fallen bestÄr posterna av nyckeln (PK) till posten som har Àndrats och sjÀlva essensen av Àndringarna: hur posten sÄg ut före och hur den ser ut efter.

  • NĂ€r det gĂ€ller INSÄTT: vĂ€rdet före (before) Ă€r lika med null, och efterĂ„t — en strĂ€ng som har lagts till.
  • NĂ€r det gĂ€ller UPPDATERA: i payload.before visar det tidigare tillstĂ„ndet för raden, och i payload.after — det nya med essensen av Ă€ndringarna.

2.2 MongoDB

Den hÀr kopplingen anvÀnder standardreplikationsmekanismen för MongoDB och lÀser information frÄn primÀrnodens oplog.

PÄ samma sÀtt som den tidigare beskrivna kopplingen för PgSQL, tas en primÀr snapshot av data vid första körningen, varefter kopplingen vÀxlar till lÀslÀge för oplogen.

Exempelkonfiguration:

{
  "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"
        }
  }

Som man kan mÀrka, finns det inga nya alternativ jÀmfört med det tidigare exemplet, men antalet alternativ som ansvarar för databaskopplingar och deras prefix har minskat.

InstÀllningar transforms denna gÄng gör följande: omvandlar namnet pÄ den mÄltopic frÄn mönstret .. pÄ data.cdc.mongo_.

Fel-tolerans

FrĂ„gan om redundans och hög tillgĂ€nglighet Ă€r viktigare Ă€n nĂ„gonsin — speciellt nĂ€r vi pratar om data och transaktioner, och spĂ„rning av dataförĂ€ndringar stĂ„r inte i sidlinjen. LĂ„t oss övervĂ€ga vad som i princip kan gĂ„ fel och vad som kommer att hĂ€nda med Debezium i varje fall.

Det finns tre typer av fel:

  1. Fel pÄ Kafka Connect. Om Connect Àr instÀllt pÄ att arbeta i distribuerat lÀge, mÄste flera arbetare ange samma group.id. DÄ kommer kopplingen att startas om pÄ en annan arbetare och fortsÀtta lÀsa frÄn den senaste bekrÀftade positionen i topicen i Kafka.
  2. Förlust av koppling till Kafka-klustret. Kopplingen kommer helt enkelt att stoppa lÀsningen vid den position som inte kunde skickas till Kafka och kommer periodiskt att försöka skicka den igen tills försöket lyckas.
  3. OtillgÀnglighet av datakÀllan. Anslutningen kommer att försöka Äteransluta till kÀllan enligt konfigurationen. Som standard Àr det 16 försök med hjÀlp av exponentiell backoff. Efter det 16:e misslyckade försöket kommer uppgiften att markeras som misslyckad och kommer att krÀva manuell omstart via REST-grÀnssnittet för Kafka Connect.
    • NĂ€r det gĂ€ller PostgreSQL Data kommer inte att gĂ„ förlorade, eftersom anvĂ€ndningen av replikationsslotar förhindrar att WAL-filer tas bort som inte har lĂ€sts av anslutningen. I detta fall finns det ocksĂ„ en nackdel: om nĂ€tverksanslutningen mellan anslutningen och databashanteraren bryts under en lĂ€ngre tid, finns det en risk för att diskutrymmet tar slut, vilket kan leda till en total felaktighet i databashanteraren.
    • NĂ€r det gĂ€ller MySQL Binlog-filer kan roteras av databashanteraren tidigare Ă€n att anslutningen Ă„terfĂ„r sin anslutning. Detta kommer att leda till att anslutningen gĂ„r in i ett misslyckat tillstĂ„nd, och för att Ă„terstĂ€lla normal funktion mĂ„ste den startas om i lĂ€get initial snapshot för att fortsĂ€tta lĂ€sa frĂ„n binloggarna.
    • Om MongoDB. Dokumentationen anger: beteendet hos anslutningen nĂ€r loggfiler/oplog har tagits bort och anslutningen inte kan fortsĂ€tta lĂ€sa frĂ„n den position dĂ€r den slutade, Ă€r detsamma för alla databashanterare. Det innebĂ€r att anslutningen gĂ„r in i ett tillstĂ„nd misslyckad och krĂ€ver en omstart i lĂ€get initial snapshot.

      Det finns dock undantag. Om anslutningen har varit avstÀngd under en lÀngre tid (eller inte har kunnat nÄ MongoDB-instansen), och oplog har roterat under den tiden, kommer anslutningen vid ÄterstÀllande av anslutningen lugnt att fortsÀtta lÀsa data frÄn den första tillgÀngliga positionen, vilket gör att en del data i Kafka inte kommer att inkluderas.

Slutsats

Debezium — min första erfarenhet av CDC-system och överlag mycket positiv. Projektet imponerade med stöd för de frĂ€msta databashanterarna, enkel konfigurering, support för kluster och ett aktivt samhĂ€lle. För dem som Ă€r intresserade av praktiken rekommenderar jag att ta del av guiderna för Kafka Connect och Debezium.

JÀmfört med JDBC-anslutningen för Kafka Connect Àr den frÀmsta fördelen med Debezium att förÀndringar lÀses frÄn databashanterarens loggar, vilket möjliggör datainsamling med minimal fördröjning. JDBC Connector (frÄn Kafka Connect-leveransen) gör begÀrningar till den övervakade tabellen med fasta intervall och (av samma anledning) genererar inte meddelanden vid borttagning av data (hur kan man begÀra data som inte finns?).

För att lösa liknande uppgifter kan man övervÀga följande lösningar (förutom Debezium):

P.S.

LÀs Àven i vÄr blogg:

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster