mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
refactor(core): Organize Redis under scaling mode (#10864)
This commit is contained in:
@@ -3,11 +3,11 @@ import { mock } from 'jest-mock-extended';
|
||||
|
||||
import config from '@/config';
|
||||
import { generateNanoId } from '@/databases/utils/generators';
|
||||
import type { RedisClientService } from '@/services/redis/redis-client.service';
|
||||
import type {
|
||||
RedisServiceCommandObject,
|
||||
RedisServiceWorkerResponseObject,
|
||||
} from '@/services/redis/redis-service-commands';
|
||||
} from '@/scaling/redis/redis-service-commands';
|
||||
import type { RedisClientService } from '@/services/redis-client.service';
|
||||
|
||||
import { Publisher } from '../pubsub/publisher.service';
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Redis as SingleNodeClient } from 'ioredis';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
|
||||
import config from '@/config';
|
||||
import type { RedisClientService } from '@/services/redis/redis-client.service';
|
||||
import type { RedisClientService } from '@/services/redis-client.service';
|
||||
|
||||
import { Subscriber } from '../pubsub/subscriber.service';
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
export const QUEUE_NAME = 'jobs';
|
||||
|
||||
export const JOB_TYPE_NAME = 'job';
|
||||
|
||||
export const COMMAND_PUBSUB_CHANNEL = 'n8n.commands';
|
||||
|
||||
export const WORKER_RESPONSE_PUBSUB_CHANNEL = 'n8n.worker-response';
|
||||
|
||||
@@ -3,11 +3,11 @@ import { Service } from 'typedi';
|
||||
|
||||
import config from '@/config';
|
||||
import { Logger } from '@/logger';
|
||||
import { RedisClientService } from '@/services/redis/redis-client.service';
|
||||
import type {
|
||||
RedisServiceCommandObject,
|
||||
RedisServiceWorkerResponseObject,
|
||||
} from '@/services/redis/redis-service-commands';
|
||||
} from '@/scaling/redis/redis-service-commands';
|
||||
import { RedisClientService } from '@/services/redis-client.service';
|
||||
|
||||
/**
|
||||
* Responsible for publishing messages into the pubsub channels used by scaling mode.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {
|
||||
COMMAND_REDIS_CHANNEL,
|
||||
WORKER_RESPONSE_REDIS_CHANNEL,
|
||||
} from '@/services/redis/redis-constants';
|
||||
import type { PushType, WorkerStatus } from '@n8n/api-types';
|
||||
|
||||
import type { IWorkflowDb } from '@/interfaces';
|
||||
|
||||
import type { COMMAND_PUBSUB_CHANNEL, WORKER_RESPONSE_PUBSUB_CHANNEL } from '../constants';
|
||||
|
||||
/**
|
||||
* Pubsub channel used by scaling mode:
|
||||
@@ -10,5 +11,90 @@ import type {
|
||||
* - `n8n.worker-response` for messages sent by workers in response to commands from main processes
|
||||
*/
|
||||
export type ScalingPubSubChannel =
|
||||
| typeof COMMAND_REDIS_CHANNEL
|
||||
| typeof WORKER_RESPONSE_REDIS_CHANNEL;
|
||||
| typeof COMMAND_PUBSUB_CHANNEL
|
||||
| typeof WORKER_RESPONSE_PUBSUB_CHANNEL;
|
||||
|
||||
export type PubSubMessageMap = {
|
||||
// #region Lifecycle
|
||||
|
||||
'reload-license': never;
|
||||
|
||||
'restart-event-bus': {
|
||||
result: 'success' | 'error';
|
||||
error?: string;
|
||||
};
|
||||
|
||||
'reload-external-secrets-providers': {
|
||||
result: 'success' | 'error';
|
||||
error?: string;
|
||||
};
|
||||
|
||||
'stop-worker': never;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Community packages
|
||||
|
||||
'community-package-install': {
|
||||
packageName: string;
|
||||
packageVersion: string;
|
||||
};
|
||||
|
||||
'community-package-update': {
|
||||
packageName: string;
|
||||
packageVersion: string;
|
||||
};
|
||||
|
||||
'community-package-uninstall': {
|
||||
packageName: string;
|
||||
packageVersion: string;
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Worker view
|
||||
|
||||
'get-worker-id': never;
|
||||
|
||||
'get-worker-status': WorkerStatus;
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Multi-main setup
|
||||
|
||||
'add-webhooks-triggers-and-pollers': {
|
||||
workflowId: string;
|
||||
};
|
||||
|
||||
'remove-triggers-and-pollers': {
|
||||
workflowId: string;
|
||||
};
|
||||
|
||||
'display-workflow-activation': {
|
||||
workflowId: string;
|
||||
};
|
||||
|
||||
'display-workflow-deactivation': {
|
||||
workflowId: string;
|
||||
};
|
||||
|
||||
// currently 'workflow-failed-to-activate'
|
||||
'display-workflow-activation-error': {
|
||||
workflowId: string;
|
||||
errorMessage: string;
|
||||
};
|
||||
|
||||
'relay-execution-lifecycle-event': {
|
||||
type: PushType;
|
||||
args: Record<string, unknown>;
|
||||
pushRef: string;
|
||||
};
|
||||
|
||||
'clear-test-webhooks': {
|
||||
webhookKey: string;
|
||||
workflowEntity: IWorkflowDb;
|
||||
pushRef: string;
|
||||
};
|
||||
|
||||
// #endregion
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Service } from 'typedi';
|
||||
|
||||
import config from '@/config';
|
||||
import { Logger } from '@/logger';
|
||||
import { RedisClientService } from '@/services/redis/redis-client.service';
|
||||
import { RedisClientService } from '@/services/redis-client.service';
|
||||
|
||||
import type { ScalingPubSubChannel } from './pubsub.types';
|
||||
|
||||
|
||||
103
packages/cli/src/scaling/redis/redis-service-commands.ts
Normal file
103
packages/cli/src/scaling/redis/redis-service-commands.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import type { PushType, WorkerStatus } from '@n8n/api-types';
|
||||
|
||||
import type { IWorkflowDb } from '@/interfaces';
|
||||
|
||||
export type RedisServiceCommand =
|
||||
| 'getStatus'
|
||||
| 'getId'
|
||||
| 'restartEventBus'
|
||||
| 'stopWorker'
|
||||
| 'reloadLicense'
|
||||
| 'reloadExternalSecretsProviders'
|
||||
| 'community-package-install'
|
||||
| 'community-package-update'
|
||||
| 'community-package-uninstall'
|
||||
| 'display-workflow-activation' // multi-main only
|
||||
| 'display-workflow-deactivation' // multi-main only
|
||||
| 'add-webhooks-triggers-and-pollers' // multi-main only
|
||||
| 'remove-triggers-and-pollers' // multi-main only
|
||||
| 'workflow-failed-to-activate' // multi-main only
|
||||
| 'relay-execution-lifecycle-event' // multi-main only
|
||||
| 'clear-test-webhooks'; // multi-main only
|
||||
|
||||
/**
|
||||
* An object to be sent via Redis pubsub from the main process to the workers.
|
||||
* @field command: The command to be executed.
|
||||
* @field targets: The targets to execute the command on. Leave empty to execute on all workers or specify worker ids.
|
||||
* @field payload: Optional arguments to be sent with the command.
|
||||
*/
|
||||
export type RedisServiceBaseCommand =
|
||||
| {
|
||||
senderId: string;
|
||||
command: Exclude<
|
||||
RedisServiceCommand,
|
||||
| 'relay-execution-lifecycle-event'
|
||||
| 'clear-test-webhooks'
|
||||
| 'community-package-install'
|
||||
| 'community-package-update'
|
||||
| 'community-package-uninstall'
|
||||
>;
|
||||
payload?: {
|
||||
[key: string]: string | number | boolean | string[] | number[] | boolean[];
|
||||
};
|
||||
}
|
||||
| {
|
||||
senderId: string;
|
||||
command: 'relay-execution-lifecycle-event';
|
||||
payload: { type: PushType; args: Record<string, unknown>; pushRef: string };
|
||||
}
|
||||
| {
|
||||
senderId: string;
|
||||
command: 'clear-test-webhooks';
|
||||
payload: { webhookKey: string; workflowEntity: IWorkflowDb; pushRef: string };
|
||||
}
|
||||
| {
|
||||
senderId: string;
|
||||
command:
|
||||
| 'community-package-install'
|
||||
| 'community-package-update'
|
||||
| 'community-package-uninstall';
|
||||
payload: { packageName: string; packageVersion: string };
|
||||
};
|
||||
|
||||
export type RedisServiceWorkerResponseObject = {
|
||||
workerId: string;
|
||||
} & (
|
||||
| RedisServiceBaseCommand
|
||||
| {
|
||||
command: 'getStatus';
|
||||
payload: WorkerStatus;
|
||||
}
|
||||
| {
|
||||
command: 'getId';
|
||||
}
|
||||
| {
|
||||
command: 'restartEventBus';
|
||||
payload: {
|
||||
result: 'success' | 'error';
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
command: 'reloadExternalSecretsProviders';
|
||||
payload: {
|
||||
result: 'success' | 'error';
|
||||
error?: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
command: 'stopWorker';
|
||||
}
|
||||
| {
|
||||
command: 'workflowActiveStateChanged';
|
||||
payload: {
|
||||
oldState: boolean;
|
||||
newState: boolean;
|
||||
workflowId: string;
|
||||
};
|
||||
}
|
||||
) & { targets?: string[] };
|
||||
|
||||
export type RedisServiceCommandObject = {
|
||||
targets?: string[];
|
||||
} & RedisServiceBaseCommand;
|
||||
19
packages/cli/src/scaling/redis/redis.types.ts
Normal file
19
packages/cli/src/scaling/redis/redis.types.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export type RedisClientType = N8nRedisClientType | BullRedisClientType;
|
||||
|
||||
/**
|
||||
* Redis client used by n8n.
|
||||
*
|
||||
* - `subscriber(n8n)` to listen for messages from scaling mode communication channels
|
||||
* - `publisher(n8n)` to send messages into scaling mode communication channels
|
||||
* - `cache(n8n)` for caching operations (variables, resource ownership, etc.)
|
||||
*/
|
||||
type N8nRedisClientType = 'subscriber(n8n)' | 'publisher(n8n)' | 'cache(n8n)';
|
||||
|
||||
/**
|
||||
* Redis client used internally by Bull. Suffixed with `(bull)` at `ScalingService.setupQueue`.
|
||||
*
|
||||
* - `subscriber(bull)` for event listening
|
||||
* - `client(bull)` for general queue operations
|
||||
* - `bclient(bull)` for blocking operations when processing jobs
|
||||
*/
|
||||
type BullRedisClientType = 'subscriber(bull)' | 'client(bull)' | 'bclient(bull)';
|
||||
@@ -24,7 +24,7 @@ import type {
|
||||
JobStatus,
|
||||
JobId,
|
||||
QueueRecoveryContext,
|
||||
PubSubMessage,
|
||||
JobReport,
|
||||
} from './scaling.types';
|
||||
|
||||
@Service()
|
||||
@@ -46,7 +46,7 @@ export class ScalingService {
|
||||
|
||||
async setupQueue() {
|
||||
const { default: BullQueue } = await import('bull');
|
||||
const { RedisClientService } = await import('@/services/redis/redis-client.service');
|
||||
const { RedisClientService } = await import('@/services/redis-client.service');
|
||||
const service = Container.get(RedisClientService);
|
||||
|
||||
const bullPrefix = this.globalConfig.queue.bull.prefix;
|
||||
@@ -265,7 +265,7 @@ export class ScalingService {
|
||||
}
|
||||
}
|
||||
|
||||
private isPubSubMessage(candidate: unknown): candidate is PubSubMessage {
|
||||
private isPubSubMessage(candidate: unknown): candidate is JobReport {
|
||||
return typeof candidate === 'object' && candidate !== null && 'kind' in candidate;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,11 +23,11 @@ export type JobStatus = Bull.JobStatus;
|
||||
|
||||
export type JobOptions = Bull.JobOptions;
|
||||
|
||||
export type PubSubMessage = MessageToMain | MessageToWorker;
|
||||
export type JobReport = JobReportToMain | JobReportToWorker;
|
||||
|
||||
type MessageToMain = RespondToWebhookMessage;
|
||||
type JobReportToMain = RespondToWebhookMessage;
|
||||
|
||||
type MessageToWorker = AbortJobMessage;
|
||||
type JobReportToWorker = AbortJobMessage;
|
||||
|
||||
type RespondToWebhookMessage = {
|
||||
kind: 'respond-to-webhook';
|
||||
|
||||
Reference in New Issue
Block a user