This commit is contained in:
Juan Ferrer 2022-10-25 13:20:22 +02:00
parent 77270ed10d
commit 4cc2dc067a
5 changed files with 138 additions and 47 deletions

2
.gitignore vendored
View File

@ -1,3 +1,3 @@
node_modules node_modules
zongji zongji
config.local.json config.local.yml

View File

@ -1,6 +1,7 @@
debug: true debug: true
testMode: true testMode: false
code: mycdc
db: db:
host: localhost host: localhost
port: 3306 port: 3306
@ -15,7 +16,7 @@ consumerDb:
database: util database: util
amqp: amqp://user:password@localhost:5672 amqp: amqp://user:password@localhost:5672
pingInterval: 60 pingInterval: 60
flushInterval: 5000 flushInterval: 10
queues: queues:
orderTotal: orderTotal:
query: CALL hedera.order_recalc(?) query: CALL hedera.order_recalc(?)
@ -23,18 +24,17 @@ queues:
includeSchema: includeSchema:
hedera: hedera:
order: order:
fk: id key: id
events:
- updaterows
columns: columns:
- id - id
- address_id - address_id
- company_id - company_id
- date_send - date_send
- customer_id - customer_id
events:
- updaterows
orderRow: orderRow:
fk: orderFk key: orderFk
table: order
columns: columns:
- id - id
- orderFk - orderFk
@ -42,25 +42,95 @@ queues:
- warehouseFk - warehouseFk
- shipment - shipment
- amount - amount
comparative: ticketTotal:
query: CALL vn.comparative_refresh(?table, ?id, ?data) query: CALL vn.ticket_recalc(?)
mode: changes mode: fk
includeSchema: includeSchema:
vn: vn:
ticket: ticket:
id: id key: id
events:
- updaterows
columns: columns:
- id - id
- shipped - shipped
- warehouseFk - warehouseFk
- isDeleted - clientFk
events:
- updaterows
sale: sale:
id: id key: ticketFk
columns: columns:
- id - id
- ticketFk - ticketFk
- itemFk - itemFk
- quantity - quantity
- price - price
comparative:
query: CALL vn.comparative_refresh(?, ?, ?)
mode: changes
includeSchema:
vn:
ticket:
key: id
columns:
- id
- shipped
- warehouseFk
- isDeleted
events:
- updaterows
sale:
key: id
columns:
- id
- ticketFk
- itemFk
- quantity
- price
stock:
query: CALL stock.available_refresh(?, ?, ?)
mode: changes
includeSchema:
vn:
ticket:
key: id
columns:
- id
- shipped
- warehouseFk
- isDeleted
events:
- updaterows
sale:
key: id
columns:
- id
- ticketFk
- itemFk
- quantity
travel:
key: id
columns:
- id
- shipped
- landing
- warehouseInFk
- warehouseOutFk
- isDelivered
- isReceived
events:
- updaterows
entry:
key: id
columns:
- id
- travelFk
events:
- updaterows
buy:
key: id
columns:
- id
- entryFk
- itemFk
- quantity
- life

View File

@ -4,6 +4,8 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const mysql = require('mysql2/promise'); const mysql = require('mysql2/promise');
const amqp = require('amqplib'); const amqp = require('amqplib');
const {cpus} = require('os');
class Consumer { class Consumer {
async start() { async start() {
@ -21,6 +23,8 @@ class Consumer {
console.log('Starting process.'); console.log('Starting process.');
await this.init(); await this.init();
console.log('Process started.'); console.log('Process started.');
await this.consumeQueues();
} }
async stop() { async stop() {
@ -33,16 +37,20 @@ class Consumer {
const config = this.config; const config = this.config;
this.onErrorListener = err => this.onError(err); this.onErrorListener = err => this.onError(err);
this.db = await mysql.createConnection(config.consumerDb); const dbConfig = Object.assign({
this.db.on('error', this.onErrorListener); connectionLimit: cpus().length
}, config.consumerDb);
this.pingInterval = setInterval( this.db = await mysql.createPool(dbConfig);
() => this.connectionPing(), config.pingInterval * 1000); this.db.on('error', this.onErrorListener);
this.consumer = await amqp.connect(config.amqp); this.consumer = await amqp.connect(config.amqp);
this.channel = await this.consumer.createChannel(); this.channel = await this.consumer.createChannel();
this.channel.prefetch(1);
}
for (const queueName in config.queues) { async consumeQueues() {
for (const queueName in this.config.queues) {
await this.channel.assertQueue(queueName, { await this.channel.assertQueue(queueName, {
durable: true durable: true
}); });
@ -52,8 +60,6 @@ class Consumer {
} }
async end(silent) { async end(silent) {
clearInterval(this.pingInterval);
await this.consumer.close(); await this.consumer.close();
this.db.off('error', this.onErrorListener); this.db.off('error', this.onErrorListener);
@ -67,29 +73,41 @@ class Consumer {
} }
} }
async connectionPing() {
this.debug('Ping', 'Sending ping to database.');
await this.db.ping();
}
async onConsume(msg, queueName) { async onConsume(msg, queueName) {
const config = this.config; const config = this.config;
const data = JSON.parse(msg.content.toString()); const data = JSON.parse(msg.content.toString());
if (config.debug) if (config.debug)
console.debug('Message:'.blue, queueName.yellow, fks); console.debug('Message:'.blue, queueName.yellow, data.table);
const queue = config.queues[queueName]; const queue = config.queues[queueName];
const query = queue.query; let query = queue.query;
if (!query) return; if (!query) return;
if (!config.testMode) // XXX: Testing
//query = 'SELECT 1 sleep';
switch(queue.mode) { switch(queue.mode) {
case 'fk': case 'fk':
for (const fk of data.fks) for (const fk of data.fks) {
const sql = this.db.format(query, fk);
this.debug('SQL', sql);
if (!config.testMode)
await this.db.query(query, fk); await this.db.query(query, fk);
}
break; break;
case 'changes': case 'changes':
const queueTable = queue.includeSchema[data.schema][data.table];
for (const row of data.rows) {
const sql = this.db.format(query, [
data.table,
row[queueTable.key],
JSON.stringify(row)
]);
this.debug('SQL', sql);
if (!config.testMode)
await this.db.query(query, row);
}
break; break;
} }

View File

@ -18,7 +18,6 @@ module.exports = class MyCDC {
this.filename = null; this.filename = null;
this.position = null; this.position = null;
this.schemaMap = new Map(); this.schemaMap = new Map();
this.fks = new Set();
this.queues = {}; this.queues = {};
} }
@ -146,7 +145,7 @@ module.exports = class MyCDC {
const [res] = await this.db.query( const [res] = await this.db.query(
'SELECT `logName`, `position` FROM `binlogQueue` WHERE code = ?', 'SELECT `logName`, `position` FROM `binlogQueue` WHERE code = ?',
[config.queue] [config.code]
); );
if (res.length) { if (res.length) {
const [row] = res; const [row] = res;
@ -181,7 +180,7 @@ module.exports = class MyCDC {
this.zongji.on('error', this.onErrorListener); this.zongji.on('error', this.onErrorListener);
this.flushInterval = setInterval( this.flushInterval = setInterval(
() => this.flushQueue(), config.flushInterval); () => this.flushQueue(), config.flushInterval * 1000);
this.pingInterval = setInterval( this.pingInterval = setInterval(
() => this.connectionPing(), config.pingInterval * 1000); () => this.connectionPing(), config.pingInterval * 1000);
@ -310,26 +309,31 @@ module.exports = class MyCDC {
change.fks = new Set(); change.fks = new Set();
break; break;
case 'changes': case 'changes':
change.rows = {}; change.rows = [];
break; break;
} }
} }
function addChange(row, queueNames) { function addChange(queueNames, row, old) {
for (const queueName of queueNames) { for (const queueName of queueNames) {
const queueInfo = tableQueues.get(queueName); const queueInfo = tableQueues.get(queueName);
const change = changes.get(queueName); const change = changes.get(queueName);
const key = row[queueInfo.key];
const oldKey = old ? old[queueInfo.key] : null;
switch(change.mode) { switch(change.mode) {
case 'fk': case 'fk':
change.fks.add(row[queueInfo.fk]); change.fks.add(key);
if (old && !equals(oldKey, key))
change.fks.add(oldKey);
break; break;
case 'changes': case 'changes':
const queueRow = {}; const queueRow = {};
for (const column of queueInfo.columns) for (const column of queueInfo.columns)
if (row[column] !== undefined) if (row[column] !== undefined)
queueRow[column] = row[column]; queueRow[column] = row[column];
change.rows[row[queueInfo.id]] = queueRow; change.rows.push(queueRow);
break; break;
} }
} }
@ -357,12 +361,11 @@ module.exports = class MyCDC {
} }
if (changedQueues.size) if (changedQueues.size)
addChange(after, changedQueues); addChange(changedQueues, after, row.before);
} }
if (!changes) return;
} else { } else {
for (const row of rows) for (const row of rows)
addChange(row, queueNames); addChange(queueNames, row);
} }
for (const [queueName, change] of changes) { for (const [queueName, change] of changes) {
@ -391,7 +394,7 @@ module.exports = class MyCDC {
this.channel.sendToQueue(queueName, this.channel.sendToQueue(queueName,
Buffer.from(data), {persistent: true}); Buffer.from(data), {persistent: true});
console.debug('Queued'.blue, queueName.yellow, `[${eventName}] ${table.tableName}`); console.debug('Queued:'.blue, `${queueName}:`.yellow, `${table.tableName}(${nChanges}) [${eventName}]`);
} }
this.position = evt.nextPosition; this.position = evt.nextPosition;
@ -403,9 +406,9 @@ module.exports = class MyCDC {
this.debug('Flush', `filename: ${this.filename}, position: ${this.position}`); this.debug('Flush', `filename: ${this.filename}, position: ${this.position}`);
const replaceQuery = const replaceQuery =
'REPLACE INTO `binlogQueue` SET `code` = ?, `logName` = ?, `position` = ?'; 'REPLACE INTO `binlogQueue` SET `code` = ?, `logName` = ?, `position` = ?';
if (!this.config.testMode) if (!this.config.testMode)
await this.db.query(replaceQuery, [this.config.queue, this.filename, this.position]); await this.db.query(replaceQuery, [this.config.code, this.filename, this.position]);
this.flushed = true; this.flushed = true;
} }

View File

@ -9,4 +9,4 @@ docker run \
-e RABBITMQ_DEFAULT_PASS=password \ -e RABBITMQ_DEFAULT_PASS=password \
-p 5672:5672 \ -p 5672:5672 \
-p 8080:15672 \ -p 8080:15672 \
rabbitmq:3-management rabbitmq:3.11.2-management