从前端实时通信的角度聊聊Long Polling的实现与优化

贝贝 交互 阅读 1,708
赞 15 收藏
二维码
手机扫码查看
反馈

项目初期的技术选型

这个项目是给一个内部管理系统做实时通知功能。一开始,客户提出的需求很简单:用户需要在操作界面时能立刻看到系统消息更新。听起来没什么特别的,对吧?但问题在于,他们的服务器环境老旧,WebSocket压根用不了,而且预算有限,也没法升级硬件。

从前端实时通信的角度聊聊Long Polling的实现与优化

我琢磨了一下,长轮询(Long Polling)可能是最现实的选择。虽然它性能上比不上WebSocket,但胜在兼容性好、实现简单。于是就这么定了。说实话,开始的时候我还挺乐观的,觉得这技术早就烂熟于心了,应该不会出什么岔子。结果嘛,后面踩的坑真是让我怀疑人生。

核心代码就这几行

先说下Long Polling的基本实现吧,其实就是客户端发起一个HTTP请求后,服务器会hold住这个连接,直到有新数据才返回响应。然后客户端收到数据后再发起新的请求,如此循环。

来看一下服务端的关键代码:

const express = require('express');
const app = express();

let messages = [];

// 模拟新消息推送
app.post('/api/sendMessage', (req, res) => {
  const message = req.query.message;
  if (message) {
    messages.push(message);
    res.send({ success: true });
  } else {
    res.status(400).send({ error: 'No message provided' });
  }
});

// Long Polling接口
app.get('/api/longPolling', (req, res) => {
  const checkForUpdates = () => {
    if (messages.length > 0) {
      const currentMessages = [...messages];
      messages = []; // 清空消息队列
      res.json(currentMessages);
    } else {
      setTimeout(checkForUpdates, 1000); // 继续等待
    }
  };
  checkForUpdates();
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

再来看前端:

function longPolling() {
  fetch('https://jztheme.com/api/longPolling')
    .then(response => response.json())
    .then(data => {
      console.log('Received messages:', data);
      // 处理接收到的消息
      updateUI(data);
      longPolling(); // 立即发起下一个请求
    })
    .catch(error => {
      console.error('Error during long polling:', error);
      setTimeout(longPolling, 5000); // 出错后稍等再重试
    });
}

// 初始化长轮询
longPolling();

看起来是不是挺简单的?我当时也是这么想的,觉得分分钟搞定上线。结果一运行,才发现事情远没有想象中顺利。

最大的坑:性能问题

刚开始测试的时候,一切正常。但是当并发用户数超过50个之后,服务器直接崩溃了。后来排查发现,问题是出在服务器hold住大量连接导致资源耗尽。每个未完成的请求都会占用一个线程,而Node.js默认的线程池大小有限,撑不住这么多连接。

解决这个问题花了我整整两天时间。我尝试了几种优化方案:

  • 限制最大连接数:通过设置一个队列,控制同时处理的请求数量。超出部分直接返回错误。
  • 使用Timeout机制:给每个请求加了一个超时时间(比如30秒)。如果超时还没新数据,就返回空值并让客户端重新发起请求。
  • 切换到集群模式:利用Node.js的cluster模块启动多个进程来分担压力。

最终效果还不错,但还是有个遗留问题:如果短时间内有大量用户频繁断开重连,服务器偶尔会出现短暂卡顿。这个问题没完全解决,不过考虑到实际使用场景,影响不算太大。

另一个意外:网络不稳定

除了性能问题,还有一个让我头疼的是网络波动导致的请求失败。尤其是在移动设备上,信号弱的地方经常出现请求中断的情况。我的第一反应是加重试机制,但很快发现这样会导致重复接收消息的问题。

折腾了半天,最后决定在服务端引入一个简单的去重逻辑:

let lastMessageId = 0;

app.get('/api/longPolling', (req, res) => {
  const clientId = req.query.clientId || Date.now(); // 假设每个客户端有一个唯一ID
  const checkForUpdates = () => {
    if (messages.length > lastMessageId) {
      const newMessages = messages.slice(lastMessageId);
      lastMessageId = messages.length;
      res.json({ clientId, messages: newMessages });
    } else {
      setTimeout(checkForUpdates, 1000);
    }
  };
  checkForUpdates();
});

这样一来,即使客户端多次重试,也不会重复接收相同的数据了。

回顾与反思

总的来说,这个项目的难点主要集中在性能优化和网络稳定性上。虽然最终没能做到完美无缺,但基本满足了客户需求。以下是一些经验教训:

  • Long Polling确实适合低预算、高兼容性的场景,但性能瓶颈不容忽视。
  • 提前规划好重试策略和去重机制,可以避免很多麻烦。
  • 对于高并发需求,还是尽量考虑WebSocket或者其他更高效的实时通信方式。

以上是我踩坑后的总结,希望对你有帮助。如果你在类似项目中有更好的解决方案,欢迎评论区交流!后续我还会继续分享更多实战经验,咱们下次见。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论