学习

如何使用 Redis 进行 API 网关缓存

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

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

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

什么是 API 网关缓存?#

因此,您正在构建一个微服务应用程序。但是您发现自己难以处理身份验证方式,这些方式可以让您重用代码并最大限度地提高性能。通常对于身份验证,您可能会使用会话、OAuth、授权令牌等。出于本教程的目的,让我们假设我们正在使用授权令牌。在单体应用程序中,身份验证非常简单

当请求进来时

  1. 1.解码 Authorization 标题。
  2. 2.验证凭据。
  3. 3.在请求对象或缓存中存储会话信息,以便应用程序以后使用。

但是,您可能会对如何在微服务中执行此操作感到困惑。通常,在微服务应用程序中,API 网关充当客户端的单个入口点,该入口点将流量路由到适当的服务。根据请求的性质,这些服务可能需要或不需要用户进行身份验证。您可能认为在每个相应服务中处理身份验证是一个好主意。

虽然这可行,但您最终会得到大量的重复代码。此外,很难理解何时何地发生减速以及如何适当地扩展服务,因为您在每个服务中重复了一些相同的工作。处理身份验证的更有效方法是在 API 网关层进行处理,然后将会话信息传递给每个服务。

一旦您决定在 API 网关层处理身份验证,您就必须决定在哪里存储会话。

假设您正在构建一个使用 MongoDB/任何关系数据库作为主要数据存储的电子商务应用程序。您可以将会话存储在主数据库中,但想想应用程序需要访问主数据库多少次才能检索会话信息。如果您有数百万客户,您不会希望为 API 的每个请求都访问数据库。

这就是 Redis 的用武之地。

为什么要将 Redis 用于 API 网关缓存?#

Redis 是一个内存中数据存储,它可以(除了其他功能外)成为缓存会话数据的完美工具。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 支持的数据库 。这只是一个例子。

该图说明了 API 网关如何使用 Redis 作为会话信息的缓存。API 网关从 Redis 获取会话,然后将其传递给每个微服务。这提供了一种简单的方法来在一个地方处理会话,并将它们传播到其余微服务中。

小贴士

使用 Redis Cloud 集群 来获得线性扩展的优势,以确保 API 调用在峰值负载下执行。这也提供了 99.999% 的正常运行时间和主动-主动地理分布,从而防止身份验证和会话数据丢失。

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

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

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

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

GITHUB 代码

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

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

使用 Redis 在微服务应用程序中进行 API 网关缓存#

微服务架构的优点在于,每个服务都可以独立扩展。现在,由于每个服务可能都需要身份验证,因此您可能希望为大多数请求获取会话信息。因此,使用 API 网关来缓存和检索会话信息,然后将信息传递给每个服务是合理的。让我们看看如何实现这一点。

在我们的示例应用程序中,所有请求都通过 API 网关路由。我们使用 Express 来设置 API 网关,以及 Authorization 标题来将授权令牌从前端传递到 API。对于每个请求,API 网关都会获取授权令牌并在 Redis 中查找它。然后,它将令牌传递给正确的微服务。

此代码验证会话

import {
  createProxyMiddleware,
  responseInterceptor,
} from 'http-proxy-middleware';

//-----
const app: Express = express();

app.use(cors());
app.use(async (req, res, next) => {
  const authorizationHeader = req.header('Authorization');
  const sessionInfo = await getSessionInfo(authorizationHeader); //---- (1)

  //add session info to request
  if (sessionInfo?.sessionData && sessionInfo?.sessionId) {
    req.session = sessionInfo?.sessionData;
    req.sessionId = sessionInfo?.sessionId;
  }
  next();
});

app.use(
  '/orders',
  createProxyMiddleware({
    // https://localhost:3000/orders/bar -> https://localhost:3001/orders/bar
    target: 'https://localhost:3001',
    changeOrigin: true,
    selfHandleResponse: true,
    onProxyReq(proxyReq, req, res) {
      // pass session info to microservice
      proxyReq.setHeader('x-session', req.session);
    },
    onProxyRes: applyAuthToResponse, //---- (2)
  }),
);

app.use(
  '/orderHistory',
  createProxyMiddleware({
    target: 'https://localhost:3002',
    changeOrigin: true,
    selfHandleResponse: true,
    onProxyReq(proxyReq, req, res) {
      // pass session info to microservice
      proxyReq.setHeader('x-session', req.session);
    },
    onProxyRes: applyAuthToResponse, //---- (2)
  }),
);
//-----

const getSessionInfo = async (authHeader?: string) => {
  // (For demo purpose only) random userId and sessionId values are created for first time, then userId is fetched gainst that sessionId for future requests
  let sessionId = '';
  let sessionData: string | null = '';

  if (!!authHeader) {
    sessionId = authHeader.split(/\s/)[1];
  } else {
    sessionId = 'SES_' + randomUUID(); // generate random new sessionId
  }

  const nodeRedisClient = getNodeRedisClient();
  if (nodeRedisClient) {
    const exists = await nodeRedisClient.exists(sessionId);
    if (!exists) {
      await nodeRedisClient.set(
        sessionId,
        JSON.stringify({ userId: 'USR_' + randomUUID() }),
      ); // generate random new userId
    }
    sessionData = await nodeRedisClient.get(sessionId);
  }

  return {
    sessionId: sessionId,
    sessionData: sessionData,
  };
};

const applyAuthToResponse = responseInterceptor(
  // adding sessionId to the response so that frontend can store it for future requests

  async (responseBuffer, proxyRes, req, res) => {
    // detect json responses
    if (
      !!proxyRes.headers['content-type'] &&
      proxyRes.headers['content-type'].includes('application/json')
    ) {
      let data = JSON.parse(responseBuffer.toString('utf8'));

      // manipulate JSON data here
      if (!!(req as Request).sessionId) {
        data = Object.assign({}, data, { auth: (req as Request).sessionId });
      }

      // return manipulated JSON
      return JSON.stringify(data);
    }

    // return other content-types as-is
    return responseBuffer;
  },
);
信息

此示例并非旨在代表处理身份验证的最佳方式。相反,它说明了您可能在 Redis 方面执行的操作。您可能会有不同的身份验证设置,但将会话存储在 Redis 中的概念是类似的。

在上面的代码中,我们检查了 Authorization 头部,如果没有找到,我们创建一个新的头部并将其存储在 Redis 中。然后我们从 Redis 中检索会话。在后续流程中,我们在调用订单服务之前将会话附加到 x-session 头部。

现在让我们看看订单服务如何接收会话。

router.post(API_NAMES.CREATE_ORDER, async (req: Request, res: Response) => {
  const body = req.body;
  const result: IApiResponseBody = {
    data: null,
    error: null,
  };

  const sessionData = req.header('x-session');
  const userId = sessionData ? JSON.parse(sessionData).userId : "";
  ...
});

上面的高亮部分展示了如何从 x-session 头部中获取会话并获取 userId。

准备好使用 Redis 进行 API 网关缓存了吗?#

就是这样!您现在知道了如何使用 Redis 进行 API 网关缓存。入门并不复杂,但这种简单的做法可以帮助您在构建微服务时进行扩展。

要了解更多关于 Redis 的信息,请查看下面的补充资源。

补充资源#

请点击您的文本内容,然后按回车键。