学习

如何使用 Redis 进行查询缓存

Will Johnston
作者
Will Johnston, Redis 开发者增长经理
Prasan Kumar
作者
Prasan Kumar, Redis 技术解决方案开发者

什么是查询缓存?#

你是否曾遇到过数据库查询速度变慢的情况?查询缓存是您需要的技术,它可以利用不同的缓存方法来加速数据库查询,同时降低成本!假设您构建了一个电子商务应用程序。它起初很小,但正在快速增长。现在,您拥有庞大的产品目录和数百万用户。

这对业务来说是好事,但对技术来说却是一件难事。您对主数据库(MongoDB/ Postgressql)的查询开始变慢,即使您已经尝试过优化它们。即使您能稍微提高一些性能,也不足以满足您的客户需求。

为什么要使用 Redis 进行查询缓存?#

Redis 是一个内存数据存储,以缓存而闻名。Redis 允许您减少对主数据库的负载,同时加速数据库读取。

在任何电子商务应用程序中,都有一种特定类型的查询被最常请求。如果您猜到是产品搜索查询,那么您就对了!

为了改进电子商务应用程序中的产品搜索,您可以实现以下缓存模式之一

  • 缓存预取:整个产品目录可以预先缓存到 Redis 中,应用程序可以在 Redis 上执行与主数据库类似的任何产品查询。
  • 缓存旁路模式:Redis 按需填充,根据前端请求的任何搜索参数进行填充。
提示

如果您使用Redis Cloud,则缓存旁路会更容易,因为它支持 JSON 和搜索。您还可以获得其他功能,例如实时性能、高可扩展性、弹性和容错性。您还可以调用高可用性功能,例如 Active-Active 地理冗余。

本教程重点介绍缓存旁路模式。这种设计模式的目标是为更好的读取操作设置最佳缓存(按需加载)。在缓存方面,您可能熟悉“缓存未命中”,即您在缓存中找不到数据,“缓存命中”,即您可以在缓存中找到数据。让我们看看缓存旁路模式如何在 Redis 中针对“缓存未命中”和“缓存命中”进行工作。

使用 Redis 的缓存旁路(缓存未命中)#

此图说明了在缓存旁路模式中发生“缓存未命中”时执行的步骤。为了理解其工作原理,请考虑以下过程

  1. 1.应用程序从后端请求数据。
  2. 2.后端检查 Redis 中是否可用数据。
  3. 3.未找到数据(缓存未命中),因此从数据库中获取数据。
  4. 4.从数据库返回的数据随后存储在 Redis 中。
  5. 5.然后将数据返回给应用程序。

使用 Redis 的缓存旁路(缓存命中)#

现在您已经了解了“缓存未命中”的运行方式,让我们来了解一下“缓存命中”。以下是同一张图,但“缓存命中”步骤用绿色突出显示。

  1. 1.应用程序从后端请求数据。
  2. 2.后端检查 Redis 中是否可用数据。
  3. 3.然后将数据返回给应用程序。

缓存旁路模式在以下情况下很有用

  1. 1.频繁查询数据:当您有大量读取操作(就像电子商务应用程序一样)时,缓存旁路模式会立即提高后续数据请求的性能。
  2. 2.按需填充缓存:缓存旁路模式在请求数据时填充缓存,而不是预先缓存,从而节省空间和成本。这在不清楚哪些数据需要缓存时很有用。
  3. 3.注重成本:由于缓存大小与云端缓存存储的成本直接相关,缓存大小越小,您支付的费用就越少。
提示

如果您使用Redis Cloud和使用 JDBC 驱动程序的数据库,您可以利用Redis 智能缓存,它可以让您在不更改代码的情况下为应用程序添加缓存。 点击此处了解更多信息!

电子商务应用程序的微服务架构#

本教程其余部分中讨论的电子商务微服务应用程序使用以下架构

  1. 1.products service:处理从数据库查询产品并将其返回给前端
  2. 2.orders service:处理订单验证和创建
  3. 3.order history service:处理查询客户的订单历史记录
  4. 4.payments service:处理订单的付款处理
  5. 5.digital identity service:处理数字身份存储和身份评分计算
  6. 6.api gateway:将服务统一到单个端点
  7. 7.mongodb/ postgresql:充当主数据库,存储订单、订单历史记录、产品等。
  8. 8.redis:充当流处理器和缓存数据库
信息

您不需要在演示应用程序中使用 MongoDB/ Postgresql 作为主数据库;您也可以使用其他prisma 支持的数据库。这只是一个示例。

使用 Next.js 和 Tailwind 的电子商务应用程序前端#

这个电子商务微服务应用程序包含一个前端,使用 Next.js 和 TailwindCSS构建。应用程序后端使用 Node.js。数据存储在 Redis 和 MongoDB/Postgressql 中,并使用 Prisma。您将在下面找到电子商务应用程序前端的截图

  • Dashboard: 显示带有搜索功能的产品列表

Shopping Cart: 将产品添加到购物车,然后使用“立即购买”按钮结账

注意

以下是用于克隆本教程中使用的应用程序源代码的命令

git clone --branch v4.2.0 https://github.com/redis-developer/redis-microservices-ecommerce-solutions

在使用 Redis 和主数据库 (MongoDB/Postgressql) 的微服务应用程序中进行缓存#

在我们的示例应用程序中,产品服务发布了一个用于过滤产品的 API。以下是 API 调用的样子

通过过滤器请求获取产品#

docs/api/get-products-by-filter.md
// POST https://localhost:3000/products/getProductsByFilter
{
  "productDisplayName": "puma"
}

通过过滤器获取产品响应(缓存未命中)#

{
  "data": [
    {
      "productId": "11000",
      "price": 3995,
      "productDisplayName": "Puma Men Slick 3HD Yellow Black Watches",
      "variantName": "Slick 3HD Yellow",
      "brandName": "Puma",
      "ageGroup": "Adults-Men",
      "gender": "Men",
      "displayCategories": "Accessories",
      "masterCategory_typeName": "Accessories",
      "subCategory_typeName": "Watches",
      "styleImages_default_imageURL": "https://host.docker.internal:8080/images/11000.jpg",
      "productDescriptors_description_value": "<p style=\"text-align: justify;\">Stylish and comfortable, ...",
      "createdOn": "2023-07-13T14:07:38.020Z",
      "createdBy": "ADMIN",
      "lastUpdatedOn": "2023-07-13T14:07:38.020Z",
      "lastUpdatedBy": null,
      "statusCode": 1
    }
    //...
  ],
  "error": null,
  "isFromCache": false
}

通过过滤器获取产品响应(缓存命中)#

{
  "data": [
    //...same data as above
  ],
  "error": null,
  "isFromCache": true // now the data comes from the cache rather DB
}

使用 Redis 和主数据库 (MongoDB/Postgressql) 实现旁路缓存#

以下代码显示了用于在主数据库中搜索产品的函数

server/src/services/products/src/service-impl.ts
async function getProductsByFilter(productFilter: Product) {
  const prisma = getPrismaClient();

  const whereQuery: Prisma.ProductWhereInput = {
    statusCode: DB_ROW_STATUS.ACTIVE,
  };

  if (productFilter && productFilter.productDisplayName) {
    whereQuery.productDisplayName = {
      contains: productFilter.productDisplayName,
      mode: 'insensitive',
    };
  }

  const products: Product[] = await prisma.product.findMany({
    where: whereQuery,
  });

  return products;
}

您只需调用主数据库 (MongoDB/Postgressql) 即可根据产品的 displayName 属性上的过滤器查找产品。您可以设置多个列以实现更好的模糊搜索,但为了本教程的目的,我们进行了简化。

直接使用主数据库而不使用 Redis 可以工作一段时间,但最终会变慢。这就是您可能使用 Redis 来加速的原因。旁路缓存模式可以帮助您平衡性能和成本。

旁路缓存的基本决策树如下所示。

当前端请求产品时

  1. 1.使用请求内容(即搜索参数)生成一个哈希值。
  2. 2.检查 Redis 以查看是否存在该哈希值的 value。
  3. 3.是否缓存命中?如果找到了该哈希值的 data,则会返回;进程到此结束。
  4. 4.是否缓存未命中?当找不到 data 时,它会从主数据库中读取,然后在返回之前存储在 Redis 中。

以下是用于实现决策树的代码

server/src/services/products/src/routes.ts
const getHashKey = (_filter: Document) => {
  let retKey = '';
  if (_filter) {
    const text = JSON.stringify(_filter);
    retKey = crypto.createHash('sha256').update(text).digest('hex');
  }
  return 'CACHE_ASIDE_' + retKey;
};

router.post(API.GET_PRODUCTS_BY_FILTER, async (req: Request, res: Response) => {
  const body = req.body;
  // using node-redis
  const redis = getNodeRedisClient();

  //get data from redis
  const hashKey = getHashKey(req.body);
  const cachedData = await redis.get(hashKey);
  const docArr = cachedData ? JSON.parse(cachedData) : [];

  if (docArr && docArr.length) {
    result.data = docArr;
    result.isFromCache = true;
  } else {
    // get data from primary database
    const dbData = await getProductsByFilter(body); //method shown earlier

    if (body && body.productDisplayName && dbData.length) {
      // set data in redis (no need to wait)
      redis.set(hashKey, JSON.stringify(dbData), {
        EX: 60, // cache expiration in seconds
      });
    }

    result.data = dbData;
  }

  res.send(result);
});
提示

您需要决定什么过期时间或生存时间 (TTL) 最适合您的特定用例。

准备使用 Redis 进行查询缓存?#

现在您已经了解了如何使用 Redis 和最常见的缓存模式之一(旁路缓存)进行缓存。您可以根据需要逐步采用 Redis 和不同的策略/模式。有关微服务主题的更多资源,请查看以下链接

其他资源#