Подтвердить что ты не робот

Надежность транспорта Websocket (потеря данных Socket.io при повторном подключении)

Б

NodeJS, Socket.io

Проблема

Представьте, что есть 2 пользователя U1 и U2, подключенные к приложению через Socket.io. Алгоритм следующий:

  • U1 полностью теряет подключение к Интернету (например, отключает Интернет)
  • U2 отправляет сообщение U1.
  • U1 еще не получил сообщение, потому что Интернет отключен.
  • Сервер обнаруживает разъединение U1 с помощью тайм-аута heartbeat
  • U1 подключается к socket.io
  • U1 никогда не получает сообщение от U2 - он потерян на шаге 4, я думаю.

Возможное объяснение

Думаю, я понимаю, почему это происходит:

  • на шаге 4 Сервер убивает экземпляр сокета и очередь сообщений U1, а также
  • Кроме того, на шаге 5 U1 и Сервер создать новое соединение (оно не используется повторно), поэтому даже если сообщение все еще поставлено в очередь, предыдущее соединение все равно потеряно.

Нужна помощь

Как я могу предотвратить такую ​​потерю данных? Я должен использовать listenbeats, потому что я не нахожусь в приложении навсегда. Кроме того, я должен предоставить возможность повторно подключиться, потому что, когда я развертываю новую версию приложения, я хочу нулевое время простоя.

P.S. То, что я называю "сообщение", - это не просто текстовое сообщение, которое я могу хранить в базе данных, но ценное системное сообщение, доставка которого должна быть гарантирована, или пользовательский интерфейс затягивается.

Спасибо!


Дополнение 1

У меня уже есть система учетных записей пользователей. Более того, моя заявка уже сложна. Добавление автономных/онлайн-статусов не поможет, потому что у меня уже есть такие вещи. Проблема другая.

Выполните шаг 2. На этом шаге мы технически не можем сказать, что если U1 отправляется в автономном режиме, он просто теряет соединение, скажем, в течение 2 секунд, возможно, из-за плохого интернета. Таким образом, U2 отправляет ему сообщение, но U1 не получает его, потому что интернет все еще для него (шаг 3). Шаг 4 необходим для обнаружения автономных пользователей, скажем, тайм-аут составляет 60 секунд. В конце концов через 10 секунд подключится интернет-соединение для U1, и он снова подключится к socket.io. Но сообщение из U2 теряется в пространстве, потому что на сервере U1 был отключен таймаутом.

В этом проблема, я не получаю 100% доставки.


Решение

  • Соберите излучение (введите имя и данные) в {} пользователь, идентифицированный случайным emitID. Отправить emit
  • Подтвердить испускание на стороне клиента (отправить emit обратно на сервер с emitID)
  • Если подтверждено - удалить объект из {}, идентифицированный emitID
  • Если пользователь повторно подключен - проверьте {} для этого пользователя и пропустите его, выполнив Шаг 1 для каждого объекта в {}
  • При отключенном или/или подключенном флажке {} для пользователя при необходимости
4b9b3361

Ответ 1

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

  • Сообщения не отправляются напрямую клиентам; вместо этого они отправляются на сервер и сохраняются в каком-то хранилище данных.
  • Клиенты несут ответственность за запрос "что я пропустил" при повторном подключении и запрошу сохраненные сообщения в хранилище данных, чтобы обновить их состояние.
  • Если сообщение отправляется на сервер, когда клиент-получатель подключен, это сообщение будет отправлено клиенту в режиме реального времени.

Конечно, в зависимости от потребностей вашего приложения вы можете настроить части этого - например, вы можете использовать, скажем, список Redis или отсортированный набор для сообщений, и очистить их, если вы знаете, что клиент обновлен.


Вот несколько примеров:

Счастливый путь:

  • U1 и U2 подключены к системе.
  • U2 отправляет на сервер сообщение, которое должен получить U1.
  • Сервер хранит сообщение в каком-то постоянном хранилище, маркируя его для U1 с некоторой меткой времени или последовательным идентификатором.
  • Сервер отправляет сообщение в U1 через Socket.IO.
  • Клиент U1 подтверждает (возможно, через обратный вызов Socket.IO), что он получил сообщение.
  • Сервер удаляет сохраненное сообщение из хранилища данных.

Автономный путь:

  • U1 теряет интернет-соединение.
  • U2 отправляет на сервер сообщение, которое должен получить U1.
  • Сервер хранит сообщение в каком-то постоянном хранилище, маркируя его для U1 с некоторой меткой времени или последовательным идентификатором.
  • Сервер отправляет сообщение в U1 через Socket.IO.
  • Клиент U1 не выполняет подтверждение получения, потому что он не работает.
  • Возможно, U2 отправляет U1 еще несколько сообщений; все они сохраняются в хранилище данных таким же образом.
  • Когда U1 снова подключается, он запрашивает сервер "Последнее сообщение, которое я видел, было X/у меня состояние X, что я пропустил".
  • Сервер отправляет U1 все сообщения, которые он пропустил из хранилища данных, на основе запроса U1
  • Клиент U1 подтверждает получение, и сервер удаляет эти сообщения из хранилища данных.

Если вам абсолютно нужна гарантированная доставка, важно создать свою систему таким образом, чтобы связать ее на самом деле не так, и что доставка в реальном времени - это просто бонус; это почти всегда связано с каким-то хранилищем данных. Как указано в комментарии пользователя568109, существуют системы обмена сообщениями, которые абстрагируют хранение и доставку указанных сообщений, и, возможно, стоит заглянуть в такое заранее построенное решение. (Вероятно, вам все равно придется записывать интеграцию Socket.IO самостоятельно.)

Если вам не интересно хранить сообщения в базе данных, вы можете уйти с их хранением в локальном массиве; сервер пытается отправить сообщение U1 и сохраняет его в списке "ожидающих сообщений", пока клиент U1 не подтвердит, что он его получил. Если клиент находится в автономном режиме, тогда, когда он вернется, он может сказать серверу "Эй, я был отключен, пожалуйста, пришлите мне что-нибудь, что я пропустил", и сервер может перебирать эти сообщения.

К счастью, Socket.IO предоставляет механизм, который позволяет клиенту "отвечать" на сообщение, которое выглядит как ответные обратные вызовы JS. Вот несколько псевдокодов:

// server
pendingMessagesForSocket = [];

function sendMessage(message) {
  pendingMessagesForSocket.push(message);
  socket.emit('message', message, function() {
    pendingMessagesForSocket.remove(message);
  }
};

socket.on('reconnection', function(lastKnownMessage) {
  // you may want to make sure you resend them in order, or one at a time, etc.
  for (message in pendingMessagesForSocket since lastKnownMessage) {
    socket.emit('message', message, function() {
      pendingMessagesForSocket.remove(message);
    }
  }
});

// client
socket.on('connection', function() {
  if (previouslyConnected) {
    socket.emit('reconnection', lastKnownMessage);
  } else {
    // first connection; any further connections means we disconnected
    previouslyConnected = true;
  }
});

socket.on('message', function(data, callback) {
  // Do something with `data`
  lastKnownMessage = data;
  callback(); // confirm we received the message
});

Это очень похоже на последнее предложение, просто без постоянного хранилища данных.


Вас также может заинтересовать концепция event sourcing.

Ответ 2

Кажется, что у вас уже есть система учетных записей пользователей. Вы знаете, какая учетная запись находится в режиме онлайн/офлайн, вы можете обрабатывать событие connect/disconnect:

Таким образом, решение есть, добавляет онлайн/оффлайн и автономные сообщения в базу данных для каждого пользователя:

chatApp.onLogin(function (user) {
   user.readOfflineMessage(function (msgs) {
       user.sendOfflineMessage(msgs, function (err) {
           if (!err) user.clearOfflineMessage();
       });
   })
});

chatApp.onMessage(function (fromUser, toUser, msg) {
   if (user.isOnline()) {
      toUser.sendMessage(msg, function (err) {
          // alert CAN NOT SEND, RETRY?
      });
   } else {
      toUser.addToOfflineQueue(msg);
   }
})

Ответ 3

Посмотрите здесь: Обработать перезагрузку браузера socket.io.

Я думаю, вы могли бы использовать решение, с которым я столкнулся. Если вы измените его правильно, он будет работать так, как вы хотите.

Ответ 4

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

Клиент:

socket.on("msg", function(){
    socket.send("msg-conf");
});

Сервер:

// Add this socket property to all users, with your existing user system
user.socket = {
    messages:[],
    io:null
}
user.send = function(msg){ // Call this method to send a message
    if(this.socket.io){ // this.io will be set to null when dissconnected
        // Wait For Confirmation that message was sent.
        var hasconf = false;
        this.socket.io.on("msg-conf", function(data){
            // Expect the client to emit "msg-conf"
            hasconf = true;
        });
        // send the message
        this.socket.io.send("msg", msg); // if connected, call socket.io send method
        setTimeout(function(){
            if(!hasconf){
                this.socket = null; // If the client did not respond, mark them as offline.
                this.socket.messages.push(msg); // Add it to the queue
            }
        }, 60 * 1000); // Make sure this is the same as your timeout.

    } else {
        this.socket.messages.push(msg); // Otherwise, it offline. Add it to the message queue
    }
}
user.flush = function(){ // Call this when user comes back online
    for(var msg in this.socket.messages){ // For every message in the queue, send it.
        this.send(msg);
    }
}
// Make Sure this runs whenever the user gets logged in/comes online
user.onconnect = function(socket){
    this.socket.io = socket; // Set the socket.io socket
    this.flush(); // Send all messages that are waiting
}
// Make sure this is called when the user disconnects/logs out
user.disconnect = function(){
    self.socket.io = null; // Set the socket to null, so any messages are queued not send.
}

Затем очередь сокетов сохраняется между разъединениями.

Убедитесь, что он сохраняет каждое имя пользователя socket в базе данных и делает методы частью вашего прототипа пользователя. База данных не имеет значения, просто сохраните ее, однако вы сохраняете своих пользователей.

Это позволит избежать проблемы, упомянутой в Additon 1, требуя подтверждения от клиента перед тем, как пометить сообщение как отправленное. Если вы действительно этого захотите, вы можете дать каждому сообщению идентификатор и отправить клиенту идентификатор сообщения msg-conf, а затем проверить его.

В этом примере user является пользователем шаблона, с которого все пользователи скопированы или похожи на прототип пользователя.

Примечание: Это не было протестировано.

Ответ 5

Посмотрел на этот материал последним и подумал, что другой путь может быть лучше.

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

Является ли стоимость запуска очереди, но она равна $0,05 за миллион операций для базовой очереди, поэтому стоимость dev будет больше от часов работы, необходимо написать систему очередей. https://azure.microsoft.com/en-us/pricing/details/service-bus/

А шина azure имеет библиотеки и примеры для PHP, С#, Xarmin, Anjular, Java Script и т.д.

Итак, отправьте сообщение на сервер и не нужно беспокоиться о его отслеживании. Клиент может использовать сообщение для отправки назад, так как средства могут выполнять балансировку нагрузки, если это необходимо.