已使用技术
NodeJS, Socket.io
问题
假设有两个用户U1和U2通过Socket.io连接到一个应用程序上,算法如下:
- U1完全失去Internet连接(例如关闭Internet)
- U2向U1发送消息。
- U1暂时无法接收到消息,因为网络中断了
- 服务器通过心跳超时检测到U1的断开连接
- U1重新连接到socket.io
- U1从未收到来自U2的消息-我猜它在第4步丢失。
可能的解释
我认为我明白为什么会发生这种情况:
- 在第4步中,服务器终止了socket实例和消息队列到U1的连接
- 此外,在第5步中,U1和服务器创建了新的连接(不是重复使用的),因此即使消息仍在排队中,先前的连接也已丢失。
需要帮助
如何防止这种数据丢失?我必须使用心跳,因为我不想让人们在应用程序中挂起。同时我必须仍然给予重新连接的可能性,因为当我部署新版本的应用程序时,我希望没有停机时间。
P.S. 我所说的“消息”不仅是我可以存储在数据库中的文本消息,而是有价值的系统消息,其传递必须得到保证,否则UI会出问题。
补充 1
我已经拥有用户账户系统。此外,我的应用程序已经很复杂了。添加离线/在线状态不会有所帮助,因为我已经有了这样的东西。问题是不同的。
请注意第2步。 在此步骤中,我们从技术上讲不能说明U1是否下线,他只会失去连接,比如因为网络不好而持续2秒钟。所以U2发送了一条消息,但由于他的Internet仍旧处于断开状态(第3步),所以U1无法接收。第4步是检测脱机用户的必要步骤,例如,超时时间为60秒。最终,另外10秒钟后,U1的Internet连接再次恢复,他重新连接到socket.io。但是来自U2的消息在空间中丢失了,因为在服务器上,U1是按超时断开连接的。
这就是问题所在,我希望实现100%的传递。
解决方法
- 通过随机emitID识别{}用户中的emit(emit名称和数据)。发送emit。
- 在客户端确认emit(使用emitID向服务器发送emit)
- 如果已确认-从由emitID标识的对象中删除
- 如果用户重新连接-检查{}是否包含此用户,并循环执行每个对象中的步骤1
- 当断开连接或/和连接时,根据需要刷新{}用户
// Server
const pendingEmits = {};
socket.on('reconnection', () => resendAllPendingLimits);
socket.on('confirm', (emitID) => { delete(pendingEmits[emitID]); });
// Client
socket.on('something', () => {
socket.emit('confirm', emitID);
});
解决方案2(有点类似)
添加于2020年2月1日。
虽然这不是Websockets的真正解决方案,但某些人可能仍会发现它很方便。我们从Websockets迁移到了SSE+Ajax。 SSE允许您从客户端连接以保持持久的TCP连接并实时接收来自服务器的消息。要从客户端向服务器发送消息 - 只需使用Ajax即可。存在延迟和开销等缺点,但SSE保证可靠性,因为它是一个TCP连接。
由于我们使用Express,因此我们使用此库进行SSE https://github.com/dpskvn/express-sse,但您可以选择适合您的库。
IE和大多数Edge版本不支持SSE,因此您需要使用polyfill:https://github.com/Yaffle/EventSource。