客戶端并發測試 Mqtt Broker

備份轉載金總的Note

概述

本文描述了對 example broker 進行性能測試時, 客戶端所需進行配置的相關步驟。

測試軟件

example broker 作為一個 Mqtt 服務器, 對它進行測試時需要一款對應的 Mqtt 客戶端。考慮到便捷性我們采用了基于 nodejs 的開源 Mqtt 客戶端: MQTT.js。在運行測試程序前需要安裝如下軟件。

  1. Git
  2. Node.js
  3. npm

在自己預設的工作目錄執行 git clone https://github.com/mqttjs/MQTT.js 下載最新的 MQTT.js。進入 MQTT.js 執行 npm install 下載所需的依賴包。

由于測試程序需要支持自定義 ip, 因此需要修改 MQTT.js 程序,允許增加額外的參數。使用文檔中所付的 tcp.js 替換目錄 MQTT.js 目錄下的同名文件。復制文檔中所付的 performace_test.js 至 MQTT.js 目錄下。

執行測試程序時需要額外的參數,以下是參數示例說明: node performance_test.js joshua 192.168.1.100 10.65.81.60:12306 50000 其中 joshua 為 mqtt client id 的前綴, 測試程序會在后面根據序號添加數字,保證每個唯一連接的 mqtt client id。192.168.1.100 為本機 ip, 應保證該 ip 為本機可用 ip 之一, 且可以正常的連通 example broker ip。10.65.81.60:12306 為需要測試的 mqtt 服務器地址及端口, 50000 為本次測試連接的客戶端數目。以上參數可根據測試所需自行修改。

操作系統

在使用測試服務器進行測試時, 如果所需并發量在 100,000 以下, 可以使用 Mac OS X 作為測試客戶端所用操作系統。如果大于 100, 000 則建議使用 Linux 操作系統, 并添加多個 ip 地址的形式進行測試。 下面會針對這兩種操作系統進行相關配置的介紹, 部分命令可能需要 root 權限。

Mac OS X

操作系統的參數修改有兩大類, 即 tcp/ip 相關的網絡參數及文件描述符的相關參數。

執行 sysctl -a | grep port, 查看 net.inet.ip.portrange.first, net.inet.ip.portrange.hifirst 的值。這兩項值定義了操作系統種用戶可用端口的起始范圍, 根據需要連接的并發量進行修改, 我們建議修改為 10,000。修改的命令如下: sysctl -w net.inet.ip.portrange.first=10000, sysctl -w net.inet.ip.portrange.hifirst=10000

執行 sysctl -a | grep files, 查看 kern.maxfiles, kern.maxfilesperproc 兩項的值。這兩項值決定了用戶可以打開的最大文件數和每個進程允許打開的最大文件數。我們建議修改為 1,000,000 和 100,000。修改的命令如下: sysctl -w kern.maxfiles=1000000, sysctl -w kern.maxfilesperproc=100000

在 4 core, 16G 內存的 rMBP 上, 單個 ip 可以穩定的保持最多 50,000 個長連接。如果需要繼續增加連接個數, 可以采取增加 ip 的方式, 但是根據實際的測試結果, 單臺 rMBP 能夠維持的穩定的長連接個數在 70,000 個左右, 因此如果需要大量長連接測試的情況, 我們推薦使用 Linux 服務器。

Linux

與 Mac OS X 類似, 首先執行 sysctl -a | grep port 查看 net.ipv4.ip_local_port_range 的值, 該項參數決定了用戶在系統內可以端口的范圍, 我們建議修改的下限為 10,000。

執行 sysctl -a | grep file 查看 fs.file-max 的值, 該項參數決定了用戶在系統中允許打開的最大文件個數, 我們建議修改為 1,500,000。

在 Linux 中可以通過添加虛擬 ip 的形式來增大測試的連接數, 在我們的測試案例中, 我們給單臺 Linux 服務器添加了 5 個虛擬 ip, 共計 6 個 ip, 單個 ip 可以穩定的維持 50,000 個長連接, 因此單臺服務器可以產生 300,000 個穩定的長連接。添加虛擬 ip 的命令示例如下: ifconfig eth0:1 192.168.190.151, 該命令在網卡 eth0 上添加了虛擬 ip 192.168.190.151

運行測試程序

將工作目錄切換至 MQTT.js 的根目錄, 執行之前介紹的客戶端命令, 即開始運行測試的客戶端程序。

var mqtt = require('./');
var EventEmitter = require('events').EventEmitter;
var connectingCount = 0;
var connectedCount = 0;
var connectClosedCount = 0;
var connectErrorCount = 0;
var tcpConnectedCount = 0;

var cid = 0;


var cmdOpts = process.argv.splice(2);
var clid = cmdOpts[0];
var localhostIp = cmdOpts[1];
var serverIp = cmdOpts[2];
var limit = cmdOpts[3];

console.log(clid);
console.log(localhostIp);
console.log(limit);

var doconnectInterval = null;

function addTimer(){
  doconnectInterval = setInterval(doConnect, 10);
}

function delTimer(){
  clearInterval(doconnectInterval);
}

addTimer();

setInterval(
  function(){
    if(connectedCount + connectingCount < limit){
      addTimer();
    }
    console.log("src[" + localhostIp
        + "] tcpConnectedCount[" + tcpConnectedCount
                + "] mqttConnectedCount[" + connectedCount
        + "] mqttConnectingCount[" + connectingCount
        + "] tcpConnectClosedCount[" + connectClosedCount
        + "] tcpConnectErrorCount[" + connectErrorCount + "]");
  },1000
);

function doConnect() {

  for(var i = 0; i < 8192; i++) {
    if( connectedCount + connectingCount >= limit || connectingCount >= 4096){
      delTimer();
      return true;
    }
    mqttTest();
  }
}

function mqttTest(){
  var client = mqtt.connect('tcp://' + serverIp, {
    clientId: 'mqtt-' + clid + '-' + (cid++),
    clean: false,
    keepalive: 300,
    reconnectPeriod: -1,
    localAddress: localhostIp
  });

  client.stream.on('connect', function(){
    tcpConnectedCount++;
  });
  connectingCount++;
  client.isConnected = false;

  client.on('connect', function(){
    connectedCount++;
    connectingCount--;
    client.isConnected = true;
    client.subscribe('hello');
  });

  client.on('close', function(data) {
    console.log('connection[' + (client.isConnected ? 'connected':'connecting') + '] close: ' + data);
    connectClosedCount++;
    if(client.isConnected){
      connectedCount--;
      tcpConnectedCount--;
    }else{
      connectingCount--
    }
  });

  client.on('message', function(topic, payload){
    console.log(topic + '\n' + payload);
  });

  client.on('error', function(error) {
    connectErrorCount++;
    console.log('mqtt error: ' + error);
  });
};
'use strict';
var net = require('net');

/*
  variables port and host can be removed since
  you have all required information in opts object
*/
function buildBuilder (client, opts) {
  var port, host, localAddress;
  opts.port = opts.port || 1883;
  opts.hostname = opts.hostname || opts.host || 'localhost';

  port = opts.port;
  host = opts.hostname;
  localAddress = opts.localAddress;

  if (!opts.localAddress) {
    return net.createConnection(port, host);
  }
  return net.createConnection({'port': port, 'host': host, 'localAddress': localAddress});
}

module.exports = buildBuilder;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容