lzwaiwai / blog

5 stars 0 forks source link

前端直播页面,消息刷屏带来的问题 #3

Open lzwaiwai opened 5 years ago

lzwaiwai commented 5 years ago

因为便于分享和传播的优势,越来越多的移动客户端做的直播业务也会出现 H5 的版本当中

这里我们就来解决一些前端直播间页面,因为消息快速刷屏导致的问题。

发现问题

  1. 随着业务的发展,直播间的功能会变得越来越多,需要长连接支持的事件也会变得越来越多,所以很多除了看到的一些交互和消息体外,背后还会有很多数据的逻辑处理。
  2. 后端推过来的数据,我们需要做很多不同的处理,一些有关系的推送数据在处理时序上不能错位,否则会出现逻辑报错。
  3. 当消息来得很猛被刷屏的时,页面有些并行的动画和交互都会出现微微的卡顿和延迟。

优化方案

这里不做过多解释,直接上代码了~

  1. 针对维持有关系的数据在处理时序不错位,我们可以将推送过来的数据放入队列中去处理,保证所有的消息都是先后按照推送顺序进行处理的。

    这里我们可以利用 async.js:

    function QueueTask() {
      this.Q = async.queue((task, callback) => {
        console.log(`queue: ${task.name}`);
        callback();
      });
    }
    QueueTask.prototype.push = function (task, callback) {
      this.Q.push(task, callback);
    };
    
    const queueTask = new QueueTask();
    
    socket.on('event-1', msg => queueTask.push({ name: 'event-1' }, () => {
      log('#event-1', msg);
    }));
    
    socket.on('event-2', msg => queueTask.push({ name: 'event-2' }, () => {
      log('#event-2', msg);
    }));
  2. 针对消息刷屏时,导致的页面卡顿是主要由大量频繁的数据处理与同步的UI渲染同时进行引起的。因为 js 自身单线程设计,正常的处理没法将两者各自运行,不受影响。

    这里我们可利用 web worker 进行优化(使用框架的话,可以了解下worker-loader):

    index.js:

    
    window.onload = () => {
      if (window.Worker) {
        const socketWorker = new Worker('worker.js');
    
        socketWorker.postMessage({
          action: 'SOCKET_INIT',
          params: { transports: ['websocket'] }
        });
    
        socketWorker.onmessage = (e) => {
          const { action, data } = e.data;
          log('#' + action + ': ', data);
        };
      } else {
        // 兼容处理
      }  
    }

    worker.js:

    importScripts(
        'https://cdn.bootcss.com/socket.io/2.1.0/socket.io.js', 
        'https://cdnjs.cloudflare.com/ajax/libs/async/2.6.1/async.min.js');
    
    function QueueTask() {
      this.Q = async.queue((task, callback) => {
        console.log(`queue: ${task.name}`);
        callback();
      });
    }
    QueueTask.prototype.push = function (task, callback) {
      this.Q.push(task, callback);
    };
    
    function SocketHandler(socket, queue) {
      this.socket = socket;
      this.queue = queue;
    }
    
    SocketHandler.prototype.queuePost = function (event, data, err) {
      this.queue.push({ name: event }, () => {
        if (err) {
          postMessage({ action: event, err });
          return;
        }
        postMessage({ action: event, data });
      });
    };
    
    SocketHandler.prototype.on = function (event, callback = (msg) => msg) {
      this.socket.on(event, msg => {
        this.queuePost(event, callback(msg));
      });
    };
    
    SocketHandler.prototype.emit = function (event, param = {}, callback = (msg) => msg) {
      this.socket.emit(event, param, (err, msg) => {
        if (err) {
          this.queuePost(event, null, err);
          return;
        }
    
        this.queuePost(event, callback(msg));
      });
    };
    
    function socketInit(params) {
      let socketInited = false;
      const socket = io('/', params);
      const queueTask = new QueueTask();
      const socketHandler = new SocketHandler(socket, queueTask);
    
      socket.on('connect', () => {
        if (socketInited) { // 防止重连多次初始化
          return;
        }
    
        socketInited = true;
    
        socketHandler.queuePost('CONNECT', socket.id);
        socketHandler.on('RECEIVE_MESSAGE');
      });
    
      // 系统事件
      socketHandler.on('disconnect');
      socketHandler.on('disconnecting');
      socketHandler.on('error');
    }
    
    onmessage = (e) => {
      const { action, params } = e.data;
    
      if (action === 'SOCKET_INIT') {
        socketInit(params);
      }
    };