Este tema describe cómo sincronizar una base de datos MySQL completa con Kafka. Este enfoque reduce la carga que múltiples trabajos ejercen sobre la base de datos MySQL.
Contexto
Una tabla de origen MySQL CDC captura datos de MySQL y sincroniza los cambios en tiempo real de dicha tabla. Esta práctica es común en escenarios de cómputo complejos, como cuando una tabla funciona como tabla de dimensiones en una operación JOIN con otras tablas de datos. Una sola tabla de MySQL puede ser dependencia de varios trabajos. Cuando múltiples trabajos procesan datos de la misma tabla MySQL, la base de datos abre varias conexiones, lo cual ejerce una presión significativa sobre el servidor MySQL y la red.
Funcionamiento
Para reducir la presión sobre la base de datos MySQL ascendente, Realtime Compute for Apache Flink permite sincronizar una base de datos MySQL completa con Kafka. Esta solución introduce Kafka como capa intermedia y utiliza un trabajo de ingesta de datos de Flink CDC para sincronizar la información hacia Kafka.
En un solo trabajo, los datos de una base de datos MySQL ascendente se sincronizan con Kafka en tiempo real. Cada tabla de MySQL se escribe en un topic de Kafka correspondiente en modo upsert. Posteriormente, los trabajos descendentes utilizan un conector upsert de Kafka para leer los datos de los topics en lugar de acceder directamente a las tablas de MySQL. Este método reduce eficazmente la presión que múltiples trabajos ejercen sobre la base de datos MySQL.

Limitaciones
Cada tabla de MySQL que sincronice debe tener una clave primaria.
Puede utilizar clústeres de Kafka autogestionados, clústeres EMR Kafka o ApsaraMQ for Kafka. Si utiliza ApsaraMQ for Kafka, solo podrá conectarse mediante un default endpoint.
El espacio de almacenamiento del clúster de Kafka debe ser mayor que el de las tablas de origen. De lo contrario, podrían perderse datos por falta de almacenamiento. Los topics creados para la sincronización de bases de datos son topics compactados. En un topic compactado, solo se conserva el mensaje más reciente para cada clave de mensaje, pero los datos nunca caducan. Esto significa que el topic compactado almacena un volumen de datos aproximadamente equivalente al tamaño de la tabla de origen.
Escenario de ejemplo
Por ejemplo, en un escenario de análisis de revisión de pedidos en tiempo real, suponga que existen tres tablas: una tabla de usuarios (user), una tabla de pedidos (order) y una tabla de comentarios de usuarios (feedback). Las tablas contienen los datos que se muestran en la siguiente figura.
Para mostrar la información de los pedidos y las reseñas de los usuarios, es necesario unir la tabla user para obtener los nombres de usuario del campo name. El siguiente ejemplo de SQL demuestra esta operación.
-- Join order information with the user table to display the username and product name for each order.
SELECT order.id as order_id, product, user.name as user_name
FROM order LEFT JOIN user
ON order.user_id = user.id;
-- Join reviews with the user table to display the content of each review and the corresponding username.
SELECT feedback.id as feedback_id, comment, user.name as user_name
FROM feedback LEFT JOIN user
ON feedback.user_id = user.id;
En los dos trabajos SQL anteriores, ambos utilizan la tabla user. Durante la ejecución, ambos trabajos leen los datos completos e incrementales de MySQL. Una lectura completa requiere crear una conexión a MySQL, mientras que una lectura incremental requiere crear un cliente de Binlog. A medida que aumenta el número de trabajos, también crece la demanda de recursos de conexión a MySQL y de clientes de Binlog, lo que ejerce una presión significativa sobre la base de datos ascendente. Para aliviar esta presión, puede utilizar un trabajo de ingesta de datos de Flink CDC para sincronizar los datos de la base de datos MySQL ascendente con Kafka en tiempo real, permitiendo así su consumo por parte de múltiples trabajos descendentes.
Requisitos previos
Realtime Compute for Apache Flink está activo. Para obtener más información, consulte Activate Realtime Compute for Apache Flink.
ApsaraMQ for Kafka está activo. Para obtener más información, consulte Deploy an ApsaraMQ for Kafka instance.
ApsaraDB RDS for MySQL está activo. Para obtener más información, consulte Create an ApsaraDB RDS for MySQL instance.
Sus servicios de Realtime Compute for Apache Flink, ApsaraDB RDS for MySQL y ApsaraMQ for Kafka se encuentran en la misma VPC. Si están en diferentes VPC, debe habilitar el acceso a la red entre VPC o utilizar endpoints públicos. Para obtener más información, consulte How do I access other services across VPCs? y How do I access the Internet?.
Si accede a los recursos como usuario de RAM o mediante un rol de RAM, debe contar con los permisos necesarios.
Preparación
Preparar el origen de datos de MySQL
-
Cree una base de datos ApsaraDB RDS for MySQL. Para obtener más información, consulte Create a database.
Cree una base de datos llamada
order_dwpara la instancia de destino. -
Prepare el origen de datos MySQL CDC.
En la página de detalles de la instancia, haga clic en Log on to Database en la parte superior de la página.
En el cuadro de diálogo de inicio de sesión de DMS que aparece, introduzca el nombre de usuario y la contraseña de la cuenta de base de datos que creó, y luego haga clic en Login.
Después de iniciar sesión, haga doble clic en la base de datos
order_dwen el panel izquierdo para cambiar de base de datos.-
En la consola SQL, introduzca las sentencias DDL para crear las tres tablas de negocio y las sentencias para insertar datos.
CREATE TABLE `user` ( id bigint not null primary key, name varchar(50) not null ); CREATE TABLE `order` ( id bigint not null primary key, product varchar(50) not null, user_id bigint not null ); CREATE TABLE `feedback` ( id bigint not null primary key, user_id bigint not null, comment varchar(50) not null ); -- Prepare data INSERT INTO `user` VALUES(1, 'Tom'),(2, 'Jerry'); INSERT INTO `order` VALUES (1, 'Football', 2), (2, 'Basket', 1); INSERT INTO `feedback` VALUES (1, 1, 'Good.'), (2, 2, 'Very good');
Haga clic en Execute y, a continuación, haga clic en Execute.
Procedimiento
-
Cree e inicie un trabajo de ingesta de datos de Flink CDC para sincronizar los datos de la base de datos MySQL ascendente con Kafka en tiempo real, de modo que puedan ser consumidos por múltiples trabajos descendentes. El trabajo de sincronización de base de datos crea automáticamente los topics. Puede definir los nombres de los topics mediante el módulo
route. Los topics utilizan la configuración predeterminada del clúster de Kafka para el número de particiones y réplicas, ycleanup.policyse establece encompact.Nombres de topics predeterminados
De forma predeterminada, los topics de Kafka creados por el trabajo de sincronización de base de datos utilizan el formato de nomenclatura
{database_name}.{table_name}. El siguiente trabajo crea tres topics:order_dw.user,order_dw.orderyorder_dw.feedback.-
En la página , cree un trabajo de ingesta de datos de Flink CDC y copie el siguiente código en el editor YAML.
source: type: mysql name: MySQL Source hostname: #{hostname} port: 3306 username: #{usernmae} password: #{password} tables: order_dw.\.* server-id: 28601-28604 # (Optional) Synchronize data from newly created tables during the incremental phase. scan.binlog.newly-added-table.enabled: true # (Optional) Synchronize table and field comments. include-comments.enabled: true # (Optional) Prioritize dispatching unbounded splits to avoid potential TaskManager OutOfMemory issues. scan.incremental.snapshot.unbounded-chunk-first.enabled: true # (Optional) Enable parsing filters to speed up reads. scan.only.deserialize.captured.tables.changelog.enabled: true sink: type: upsert-kafka name: upsert-kafka Sink properties.bootstrap.servers: xxxx.alikafka.aliyuncs.com:9092 # The following parameters are required for ApsaraMQ for Kafka. aliyun.kafka.accessKeyId: #{ak} aliyun.kafka.accessKeySecret: #{sk} aliyun.kafka.instanceId: #{instanceId} aliyun.kafka.endpoint: #{endpoint} aliyun.kafka.regionId: #{regionId} En la esquina superior derecha, haga clic en Deploy para desplegar el trabajo.
En la barra de navegación izquierda, haga clic en . En la columna Actions del trabajo objetivo, haga clic en Start, seleccione Initial Mode y, a continuación, haga clic en Start.
Nombres de topics por tabla
Puede utilizar el módulo
routepara especificar un nombre de topic para cada tabla. El siguiente trabajo crea tres topics:user1,order2yfeedback3.-
En la página , cree un trabajo de ingesta de datos de Flink CDC y copie el siguiente código en el editor YAML.
source: type: mysql name: MySQL Source hostname: #{hostname} port: 3306 username: #{usernmae} password: #{password} tables: order_dw.\.* server-id: 28601-28604 # (Optional) Synchronize data from newly created tables during the incremental phase. scan.binlog.newly-added-table.enabled: true # (Optional) Synchronize table and field comments. include-comments.enabled: true # (Optional) Prioritize dispatching unbounded splits to avoid potential TaskManager OutOfMemory issues. scan.incremental.snapshot.unbounded-chunk-first.enabled: true # (Optional) Enable parsing filters to speed up reads. scan.only.deserialize.captured.tables.changelog.enabled: true route: - source-table: order_dw.user sink-table: user1 - source-table: order_dw.order sink-table: order2 - source-table: order_dw.feedback sink-table: feedback3 sink: type: upsert-kafka name: upsert-kafka Sink properties.bootstrap.servers: xxxx.alikafka.aliyuncs.com:9092 # The following parameters are required for ApsaraMQ for Kafka. aliyun.kafka.accessKeyId: #{ak} aliyun.kafka.accessKeySecret: #{sk} aliyun.kafka.instanceId: #{instanceId} aliyun.kafka.endpoint: #{endpoint} aliyun.kafka.regionId: #{regionId} En la esquina superior derecha, haga clic en Deploy para desplegar el trabajo.
En la barra de navegación izquierda, seleccione , haga clic en Start en la columna Actions del trabajo objetivo, seleccione Initial Mode y, a continuación, haga clic en Start.
Nombres de topics por lotes
Puede utilizar el módulo
routepara especificar un patrón para los nombres de los topics generados. El siguiente trabajo crea tres topics:topic_user,topic_orderytopic_feedback.-
En la página , cree un trabajo de ingesta de datos de Flink CDC y copie el siguiente código en el editor YAML.
source: type: mysql name: MySQL Source hostname: #{hostname} port: 3306 username: #{usernmae} password: #{password} tables: order_dw.\.* server-id: 28601-28604 # (Optional) Synchronize data from newly created tables during the incremental phase. scan.binlog.newly-added-table.enabled: true # (Optional) Synchronize table and field comments. include-comments.enabled: true # (Optional) Prioritize dispatching unbounded splits to avoid potential TaskManager OutOfMemory issues. scan.incremental.snapshot.unbounded-chunk-first.enabled: true # (Optional) Enable parsing filters to speed up reads. scan.only.deserialize.captured.tables.changelog.enabled: true route: - source-table: order_dw.\.* sink-table: topic_<> replace-symbol: <> sink: type: upsert-kafka name: upsert-kafka Sink properties.bootstrap.servers: xxxx.alikafka.aliyuncs.com:9092 # The following parameters are required for ApsaraMQ for Kafka. aliyun.kafka.accessKeyId: #{ak} aliyun.kafka.accessKeySecret: #{sk} aliyun.kafka.instanceId: #{instanceId} aliyun.kafka.endpoint: #{endpoint} aliyun.kafka.regionId: #{regionId} En la esquina superior derecha, haga clic en Deploy para desplegar el trabajo.
En la barra de navegación izquierda, haga clic en . Haga clic en Start en la columna Actions del trabajo objetivo, seleccione Initial Mode y, a continuación, haga clic en Start.
-
-
Consuma datos de Kafka en tiempo real.
El trabajo de ingesta de datos escribe la información de la base de datos MySQL ascendente en Kafka en formato JSON. Posteriormente, múltiples trabajos descendentes pueden consumir datos de un solo topic para recuperar el estado más reciente de las tablas de la base de datos. Puede consumir los datos de las tablas sincronizadas con Kafka de una de las siguientes maneras:
Mediante catálogo
Lea datos de un topic de Kafka utilizándolo como tabla de origen.
-
En la página , cree un trabajo SQL de streaming y copie el siguiente código en el editor SQL.
CREATE TEMPORARY TABLE print_user_proudct( order_id BIGINT, product STRING, user_name STRING ) WITH ( 'connector'='print', 'logger'='true' ); CREATE TEMPORARY TABLE print_user_feedback( feedback_id BIGINT, `comment` STRING, user_name STRING ) WITH ( 'connector'='print', 'logger'='true' ); BEGIN STATEMENT SET; -- Required when writing to multiple sinks. -- Join order information with the user table in the Kafka JSON Catalog to display the username and product name for each order. INSERT INTO print_user_proudct SELECT `order`.key_id as order_id, value_product as product, `user`.value_name as user_name FROM `kafka-catalog`.`kafka`.`order`/*+OPTIONS('properties.group.id'='<yourGroupName>', 'scan.startup.mode'='earliest-offset')*/ as `order` -- Specify the group and startup mode. LEFT JOIN `kafka-catalog`.`kafka`.`user`/*+OPTIONS('properties.group.id'='<yourGroupName>', 'scan.startup.mode'='earliest-offset')*/ as `user` -- Specify the group and startup mode. ON `order`.value_user_id = `user`.key_id; -- Join reviews with the user table to display the content of each review and the corresponding username. INSERT INTO print_user_feedback SELECT feedback.key_id as feedback_id, value_comment as `comment`, `user`.value_name as user_name FROM `kafka-catalog`.`kafka`.feedback/*+OPTIONS('properties.group.id'='<yourGroupName>', 'scan.startup.mode'='earliest-offset')*/ as feedback -- Specify the group and startup mode. LEFT JOIN `kafka-catalog`.`kafka`.`user`/*+OPTIONS('properties.group.id'='<yourGroupName>', 'scan.startup.mode'='earliest-offset')*/ as `user` -- Specify the group and startup mode. ON feedback.value_user_id = `user`.key_id; END; -- Required when writing to multiple sinks.Este ejemplo utiliza el conector Print para imprimir los resultados directamente. También puede enviar los resultados a una tabla de resultados que utilice otro conector para un análisis posterior. Para obtener más información sobre la sintaxis para escribir en múltiples destinos, consulte INSERT INTO statement.
NotaCuando utiliza este método directamente, pueden producirse cambios en el esquema. Como resultado, el esquema analizado por el catálogo JSON de Kafka puede diferir del esquema de la tabla MySQL correspondiente. Por ejemplo, los campos eliminados pueden seguir apareciendo y algunos campos pueden tener valores nulos.
El esquema leído del catálogo consta de campos provenientes de los datos consumidos. Si se elimina un campo pero sus mensajes no han caducado, el campo puede seguir apareciendo con un valor nulo. No se requiere ningún manejo especial para este caso.
En la esquina superior derecha, haga clic en Deploy para desplegar el trabajo.
En la barra de navegación izquierda, haga clic en , haga clic en Start en la columna Actions del trabajo objetivo, seleccione Initial Mode y, a continuación, haga clic en Start.
Mediante tabla temporal
Defina un esquema personalizado y lea datos desde una tabla temporal.
-
En la página , cree un trabajo SQL de streaming y copie el siguiente código en el editor SQL.
CREATE TEMPORARY TABLE user_source ( key_id BIGINT, value_name STRING ) WITH ( 'connector' = 'kafka', 'topic' = 'user', 'properties.bootstrap.servers' = '<yourKafkaBrokers>', 'scan.startup.mode' = 'earliest-offset', 'key.format' = 'json', 'value.format' = 'json', 'key.fields' = 'key_id', 'key.fields-prefix' = 'key_', 'value.fields-prefix' = 'value_', 'value.fields-include' = 'EXCEPT_KEY', 'value.json.infer-schema.flatten-nested-columns.enable' = 'false', 'value.json.infer-schema.primitive-as-string' = 'false' ); CREATE TEMPORARY TABLE order_source ( key_id BIGINT, value_product STRING, value_user_id BIGINT ) WITH ( 'connector' = 'kafka', 'topic' = 'order', 'properties.bootstrap.servers' = '<yourKafkaBrokers>', 'scan.startup.mode' = 'earliest-offset', 'key.format' = 'json', 'value.format' = 'json', 'key.fields' = 'key_id', 'key.fields-prefix' = 'key_', 'value.fields-prefix' = 'value_', 'value.fields-include' = 'EXCEPT_KEY', 'value.json.infer-schema.flatten-nested-columns.enable' = 'false', 'value.json.infer-schema.primitive-as-string' = 'false' ); CREATE TEMPORARY TABLE feedback_source ( key_id BIGINT, value_user_id BIGINT, value_comment STRING ) WITH ( 'connector' = 'kafka', 'topic' = 'feedback', 'properties.bootstrap.servers' = '<yourKafkaBrokers>', 'scan.startup.mode' = 'earliest-offset', 'key.format' = 'json', 'value.format' = 'json', 'key.fields' = 'key_id', 'key.fields-prefix' = 'key_', 'value.fields-prefix' = 'value_', 'value.fields-include' = 'EXCEPT_KEY', 'value.json.infer-schema.flatten-nested-columns.enable' = 'false', 'value.json.infer-schema.primitive-as-string' = 'false' ); CREATE TEMPORARY TABLE print_user_proudct( order_id BIGINT, product STRING, user_name STRING ) WITH ( 'connector'='print', 'logger'='true' ); CREATE TEMPORARY TABLE print_user_feedback( feedback_id BIGINT, `comment` STRING, user_name STRING ) WITH ( 'connector'='print', 'logger'='true' ); BEGIN STATEMENT SET; -- Required when writing to multiple sinks. -- Join order information with the user table from the Kafka JSON Catalog to display the username and product name for each order. INSERT INTO print_user_proudct SELECT order_source.key_id as order_id, value_product as product, user_source.value_name as user_name FROM order_source LEFT JOIN user_source ON order_source.value_user_id = user_source.key_id; -- Join reviews with the user table to display the content of each review and the corresponding username. INSERT INTO print_user_feedback SELECT feedback_source.key_id as feedback_id, value_comment as `comment`, user_source.value_name as user_name FROM feedback_source LEFT JOIN user_source ON feedback_source.value_user_id = user_source.key_id; END; -- Required when writing to multiple sinks.Este ejemplo utiliza el conector Print para imprimir los resultados directamente. También puede enviar los resultados a una tabla de resultados de otro conector para un análisis posterior. Para obtener más información sobre la sintaxis para escribir en múltiples destinos, consulte INSERT INTO statement.
La siguiente tabla describe los parámetros de configuración de la tabla temporal.
Parámetro
Descripción
Notas
connectorEl tipo de conector.
Establezca el valor en
kafka.topicEl nombre del topic correspondiente.
Debe coincidir con la descripción del catálogo JSON de Kafka.
properties.bootstrap.serversLas direcciones de los brokers de Kafka.
El formato es
host:port,host:port,host:port, separado por comas (,).scan.startup.modeLa posición inicial para leer datos de Kafka.
Valores válidos:
-
earliest-offset: Comienza a leer desde el offset disponible más antiguo. -
latest-offset: Comienza a leer desde el offset más reciente. -
group-offsets(predeterminado): Lee desde el offset confirmado para el grupo especificado por properties.group.id. -
timestamp: Lee desde la marca de tiempo especificada por scan.startup.timestamp-millis.
-
specific-offsets: Comienza a leer desde los offsets especificados en scan.startup.specific-offsets.
Nota
Este parámetro surte efecto cuando el trabajo se inicia sin un estado guardado. Cuando el trabajo se reinicia o se recupera de un punto de control, prioriza la lectura desde el estado guardado.
key.formatEl formato que utiliza el conector Flink Kafka para serializar o deserializar la clave del mensaje de Kafka.
Establezca el valor en
json.key.fieldsLos campos de la tabla de origen o de resultados que corresponden a la clave del mensaje de Kafka.
Utilice punto y coma (;) para separar varios nombres de campo. Por ejemplo,
field1;field2.key.fields-prefixUn prefijo personalizado para todos los campos de clave de mensaje de Kafka, con el fin de evitar conflictos de nomenclatura con los campos de valor del mensaje o los campos de metadatos.
Este valor debe coincidir con el valor del parámetro key.fields-prefix del catálogo JSON de Kafka.
value.formatEl formato que utiliza el conector Flink Kafka para serializar o deserializar el valor del mensaje de Kafka.
Establezca el valor en
json.value.fields-prefixUn prefijo personalizado para todos los campos de valor de mensaje de Kafka, con el fin de evitar conflictos de nomenclatura con los campos de clave del mensaje o los campos de metadatos.
Debe coincidir con el valor del parámetro value.fields-prefix del catálogo JSON de Kafka.
value.fields-includeLa directiva para manejar los campos de clave de mensaje dentro del valor del mensaje.
Establezca el valor en
EXCEPT_KEY. Esto indica que el valor del mensaje no incluye los campos de la clave del mensaje.value.json.infer-schema.flatten-nested-columns.enableEspecifica si se deben expandir recursivamente las columnas JSON anidadas en el valor del mensaje de Kafka.
El valor del parámetro infer-schema.flatten-nested-columns.enable del catálogo correspondiente.
value.json.infer-schema.primitive-as-stringEspecifica si se deben inferir todos los tipos primitivos como String en el valor del mensaje de Kafka.
El valor del parámetro infer-schema.primitive-as-string del catálogo correspondiente.
-
En la esquina superior derecha, haga clic en Deploy para desplegar el trabajo.
En la barra de navegación izquierda, haga clic en , haga clic en Start en la columna Actions del trabajo objetivo, seleccione Initial Mode y, a continuación, haga clic en Start.
-
-
Consulte los resultados del trabajo.
En la barra de navegación izquierda, haga clic en y, a continuación, haga clic en el trabajo objetivo.
En la pestaña Logs, dentro de la pestaña Running Task Managers, haga clic en la tarea que tenga la Path, ID que desea consultar.
-
Haga clic en Logs y busque información de registro relacionada con
PrintSinkOutputWriter.Busque en los registros la salida de
PrintSinkOutputWriter. La salida contiene cuatro registros de datos unidos:+I[1, Good., Tom],+I[2, Very good, Jerry],+I[2, Basket, Tom]y+I[1, Football, Jerry]. Esto indica que las uniones entre la tabla de usuarios y las tablas de pedidos y comentarios se realizaron correctamente.