Использование уведомлений о пространстве ключей Redis для реализации отложенных задач (версия NodeJS)

Node.js Redis

1. Сценарии использования

Во многих системах, особенно в системах электронной коммерции, часто возникают отложенные задачи, которые необходимо выполнить. Например, отложенный ордер нужно автоматически закрыть через 30 минут. Хотя есть много способов добиться этого, например, работа и т. д., здесь представлено основное введение в использование новой функции уведомлений о пространстве ключей Redis для достижения.

2. Базовые знания

Смысл! ! ! Версия Redis 2.8.0 поддерживает уведомления о пространстве ключей. Если ваша версия Redis слишком низкая, вы можете помыться и спать...

Если вы не знаете о Pub/Sub Redis, настоятельно рекомендуется сначала прочитать эту статью:Redis публиковать и подписываться

Далее, давайте поговорим о нашем главном герое: уведомления о пространстве клавиш.

Уведомления о ключевом пространстве по умолчанию отключены.Чтобы включить, вам нужно изменить файл redis.conf или включить или отключить эту функцию через CONFIG SET. Здесь мы используем CONFIG SET, чтобы включить:

$ redis-cli config set notify-keyspace-events Ex

Некоторые люди здесь спросят, что означает Ex? Этоnotify-keyspace-eventsПолный список параметров смотрите в таблице ниже:

персонаж отправлено уведомление
K уведомления о ключевом пространстве, все уведомления начинаются с__keyspace@<db>__префикс
E уведомления о ключевых событиях, все уведомления начинаются с__keyevent@<db>__префикс
g DEL,EXPIRE,RENAMEУведомление об общих командах, таких как независимые от типа
$ Уведомления для строковых команд
l Уведомление о командах списка
s Уведомление о командах сбора
h Уведомление о хеш-командах
z Уведомления для команд упорядоченного набора
x Событие с истекшим сроком действия: отправляется при удалении ключа с истекшим сроком действия.
e Событие выселения (выселения): всякий раз, когда есть ключ, потому чтоmaxmemoryОтправляется при удалении политики
A параметрg$lshzxeпсевдоним

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

3. План реализации

Какими свойствами должна обладать отложенная задача? Я думаю, что есть как минимум следующие свойства:

  • тип задачи. (Пример: закрыть заказ)
  • Идентификатор задачи. (Пример: идентификатор заказа)
  • Время задержки задачи. (Пример: 30 минут)
  • Дополнительные данные задачи. (Пример: другие данные, связанные с заказом)

После этого можем продолжить спуск.

3.1 Регистрация обработчиков событий

Во-первых, после запуска проекта нам нужно зарегистрировать разные обработчики для разных событий:

const _ = require('lodash')
// 任务处理器map
const handlers = {}
// 事件类型map
const events = {}
const registerEventHandler = (type, handler) => {  
  if (!type) {    
      throw new Error('type不能为空')  
  }  
  if (!_.isFunction(handler)) {    
      throw new Error('handler类型非function')  
  }  
  handlers[type] = handler  
  events[type] = true
}

3.2 Создание отложенной задачи

const redis = require('redis')
const client = redis.createClient()
const eventKeyPrefix = 'custom_event_'// 任务列表
const jobs = {}
const addDelayEvent = (type, id, body = {}, delay = 10 * 60) => {  
  const key = `${eventKeyPrefix}${type}_${id}`  
  const jobKey = `${type}_${id}`  
  client.setex(key, delay, 'delay event', (err) => {    
    if (err) {      
      return console.log('添加延迟事件失败:', err);    
    }    
    console.log('添加延迟事件成功');    
    jobs[jobKey] = body  
  })
}

Ключевым моментом здесь является метод client.setex(key, expired, value) Нам нужно добавить время истечения срока действия к ключу, а затем redis выдаст событие истечения срока действия, когда срок действия ключа истечет.

3.3 События с истекшим сроком действия подписки

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

const redis = require('redis')
const sub = redis.createClient()

sub.on('pmessage', (pattern, channel, message) => {  
  // match key  
  const keyMatcher = new RegExp(`^${eventKeyPrefix}(${_.keys(events).join('|')})_(\\S+)$`)  
  const result = message.match(keyMatcher)  
  if (result) {    
    const type = result[1];    
    const id = result[2];    
    const handler = handlers[type]    
    console.log('订阅消息:type=%s, id=%s', type, id);    
    if (_.isFunction(handler)) {      
      const jobKey = `${type}_${id}`      
      if (jobs[jobKey]) {        
        handler(id, jobs[jobKey])      
      } else {        
        console.log('未找到延迟事件,type=%s,id=%s', type, id);      
      }    
    } else {      
      console.log('未找到事件处理器。type=%s', type)    
    }  
  }
})
// 订阅频道
sub.psubscribe('__key*__:expired')

3.4 Написать демонстрацию

Наконец, мы пишем демо, чтобы проверить нашу функцию.

const eventManager = require('./utils/eventManager')

eventManager.registerEventHandler('closeorder', (id, body) => {  
    console.log('关闭订单 id=%s, body=%o', id, body);
})

eventManager.addDelayEvent('closeorder', 1111, {name: 'test'}, 5)


Done!