Scheduled task: According to the time rule, the system executes corresponding tasks in the background. Scheduled tasks are essential functions for projects, such as sending notifications to users regularly and data integration and processing regularly.
A few days ago, scheduled tasks were needed to realize business logic in the project, so I sorted out the design and implementation of scheduled tasks
The project was written in TS and constructed based on KOA2
Basic parameters
These basic parameters are required for a scheduled task
/** * @description * task object * @interface scheduleInfo */
export interface IScheduleInfo {
/** * Timing rule */
corn: string;
/** * Task name */
name: string;
/** * Task switch */
switch: boolean;
}
Copy the code
Name is the name of a scheduled task, and the name of the task should be unique. Two tasks cannot use the same name. The function of this name will be explained later
Creating a Scheduled Task
We use the node-schedule library to create scheduled tasks in a simple way
var schedule = require('node-schedule');
var j = schedule.scheduleJob('42 * * * *'.function(){
console.log('The answer to life, the universe, and everything! ');
});
Copy the code
node-schedule
Transaction lock control
In distributed deployment, you need to ensure that the same scheduled task can run only once, so you need to use transaction locks to control it. I have written about this in the past
Node.js uses Redis to implement distributed transaction locks
Encapsulation abstract
According to the object-oriented design, we abstract the timing task from the parent class, and put the creation task, transaction control, etc., into the parent class. The subclasses that inherit the parent class only need to implement the business logic
import * as schedule from 'node-schedule';
import { IScheduleInfo } from './i_schedule_info';
/** * @description * @export * @class AbstractSchedule */
export abstract class AbstractSchedule {
/** * Task object */
public scheduleInfo: IScheduleInfo;
public redLock;
public name: string;
public app: Core;
/** * redLock expiration time */
private _redLockTTL: number;
constructor(app) {
this.app = app;
this.redLock = app.redLock;
this._redLockTTL = 60000;
}
/** * @description * To synchronize tasks * @private * @param {any} lock * @returns * @memberof AbstractSchedule */
private async _execTask(lock) {
this.app.logger.info('Execute scheduled task, task name:The ${this.scheduleInfo.name}; Execution time:The ${new Date()}`);
await this.task();
await this._sleep(6000);
return lock.unlock()
.catch((err) = > {
console.error(err);
});
}
/** * @description * delay * @private * @param {any} ms * @returns * @memberof AbstractSchedule */
private _sleep(ms) {
return new Promise((resolve) = > {
setTimeout((a)= > {
resolve();
}, ms);
});
}
/** * @description * Start task, use redis lock, * @private * @param IScheduleInfo scheduleInfo * @param {Function} callback * @param {*} name * @returns * @memberof AbstractSchedule */
public startSchedule() {
return schedule.scheduleJob(this.scheduleInfo.corn, () => {
this.redLock.lock(this.scheduleInfo.name, this._redLockTTL).then((lock) = > {
this._execTask(lock);
}, err => this.app.logger.info('This instance does not perform scheduled tasks:The ${this.scheduleInfo.name}, executed by other instances));
});
}
/** * @description * @author lizc * @abstract * @memberof AbstractSchedule */
public start() {
this.startSchedule();
}
/** * @description defines the task * @abstract * @memberof AbstractSchedule */
public abstract task();
}
Copy the code
The abstract class has an abstract method task in which subclasses implement concrete logical code
The subclass implementation
There are two scenarios for a scheduled task
- The configuration parameters of the task are written directly in the code
export default class TestSchedule extends AbstractSchedule {
constructor(app: Core) {
super(app);
this.scheduleInfo = {
corn: '* */30 * * * *'.// Updates every 30 minutes
name: 'test'.switch: true
};
}
/** * Business implementation */
public task() { }
}
Copy the code
- Task parameters are controlled by the configuration center, and configuration parameters are imported from outside
export default class TestSchedule extends AbstractSchedule {
constructor(app: Core, scheduleInfo: IScheduleInfo) {
super(app);
this.scheduleInfo = scheduleInfo;
}
/** * Business implementation */
public task() { }
}
Copy the code
Start to realize
Locally configured tasks, very easy to start, will create instances on the line. To associate remote configuration tasks with implementation classes, make the following conventions:
- The file name of the task is as follows
${name}_schedule.ts
This format - The task name of remote configuration should be
${name}_schedule.ts
In the correspondingname
For example, if there is a scheduled task associated with a user, the name of the file is user_schedule.ts, then the task name in the remote configuration is name=user
This object can then be exported and created by importing (${name}_schedule.ts)
With this convention, we can associate configuration items with corresponding tasks
The complete implementation code is as follows
import { AbstractSchedule } from './abstract_schedule';
export class ScheduleHelper {
private scheduleList: Array<AbstractSchedule> = [];
private app;
constructor(app) {
this.app = app;
this.initStaticTask();
}
/** * Locally configured scheduled task */
private initStaticTask() {
this.scheduleList.push(new TestSchedule(this.app));
}
/** * Remote configured scheduled task */
private async initTaskFromConfig() {
const taskList: Array<IScheduleInfo> = this.app.config.scheduleConfig.taskList;
for (const taskItem of taskList) {
const path = `The ${this.app.config.rootPath}/schedule/task/${taskItem.name}_schedule`;
import(path).then((taskBusiness) = > {
const scheduleItem: AbstractSchedule = new taskBusiness.default(this.app, taskItem);
this.scheduleList.push(scheduleItem);
}, (err) => {
console.error('[schedule] initialization failed. Configuration file could not be found${err.message}`); }); }}/** * start entry */
public async taskListRun() {
await this.initTaskFromConfig();
for (const schedule of this.scheduleList) {
if(schedule.scheduleInfo.switch) { schedule.start(); }}}}Copy the code
Configuration center
The configuration center in the project uses Apollo, developed by Ctrip
summary
Design comes from the scene, the current design basically meets the current business, 100% of the scene is still not satisfied, welcome friends to discuss better design scheme ~