Надежность транспорта Websocket (Socket.io потеря данных при повторном подключении)
использовать
NodeJS, Socket.io
представьте, что есть 2 пользователя U1 & U2, подключенный к приложению через Socket.io. алгоритм следующий:
U1 полностью теряет подключение к интернету (исх. выключает интернет)
U2 отправить сообщение U1.
U1 пока не получает сообщение, потому что Интернет не работает
сервер обнаруживает U1 отключение по таймауту сердцебиения
U1 повторное подключение к socket.io
U1 никогда не получает сообщение от U2 - он потерян на шаге 4, я думаю.
возможное объяснение
Я думаю, что понимаю, почему это происходит:
- Шаг 4 сервер убивает экземпляр сокета и очередь сообщений на U1 а также
- кроме того, на шаге 5 U1 и сервер создать новое соединение (оно не используется повторно), поэтому даже если сообщение все еще находится в очереди, предыдущее соединение все равно теряется.
нужна помощь
Как я могу предотвратить такую потерю данных? Я должен использовать hearbeats, потому что я не люди висят в приложении навсегда. Также я должен еще дать возможность переподключиться, потому что при развертывании нового версия приложения я хочу нулевое время простоя.
спасибо!
Дополнение 1
у меня уже есть система учетных записей пользователей. Более того, мое приложение уже сложное. Добавление оффлайн / онлайн статусов не поможет, потому что у меня уже есть такие вещи. Проблема в другом.
Шаг 2. На этом этапе мы технически Не могу сказать, если U1 переходит в автономный режим, он просто теряет соединение, скажем, на 2 секунды, вероятно, из-за плохого интернета. Поэтому U2 отправляет ему сообщение, но U1 не получает его, потому что интернет все еще не работает для него (Шаг 3). Шаг 4 необходим для обнаружения автономных пользователей, скажем, тайм-аут составляет 60 секунд. В конце концов, еще через 10 секунд подключение к интернету для U1 будет восстановлено, и он снова подключится к socket.io но сообщение от U2 теряется в пространстве, потому что на сервере U1 был отключен по таймауту.
вот в чем проблема, я не хочу 100% доставки.
решение
- соберите эмиссию (имя и данные эмиссии) в {} Пользователь, идентифицированный случайным эмиссией. Отправить эмиссию
- подтвердите эмиссию на стороне клиента (отправьте эмиссию обратно на сервер с помощью emitID)
- если подтверждено-удалить объект из {}, идентифицированный emitID
- если пользователь снова подключен-проверьте {} для этот пользователь и цикл через него выполнение шага 1 для каждого объекта в {}
при отключении или / и подключении flush {} для пользователя при необходимости
// сервер
const pendingEmits = {};
гнездо.on ('reconnection', () = > resendAllPendingLimits);
разъем.on ('confirm', (emitID) = > { delete(pendingEmits[emitID]);});
// клиент
разъем.on ('something', () => {
разъем.emit ('confirm', emitID);
});
5 ответов:
другие намекали на это в других ответах и комментариях, но основная проблема заключается в том, что Socket.IO это просто механизм доставки, а вы не может зависите от его самостоятельно для надежной поставки. Единственный человек, который точно знает, что сообщение было успешно доставлено клиенту сам клиент. Для такого рода системы я бы рекомендовал сделать следующие утверждения:
- сообщения не отправляются непосредственно клиентам; вместо этого, они отправляются на сервер и хранится в каком-то хранилище данных.
- клиенты отвечают за запрос "что я пропустил" при повторном подключении и будут запрашивать сохраненные сообщения в хранилище данных для обновления их состояния.
- если сообщение отправлено на сервер пока клиент получателя подключен, это сообщение будет отправлено в режиме реального времени клиенту.
конечно, в зависимости от потребностей вашего приложения, вы можете настроить кусочки например, вы можете использовать, скажем, список Redis или сортированный набор для сообщений и очистить их, если вы знаете, что клиент обновлен.
вот несколько примеров:
счастливого пути:
- U1 и U2 оба подключены к системе.
- U2 отправляет сообщение на сервер, который должен получить U1.
- сервер хранит сообщение в каком-то постоянном хранилище, помечая его для U1 с помощью какая-то временная метка или последовательный идентификатор.
- сервер отправляет сообщение на U1 через Socket.IO.
- клиент U1 подтверждает (возможно, через a Socket.IO обратный вызов), чтобы он получил сообщение.
- сервер удаляет сохраненное сообщение из хранилища данных.
автономный путь:
- U1 теряет подключение к интернету.
- U2 отправляет сообщение на сервер, который должен получить U1.
- в сервер хранит сообщение в каком-то постоянном хранилище, помечая его для U1 какой-то временной меткой или последовательным идентификатором.
- сервер отправляет сообщение на U1 через Socket.IO.
- клиент U1 не подтвердите получение, потому что они находятся в автономном режиме.
- возможно, U2 отправляет U1 еще несколько сообщений; все они хранятся в хранилище данных таким же образом.
- когда U1 снова подключается, он спрашивает сервер "последнее сообщение, которое я видел, было X / у меня состояние X, что я пропустил."
- сервер отправляет U1 все сообщения, которые он пропустил из хранилища данных на основе запроса U1
- клиент U1 подтверждает получение, и сервер удаляет эти сообщения из хранилища данных.
Если вы абсолютно хотите гарантированную доставку, то важно спроектировать свою систему таким образом, что подключение на самом деле не имеет значения, и что доставка в реальном времени-это просто бонус; это почти всегда включает в себя хранилище данных какой-то. Как упоминалось в комментарии user568109, существуют системы обмена сообщениями, которые абстрагируются от хранения и доставки указанных сообщений, и, возможно, стоит изучить такое готовое решение. (Вам, вероятно, все равно придется написать 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 });Это очень похоже на последнее предложение, просто без постоянного хранилища данных.
вас также может заинтересовать понятие событие поиска.
похоже, что у вас уже есть система учетных записей пользователей. Вы знаете, какая учетная запись находится в сети / в автономном режиме, вы можете обрабатывать события подключения / отключения:
таким образом, решение заключается в добавлении онлайн / оффлайн и оффлайн сообщений в базу данных для каждого пользователя:
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); } })
смотрите здесь: обрабатывать перезагрузку браузера socket.io.
Я думаю, вы могли бы использовать решение, которое я придумал. Если вы измените его правильно, он должен работать как вы хотите.
Я думаю, что вы хотите иметь многоразовый сокет для каждого пользователя, что-то вроде:
клиент:
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's 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's 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свойство базы данных и сделать методы частью вашего прототипа пользователя. База данных не имеет значения, просто сохраните ее, однако вы сохраняете своих пользователей.Это позволит избежать в дополнение 1, требуя подтверждения от клиента, прежде чем пометить сообщение как отправленное. Если вы действительно хотите, вы можете дать каждому сообщению идентификатор и заставить клиента отправить идентификатор сообщения в
msg-conf, а затем проверить его.в этом примере
user- это пользователь шаблона, из которого копируются все пользователи, или как прототип пользователя.Примечание: это не было проверено.
смотрю на эти вещи в последнее время и думаю, что другой путь может быть лучше.
попробуйте посмотреть на служебную шину Azure, вопросы и темы позаботьтесь о состоянии off line. Сообщение подождите, пока пользователь вернется, а затем они получат сообщение.
Это стоимость запуска очереди, но ее как $ 0,05 за миллион операций для базовой очереди, поэтому стоимость dev будет больше от часов работы нужно написать очередь система. https://azure.microsoft.com/en-us/pricing/details/service-bus/
и azure bus имеет библиотеки и примеры для PHP, C#, Xarmin, Anjular, Java Script и т. д.
таким образом, сервер отправляет сообщение и не нужно беспокоиться об их отслеживании. Клиент может использовать сообщение для отправки обратно также как средство может обрабатывать балансировку нагрузки, если это необходимо.
Comments