同步計(jì)數(shù)器的并發(fā)性能

一個(gè)常見的需求是統(tǒng)計(jì)網(wǎng)頁(yè)的瀏覽量,我們將使用不同的技術(shù)方案來(lái)比較對(duì)并發(fā)性能的影響,常用的技術(shù)方案包括使用內(nèi)存,redis,mysql等保存計(jì)數(shù),這里為了突出個(gè)方案對(duì)并發(fā)性能造成的影響,我們將使用同步計(jì)數(shù)器,即計(jì)數(shù)完成之后才返回頁(yè)面,我們將使用express.js框架來(lái)提供接口并使用autocannon工具進(jìn)行壓測(cè)。

首先我們看下一個(gè)簡(jiǎn)單的helloworld頁(yè)面:

const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

看一下這個(gè)頁(yè)面的qps,平均2萬(wàn)1左右,我們將使用這個(gè)作為基準(zhǔn)看各個(gè)方案對(duì)并發(fā)性能造成的影響:

Running 10s test @ http://localhost:3000
10 connections

┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬─────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max     │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼─────────┤
│ Latency │ 0 ms │ 0 ms │ 0 ms  │ 1 ms │ 0.02 ms │ 0.14 ms │ 8.21 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴─────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg      │ Stdev  │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼────────┼─────────┤
│ Req/Sec   │ 20543   │ 20543   │ 22159   │ 22287   │ 21785.46 │ 625.12 │ 20533   │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼────────┼─────────┤
│ Bytes/Sec │ 4.44 MB │ 4.44 MB │ 4.79 MB │ 4.81 MB │ 4.71 MB  │ 135 kB │ 4.44 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴────────┴─────────┘

Req/Bytes counts sampled once per second.

240k requests in 11.05s, 51.8 MB read

(1)首先,我們考慮使用內(nèi)存計(jì)數(shù)器:

const express = require('express')
const app = express()
const port = 3000
let counter = 0;

app.get('/', (req, res) => {
  counter++;
  res.send('Hello World!')
})

app.get('/counter', (req, res) => {
  res.json({ counter });
})

app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

看一下并發(fā)性能,qps依舊保持在2萬(wàn)多,對(duì)性能幾乎沒(méi)有什么影響,但內(nèi)存計(jì)數(shù)器的劣勢(shì)也是很明顯的,不支持集群,不能做持久化,node進(jìn)程結(jié)束內(nèi)存中的數(shù)據(jù)就丟了。

Running 10s test @ http://localhost:3000
10 connections

┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 0 ms │ 0 ms │ 1 ms  │ 1 ms │ 0.04 ms │ 0.21 ms │ 16.01 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Req/Sec   │ 13263   │ 13263   │ 21023   │ 21871   │ 20395.6 │ 2456.94 │ 13259   │
├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤
│ Bytes/Sec │ 2.87 MB │ 2.87 MB │ 4.54 MB │ 4.72 MB │ 4.41 MB │ 531 kB  │ 2.86 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘

Req/Bytes counts sampled once per second.

204k requests in 10.04s, 44.1 MB read

(2)要支持集群和持久化的話,我們首先想到的是使用redis,redis提供了INCR命令實(shí)現(xiàn)自增,很適合用作計(jì)數(shù)器:

const express = require('express')
const app = express()
const port = 3000
const redis = require('redis');
const client = redis.createClient();

app.get('/', (req, res) => {
  client.incr('counter', function(err, reply) {
    res.send('Hello World!')
  });
})

app.get('/counter', (req, res) => {
  client.get('counter', (err, counter) => {
    res.json({ counter });
  })
})

app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

看一下并發(fā)性能,qps保持在1萬(wàn)9左右,比內(nèi)存計(jì)數(shù)器要稍微少一點(diǎn),但并沒(méi)有損失多少,是一個(gè)很理想的替代方案。

Running 10s test @ http://localhost:3000
10 connections

┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 0 ms │ 0 ms │ 0 ms  │ 1 ms │ 0.03 ms │ 0.22 ms │ 17.19 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬─────────┬─────────┬─────────┬─────────┬──────────┬─────────┬─────────┐
│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg      │ Stdev   │ Min     │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Req/Sec   │ 11895   │ 11895   │ 20431   │ 20767   │ 19630.91 │ 2464.33 │ 11889   │
├───────────┼─────────┼─────────┼─────────┼─────────┼──────────┼─────────┼─────────┤
│ Bytes/Sec │ 2.57 MB │ 2.57 MB │ 4.41 MB │ 4.49 MB │ 4.24 MB  │ 533 kB  │ 2.57 MB │
└───────────┴─────────┴─────────┴─────────┴─────────┴──────────┴─────────┴─────────┘

Req/Bytes counts sampled once per second.

216k requests in 11.05s, 46.6 MB read

(3)我們?cè)趤?lái)看使用mysql的話,性能會(huì)影響多少,首先我們來(lái)設(shè)計(jì)一張表來(lái)保存計(jì)數(shù):

CREATE TABLE `hit_counter` (
  `id` int NOT NULL,
  `cnt` int unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `hit_counter`(`id`, `cnt`) VALUES (1, 0);

代碼如下:

const express = require('express')
const app = express()
const port = 3000
const mysql = require('mysql');
const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: '******',
  database: 'webapp'
});
connection.connect();

app.get('/', (req, res) => {
  connection.query('update hit_counter set cnt = cnt + 1 where id = 1', function(err, results) {
    res.send('Hello World!')
  });
})

app.get('/counter', (req, res, next) => {
  connection.query('select * from hit_counter where id = 1', function(err, results) {
    if (err) next(err);
    const counter = results[0].cnt;
    res.json({ counter });
  })
})

app.listen(port, () => console.log(`Example app listening at http://localhost:${port}`))

我們看下并發(fā)性能,發(fā)現(xiàn)qps降到了2千多,差不多只有原接口的1/10,響應(yīng)時(shí)間也由原來(lái)的0.02ms增加到4.14ms,對(duì)性能的影響還是很大的。

Running 10s test @ http://localhost:3000
10 connections

┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐
│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │
├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤
│ Latency │ 4 ms │ 4 ms │ 5 ms  │ 7 ms │ 4.14 ms │ 0.59 ms │ 14.35 ms │
└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘
┌───────────┬────────┬────────┬────────┬────────┬─────────┬─────────┬────────┐
│ Stat      │ 1%     │ 2.5%   │ 50%    │ 97.5%  │ Avg     │ Stdev   │ Min    │
├───────────┼────────┼────────┼────────┼────────┼─────────┼─────────┼────────┤
│ Req/Sec   │ 1925   │ 1925   │ 2157   │ 2193   │ 2135.73 │ 71.85   │ 1925   │
├───────────┼────────┼────────┼────────┼────────┼─────────┼─────────┼────────┤
│ Bytes/Sec │ 416 kB │ 416 kB │ 466 kB │ 474 kB │ 461 kB  │ 15.5 kB │ 416 kB │
└───────────┴────────┴────────┴────────┴────────┴─────────┴─────────┴────────┘

Req/Bytes counts sampled once per second.

23k requests in 11.03s, 5.07 MB read

總結(jié)
在上面的例子中,我們使用了同步計(jì)數(shù)器來(lái)比較內(nèi)存、redis和mysql對(duì)并發(fā)性能的影響,內(nèi)存和redis對(duì)性能的影響較小,mysql影響較大,而且redis還可以使用在集群中并且支持持久化,因此是最佳選擇。在真實(shí)的使用場(chǎng)景中,對(duì)于計(jì)數(shù)器,我們一般使用異步的方式,因此對(duì)性能影響不會(huì)有較大差異,但使用redis依舊是很好的選擇。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容