mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
feat(server): add cluster mode for starting app (#4895)
* feat(server): add cluster mode for starting app * chore(env): adjust order of env item * chore: sync in main data source * chore: onSync in plugin * refactor(server): change onSync to plugin method --------- Co-authored-by: Chareice <chareice@live.com>
This commit is contained in:
parent
c1dba8ac91
commit
1c9e71dbf6
@ -29,6 +29,12 @@ LOGGER_MAX_SIZE=
|
|||||||
# console | json | logfmt | delimiter
|
# console | json | logfmt | delimiter
|
||||||
LOGGER_FORMAT=
|
LOGGER_FORMAT=
|
||||||
|
|
||||||
|
# Start application in cluster mode when the value is set (same as pm2 -i <cluster_mode>).
|
||||||
|
# Cluster mode will only work properly when plugins related to distributed architecture are enabled.
|
||||||
|
# Otherwise, the application's functionality may encounter unexpected issues.
|
||||||
|
# The cluster mode will not work in development mode either.
|
||||||
|
CLUSTER_MODE=
|
||||||
|
|
||||||
################# DATABASE #################
|
################# DATABASE #################
|
||||||
|
|
||||||
# postgres | msysql | mariadb | sqlite
|
# postgres | msysql | mariadb | sqlite
|
||||||
|
@ -33,6 +33,7 @@ module.exports = (cli) => {
|
|||||||
.command('start')
|
.command('start')
|
||||||
.option('-p, --port [port]')
|
.option('-p, --port [port]')
|
||||||
.option('-d, --daemon')
|
.option('-d, --daemon')
|
||||||
|
.option('-i, --instances [instances]')
|
||||||
.option('--db-sync')
|
.option('--db-sync')
|
||||||
.option('--quickstart')
|
.option('--quickstart')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
@ -61,13 +62,16 @@ module.exports = (cli) => {
|
|||||||
}
|
}
|
||||||
await postCheck(opts);
|
await postCheck(opts);
|
||||||
deleteSockFiles();
|
deleteSockFiles();
|
||||||
|
const instances = opts.instances || process.env.CLUSTER_MODE;
|
||||||
|
const instancesArgs = instances ? ['-i', instances] : [];
|
||||||
if (opts.daemon) {
|
if (opts.daemon) {
|
||||||
run('pm2', ['start', `${APP_PACKAGE_ROOT}/lib/index.js`, '--', ...process.argv.slice(2)]);
|
run('pm2', ['start', ...instancesArgs, `${APP_PACKAGE_ROOT}/lib/index.js`, '--', ...process.argv.slice(2)]);
|
||||||
} else {
|
} else {
|
||||||
run(
|
run(
|
||||||
'pm2-runtime',
|
'pm2-runtime',
|
||||||
[
|
[
|
||||||
'start',
|
'start',
|
||||||
|
...instancesArgs,
|
||||||
`${APP_PACKAGE_ROOT}/lib/index.js`,
|
`${APP_PACKAGE_ROOT}/lib/index.js`,
|
||||||
NODE_ARGS ? `--node-args="${NODE_ARGS}"` : undefined,
|
NODE_ARGS ? `--node-args="${NODE_ARGS}"` : undefined,
|
||||||
'--',
|
'--',
|
||||||
|
@ -18,6 +18,7 @@ import { resolve } from 'path';
|
|||||||
import { Application } from './application';
|
import { Application } from './application';
|
||||||
import { InstallOptions, getExposeChangelogUrl, getExposeReadmeUrl } from './plugin-manager';
|
import { InstallOptions, getExposeChangelogUrl, getExposeReadmeUrl } from './plugin-manager';
|
||||||
import { checkAndGetCompatible, getPluginBasePath } from './plugin-manager/utils';
|
import { checkAndGetCompatible, getPluginBasePath } from './plugin-manager/utils';
|
||||||
|
import { SyncMessageData } from './sync-manager';
|
||||||
|
|
||||||
export interface PluginInterface {
|
export interface PluginInterface {
|
||||||
beforeLoad?: () => void;
|
beforeLoad?: () => void;
|
||||||
@ -133,6 +134,12 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
|||||||
|
|
||||||
async afterRemove() {}
|
async afterRemove() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired when a sync message is received.
|
||||||
|
* @experimental
|
||||||
|
*/
|
||||||
|
onSync(message: SyncMessageData): Promise<void> | void {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
|
@ -34,7 +34,7 @@ export class SyncManager {
|
|||||||
private nodeId: string;
|
private nodeId: string;
|
||||||
private app: Application;
|
private app: Application;
|
||||||
private eventEmitter = new EventEmitter();
|
private eventEmitter = new EventEmitter();
|
||||||
private adapter = null;
|
private adapter: SyncAdapter = null;
|
||||||
private incomingBuffer: SyncMessageData[] = [];
|
private incomingBuffer: SyncMessageData[] = [];
|
||||||
private outgoingBuffer: [string, SyncMessageData][] = [];
|
private outgoingBuffer: [string, SyncMessageData][] = [];
|
||||||
private flushTimer: NodeJS.Timeout = null;
|
private flushTimer: NodeJS.Timeout = null;
|
||||||
@ -43,12 +43,21 @@ export class SyncManager {
|
|||||||
return this.adapter ? this.adapter.ready : false;
|
return this.adapter ? this.adapter.ready : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onMessage(namespace, message) {
|
||||||
|
this.app.logger.info(`emit sync event in namespace ${namespace}`);
|
||||||
|
this.eventEmitter.emit(namespace, message);
|
||||||
|
const pluginInstance = this.app.pm.get(namespace);
|
||||||
|
pluginInstance.onSync(message);
|
||||||
|
}
|
||||||
|
|
||||||
private onSync = (messages: SyncMessage[]) => {
|
private onSync = (messages: SyncMessage[]) => {
|
||||||
this.app.logger.info('sync messages received into buffer:', messages);
|
this.app.logger.info('sync messages received, save into buffer:', messages);
|
||||||
|
|
||||||
if (this.flushTimer) {
|
if (this.flushTimer) {
|
||||||
clearTimeout(this.flushTimer);
|
clearTimeout(this.flushTimer);
|
||||||
this.flushTimer = null;
|
this.flushTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.incomingBuffer = uniqWith(
|
this.incomingBuffer = uniqWith(
|
||||||
this.incomingBuffer.concat(
|
this.incomingBuffer.concat(
|
||||||
messages
|
messages
|
||||||
@ -60,9 +69,9 @@ export class SyncManager {
|
|||||||
|
|
||||||
this.flushTimer = setTimeout(() => {
|
this.flushTimer = setTimeout(() => {
|
||||||
this.incomingBuffer.forEach(({ namespace, ...message }) => {
|
this.incomingBuffer.forEach(({ namespace, ...message }) => {
|
||||||
this.app.logger.info(`emit sync event in namespace ${namespace}`);
|
this.onMessage(namespace, message);
|
||||||
this.eventEmitter.emit(namespace, message);
|
|
||||||
});
|
});
|
||||||
|
this.incomingBuffer = [];
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -82,9 +91,11 @@ export class SyncManager {
|
|||||||
if (this.adapter) {
|
if (this.adapter) {
|
||||||
throw new Error('sync adapter is already exists');
|
throw new Error('sync adapter is already exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.adapter = adapter;
|
this.adapter = adapter;
|
||||||
this.adapter.on('message', this.onSync);
|
this.adapter.on('message', this.onSync);
|
||||||
this.adapter.on('ready', this.onReady);
|
this.adapter.on('ready', this.onReady);
|
||||||
|
@ -40,6 +40,19 @@ export class PluginDataSourceMainServer extends Plugin {
|
|||||||
this.loadFilter = filter;
|
this.loadFilter = filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async onSync(message) {
|
||||||
|
const { type, collectionName } = message;
|
||||||
|
if (type === 'newCollection') {
|
||||||
|
const collectionModel: CollectionModel = await this.app.db.getCollection('collections').repository.findOne({
|
||||||
|
filter: {
|
||||||
|
name: collectionName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await collectionModel.load();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
if (this.app.db.inDialect('postgres')) {
|
if (this.app.db.inDialect('postgres')) {
|
||||||
this.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public';
|
this.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public';
|
||||||
@ -77,6 +90,11 @@ export class PluginDataSourceMainServer extends Plugin {
|
|||||||
await model.migrate({
|
await model.migrate({
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.app.syncManager.publish(this.name, {
|
||||||
|
type: 'newCollection',
|
||||||
|
collectionName: model.get('name'),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -66,7 +66,7 @@ export default class PluginFileManagerServer extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSync = async (message) => {
|
async onSync(message) {
|
||||||
if (message.type === 'storageChange') {
|
if (message.type === 'storageChange') {
|
||||||
const storage = await this.db.getRepository('storages').findOne({
|
const storage = await this.db.getRepository('storages').findOne({
|
||||||
filterByTk: message.storageId,
|
filterByTk: message.storageId,
|
||||||
@ -79,7 +79,7 @@ export default class PluginFileManagerServer extends Plugin {
|
|||||||
const id = Number.parseInt(message.storageId, 10);
|
const id = Number.parseInt(message.storageId, 10);
|
||||||
this.storagesCache.delete(id);
|
this.storagesCache.delete(id);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
async beforeLoad() {
|
async beforeLoad() {
|
||||||
this.db.registerModels({ FileModel });
|
this.db.registerModels({ FileModel });
|
||||||
@ -90,8 +90,6 @@ export default class PluginFileManagerServer extends Plugin {
|
|||||||
});
|
});
|
||||||
this.app.on('afterStart', async () => {
|
this.app.on('afterStart', async () => {
|
||||||
await this.loadStorages();
|
await this.loadStorages();
|
||||||
|
|
||||||
this.app.syncManager.subscribe(this.name, this.onSync);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSync = async (message) => {
|
async onSync(message) {
|
||||||
if (message.type === 'statusChange') {
|
if (message.type === 'statusChange') {
|
||||||
const workflowId = Number.parseInt(message.workflowId, 10);
|
const workflowId = Number.parseInt(message.workflowId, 10);
|
||||||
const enabled = Number.parseInt(message.enabled, 10);
|
const enabled = Number.parseInt(message.enabled, 10);
|
||||||
@ -133,7 +133,7 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @experimental
|
* @experimental
|
||||||
@ -284,7 +284,6 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
this.app.on('afterStart', async () => {
|
this.app.on('afterStart', async () => {
|
||||||
this.app.setMaintainingMessage('check for not started executions');
|
this.app.setMaintainingMessage('check for not started executions');
|
||||||
this.ready = true;
|
this.ready = true;
|
||||||
this.app.syncManager.subscribe(this.name, this.onSync);
|
|
||||||
|
|
||||||
const collection = db.getCollection('workflows');
|
const collection = db.getCollection('workflows');
|
||||||
const workflows = await collection.repository.find({
|
const workflows = await collection.repository.find({
|
||||||
@ -308,8 +307,6 @@ export default class PluginWorkflowServer extends Plugin {
|
|||||||
this.toggle(workflow, false);
|
this.toggle(workflow, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.app.syncManager.unsubscribe('workflow', this.onSync);
|
|
||||||
|
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
if (this.events.length) {
|
if (this.events.length) {
|
||||||
await this.prepare();
|
await this.prepare();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user