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
|
||||
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 #################
|
||||
|
||||
# postgres | msysql | mariadb | sqlite
|
||||
|
@ -33,6 +33,7 @@ module.exports = (cli) => {
|
||||
.command('start')
|
||||
.option('-p, --port [port]')
|
||||
.option('-d, --daemon')
|
||||
.option('-i, --instances [instances]')
|
||||
.option('--db-sync')
|
||||
.option('--quickstart')
|
||||
.allowUnknownOption()
|
||||
@ -61,13 +62,16 @@ module.exports = (cli) => {
|
||||
}
|
||||
await postCheck(opts);
|
||||
deleteSockFiles();
|
||||
const instances = opts.instances || process.env.CLUSTER_MODE;
|
||||
const instancesArgs = instances ? ['-i', instances] : [];
|
||||
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 {
|
||||
run(
|
||||
'pm2-runtime',
|
||||
[
|
||||
'start',
|
||||
...instancesArgs,
|
||||
`${APP_PACKAGE_ROOT}/lib/index.js`,
|
||||
NODE_ARGS ? `--node-args="${NODE_ARGS}"` : undefined,
|
||||
'--',
|
||||
|
@ -18,6 +18,7 @@ import { resolve } from 'path';
|
||||
import { Application } from './application';
|
||||
import { InstallOptions, getExposeChangelogUrl, getExposeReadmeUrl } from './plugin-manager';
|
||||
import { checkAndGetCompatible, getPluginBasePath } from './plugin-manager/utils';
|
||||
import { SyncMessageData } from './sync-manager';
|
||||
|
||||
export interface PluginInterface {
|
||||
beforeLoad?: () => void;
|
||||
@ -133,6 +134,12 @@ export abstract class Plugin<O = any> implements PluginInterface {
|
||||
|
||||
async afterRemove() {}
|
||||
|
||||
/**
|
||||
* Fired when a sync message is received.
|
||||
* @experimental
|
||||
*/
|
||||
onSync(message: SyncMessageData): Promise<void> | void {}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
@ -34,7 +34,7 @@ export class SyncManager {
|
||||
private nodeId: string;
|
||||
private app: Application;
|
||||
private eventEmitter = new EventEmitter();
|
||||
private adapter = null;
|
||||
private adapter: SyncAdapter = null;
|
||||
private incomingBuffer: SyncMessageData[] = [];
|
||||
private outgoingBuffer: [string, SyncMessageData][] = [];
|
||||
private flushTimer: NodeJS.Timeout = null;
|
||||
@ -43,12 +43,21 @@ export class SyncManager {
|
||||
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[]) => {
|
||||
this.app.logger.info('sync messages received into buffer:', messages);
|
||||
this.app.logger.info('sync messages received, save into buffer:', messages);
|
||||
|
||||
if (this.flushTimer) {
|
||||
clearTimeout(this.flushTimer);
|
||||
this.flushTimer = null;
|
||||
}
|
||||
|
||||
this.incomingBuffer = uniqWith(
|
||||
this.incomingBuffer.concat(
|
||||
messages
|
||||
@ -60,9 +69,9 @@ export class SyncManager {
|
||||
|
||||
this.flushTimer = setTimeout(() => {
|
||||
this.incomingBuffer.forEach(({ namespace, ...message }) => {
|
||||
this.app.logger.info(`emit sync event in namespace ${namespace}`);
|
||||
this.eventEmitter.emit(namespace, message);
|
||||
this.onMessage(namespace, message);
|
||||
});
|
||||
this.incomingBuffer = [];
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
@ -82,9 +91,11 @@ export class SyncManager {
|
||||
if (this.adapter) {
|
||||
throw new Error('sync adapter is already exists');
|
||||
}
|
||||
|
||||
if (!adapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.adapter = adapter;
|
||||
this.adapter.on('message', this.onSync);
|
||||
this.adapter.on('ready', this.onReady);
|
||||
|
@ -40,6 +40,19 @@ export class PluginDataSourceMainServer extends Plugin {
|
||||
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() {
|
||||
if (this.app.db.inDialect('postgres')) {
|
||||
this.schema = process.env.COLLECTION_MANAGER_SCHEMA || this.db.options.schema || 'public';
|
||||
@ -77,6 +90,11 @@ export class PluginDataSourceMainServer extends Plugin {
|
||||
await model.migrate({
|
||||
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') {
|
||||
const storage = await this.db.getRepository('storages').findOne({
|
||||
filterByTk: message.storageId,
|
||||
@ -79,7 +79,7 @@ export default class PluginFileManagerServer extends Plugin {
|
||||
const id = Number.parseInt(message.storageId, 10);
|
||||
this.storagesCache.delete(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async beforeLoad() {
|
||||
this.db.registerModels({ FileModel });
|
||||
@ -90,8 +90,6 @@ export default class PluginFileManagerServer extends Plugin {
|
||||
});
|
||||
this.app.on('afterStart', async () => {
|
||||
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') {
|
||||
const workflowId = Number.parseInt(message.workflowId, 10);
|
||||
const enabled = Number.parseInt(message.enabled, 10);
|
||||
@ -133,7 +133,7 @@ export default class PluginWorkflowServer extends Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @experimental
|
||||
@ -284,7 +284,6 @@ export default class PluginWorkflowServer extends Plugin {
|
||||
this.app.on('afterStart', async () => {
|
||||
this.app.setMaintainingMessage('check for not started executions');
|
||||
this.ready = true;
|
||||
this.app.syncManager.subscribe(this.name, this.onSync);
|
||||
|
||||
const collection = db.getCollection('workflows');
|
||||
const workflows = await collection.repository.find({
|
||||
@ -308,8 +307,6 @@ export default class PluginWorkflowServer extends Plugin {
|
||||
this.toggle(workflow, false);
|
||||
}
|
||||
|
||||
this.app.syncManager.unsubscribe('workflow', this.onSync);
|
||||
|
||||
this.ready = false;
|
||||
if (this.events.length) {
|
||||
await this.prepare();
|
||||
|
Loading…
x
Reference in New Issue
Block a user