Съхраняване на върнатата стойност на node.js setTimeout в 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 Трябва да се провери дали даден потребител случайно е прекъснал връзката с websocket за повече от 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 ohhh Нямах представа, че използва това във фонов режим за стаи. Много яко. Ами каналите? използвам 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 връща Handler (препратка към обект).

Една идея би била да създадете свой собствен асоциативен масив в паметта и да съхраните индекса в 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

В по-новите версии на node можете да използвате идентификатора на обекта 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. Както можете да видите, можете да вземете идентификатора на обекта Timeout и да го съхраните, за да прекратите изпълнението на setInterval, вместо да се опитвате да съхраните целия обект.

Ето връзката към документите на възела: https://nodejs.org/api/timers.html#timers_timeout_symbol_toprimitive

person Goose9192    schedule 24.03.2021