Websocket传输可靠性(重新连接时Socket.io数据丢失)

igo*_*lov 72 node.js socket.io

用过的

NodeJS,Socket.io

问题

想象一下,有2个用户U1U2,通过Socket.io连接到应用程序.算法如下:

  1. U1完全失去互联网连接(例如关闭互联网)
  2. U2U1发送消息.
  3. U1尚未收到该消息,因为Internet已关闭
  4. 服务器通过心跳超时检测到U1断开连接
  5. U1重新连接到socket.io
  6. U1从未收到来自U2的消息- 我猜它在第4步丢失了.

可能的解释

我想我明白为什么会这样:

  • 第4步服务器杀死Socket实例和消息队列U1以及
  • 此外,在步骤5 U1服务器上创建新连接(它不被重用),因此即使消息仍然排队,以前的连接仍然会丢失.

需要帮忙

如何防止这种数据丢失?我必须使用hearbeats,因为我没有人永远挂在应用程序中.此外,我仍然必须重新连接,因为当我部署新版本的应用程序时,我希望零停机时间.

PS我称之为"消息"的东西不仅仅是我可以存储在数据库中的文本消息,而是有价值的系统消息,必须保证交付,或UI搞砸了.

谢谢!


增加1

我已经有了一个用户帐户系统.而且,我的申请已经很复杂了.添加离线/在线状态无济于事,因为我已经有了这种东西.问题不同了.

检查第2步.在这一步我们技术上不能说U1是否脱机,他只是失去连接让我说2秒,可能是因为互联网不好.所以U2给他发了一条消息,但U1没有收到它,因为互联网仍在为他服务(步骤3).需要步骤4来检测离线用户,假设超时为60秒.最后,在另一个10秒内,U1的互联网连接正常,他重新连接到socket.io.但来自U2的消息在空间中丢失,因为服务器U1在超时时断开连接.

这是问题,我不是100%交付.


  1. 收集{}用户的emit(发出名称和数据),由随机的emitID标识.发送发射
  2. 确认客户端的emit(使用emitID发送回服务器)
  3. 如果确认 - 从emitID标识的{}中删除对象
  4. 如果用户重新连接 - 请检查此用户的{}并循环执行它,为{}中的每个对象执行步骤1
  5. 在必要时断开连接或/和连接flush {}

    //服务器const pendingEmits = {};

    socket.on('reconnection',()=> resendAllPendingLimits); socket.on('confirm',(emitID)=> {delete(pendingEmits [emitID]);});

    // Client socket.on('something',()=> {socket.emit('confirm',emitID);});

Mic*_*ley 85

其他人在其他答案和评论中暗示了这一点,但根本问题是Socket.IO只是一种交付机制,你无法单独依靠它来实现可靠的交付.唯一确定消息已成功传递给客户端的人是客户端本身.对于这种系统,我建议做出以下断言:

  1. 消息不会直接发送给客户端; 相反,它们被发送到服务器并存储在某种数据存储中.
  2. 客户端负责在重新连接时询问"我错过了什么",并将查询数据存储中存储的消息以更新其状态.
  3. 如果在连接收件人客户端时将消息发送到服务器,则该消息将实时发送到客户端.

当然,根据您的应用程序的需要,您可以调整其中的一部分 - 例如,您可以使用Redis列表或消息的排序集,如果您知道客户端已启动,请将其清除至今.


以下是几个例子:

快乐的道路:

  • U1和U2都连接到系统.
  • U2向服务器发送U1应该接收的消息.
  • 服务器将消息存储在某种持久性存储中,使用某种时间戳或顺序ID将其标记为U1.
  • 服务器通过Socket.IO将消息发送到U1.
  • U1的客户端确认(可能通过Socket.IO回调)它收到了消息.
  • 服务器从数据存储中删除持久性消息.

离线路径:

  • U1失去了互联网连接.
  • U2向服务器发送U1应该接收的消息.
  • 服务器将消息存储在某种持久性存储中,使用某种时间戳或顺序ID将其标记为U1.
  • 服务器通过Socket.IO将消息发送到U1.
  • 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
});
Run Code Online (Sandbox Code Playgroud)

这与上一个建议完全相似,只是没有持久数据存储.


您可能也对事件采购的概念感兴趣.

  • 我等待最终的全面答复,并声明客户必须确认交货.似乎没有别的办法. (2认同)