Сохранение возвращаемого значения setTimeout node.js в redis

Я использую setTimeout в Node.js, и, похоже, он ведет себя иначе, чем на стороне клиента setTimeout, поскольку возвращает объект вместо числа. Я хочу сохранить это в redis, но поскольку redis хранит только строки, мне нужно преобразовать объект в строку. Однако использование JSON.stringify вызывает ошибку циклической ссылки. Как я могу сохранить этот объект в Redis, если я хочу получить его из Redis и вызвать clearTimeout?


person user730569    schedule 02.07.2012    source источник
comment
Я не думаю, что вызов setTimeout имеет какое-либо отношение к ошибке циклической ссылки. stackoverflow.com/questions/1493453 /   -  person Trevor    schedule 03.07.2012
comment
@Trevor setTimeout создает этот объект: { _idleTimeout: 1000000000, _idlePrev: { _idleNext: [Circular], _idlePrev: [Circular], ontimeout: [Function] }, _idleNext: { _idleNext: [Circular], _idlePrev: [Circular], ontimeout: [Function] }, _onTimeout: [Function], _idleStart: Mon, 02 Jul 2012 20:28:18 GMT }   -  person user730569    schedule 03.07.2012
comment
Клавиши _idleNext и _idlePrev кажутся циклическими ссылками ...   -  person user730569    schedule 03.07.2012
comment
Из ваших комментариев я понял, что вам действительно нужно использовать redis для масштабирования socket.io между экземплярами. Я рекомендую вам изучить RedisStore socket.io - см., Например, этот мой ответ.   -  person Linus Thiel    schedule 03.07.2012
comment
@LinusGThiel Да, я уже использую RedisStore Socket.io. Проблема заключается в том, чтобы сохранить возвращаемое значение метода setTimeout.   -  person user730569    schedule 03.07.2012
comment
Хорошо, я вижу. Не могли бы вы рассказать нам еще о том, для чего вам нужны тайм-ауты? То есть, чего вы на самом деле пытаетесь достичь? Возможно, есть способ получше.   -  person Linus Thiel    schedule 03.07.2012
comment
@LinusGThiel Это проверяет, не отключился ли пользователь от подключения к веб-сокету дольше X секунд (например, тайм-аут интернета). Это отличается от перехода со страницы на страницу, на которой вы ненадолго отключаетесь. Поэтому я создаю таймер для события disconnect, но если они возвращаются в течение X секунд, я беру таймер из хранилища и очищаю его. В противном случае таймер выдает событие случайного отключения по истечении этих X секунд.   -  person user730569    schedule 03.07.2012
comment
У меня такое чувство, что тебе нужно немного подумать над этим. Пользователь отключается. Если он снова подключается, вы не хотите ничего делать. В противном случае вы хотите произвести случайное отключение, где именно?   -  person Linus Thiel    schedule 03.07.2012
comment
@LinusGThiel К другим сокетам, подключенным к тому же каналу.   -  person user730569    schedule 03.07.2012
comment
В этом случае вы должны иметь возможность просто emit подключиться к каналу на том экземпляре, который изначально обрабатывал пользователя. Я почти уверен, что RedisStore обработает распространение на другие экземпляры.   -  person Linus Thiel    schedule 03.07.2012
comment
@LinusGThiel Понятно ... Это что, Redis publish/subscribe?   -  person user730569    schedule 03.07.2012
comment
Верно, так что со значением по умолчанию MemoryStore, если вы это сделаете, например io.sockets.of('foo').emit('foo', 'bar'), который будет отправлен всем клиентам, подключенным к этому экземпляру. При использовании RedisStore сообщение будет отправлено всем клиентам во всех экземплярах с использованием publish/subscribe материала redis.   -  person Linus Thiel    schedule 03.07.2012
comment
@LinusGThiel ооо, я понятия не имел, что он использует это в фоновом режиме для комнат. Очень круто. А как насчет каналов? Я использую socket.join("channel name");   -  person user730569    schedule 03.07.2012
comment
Я почти уверен, что комнаты и каналы socket.io - это одно и то же, то есть выполнение var socket = io.connect(); socket.join('foo'); присоединится к комнате 'foo', а var socket = io.connect('/foo') также присоединится к комнате 'foo'.   -  person Linus Thiel    schedule 03.07.2012
comment
@LinusGThiel Я кое-что понял. Излучение по каналу на сокеты, подключенные ко всем экземплярам, ​​будет работать при распространении события случайного отключения. Однако, если я не сохраню объект, возвращенный из setTimeout, в redis, как я смогу захватить этот объект и сбросить тайм-аут при повторном подключении клиента? Есть ли способ убедиться, что клиент повторно подключается к тому же серверу в том же экземпляре? В противном случае вновь подключенный сервер не сможет найти объект таймера на предыдущем сервере, если он находится в памяти с другим экземпляром и / или другим процессом узла, верно?   -  person user730569    schedule 28.11.2012


Ответы (4)


Вы не можете сохранить объект в Redis. Метод setTimeout возвращает обработчик (ссылку на объект).

Одна из идей - создать собственный ассоциативный массив в памяти и сохранить индекс в Redis. Например:

var nextTimerIndex = 0;
var timerMap = {};

var timer = setTimeout(function(timerIndex) {
    console.log('Ding!');

    // Free timer reference!
    delete timerMap[timerIndex];
}, 5 * 1000, nextTimerIndex);

// Store index in Redis...

// Then, store the timer object for later reference
timerMap[nextTimerIndex++] = timer;

// ...
// To clear the timeout
clearTimeout(timerMap[myTimerIndex]);
person legege    schedule 02.07.2012
comment
Хорошо, но мне нужно иметь возможность вызывать clearTimeout для объекта после его получения из redis - person user730569; 03.07.2012
comment
Я хочу иметь возможность делиться им между экземплярами, чтобы в памяти не было варианта - person user730569; 03.07.2012
comment
@ user730569 Вам удалось сохранить объект тайм-аута в Redis? Вы отметили это как ответ, но это сохраняет его в памяти - person scanales; 27.11.2012
comment
@scanales Мне не удалось сохранить в redis. Однако я еще не масштабировал один экземпляр или сервер. - person user730569; 27.11.2012

Я пытался сделать то же самое, что и OP. Мое решение заключалось в том, чтобы установить тайм-аут с условной проверкой нового ключа внутри тайм-аута в моем обработчике отключения:

redis.hset("userDisconnecting:" + userId, "disconnect", 1);

setTimeout(function() {
    redis.hget("userDisconnecting:" + userId, "disconnect",
     function(err, result) {
        if (result.toString() === "1") {
           //do stuff, like notify other clients of the disconnect.
        }
    });
}, 10000);

Затем, когда клиент снова подключается, я устанавливаю этот ключ на 0, чтобы то, что должно срабатывать при истинном отключении, не происходило:

redis.hset("userDisconnecting:" + userId, "disconnect", 0);

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

person Mike Atlas    schedule 10.04.2014

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

var timeouts = {};

app.get('/', function (req, res) {
  var index = timeouts.length;
  timeouts[index] = setTimeout(console.log, 1000000, req.user.name);

  redis.set('timeout:' + req.user.name, index, function (err, reply) {
    res.end();
  });
});

app.get('/clear', function (req, res) {
  redis.get('timeout:' + req.user.name, function (err, index) {
   clearTimeout(timeouts[index]);
   delete timeouts[index];
   redis.delete('timeout:' + req.user.name);
   res.end();
  });
});

Если вам нужно, чтобы тайм-ауты сохранялись при перезапусках сервера, вам может потребоваться сохранить значения _idleStart и _idleTimeout для каждого таймера в redis и загружать их каждый раз при перезапуске сервера.

app.get('/', function (req, res) {
  var timeout = setTimeout(console.log, 1000000, req.user.name);
  var time = timeout._idleStart.getTime() + timeout._idleTimeout;

  redis.set('timeout:' + req.user.name, time, function (err, reply) {
    res.end();
  });
});

app.get('/clear', function (req, res) {
  redis.delete('timeout:' + req.user.name);
  res.end();
});

// Load timeouts on server start
// *I know this is not the correct redis command*
// *It's not accurate, only approx*
redis.get('timeout:*', function (err, vals) {
  vals.forEach(function (val) {
    var time = val - new Date().getTime();
    setTimeout(console.log, time, username)
  });
});
person Pavan Kumar Sunkara    schedule 02.07.2012
comment
У вас утечка ссылки на объект таймера. - person legege; 03.07.2012
comment
@ user730569 Это не сработает ... таймер пропадет после перезапуска. Вам нужно найти способ перенести их после перезапуска. Вы можете сохранить время начала и пересчитать значение тайм-аута при перезапуске. - person legege; 03.07.2012
comment
@legege Я не имел в виду перезапуск сервера ... на самом деле я имел в виду разные экземпляры, поэтому что-либо в памяти не является вариантом - person user730569; 03.07.2012
comment
@ user730569 В разных экземплярах? Таймер запущен на n экземплярах? Стоит ли его везде отменять? Пожалуйста, предоставьте более подробный контекст вашего вопроса. - person legege; 03.07.2012
comment
@legege Хорошо, поэтому я использую хранилище socket.io, и в документации утверждается, что для масштабирования между несколькими экземплярами мне нужно перейти от хранилища памяти к redis. - person user730569; 03.07.2012
comment
@ user730569 Хорошо, а твой таймер? - person legege; 03.07.2012
comment
@legege, если честно, я действительно не уверен. Я никогда не масштабировал приложение за пределы экземпляра, поэтому понятия не имею. Ваше решение в памяти определенно подойдет мне на данный момент. - person user730569; 03.07.2012

В более новых версиях узла вы можете использовать идентификатор объекта Timeout вместо самого объекта для завершения цикла.

   redisClient.set('time', JSON.stringify(10))
   let timeoutObject = setInterval(async function(){
      let time = await JSON.parse(redisClient.get('time'))
      if(time === 0){
       let intervalId = await JSON.parse(redisClient.get('intervalId'))
       clearInterval(intervalId)
      }
       time -= 1
       redisClient.set('time', JSON.stringify(time))
    }, 1000)
    
    let intervalId = timeoutObject[Symbol.toPrimitive]()
    redisClient.set('intervalId', JSON.stringify(intervalId))

Это всего лишь пример таймера, построенного с комбинированием setInterval и redis. Как видите, вы можете получить идентификатор объекта тайм-аута и сохранить его, чтобы завершить выполнение setInterval, вместо того, чтобы пытаться сохранить весь объект.

Вот ссылка на документы узла: https://nodejs.org/api/timers.html#timers_timeout_symbol_toprimitive

person Goose9192    schedule 24.03.2021