让我们尝试构建一个可扩展的系统

2025-06-08

让我们尝试构建一个可扩展的系统

覆盖

我之前写过:

在本文中,我们将了解作为软件工程师构建可扩展系统可以采取的初步步骤。

让我们看看如何将负载测试时间从 187 秒减少到 31 秒

注意:我将使用Node.js,但不要跳过阅读,尝试吸收这个概念,特别是如果你是初学者。

任务如下:

构建一个只有一个请求的服务器,GET返回 0 - N 之间的最大素数

我的设置

  • 我使用纯 Node.js(不是express.js)创建我的服务器和路由,你可以自由使用express.js
  • 您可以使用任何语言来运用这个想法,因此不要跳过阅读,但您可以跳过代码/代码库。

开始吧!

我把这作为招聘(经验丰富的)开发人员的一项任务。面试环节通常采用结对编程的方式,候选人可以自由使用互联网和自己选择的工具。考虑到我的日常工作,这样的任务安排确实很有帮助。

当你写了一个暴力破解方法

假设你用基本算法创建了服务器来查找素数。以下是一个暴力破解示例:

// just trying the first thought in mind
function isPrime(n) {
  for(let i = 2; i <= Math.sqrt(n); i += 1) {
    if (n % i === 0){
      return false;
    }
  }
  return true;
}

function calculateGreatestPrimeInRange(num) {
    const primes = [];
    for (let i = 2; i <= num; i += 1) {
      if (this.isPrime(i)) primes.push(i);
    }
    return primes.length ? primes.pop() : -1;
  }

你可以试着在你的路线上使用它,GET就像这样https:localhost:9090/prime?num=20,它会工作得很好,你会感觉很好。你用一些数字试了一下,?num=10, 55, 101, 1099你会得到即时响应,感觉很棒 :)

坚持,稍等!

一旦你尝试一个大的数字num=10101091你就会感觉到滞后(我已经在浏览器中尝试过了,你可以使用Postman

由于我们现在没有使用PM2(它可以做很多初学者不知道的事情),您会注意到,当您尝试打开一个新选项卡并尝试一个较小的数字时,您的选项卡将等待上一个选项卡的结果。

你现在可以做什么?

让我们引入并发!

  • 集群模式来救援!

以下是集群模式实际运行的代码块。如果您不了解集群模块,请阅读相关内容。

const http = require('http');
const cluster = require('cluster');
const os = require('os');
const routes = require('./routes');

const cpuCount = os.cpus().length;

// check if the process is the master process
if (cluster.isMaster) {
  // print the number of CPUs
  console.log(`Total CPUs are: ${cpuCount}`);

  for (let i = 0; i < cpuCount; i += 1) cluster.fork();

  // when a new worker is started
  cluster.on('online', worker => console.log(`Worker started with Worker Id: ${worker.id} having Process Id: ${worker.process.pid}`));

  // when the worker exits
  cluster.on('exit', worker => {
    // log
    console.log(`Worker with Worker Id: ${worker.id} having Process Id: ${worker.process.pid} went offline`);
    // let's fork another worker
    cluster.fork();
  });
} else {
  // when the process is not a master process, run the app status
  const server = http.createServer(routes.handleRequests).listen(9090, () => console.log('App running at http://localhost:9090'));
}

瞧!

实施集群模块后,您将看到巨大的变化!

您可以注意到,使用线程后,编号较小的浏览器选项卡将快速获得响应,而另一个选项卡则忙于进行计算(您也可以在 Postman 中尝试一下)

对于那些不使用 Node.js 的人来说,集群模式意味着使用 CPU 中可用的线程以并发模式运行你的应用程序。

现在我们可以稍微放松一下了,但是我们还能做些什么来提高它的性能呢,因为我们的单个请求数量仍然很多,所以它仍然滞后?

算法来拯救你!

我知道这是一个令人难以忘怀的词,但它是一个你不能忽视的重要工具,最终,在实施一种新的算法之后,你就会意识到算法的价值。

对于素数,我们有一个埃拉托斯特尼筛法。
为了适应我们的用例,我们需要对其进行一些调整。完整的代码可以在 repo 中的类中找到Prime

让我们看一下负载测试结果

  • 暴力破解方法num=20234456

传递给loadtest 模块的命令:

loadtest -n 10 -c 10 --rps 200 "http://localhost:9090/prime?num=20234456"

结果:

INFO Total time:          187.492294273 s
INFO Requests per second: 0
INFO Mean latency:        97231.6 ms
INFO 
INFO Percentage of the requests served within a certain time
INFO   50%      108942 ms
INFO   90%      187258 ms
INFO   95%      187258 ms
INFO   99%      187258 ms
INFO  100%      187258 ms (longest request)
  • 使用经过修改的 SOEnum=20234456

传递给loadtest 模块的命令:

loadtest -n 10 -c 10 --rps 200 "http://localhost:9090/prime?num=20234456"

结果:

INFO Total time:          32.284605092999996 s
INFO Requests per second: 0
INFO Mean latency:        19377.3 ms
INFO 
INFO Percentage of the requests served within a certain time
INFO   50%      22603 ms
INFO   90%      32035 ms
INFO   95%      32035 ms
INFO   99%      32035 ms
INFO  100%      32035 ms (longest request)

您可以比较上述两个结果,并可以看到 SOE 显然是赢家。

我们能进一步改进它吗?

是的,我们可以,我们可以添加一个缓存,即 Javascript 中的一个普通对象,可以用作HashMap

使用缓存将存储给定数字 N 的结果,如果我们再次收到 N 的请求,我们可以简单地从存储中返回它,而不是进行计算。

REDIS 在这方面会做得更好

让我们看看结果

  • 利用缓存进行暴力破解num=20234456
INFO Target URL:          http://localhost:9090/prime?num=20234456
INFO Max requests:        10
INFO Concurrency level:   10
INFO Agent:               none
INFO Requests per second: 200
INFO 
INFO Completed requests:  10
INFO Total errors:        0
INFO Total time:          47.291413455000004 s
INFO Requests per second: 0
INFO Mean latency:        28059.6 ms
INFO 
INFO Percentage of the requests served within a certain time
INFO   50%      46656 ms
INFO   90%      46943 ms
INFO   95%      46943 ms
INFO   99%      46943 ms
INFO  100%      46943 ms (longest request)

  • 使用带有修改和缓存的SOEnum=20234456

INFO Target URL:          http://localhost:9090/prime-enhanced?num=20234456
INFO Max requests:        10
INFO Concurrency level:   10
INFO Agent:               none
INFO Requests per second: 200
INFO 
INFO Completed requests:  10
INFO Total errors:        0
INFO Total time:          31.047955697999996 s
INFO Requests per second: 0
INFO Mean latency:        19081.8 ms
INFO 
INFO Percentage of the requests served within a certain time
INFO   50%      23192 ms
INFO   90%      32657 ms
INFO   95%      32657 ms
INFO   99%      32657 ms
INFO  100%      32657 ms (longest request)

时间分析

状况 时间
使用基本算法 187.492294273 秒
带缓存 47.291413455000004 秒
有了 SOE 32.284605092999996 秒
带有 SOE 和缓存 31.047955697999996 秒

最后

我希望您了解以下内容的好处:

  • 多线程
  • 算法
  • 缓存又称为记忆化

希望你喜欢这篇短文,欢迎提出建议。代码仓库如下:find-highest-prime

您可以在GithubLinkedInTwitter上找到我

鏂囩珷鏉ユ簮锛�https://dev.to/ashokdey_/building-a-scalable-system-4l41
PREV
高度可扩展的代码库架构
NEXT
探索任何 JS 框架的完美首个项目