Silicon Cloud部署Express应用:使用MemCachier实现高性能扩展

引言

Express是一种流行的框架,用于通过Node.js构建快速的网络应用程序和API。Silicon Cloud的应用平台是一款”平台即服务”(PaaS)产品,用于配置和部署代码库中的应用程序。它提供了一种快速高效的方式来部署您的Express应用程序。在本教程中,您将把一个Express应用程序部署到Silicon Cloud的应用平台,并通过Silicon Cloud市场中的MemCachier附加组件添加缓存来扩展它。MemCachier兼容memcached对象缓存系统,但具有更好的故障恢复能力和高可用性集群等多个优势。

首先,您将构建一个Express应用程序,用于计算质数,包含一个点赞按钮,并使用模板引擎。这些特性将使您能够在稍后实施几种缓存策略。然后,您将把应用程序的代码推送到GitHub,并部署在App Platform上。最后,您将实施三种对象缓存技术,使您的应用程序更快、更可扩展。通过本教程的学习,您将能够将Express应用程序部署到App Platform,并实施资源密集型计算、渲染视图和会话的缓存技术。

先决条件

  • 您的机器上已安装Node.js,您可以通过《如何在Ubuntu 22.04上安装Node.js》进行设置。在其他操作系统上,请按照《如何安装Node.js并创建本地开发环境》中的相应指南操作。
  • 一个使用Node.js的基本Express服务器。请按照我们关于《如何开始使用Node.js和Express》教程中的步骤1-2操作。
  • 一个GitHub账户和已在本地机器上安装的Git。这些是必需的,因为您需要将代码推送到GitHub以便从Silicon Cloud App Platform部署。您可以按照我们的《如何安装Git》教程进行设置。
  • 一个用于部署到App Platform的Silicon Cloud账户。在App Platform上运行此应用程序将产生费用。有关详细信息,请参阅App Platform定价。
  • 一个Web浏览器,如Firefox或Chrome。
  • 对Express模板引擎的理解。
  • 对Express中间件的理解。您可以在我们的教程《如何在Express.js中创建自定义中间件》中阅读更多关于此主题的内容。

第一步 – 设置一个使用Express模板渲染的视图

在这个步骤中,您将为Express安装模板引擎,并为您的应用程序的主页路由(GET /)创建一个模板,并更新该路由以使用该模板。模板引擎可以帮助您缓存渲染后的视图,提高请求处理速度并减少资源使用。

要开始,如果还没有打开,请使用编辑器导航到Express服务器的项目目录。您可以返回到有关如何开始使用Node.js和Express的先决条件教程,以确定您保存项目文件的位置。

您需要为Express安装一个模板引擎,以便在应用程序中使用静态模板文件。模板引擎会将模板文件中的变量替换为对应的值,并将模板转换成HTML文件,作为响应发送出去。使用模板可以更方便地处理HTML代码。

安装嵌入式JavaScript模板(ejs)库。如果您更喜欢,也可以使用Express支持的其他模板引擎,例如Mustache、Pug或Nunjucks。

  1. npm install ejs

现在已经安装了ejs,您需要配置Express应用程序来使用它。

在您的编辑器中打开文件server.js。然后,添加高亮显示的那一行。

server.js
const express = require('express');

const app = express();

app.set('view engine', 'ejs');

app.get('/', (req, res) => {
  res.send('Successful response.');
});

...

这行代码将应用程序的视图引擎属性设置为ejs。

保存文件。

注意

对于本教程,您将使用视图引擎设置,但另一个有用的设置是视图。视图设置告诉Express应用程序在哪里找到模板文件。默认值为./views。

接下来,创建一个views目录。然后,在您的编辑器中创建views/index.ejs文件并打开它。

将起始模板标记添加到该文件中。

views/index.ejs
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>查找最大的质数</title>
  </head>
  <body>
    <h1>查找最大的质数</h1>

    <p>
      对于任意数字N,找出小于或等于N的最大质数。
    </p>
  </body>
</html>

保存文件。

使用已创建的模板,您将更新您的路由以使用它。

打开文件server.js,并更新高亮代码。

server.js
...

app.get('/', (req, res) => {
  res.render('index');
});

...

在这种情况下,响应渲染方法将模板的名称作为其第一个参数。这里,index匹配文件views/index.ejs。

重新启动您的应用程序以加载更改。如果服务器正在运行,请在终端中按下CTRL+C停止服务器。然后再次启动服务器。

  1. node server.js

在您的网页浏览器中访问localhost:3000,这将显示您模板的内容。

您渲染的模板,包含标题和段落

你的应用程序现在有一个模板渲染的视图,但它还没有任何功能。接下来,你将添加查找质数的功能。

步骤二 – 向您的Express应用程序添加功能

在这一步中,你将添加用于找到质数和使用”喜欢”按钮来区分数字的功能。一旦你在第四步部署到应用程序平台,你将可以使用这些功能与应用程序进行交互。

寻找一个质数

在这一部分中,您将向您的应用程序添加一个函数,该函数可以找到小于或等于N的最大素数,其中N表示任意数字。

N将通过使用GET方法提交到主页路由(/)的表单中,N将作为查询参数附加:localhost:3000/?n=10(其中10是示例查询)。主页路由可以有多个URL产生渲染视图,每个URL都可以独立进行缓存。

在views/index.ejs中,添加一个带有输入元素的表单,用于输入N。

在中文中对以下内容进行同义复述(仅提供一种选择):views/index.ejs

...

<p>
  对于任意数字N,找到小于或等于N的最大质数。
</p>

<form action="/" method="get">
  <label>
    N
    <input type="number" name="n" placeholder="例如:10" required>
  </label>
  <button>查找质数</button>
</form>

...

表单的action提交到 /,这由server.js中的主要路径app.get(‘/’)处理。由于表单的方法被设置为get,数据n将作为查询参数附加到action的URL上。

保存文件。

接下来,当使用n作为查询参数发起请求时,你将把该数据传递给模板。

在server.js文件中,添加以下代码:

服务器.js

...

app.get('/', (req, res) => {
  const n = req.query.n;
  
  if (!n) {
    res.render('index');
    return;
  }
  
  const locals = { n };
  res.render('index', locals);
});

...

注意

注意:用户输入并不总是可信的,因此生产就绪的应用程序最佳实践是使用类似Joi的库对输入进行验证。

render方法有一个可选的第二个参数locals。该参数定义了传递给模板以渲染视图的局部变量。一个简化的属性名定义了locals对象的n属性。当一个变量的名称与它被赋值给的对象属性的名称相同时,可以省略变量名。因此,{ n: n }可以写成{ n }。

保存文件。

既然模板已经有了一些数据,你可以展示它了。

在views/index.ejs文件中,添加以下已标记的代码行以显示N的值:

查看/index.ejs

...

<form action="/" method="get">
  <label>
    N
    <input type="number" name="n" placeholder="例如:10" required>
  </label>
  <button>查找质数</button>
</form>

<% if (locals.n) { %>
  <p>N: <%= n %></p>
<% } %>

...

如果这个视图存在一个本地变量n,应用就会显示它。

保存文件,然后重新启动服务器以刷新应用程序。现在,表单将加载一个按钮来查找质数。该应用程序将能够接受用户输入并在表单下方显示。

现在带有查找质数表单的渲染模板

在表格中填写任何数字。提交表格后,URL将会改变并添加一个n查询参数,例如如果你填写了40,URL会变为http://localhost:3000/?n=40。你填写的值也会在表格下方显示,标注为N: 40。

现在显示表格下方提交数字的渲染模板

现在,可以提交和显示一个N的值,您将添加一个函数来找到小于或等于N的最大质数。然后,您将在视图中显示该结果。

创建一个名为utils的文件夹。然后,在其中创建一个名为findPrime.js的文件。

打开你的编辑器中的findPrime.js文件,并添加找到素数的函数:

找到质数的工具函数:utils/findPrime.js

/**
 * 查找小于或等于 `n` 的最大质数
 * @param {number} n 一个大于最小质数2的正整数
 * @returns {number}
 */
module.exports = function (n) {
  let prime = 2; // 初始化为最小质数
  for (let i = n; i > 1; i--) {
    let isPrime = true;
    for (let j = 2; j < i; j++) {
      if (i % j == 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      prime = i;
      break;
    }
  }
  return prime;
};

一个JSDoc注释记录了该函数。该算法从第一个质数(2)开始,然后通过循环遍历数字,从n开始并在每次循环中减1。该函数将继续循环并寻找质数,直到数字为2,即最小的质数。

每个循环假设当前数字是一个质数,并进行测试。它将检查当前数字是否有除了1和它自身之外的因数。如果当前数字能被大于1且小于自身的任何数字整除而不余数,则它不是一个质数。然后函数会尝试下一个数字。

保存该文件。

接下来,在 server.js 文件中导入”查找质数”函数。

服务器.js

这是文章《如何在Silicon Cloud应用平台上部署Express应用并使用MemCachier进行扩展》的第4部分(共22部分)。

const express = require('express');
const findPrime = require('./utils/findPrime');

...

在你的主页路由控制器中更新代码,找到一个质数并将其值传递到模板中。还在server.js中加入下面突出显示的代码。

服务器.js

...

app.get('/', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }
  
  const prime = findPrime(n);

  const locals = { n, prime };
  res.render('index', locals);
});

...

保存文件。

现在,您将添加代码以在模板中显示结果。在views/index.ejs中,显示N的值。

views/index.ejs

...

<form action="/" method="get">
  <label>
    N
    <input type="number" name="n" placeholder="例如:10" required>
  </label>
  <button>查找质数</button>
</form>

<% if (locals.n && locals.prime) { %>
  <p>
    小于或等于 <%= n %> 的最大质数是 <strong><%= prime %></strong>。
  </p>
<% } %>
...

保存文件。

现在重新启动服务器。

为了测试功能,请提交任意数字。以此教程为例,我们将使用数字10。如果您提交数字10,您将收到一条回应,内容为:小于或等于10的最大质数是7。

你的应用现在可以输入一个数字,然后找出并显示一个质数。接下来,你将添加一个点赞按钮。

添加一个”赞”按钮

目前,您的应用程序可以根据提交的每个数字N生成不同的视图。除了更新文字外,这些视图的内容可能会保持不变。您将在此部分中添加的”赞”按钮将提供一种更新视图内容的方法。该按钮展示了当视图内容发生变化时无效缓存视图的需要,在本教程后面缓存渲染视图时将会带来好处。

在有一个”赞”按钮的应用中,需要一个地方来存储点赞数据。虽然持久化存储是理想的选择,但由于数据库的实现超出了本教程的范围,因此您将在内存中存储点赞数据。因此,这些数据将是短暂的,也就是说当服务器停止时,所有数据都会丢失。

打开server.js文件,添加以下变量:

服务器.js

...

app.set('view engine', 'ejs');

/**
 * 键是 `n`
 * 值是 `n` 的点赞数
 */
const likesMap = {};

...

likesMap对象被用作一个映射,用于存储所有请求数字的点赞数。其键是n,值是n的点赞数。

当提交一个数字时,需要初始化该数字的点赞数。在 server.js 文件中,添加以下突出显示的代码来初始化 N 的点赞数。

服务器.js

...

  const prime = findPrime(n);

  // 必要时初始化该数字的点赞数
  if (!likesMap[n]) likesMap[n] = 0;

  const locals = { n, prime };
  res.render('index', locals);

...

这个if语句检查当前数字是否存在点赞。如果不存在点赞,则将likesMap数字初始化为0。

接下来,在视图中添加”likes”作为一个本地变量。

服务器.js

...

  const prime = findPrime(n);

  // 必要时初始化该数字的点赞数
  if (!likesMap[n]) likesMap[n] = 0;

  const locals = { n, prime, likes: likesMap[n] };
  res.render('index', locals);

...

保存文件。

现在视图有了点赞数据,你可以显示它的值并添加一个点赞按钮。

在views/index.ejs中,添加”喜欢”按钮的标记代码。

视图/索引.ejs

...

<% if (locals.n && locals.prime) { %>
  <p>
    小于或等于 <%= n %> 的最大质数是 <strong><%= prime %></strong>。
  </p>

  <form action="/like" method="get">
    <input type="hidden" name="n" value="<%= n %>">
    <input type="submit" value="点赞"> <%= likes %>
  </form>
<% } %>
...

你完成的文件现在应该与以下内容相匹配。

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>查找最大质数</title>
  </head>
  <body>
    <h1>查找最大质数</h1>

    <p>
      对于任意数字N,找出小于或等于N的最大质数。
    </p>

    <form action="/" method="get">
      <label>
        N
        <input type="number" name="n" placeholder="例如:10" required>
      </label>
      <button>查找质数</button>
    </form>

    <% if (locals.n && locals.prime) { %>
      <p>
        小于或等于 <%= n %> 的最大质数是 <strong><%= prime %></strong>。
      </p>
      <form action="/like" method="get">
        <input type="hidden" name="n" value="<%= n %>">
        <input type="submit" value="点赞"> <%= likes %>
      </form>
    <% } %>
  </body>
</html>

保存文件。

重启服务器,然后提交一个数字。质数结果出现后将会有一个点赞按钮,初始点赞数为0。

页面截图,其中有一个框围绕新添加的点赞按钮

点击”点赞”按钮将发送GET请求至/like,并通过隐藏输入框将当前的N值作为查询参数传递。目前,你会收到一个404错误页面,显示”无法获取/like”,因为你的应用程序还没有相应的路由设置。

你现在将添加处理该请求的路由。

在server.js文件中,添加路由:

服务器.js

...

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

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  res.redirect(`/?n=${n}`);
});

...

这条新路由会检查是否存在N。如果不存在,它会重定向到主页。否则,它会增加此数的点赞数。最后,它会重定向到点击所在的视图。

你已完成的文件现在应该与以下内容相匹配:

server.js(服务器.js)
const express = require('express');
const findPrime = require('./utils/findPrime');

const app = express();

app.set('view engine', 'ejs');

/**
 * 键是 `n`
 * 值是 `n` 的点赞数
 */
const likesMap = {};

app.get('/', (req, res) => {
  const n = req.query.n;
  
  if (!n) {
    res.render('index');
    return;
  }
  
  const prime = findPrime(n);

  // 必要时初始化此数字的点赞数
  if (!likesMap[n]) likesMap[n] = 0;

  const locals = { n, prime, likes: likesMap[n] };
  res.render('index', locals);
});

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  res.redirect(`/?n=${n}`);
});

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`示例应用正在监听端口 ${port}`)
);

保存文件。

重新启动应用并再次测试点赞按钮。每次点击,点赞数都会增加。

注意:

您也可以使用POST方法而不是GET方法来访问此路径。这样做将更符合RESTful规范,因为我们对资源进行了更新。本教程使用GET方法而不是介绍表单POST请求主体处理,以便您可以使用已经熟悉的请求查询参数进行操作。

你的应用现在具备全功能特性,可以准备将其部署到应用平台。在下一步中,你将用 git 提交应用的代码,并将该代码推送到 GitHub。

第三步 – 创建您的代码存储库

在这一步中,您将创建一个代码仓库来存放所有部署所需的文件。首先,您将把代码提交到 Git,然后将其推送到 GitHub 仓库。您将使用此仓库来使用应用平台进行部署。

将你的代码提交到Git上

在这一部分中,你将把你的代码提交到git,以便准备好推送到GitHub。

注意:

如果您还没有为您的用户名配置设置,请确保设置Git并使用SSH对您的GitHub账户进行身份验证。

首先,初始化一个git仓库。

  1. git init

接下来,告诉 Git 排除您的应用程序依赖项。创建一个名为 .gitignore 的新文件,然后添加以下内容:

.gitignore文件
node_modules

# macOS文件
.DS_Store

注意:

.DS_Store行是特定于macOS的,对于其他操作系统并不需要出现。

保存并关闭文件。

现在,将所有文件添加到git中。

  1. git add .

最后,使用以下命令提交这些更改:

  1. git commit -m "初始提交"

-m选项用于指定提交的消息,您可以使用任何您想要的消息来更新。

提交代码后,您将收到以下这样的输出。

[main (root-commit) deab84e] 初始提交 6 files changed, 1259 insertions(+) create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 server.js create mode 100644 utils/findPrime.js create mode 100644 views/index.ejs

你已经把你的代码提交到了 git 上。接下来,你要把它推送到 GitHub 上。

将你的代码推送到GitHub存储库

现在你的应用程序代码已经提交到了git,你可以准备将其推送到GitHub。然后,你可以将代码与Silicon Cloud应用平台连接并部署。

首先,在您的浏览器中登录到GitHub,创建一个名为express-memcache的新存储库。创建一个没有README、.gitignore或许可证文件的空存储库。您可以将存储库设置为私有或公开。您还可以查看GitHub关于创建新存储库的文档。

在您的终端中,将新建的存储库添加为远程 origin,并更新您的用户名。

  1. git remote add origin https://github.com/你的用户名/express-memcache.git

这个命令告诉Git把你的代码推送到哪里。

接下来,将默认分支 main 重命名为:

  1. git branch -M main

最后,将代码推送到你的代码库中。

  1. git push -u origin main

如果出现提示,请输入您的凭据。

您将收到类似于以下的输出:

枚举对象: 10, 完成. 计数对象: 100% (10/10), 完成. 使用最多8个线程进行增量压缩 压缩对象: 100% (7/7), 完成. 写入对象: 100% (10/10), 9.50 KiB | 9.50 MiB/s, 完成. 总计 10 (增量 0), 重用 0 (增量 0), 包重用 0 到 https://github.com/你的用户名/express-memcache.git * [新分支] main -> main 分支 'main' 设置为跟踪来自 'origin' 的远程分支 'main'.

你的应用代码现在已经在GitHub上,可以准备通过应用平台进行部署。

第四步 — 在应用平台上部署

在这个步骤中,您将把您的Express应用部署到Silicon Cloud应用平台上。您将创建一个应用平台应用,允许其访问您的GitHub存储库,然后进行部署。

您将首先更新环境设置,以便您的配置可以从PORT环境标签中读取。

更新您的应用程序的环境设置

在这个部分,你将扩展你的 Express 服务器,使得该应用的端口配置能够从环境变量中读取。由于在部署之间配置很可能会改变,这次更新将使你的应用能够从其 App 平台环境中读取端口。

在您的编辑器中打开文件 server.js。然后,在文件底部,更新突出显示的代码以替换现有的 app.listen 行,并添加一个新的 const port 行:

服务器.js
...

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`Example app is listening on port ${port}.`)
);

如果存在PORT环境变量,则代码会使用该变量,否则默认使用端口3000。

你的应用程序现在将能够从应用平台环境中读取端口,以便你能够将其部署。

在应用平台上创建和部署您的应用程序

你现在可以使用应用平台来设置你的应用程序。

在应用平台上运行此应用程序将产生费用,网络服务按秒计费(最低计费一分钟)。定价信息显示在审核界面上。有关详细信息,请参阅应用平台定价。

首先,登录您的Silicon Cloud账户。从应用程序仪表板中,点击创建,然后选择应用程序。您也可以按照我们的产品文档中关于如何在应用平台上创建应用程序的指南操作。

在”从源代码创建资源”界面上,选择GitHub作为服务提供商。然后,允许Silicon Cloud访问您的存储库。最佳实践是只选择要部署的存储库。如果您还没有这样做,系统将提示您安装Silicon Cloud GitHub应用程序。从列表中选择您的存储库,然后点击下一步。

在”资源”屏幕上,点击”编辑计划”选择您的计划大小。本教程将使用最小规模的网页服务(512MB内存 | 1虚拟CPU)的基本计划作为express-memcache资源。基本计划和最小规模的网页服务为这个样例Express应用程序提供足够的资源。设置完计划后,点击”返回”。

接下来,点击左侧导航栏的”信息”选项卡并记录你的应用程序所在的地区。在下一步添加MemCachier的Silicon Cloud Marketplace插件时,你将需要这个信息。

最后,点击”审查”选项卡,然后点击”创建资源”按钮来构建和部署您的应用程序。建立过程可能需要一点时间。当完成后,您将收到一个带有您已部署应用程序链接的成功消息。

到目前为止,你已经创建了一个能找到质数并拥有”赞”按钮的 Express 应用程序。你将应用程序的代码提交到了 Git 并推送到了 GitHub,并在应用平台上部署了该应用程序。

为了使你的 Express 应用程序更快速和可扩展,你将实施三种对象缓存策略。你需要一个缓存,在下一步中将创建它。

第五步-使用MemCachier配置对象缓存

在这一步中,您将创建和配置一个对象缓存。任何与Memcached兼容的缓存都适用于本教程。您将通过Silicon Cloud Marketplace的MemCachier附加组件来提供一个。MemCachier缓存是一个内存中的键值存储。

首先,您需要从Silicon Cloud Marketplace中添加MemCachier附加组件。访问MemCachier附加组件页面,然后点击”添加MemCachier”。在下一个页面上,选择您之前注意到的应用平台应用所在的地区。您的应用和缓存应该位于相同的地区,以尽量降低延迟。如果您需要再次查找地区,可以查看您的应用平台应用的设置。您可以选择一个计划,然后点击”添加MemCachier”来部署您的缓存。

请查询Silicon Cloud的可用数据中心以了解地区名称与标识之间的映射。例如,旧金山地区对应标识sfo3。

接下来,您将配置 Express 应用程序使用缓存。访问附加组件仪表板,然后点击您的 MemCachier 附加组件的名称以打开其仪表板。在 MemCachier 附加组件仪表板上,点击配置变量的显示按钮,以加载显示 MEMCACHIER_USERNAME、MEMCACHIER_PASSWORD 和 MEMCACHIER_SERVERS 的值。请记下这些值,因为您接下来会需要它们。

附加组件仪表板中配置变量的已编辑值截图

现在,您需要将MemCachier的配置变量保存为应用的环境变量。返回到您的应用平台应用的仪表板,点击设置。然后,在组件下,点击express-memc…。滚动到环境变量部分,点击编辑,然后使用从MemCachier仪表板中获取的三个键(MEMCACHIER_USERNAME、MEMCACHIER_PASSWORD和MEMCACHIER_SERVERS)及其对应的值添加您的MemCachier配置变量。对于MEMCACHIER_PASSWORD,选中加密,因为它是一个密码值。点击保存以更新应用程序。

应用平台环境变量配置窗口截图

现在,您将会在您的Express应用程序中配置一个memcache客户端,使用刚刚保存的环境变量,以便应用程序可以与您的缓存进行通信。

在你的终端中,安装memjs库。

  1. npm install memjs

接下来,创建一个服务目录。然后,在编辑器中创建services/memcache.js文件并打开它。在文件顶部,导入memjs并配置一个缓存客户端。

服务/内存缓存.js
const { Client } = require('memjs');

module.exports = Client.create(process.env.MEMCACHIER_SERVERS, {
  failover: true,
  timeout: 1,
  keepAlive: true,
});

保存文件。

此代码创建了一个MemCachier缓存客户端。至于选项,失败转移(failover)设置为true以使用MemCachier的高可用集群。如果服务器出现故障,存储在该服务器上的所有密钥的命令将自动发送到下一个可用的服务器。相比默认的0.5秒,1秒的超时(timeout)对于已部署应用来说更好。keepAlive设置为true即使处于闲置状态也保持与缓存的连接打开,这是可取的,因为建立连接是缓慢的,而缓存必须是高效的。

在这一步中,您使用Silicon Cloud Marketplace上的MemCachier附加组件配置了一个缓存。然后您将缓存的配置设置添加为App Platform环境变量,从而使您能够使用memjs配置客户端,以便您的Express应用程序可以与缓存进行通信。

一切准备就绪,接下来你将开始在Express中实施缓存。

第六步 – 使用MemCachier在Express中实现缓存

在您部署了Express应用程序并配置了MemCachier附加组件之后,您现在可以使用对象缓存。在这一步中,您将实现三种对象缓存技术。首先,您将缓存资源密集的计算以提高使用速度和效率。然后,您将实现在用户输入后缓存渲染视图的技术,以改善请求处理,并缓存短期会话,为超越本教程的应用程序扩展做好准备。

缓存高资源计算

在这个部分中,你将缓存资源密集的计算以加快应用程序的速度,从而实现更高效的CPU使用。当提交一个足够大的数字时,findPrime函数就是一个资源密集型的计算。你将缓存它的结果,并在有缓存值可用时提供该值,而不是重复进行计算。

首先,打开server.js文件以添加memcache客户端。

server.js(服务器.js)
const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');

...

然后,将计算得到的质数存储在缓存中。

服务器.js(server.js)
...

  const prime = findPrime(n);

  const key = 'prime_' + n;

  memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
    if (err) console.log(err);
  });

...

保存文件。

set方法接受一个键作为第一个参数,一个字符串值作为第二个参数,因此将质数转换为字符串。第三个选项参数确保存储的项目永不过期。第四个且最后一个参数是一个可选的回调函数,如果存在错误,则会抛出错误。

注意:作为一项最佳实践,缓存错误应该以优雅的方式处理。缓存是一种增强功能,通常不应在出错时导致应用程序崩溃。即使没有缓存,应用程序仍然可以正常运行,尽管速度可能会变慢。

注意:在这一点上,您的应用程序将继续在本地工作,但不会进行缓存。当调用 memcache.set 时,将输出错误,因为它将无法找到缓存服务器:

输出:MemJS: 连接到 <localhost:11211> 时进行 (2) 次重试后失败,错误为 – connect ECONNREFUSED 127.0.0.1:11211

错误:没有可用的服务器

在本教程的其余部分,您不需要本地缓存。如果您希望它起作用,您可以在 localhost: 11211 运行 memcached,这是 memjs 的默认选项。

接下来,将您的更改提交到版本控制系统中。

  
    
  1. git add . && git commit -m "添加memjs客户端并缓存质数"

然后,将这些更改推送到GitHub,应该会自动部署到App平台。

  
    
  1. git push

您的应用平台仪表板将从”已部署”消息转换为显示正在构建的消息。当构建完成后,请在浏览器中打开应用程序并提交一个数字以找到其最大质数。

注意:您的仪表板可能会显示等待服务的消息。这个消息通常会自动解决。如果仍然存在,请尝试刷新您的应用程序以检查构建是否已部署。

接下来,返回到”Add-Ons”仪表板,然后点击所命名的服务的”查看MemCachier”选项,以查看缓存的分析仪表板。

MemCachier分析仪表板

在这个仪表盘上,无论是在”所有时间统计”面板上的”设置命令”选项,还是在”存储”面板上的”物品统计”,都增加了1。每次提交一个数字,设置命令和物品都会增加。您需要按下刷新按钮来加载任何新的统计数据。

注意:在应用程序平台上检查您的应用程序日志对于调试很有价值。从您的应用程序仪表板上,点击运行时日志来查看它们。

有了缓存中存储的项目,您可以利用它们。您现在将检查项目是否已缓存,如果是,则将从缓存中提供;否则,将像以前一样找到质数。

回到server.js,使用下面突出显示的代码更新你的文件。你需要修改现有的代码行并添加新的行以用于缓存。

服务器.js
...

app.get('/', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }

  let prime;

  const key = 'prime_' + n;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // 使用缓存中的值
      // 在转换为数字之前先转换Buffer字符串
      prime = parseInt(val.toString());
    } else {
      // 没有可用的缓存值,进行计算
      prime = findPrime(n);

      memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
        if (err) console.log(err);
      });
    }

    // 必要时初始化此数字的点赞数
    if (!likesMap[n]) likesMap[n] = 0;

    const locals = { n, prime, likes: likesMap[n] };
    res.render('index', locals);
  });
});

...

保存文件。

该代码使用let关键字将prime初始化为无值,因为它的值将被重新分配。然后,memcache.get尝试检索缓存的质数。现在,大部分控制器的代码都位于memcache.get回调函数中,因为需要其结果来确定如何处理请求。如果有可用的缓存值,则使用它。否则,进行计算以找到质数,并像以前一样将结果存储在缓存中。

在memcache.get回调中返回的值是Buffer,所以在将其转换回数字之前,需要将其转换为字符串。

提交您的更改并将其推送到GitHub以部署。

  1. git add . && git commit -m "检查质数缓存" && git push

当您向您的应用程序提交一个尚未缓存的数字时,您的MemCachier仪表板上的Set命令、项和未命中统计数据将增加1。未命中是因为在设置之前尝试从缓存中获取该项。该项不在缓存中,导致未命中,然后将该项存储。当您提交一个已缓存的数字时,获取命中将增加。

你现在正在缓存资源密集型的计算。接下来,你将缓存你的应用程序的渲染视图。

缓存已呈现的视图

在这个部分中,你将使用中间件缓存Express应用程序渲染的视图。之前,你已经设置了ejs作为模板引擎,并创建了一个模板来渲染每个提交的数字N的视图。渲染视图可能需要消耗很多资源,因此缓存它们可以加快请求处理速度并节省资源。

首先,创建一个中间件目录。然后,创建文件middleware/cacheView.js并在编辑器中打开它。在cacheView.js中,为中间件函数添加以下行:

中间件/缓存视图.js

const memcache = require('../services/memcache');

/**
 * 用于缓存视图并提供缓存视图的Express中间件
 */
module.exports = function (req, res, next) {
  const key = `view_${req.url}`;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // 将Buffer字符串转换为响应主体发送
      res.send(val.toString());
      return;
    }
  });
};

首先导入memcache客户端。然后,声明一个键,比如view_/?n=100。接下来,使用memcache.get来检查缓存中是否存在该键的视图。如果没有错误并且该键存在对应的值,那么就无需进行其他操作,请求会将视图发送回客户端。

接下来,如果一个视图没有被缓存,你希望将其缓存起来。为了实现这一点,可以通过添加下面高亮的代码来重写res.send方法:

中间件/缓存视图.js
...

module.exports = function (req, res, next) {
  const key = `view_${req.url}`;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // 将Buffer转换为UTF-8字符串以作为响应体发送
      res.send(val.toString());
      return;
    }

    const originalSend = res.send;
    res.send = function (body) {
      memcache.set(key, body, { expires: 0 }, (err) => {
        if (err) console.log(err);
      });

      originalSend.call(this, body);
    };
  });
};

您可以使用一个函数来覆盖 res.send 方法,在调用原始的 send 函数之前将视图存储在缓存中。您可以使用 call 方法来调用原始的 send 函数,并将其上下文设置为未被覆盖时的值。请确保使用匿名函数表达式(而非箭头函数),以便正确的 this 值会被指定。

然后,通过添加需要强调的那一行将控制权传递给下一个中间件。

中间件/缓存视图.js

...

/**
 * 用于缓存视图并提供缓存视图的Express中间件
 */
module.exports = function (req, res, next) {
  const key = `view_${req.url}`;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // 将Buffer转换为UTF-8字符串以作为响应体发送
      res.send(val.toString());
      return;
    }

    const originalSend = res.send;
    res.send = function (body) {
      memcache.set(key, body, { expires: 0 }, (err) => {
        if (err) console.log(err);
      });

      originalSend.call(this, body);
    };

    next();
  });
};

...

调用next()会在应用程序中调用下一个中间件函数。在您的情况下,没有其他中间件,所以会调用控制器。Express中的res.render方法用于渲染视图,然后使用渲染后的视图内部调用res.send。因此,现在,在主页路由的控制器中,当调用res.render时,会调用您的覆盖函数,将视图存储在缓存中,最后调用原始的send函数来完成响应。

保存文件。

注意:您还可以将回调函数传递给控制器中的渲染方法,但是您将需要为每个被缓存的路由在控制器中复制视图缓存代码。

现在将视图缓存中间件导入到server.js中。

服务器.js

const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');

...

在GET / home路由中添加以下代码以使用它:

服务器.js

...

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

...

保存文件。

然后提交您的更改并将它们推送到GitHub进行部署。


  1. git add . && git commit -m "添加视图缓存" && git push

当您在您的应用程序中提交一个数字时,一切都应该像平常一样。如果您提交一个新的数字,MemCachier仪表盘上的Set Cmds、Items和get misses的统计数据都会增加两次:一次是用于素数计算,一次是用于视图。如果您用相同的数字刷新应用程序,您将在MemCachier仪表盘上看到一个单独的get hit。视图成功地从缓存中检索到,因此无需获取素数的结果。

注意:在生产环境中,默认情况下,Express应用程序启用了视图缓存。这个视图缓存只缓存了模板本身,而不是模板的输出内容。每次请求时,视图都会重新渲染,即使缓存是开启的。因此,它与你实现的渲染视图缓存不同但互补。

现在你正在缓存视图,你可能会注意到点赞按钮不再起作用了。如果你记录点赞的值,这个值确实会改变。然而,当点赞数量发生变化时,缓存视图仍然需要更新。当视图发生变化时,缓存视图需要被使无效。

下一步,当喜欢的内容发生改变时,你将通过从缓存中删除它来使之无效。回到server.js,更新重定向函数并添加以下突出显示的代码行:

服务器.js
...

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  // 被点赞的页面的URL
  const url = `/?n=${n}`;

  res.redirect(url);
});

...

由于该视图的点赞数已发生变化,缓存版本将不再有效。在点赞数发生变化时,请添加下面标记的代码行来从缓存中删除该点赞数。

服务器.js

...
  const url = `/?n=${n}`;

  // 此URL的视图已发生变化,因此缓存版本不再有效,将其从缓存中删除。
  const key = `view_${url}`;
  memcache.delete(key, (err) => {
    if (err) console.log(err);
  });

  res.redirect(url);
...

现在,您的server.js文件应该与以下部分匹配:

const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');

const app = express();

app.set('view engine', 'ejs');

/**
 * 键是 `n`
 * 值是 `n` 的"点赞"数量
 */
const likesMap = {};

app.get('/', cacheView, (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }

  let prime;

  const key = 'prime_' + n;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // 使用缓存中的值
      // 在转换为数字之前,先转换Buffer字符串
      prime = parseInt(val.toString());
    } else {
      // 没有可用的缓存值,查找它
      prime = findPrime(n);

      memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
        if (err) console.log(err);
      });
    }

    // 必要时初始化此数字的点赞数
    if (!likesMap[n]) likesMap[n] = 0;

    const locals = { n, prime, likes: likesMap[n] };
    res.render('index', locals);
  });
});

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  // 被"点赞"的页面的URL
  const url = `/?n=${n}`;

  // 此URL的视图已更改,因此缓存版本不再有效,将其从缓存中删除。
  const key = `view_${url}`;
  memcache.delete(key, (err) => {
    if (err) console.log(err);
  });

  res.redirect(url);
});

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`示例应用正在监听端口 ${port}`)
);

保存文件。

提交并推送更改以进行部署。

  1. git add . && git commit -m "删除无效的缓存视图" && git push

您的应用程序上的”喜欢”按钮现在将正常工作。当一个视图被点赞时,您的MemCachier仪表板上的以下统计数据将发生变化。

  • 删除命中次数会随着视图被删除而增加。
  • 获取未命中次数会增加,因为视图已被删除且不在缓存中。
  • 获取命中次数会增加,因为在缓存中找到了质数。
  • 设置命令次数会增加,因为更新的视图被添加到缓存中。
  • 项目数量保持不变,因为视图被删除并重新添加。

你已经实施了渲染视图缓存,并在视图更改时使缓存失效。你将要实现的最终策略是会话缓存。

缓存会话

在这一部分中,您将在Express应用程序中添加和缓存会话,使缓存成为会话存储。会话的常见用途是用户登录,因此您可以将在缓存中缓存会话的部分视为实施将来用户登录系统的初步步骤(尽管用户登录系统超出了本教程的范围)。将短期会话存储在缓存中比存储在许多数据库中更快速和可扩展。

注意:

缓存适合存储有超时时间的短期会话。然而,缓存是非持久性的;长期会话更适合使用像数据库这样的永久存储解决方案。

安装express-session工具将会话加入到你的Express应用中,并使用connect-memjs作为会话存储器来启用你的MemCachier缓存的使用。

  1. npm install express-session connect-memjs

在server.js中,导入express-session和connect-memjs。

server.js 文件内容如下:
const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');
const session = require('express-session');
const MemcacheStore = require('connect-memjs')(session);

...

保存文件。

会话中间件被传递给connect-memjs模块,使其能够继承自express.session.Store。

仍然在server.js中,配置会话中间件以使用缓存作为其存储。添加以下突出显示的行:

server.js:服务器引擎文件。
...

app.set('view engine', 'ejs');

app.use(
  session({
    secret: 'your-session-secret',
    resave: false,
    saveUninitialized: true,
    store: new MemcacheStore({
      servers: [process.env.MEMCACHIER_SERVERS],
      prefix: 'session_',
    }),
  })
);

...

使用密钥对会话Cookie进行签名。请确保将您的会话密钥更新为唯一的字符串。

注意:对于生产环境的设置,您应该使用环境变量来设置您的密钥。为此,您可以使用 secret: process.env.SESSION_SECRET || ‘your-session-secret’ 来设置密钥,但同时您也需要在您的App平台仪表板中设置环境变量。

如果在请求期间未对其进行修改,则重新保存会话。您不希望不必要地再次将项目存储在缓存中,因此将其设置为假。

当你只想保存已修改的会话时,saveUninitialized: false非常有用,这通常适用于登录会话,在身份验证后可能会将用户属性添加到会话中。在这种情况下,你会无差别地存储所有会话,所以将其设置为true。

最后,将存储器设置为缓存,并将会话缓存键的前缀设置为 session_。这意味着缓存中会话项的键将会像 session_<会话ID> 这样。

接下来,加入一些应用级别的调试中间件,使用下划线标出的行,这将有助于识别缓存会话的运行情况。

server.js

...

app.use(
  session({
    ...
  })
);

/**
 * 会话完整性检查中间件
 */
app.use(function (req, res, next) {
  console.log('会话ID:', req.session.id);

  // 从缓存中获取项目
  memcache.get(`session_${req.session.id}`, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      console.log('来自缓存的会话:', val.toString());
    }
  });

  next();
});

...

该中间件将记录每个请求的会话ID。然后它从缓存中获取该ID的会话并记录其内容。这种方法证明了会话正在正常工作,并且已被缓存。

保存文件,然后提交并推送您的更改以进行部署。

  1. git add . && git commit -m "添加会话缓存" && git push

在您的应用中提交一个数字,然后在您的应用平台仪表板中检查运行时日志,以访问您的调试信息。您将找到会话ID和您记录的值,证明会话正在正常工作并被缓存。

在您的 MemCachier 仪表盘上,一旦视图和会话被缓存,每次页面刷新时您会看到 3 次缓存命中:1 次用于视图,1 次用于会话,以及 1 次用于在调试中间件中获取会话。

您现在已经成功实施了会话缓存。您可以停在这里,或者您可以在可选的最后一步中清理您的应用程序。

(可选)第七步 – 清理您的资源

在本教程中部署的应用将产生费用,因此您可以选择在使用完成后销毁应用程序和MemCachier附加组件。

从应用程序的仪表板上,点击操作,然后选择销毁应用程序。

为了清理你的MemCachier插件,点击插件,然后选择你的MemCachier插件的名称。接下来,点击设置并选择销毁。一个免费的MemCachier缓存在30天不活动后会被停用,但是清理你的工具是一个好的做法。

结论

在这个教程中,你创建了一个Express应用程序,使用一个“喜欢”按钮来找到一个素数。然后你将该应用程序推送到GitHub,并在Silicon Cloud 应用程序平台上部署它。最后,你通过使用MemCachier Add-On来实现三种对象缓存技术,可以使Express应用程序更快、更可扩展,包括缓存资源密集型计算、渲染视图和会话。你可以在Silicon Cloud Community存储库中查看本教程的所有文件。

在你实施的每种缓存策略中,键都有一个前缀:prime_、view_和session_。除了命名空间的优势外,前缀还提供了额外的好处,可以让你对缓存性能进行分析。你在本教程中使用了MemCachier开发者计划,但你也可以尝试一个带有内省特性集的完全托管计划,使你能够追踪各个前缀的性能。例如,你可以监视任何前缀的命中率或命中比率,提供对缓存性能的详细洞察。要继续使用MemCachier,你可以查阅他们的文档。

为了继续在Silicon Cloud应用平台上进行构建,请尝试使用我们的应用平台操作指南,并深入阅读我们的应用平台文档。

bannerAds