Запланированные задачи: в соответствии с временными правилами система выполняет соответствующие задачи в фоновом режиме. Запланированные задачи являются важными функциями для проектов, такими как регулярная отправка уведомлений пользователям, а также интеграция и обработка синхронизированных данных.
В последнее время в проекте необходимо использовать запланированную задачу для реализации бизнес-логики, поэтому заканчивая следующие регулярные задачи проектирования и реализации
Использование проекта
tsнаписано, на основеkoa2строить
основные параметры
Запланированные задачи требуют этих основных параметров
/**
* @description
* 任务对象
* @interface scheduleInfo
*/
export interface IScheduleInfo {
/**
* 定时规则
*/
corn: string;
/**
* 任务名称
*/
name: string;
/**
* 任务开关
*/
switch: boolean;
}
использование установки времениcronвыражение,cronФункция очень мощная, а метод использования лаконичен и прост для понимания, здесь он не будет подробно описываться.nameЭто имя запланированной задачи. Имя задачи должно быть уникальным. Одно и то же имя не может быть использовано для двух задач. Функция этого имени будет подробно описана позже.switchУказывает, включена ли эта задача
Создать запланированное задание
Мы используемnode-scheduleЭта библиотека для создания задач на время очень проста в использовании.
var schedule = require('node-schedule');
var j = schedule.scheduleJob('42 * * * *', function(){
console.log('The answer to life, the universe, and everything!');
});
Управление блокировкой транзакций
При распределенном развертывании необходимо гарантировать, что одна и та же запланированная задача может быть запущена только один раз, поэтому она должна контролироваться блокировками транзакций.Я уже писал статьи на эту тему.
Распределенная блокировка транзакций с использованием Redis в node.js
инкапсулировать абстракцию
В соответствии с объектно-ориентированным дизайном мы абстрагируем временные задачи от родительского класса и помещаем в родительский класс задачи создания, управления транзакциями и т. д. Подклассы, которые наследуют родительский класс, должны только реализовать бизнес-логику. Код родительского класса выглядит следующим образом
import * as schedule from 'node-schedule';
import { IScheduleInfo } from './i_schedule_info';
/**
* @description
* 定时任务
* @export
* @class AbstractSchedule
*/
export abstract class AbstractSchedule {
/**
* 任务对象
*/
public scheduleInfo: IScheduleInfo;
public redLock;
public name: string;
public app: Core;
/**
* redLock 过期时间
*/
private _redLockTTL: number;
constructor(app) {
this.app = app;
this.redLock = app.redLock;
this._redLockTTL = 60000;
}
/**
* @description
* 同步执行任务
* @private
* @param {any} lock
* @returns
* @memberof AbstractSchedule
*/
private async _execTask(lock) {
this.app.logger.info(`执行定时任务,任务名称: ${this.scheduleInfo.name} ; 执行时间: ${new Date()}`);
await this.task();
await this._sleep(6000);
return lock.unlock()
.catch((err) => {
console.error(err);
});
}
/**
* @description
* 延迟
* @private
* @param {any} ms
* @returns
* @memberof AbstractSchedule
*/
private _sleep(ms) {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
/**
* @description
* 开启任务,使用redis同步锁,保证任务单实例执行
* @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.scheduleInfo.name},由其他实例执行`));
});
}
/**
* @description
* 启动入口
* @author lizc
* @abstract
* @memberof AbstractSchedule
*/
public start() {
this.startSchedule();
}
/**
* @description 定义任务
* @abstract
* @memberof AbstractSchedule
*/
public abstract task();
}
Абстрактный класс имеет абстрактный методtask, в котором подклассы реализуют определенный логический код
Реализация подкласса
Есть две ситуации для задач на время
- Сразу после запуска системы параметры конфигурации задачи прописываются прямо в коде
export default class TestSchedule extends AbstractSchedule {
constructor(app: Core) {
super(app);
this.scheduleInfo = {
corn: '* */30 * * * *', // 每30分鐘更新一次
name: 'test',
switch: true
};
}
/**
* 业务实现
*/
public task() { }
}
- Параметры задачи контролируются центром конфигурации, а параметры конфигурации передаются извне.
export default class TestSchedule extends AbstractSchedule {
constructor(app: Core, scheduleInfo: IScheduleInfo) {
super(app);
this.scheduleInfo = scheduleInfo;
}
/**
* 业务实现
*/
public task() { }
}
Начать реализацию
Задачу локальной настройки очень легко запустить, достаточно создать экземпляр. Для задачи удаленной настройки, чтобы реализовать связь между конфигурацией и классом реализации, необходимо принять следующие соглашения:
- Имя файла задания, согласно
${name}_schedule.tsэтот формат - Имя задачи удаленной настройки, которое должно быть
${name}_schedule.tsсоответствующийname
Например, если есть запланированная задача, связанная с пользователем, то имя файла будет называться следующим образом.user_schedule.ts, то имя задачи в удаленной конфигурации будетname=user
затем пройтиimport(${name}_schedule.ts)Вы можете экспортировать и создавать этот объект
Благодаря этому соглашению мы можем связать элементы конфигурации с соответствующими задачами.
Полный код реализации выглядит следующим образом
import { AbstractSchedule } from './abstract_schedule';
export class ScheduleHelper {
private scheduleList: Array<AbstractSchedule> = [];
private app;
constructor(app) {
this.app = app;
this.initStaticTask();
}
/**
* 本地配置的定时任务
*/
private initStaticTask() {
this.scheduleList.push(new TestSchedule(this.app));
}
/**
* 远程配置的定时任务
*/
private async initTaskFromConfig() {
const taskList: Array<IScheduleInfo> = this.app.config.scheduleConfig.taskList;
for (const taskItem of taskList) {
const path = `${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]初始化失败,找不到配置文件 ${err.message}`);
});
}
}
/**
* 启动入口
*/
public async taskListRun() {
await this.initTaskFromConfig();
for (const schedule of this.scheduleList) {
if (schedule.scheduleInfo.switch) {
schedule.start();
}
}
}
}
Центр конфигурации
Конфигурационный центр в проекте разработан компанией Ctrip.apollo
резюме
Дизайн исходит от сцены. Текущий дизайн в основном соответствует текущему бизнесу, но все еще не может быть удовлетворен на 100% сцены. Добро пожаловать, друзья, чтобы общаться и обсуждать лучшие дизайнерские решения ~