docs: function(channelService, route, msg, groups, opts, cb) {
    let app = channelService.app;
    let namespace = 'sys';
    let service = 'channelRemote';
    let method = 'pushMessage';
    let count = utils.size(groups);
    let successFlag = false;
    let failIds = [];
  
    logger.debug('[%s] channelService sendMessageByGroup route: %s, msg: %j, groups: %j, opts: %j', app.serverId, route, msg, groups, opts);
    if(count === 0) {
      // group is empty
      utils.invokeCallback(cb);
      return;
    }
  
    let latch = countDownLatch.createCountDownLatch(count, function() {
      if(!successFlag) {
        utils.invokeCallback(cb, new Error('all uids push message fail'));
        return;
      }
      utils.invokeCallback(cb, null, failIds);
    });
  
    let rpcCB = function(serverId) {
      return function(err, fails) {
        if(err) {
          logger.error('[pushMessage] fail to dispatch msg to serverId: ' + serverId + ', err:' + err.stack);
          latch.done();
          return;
        }
        if(fails) {
          failIds = failIds.concat(fails);
        }
        successFlag = true;
        latch.done();
      };
    };
  
    opts = {type: 'push', userOptions: opts || {}};
    // for compatiblity
    opts.isPush = true;
    
    let sendMessage = function(sid) {
      return (function() {
        if(sid === app.serverId) {
          channelService.channelRemote[method](route, msg, groups[sid], opts, rpcCB(sid));
        } else {
          app.rpcInvoke(sid, {namespace: namespace, service: service,
            method: method, args: [route, msg, groups[sid], opts]}, rpcCB(sid));
        }
      })();
    };
  
    let group;
    for(let sid in groups) {
      group = groups[sid];
      if(group && group.length > 0) {
        sendMessage(sid);
      } else {
        // empty group
        process.nextTick(rpcCB(sid));
      }
    }
  }