以前作過一些爬取方面的工做,因爲node不能多線程,爲了提升抓取效率,都是使用child_process.fork來多進程跑任務,而後經過message事件與主進程進行通訊,代碼編寫的時候都是用的yield/await之類的同步寫法,因而此次嘗試利用node非阻塞I/O的機制,利用多個函數同時運行來模擬多線程,效果如何呢?html
server.jsnode
用來統計qps,將產出的數據status.txt裏的內容複製到echarts的官方示例裏進行可視化,從而驗證是否能達到「並行」的效果linux
const fs = require('fs');
const Koa = require('koa');
const app = new Koa();
// 用來統計qps
let last_time = new Date(),
init_timestamp = last_time.getTime(),
count = 0;
// 運行時長60s
let run_secs = 60;
// 用來存儲qps歷史,用於繪製曲線圖
let qps_list = [],
timestr_list = [];
app.use(async ctx => {
// 簡單的模擬計算qps
let cur_time = new Date(),
cur_timestr = cur_time.toLocaleTimeString(),
cur_timestamp = cur_time.getTime(),
last_timestr = last_time.toLocaleTimeString();
if (cur_timestr !== last_timestr) {
let timestamp_cost = Math.round((cur_timestamp - init_timestamp) / 1000);
console.log(`\n${cur_timestr}: ${timestamp_cost} qps*********************************`);
console.log(count);
qps_list.push(count);
timestr_list.push(cur_timestr);
if (timestamp_cost >= run_secs) {
// 將運行結果存儲起來,打開http://echarts.baidu.com/examples/editor.html?c=line-smooth,複製內容查看曲線圖
let option_str = JSON.stringify({
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: timestr_list
},
yAxis: {
type: 'value'
},
series: [{
data: qps_list,
type: 'line',
smooth: true
}]
}, null, 2);
fs.writeFileSync('./status.txt', `option=${option_str}`);
console.log('1.複製status.txt的內容');
console.log('2.打開http://echarts.baidu.com/examples/editor.html?c=line-smooth');
console.log('3.粘貼在左邊代碼區域');
console.log('4.點擊"運行",在右側區域查看');
process.exit();
}
last_time = cur_time;
count = 1;
} else {
count++;
}
// 模擬服務端處理請求的時間
await delay();
ctx.body = 'hello';
});
function delay () {
return new Promise((resolve) => {
setTimeout(resolve, 250);
});
}
app.listen(3000);
複製代碼
client.jsios
const axios = require('axios');
async function sendRequest (id) {
return new Promise((resolve, reject) => {
axios.get(`http://localhost:3000?id=${id}`).then(res => {
resolve(res.data);
}).catch(e => {
reject(e);
});
});
}
function run () {
let threads = 1;
for (let i = 0; i < threads; i++) {
makeThread(i);
}
}
async function makeThread (id) {
while (true) {
try {
await sendRequest(id);
} catch (e) {
console.log(id, e.message);
process.exit();
}
}
}
run();
複製代碼
client_center.jsaxios
const fork = require('child_process').fork;
function run () {
let threads = 1;
for (let i = 0; i < threads; i++) {
fork('./client_worker.js', [i]);
}
}
run();
複製代碼
client_worker.jsbash
const axios = require('axios');
let id = process.argv[2];
async function sendRequest () {
return new Promise((resolve, reject) => {
axios.get(`http://localhost:3000?id=${id}`).then(res => {
resolve(res.data);
}).catch(e => {
reject(e);
});
});
}
async function makeThread () {
while (true) {
try {
await sendRequest();
} catch (e) {
console.log(id, e.message);
process.exit();
}
}
}
makeThread();
複製代碼
2核機器服務器
threads | 單進程版本 | 多進程版本 | 備註 |
---|---|---|---|
1 | 區別不大 | ||
5 | 區別不大 | ||
50 | 多進程效果弱於單進程版本 | ||
100 | 多進程效果弱於單進程版本 | ||
200 | 多進程弱於單進程版本,且多進程版本老是報錯:read ECONNRESET/connect ECONNRESET/socket hang up | ||
300 | 多進程弱於單進程版本,且多進程版本老是報錯:read ECONNRESET/connect ECONNRESET/socket hang up |
對比結果讓我挺吃驚的,這樣看來單進程的模擬效果竟然會比多進程好,但忽然想到本身電腦上才幾核,怎麼同時跑幾百個進程....... 登陸到公司服務器上(48核)繼續實驗:網絡
48核機器多線程
threads | 單進程版本 | 多進程版本 | 備註 |
---|---|---|---|
30 | 區別不大 | ||
40 | 區別不大 | ||
100 | qps峯值相同,但多進程更穩定 | ||
200 | 多進程版本優於單進程版本 | ||
300 | 多進程版本優於單進程版本 | ||
1000 | 多進程版本優於單進程版本 | ||
1500 | 多進程版本優於單進程版本,但threads增長所帶來的收益較低,多進程版本峯值4318<1500*4,單進程版本峯值3058<1500*4 | ||
3000 | 單進程版本(峯值2969)優於多進程版本(峯值1500) |