在Web Worker或Service Worker中运行WebSocket - JavaScript

17

我有来自不同站点的9个WebSocket连接,用于更新DOM中的数据。目前,我正在连接到所有WebSocket并监听它们,并通过函数调用来更新数据。

我面临的问题是有很多WebSocket连接,存在内存和CPU使用问题。我该如何使用服务工作器和Web Workers来优化这么多WebSocket连接?

async function appendGatePublicTickersData(e) {
  if (e.event == "update" && e.result[0].contract == "BTC_USD") {
    if ('total_size' in e.result[0]) {
      $(".gate-btc-open-interest").html(commaNumber(e.result[0].total_size))
      if ('last' in e.result[0]) {
        $(".gate-btc-open-value").html(commaNumber(customFixedRounding((e.result[0].total_size / e.result[0].last), 4)))
      }
    }

    if ('volume_24h_usd' in e.result[0]) {
      $(".gate-btc-24-volume").html(commaNumber(e.result[0].volume_24h_usd))
    }

    if ('volume_24h_btc' in e.result[0]) {
      $(".gate-btc-24-turnover").html(commaNumber(e.result[0].volume_24h_btc))
    }

    if ('funding_rate' in e.result[0]) {
      var fundingRateBtcGate = customFixedRounding(e.result[0].funding_rate * 100, 4)
      $(".public-gate-btc-funding").html(fundingRateBtcGate)
    }

    if ('funding_rate_indicative' in e.result[0]) {
      var predictedRateBtcGate = customFixedRounding(e.result[0].funding_rate_indicative * 100, 4)
      $(".public-gate-btc-predicted").html(predictedRateBtcGate)
    }
  }
}

var pubGateWs = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/btc");

pubGateWs.addEventListener("open", function() {
  pubGateWs.send(JSON.stringify({
    "time": 123456,
    "channel": "futures.tickers",
    "event": "subscribe",
    "payload": ["BTC_USD", "ETH_USD"]
  }))
});

pubGateWs.addEventListener("message", function(e) {
  e = JSON.parse(e.data)
  appendGatePublicTickersData(e)
});

pubGateWs.addEventListener("close", function() {});

将Web Worker中的发送和接收数据部分移动可能会提高该端的性能。瓶颈在于数据传输还是DOM更新? - Emiel Zuurbier
我不确定,但当我连接到所有的Websockets时,CPU和RAM的使用量会显著增加。如何将Websocket移动到Web Worker? - Pavneet Singh
1个回答

30
由于您正在使用Web Sockets,建议使用SharedWorker为Web Sockets创建一个新线程。 普通的WebWorkerSharedWorker之间的区别在于,在加载页面时,Web Worker将在每个选项卡或浏览器中创建一个新会话,而共享工作者将在每个选项卡中使用相同的会话。 因此,所有选项卡或窗口都将具有相同的工作者和相同的Web Socket连接以供使用。
如果数据经常更新(每秒钟超过60次),并且DOM必须每次更新时进行更新,请使用requestAnimationFrame方法来限制DOM被更新的数量。 它将等待下一次重绘周期,然后使用新内容更新DOM,大约为每秒钟60次或60FPS。
实现示例如下:

主线程。

// Create shared worker.
const webSocketWorker = new SharedWorker('web-sockets-worker.js');

/**
 * Sends a message to the worker and passes that to the Web Socket.
 * @param {any} message 
 */
const sendMessageToSocket = message => {
  webSocketWorker.port.postMessage({ 
    action: 'send', 
    value: message,
  });
};

// Event to listen for incoming data from the worker and update the DOM.
webSocketWorker.port.addEventListener('message', ({ data }) => {
  requestAnimationFrame(() => {
    appendGatePublicTickersData(data);
  });
});
  
// Initialize the port connection.
webSocketWorker.port.start();

// Remove the current worker port from the connected ports list.
// This way your connectedPorts list stays true to the actual connected ports, 
// as they array won't get automatically updated when a port is disconnected.
window.addEventListener('beforeunload', () => {
  webSocketWorker.port.postMessage({ 
    action: 'unload', 
    value: null,
  });

  webSocketWorker.port.close();
});

共享工作者。

/**
 * Array to store all the connected ports in.
 */
const connectedPorts = [];

// Create socket instance.
const socket = new WebSocket("wss://fx-ws.gateio.ws/v4/ws/btc");

// Send initial package on open.
socket.addEventListener('open', () => {
  const data = JSON.stringify({
    "time": 123456,
    "channel": "futures.tickers",
    "event": "subscribe",
    "payload": ["BTC_USD", "ETH_USD"]
  });

  socket.send(data);
});

// Send data from socket to all open tabs.
socket.addEventListener('message', ({ data }) => {
  const payload = JSON.parse(data);
  connectedPorts.forEach(port => port.postMessage(payload));
});

/**
 * When a new thread is connected to the shared worker,
 * start listening for messages from the new thread.
 */
self.addEventListener('connect', ({ ports }) => {
  const port = ports[0];

  // Add this new port to the list of connected ports.
  connectedPorts.push(port);

  /**
   * Receive data from main thread and determine which
   * actions it should take based on the received data.
   */
  port.addEventListener('message', ({ data }) => {
    const { action, value } = data;

    // Send message to socket.
    if (action === 'send') {
      socket.send(JSON.stringify(value));

    // Remove port from connected ports list.
    } else if (action === 'unload') {
      const index = connectedPorts.indexOf(port);
      connectedPorts.splice(index, 1);
    }
  });

  // Start the port broadcasting.
  port.start();
});

顺便提一下:您的appendGatePublicTickersData没有使用await关键字,因此它不必是一个async函数。


现在支持Safari 16。
共享Web Workers的浏览器支持


1
描述与代码不符。即使您正在使用共享工作者,但在“connect”事件中创建了一个新的WebSocket,因此每个浏览器选项卡都使用一个WebSocket,而不是每个来源使用一个WebSocket。要按原点执行它,您需要在“connect”事件之外启动WebSocket,然后维护已连接端口的数组并为每个端口调用“postMessage”。 - whitehat101
1
@whitehat101,你说得完全正确。我已经修改了答案以包含您建议的方法。这样应该为同一域上的所有选项卡使用单个WS连接。 - Emiel Zuurbier
3
我认为你应该三思而后行使用sharedWorkers,因为它们不再受到WebKit的支持。所以在Safari上无法使用。 - Winston Jude
@EmielZuurbier,有趣。我几乎可以确定我之前在caniuse上检查过它,但自从15.4发布以来,也许事情已经发生了变化。我还阅读了这篇文章:https://itnext.io/safari-now-fully-supports-sharedworkers-534733b56b4c - Larry Maccherone
@LarryMaccherone 两天前,Safari的支持已经停止! - Emiel Zuurbier
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接