node project - как проектировать временные задачи

Node.js Redis
node project - как проектировать временные задачи

Запланированные задачи: в соответствии с временными правилами система выполняет соответствующие задачи в фоновом режиме. Запланированные задачи являются важными функциями для проектов, такими как регулярная отправка уведомлений пользователям, а также интеграция и обработка синхронизированных данных.

В последнее время в проекте необходимо использовать запланированную задачу для реализации бизнес-логики, поэтому заканчивая следующие регулярные задачи проектирования и реализации

Использование проекта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!');
});

node-schedule

Управление блокировкой транзакций

При распределенном развертывании необходимо гарантировать, что одна и та же запланированная задача может быть запущена только один раз, поэтому она должна контролироваться блокировками транзакций.Я уже писал статьи на эту тему.

Распределенная блокировка транзакций с использованием 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, в котором подклассы реализуют определенный логический код

Реализация подкласса

Есть две ситуации для задач на время

  1. Сразу после запуска системы параметры конфигурации задачи прописываются прямо в коде
export default class TestSchedule extends AbstractSchedule {

    constructor(app: Core) {
        super(app);

        this.scheduleInfo = {
            corn: '* */30 * * * *', // 每30分鐘更新一次
            name: 'test',
            switch: true
        };

    }
    /**
     * 业务实现
     */
    public task() { }

}
  1. Параметры задачи контролируются центром конфигурации, а параметры конфигурации передаются извне.
export default class TestSchedule extends AbstractSchedule {

    constructor(app: Core, scheduleInfo: IScheduleInfo) {
        super(app);
        this.scheduleInfo = scheduleInfo;
    }
    /**
     * 业务实现
     */
    public task() { }

}

Начать реализацию

Задачу локальной настройки очень легко запустить, достаточно создать экземпляр. Для задачи удаленной настройки, чтобы реализовать связь между конфигурацией и классом реализации, необходимо принять следующие соглашения:

  1. Имя файла задания, согласно${name}_schedule.tsэтот формат
  2. Имя задачи удаленной настройки, которое должно быть${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% сцены. Добро пожаловать, друзья, чтобы общаться и обсуждать лучшие дизайнерские решения ~

Категории