Computer >> 컴퓨터 >  >> 프로그램 작성 >> Redis

Cloudflare 작업자 및 서버리스 Redis를 사용하여 웹 사이트를 위한 자체 대기실 구축

이 블로그 게시물에서는 웹사이트의 대기실 페이지를 구현합니다.

왜?

웹사이트 방문자 수가 많다는 것은 일반적으로 좋은 일이지만 항상 그런 것은 아닙니다. 갑자기 많은 트래픽이 애플리케이션을 압도하여 서비스를 완전히 중단시킬 수 있습니다. 대기실은 트래픽이 급증하는 동안 트래픽을 제어하고 리소스를 보호하는 데 도움이 되는 솔루션입니다. Cloudflare Waiting Room은 좋은 솔루션이지만 비즈니스 및 엔터프라이즈 계정에만 사용할 수 있습니다. 걱정하지 마세요. 이 블로그에서는 Cloudflare 작업자와 Upstash Redis를 사용하여 모든 유형의 웹 사이트를 위한 대기실을 구축할 것입니다.

어떻게?

대기실을 제어하는 ​​두 가지 매개변수가 있습니다.

  • 최대 세션 시간 :방문자가 웹사이트에서 얼마나 오랫동안 유휴 상태를 유지할 수 있습니까?
  • 최대 웹사이트 용량 :웹사이트에서 동시에 허용할 수 있는 방문자 수는 몇 명입니까?

방문자가 웹사이트에 들어오면 세션 키와 같은 고유 키를 생성하여 Redis에 보관합니다. 이 키를 최대 세션 기간인 만료 시간과 함께 Redis에 저장합니다. . 방문자를 웹사이트에 들어오게 하기 전에 Redis의 dbsize를 확인합니다. 크기가 **웹사이트 최대 수용인원보다 클 경우 **방문객을 대기실로 안내해 드립니다. 대기실은 정적 html이지만 30초마다 새로 고침됩니다. 즉, 30초에 한 번, 방문자가 웹사이트에 여유가 있을 경우 입장할 수 있습니다.

우리가 생성한 고유 키도 방문자 요청에 쿠키로 기록됩니다. 따라서 동일한 방문자의 요청에는 동일한 키가 있습니다. 정원이 찼을 때 방문자가 웹 사이트에 이미 세션이 있는지 확인하기 위해 필요합니다. 따라서 방문자로서 귀하의 키가 Redis 키스페이스에 존재하는 한 용량이 가득 차더라도 웹사이트에 들어갈 수 있습니다. 최대 세션 시간보다 오래 유휴 상태를 유지하면 키가 Redis에서 삭제됩니다. 그리고 신청시 정원이 차면 대기실로 이동됩니다.

Cloudflare 작업자에서 이 논리를 구현합니다. Upstash를 Redis 스토어로 사용할 것입니다. 이제 이러한 기술 결정의 배경을 설명하겠습니다.

Cloudflare 작업자가 필요한 이유

대기실 구현은 웹사이트에 대한 모든 요청을 가로챕니다. 따라서 성능 오버헤드는 최소화되어야 합니다. Cloudflare 작업자는 Cloudflare 에지 인프라를 활용하므로 전 세계적으로 최소 지연 시간을 제공합니다. 또한 AWS Lambda와 달리 콜드 스타트 ​​문제가 없습니다. 서버리스 기술이므로 확장성도 문제가 되지 않습니다.

Upstash Redis가 필요한 이유

각각의 새로운 방문자를 허용하기 전에 웹사이트의 현재 크기를 확인해야 합니다. Cloudflare 작업자는 상태 비저장이므로 이 정보를 외부에 보관해야 합니다. Redis는 대기 시간이 짧은 최고의 선택입니다. 그러나 Redis 제품에는 Cloudflare 작업자가 지원하지 않는 TCP 기반 연결이 필요합니다. Upstash는 REST API가 내장된 유일한 Redis 제품입니다. 또한 글로벌 복제 덕분에 전 세계적으로 짧은 대기 시간을 제공합니다.

단계별 구현

아래에서 단계별로 프로젝트를 구현하겠습니다. 프로젝트를 복제하고 웹사이트의 대기실을 직접 설정하려면 소스 코드의 readme에 있는 단계를 따르세요.

1 프로젝트 설정

랭글러를 사용하여 프로젝트를 만드세요.


wrangler generate waiting-room

그런 다음 종속성을 설치합니다.


npm install cookie upstash@redis

2 wrangler.toml 업데이트

유형 업데이트:

type = "webpack"

Cloudflare 계정 ID를 설정합니다. 계정 ID를 찾으려면 이것을 확인하십시오.

account_id = "REPLACE_HERE"

다음 변수를 추가하십시오.

[vars]
UPSTASH_REDIS_REST_TOKEN = "REPLACE_HERE"
UPSTASH_REDIS_REST_URL = "REPLACE_HERE"
TOTAL_ACTIVE_USERS = 10
SESSION_DURATION_SECONDS = 30

Upstash 콘솔에서 글로벌 데이터베이스를 생성해야 합니다. 콘솔에서 REST 토큰과 URL을 복사하여 붙여넣기만 하면 됩니다. Redis 데이터베이스는 처음에는 비어 있어야 하며 이 애플리케이션에서만 사용해야 합니다.

자신의 요구 사항에 따라 TOTAL_ACTIVE_USERS 및 SESSION_DURATION_SECONDS를 설정해야 합니다.

3 index.js

Index.js는 Cloudflare 작업자 구현 파일입니다. 그래서 우리는 그 안에 모든 논리를 넣을 것입니다. 아래 코드를 복사하여 붙여넣습니다.

import { parse } from "cookie";
import { Redis } from "@upstash/redis/cloudflare";

const redis = Redis.fromEnv();

addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event.request).catch(
      (err) => new Response(err.stack, { status: 500 })
    )
  );
});

const COOKIE_NAME_ID = "__waiting_room_id";
const COOKIE_NAME_TIME = "__waiting_room_last_update_time";

const init = {
  headers: {
    Authorization: "Bearer " + UPSTASH_REDIS_REST_TOKEN,
  },
};

async function handleRequest(request) {
  const { pathname } = new URL(request.url);
  if (!pathname.startsWith("/favicon")) {
    const cookie = parse(request.headers.get("Cookie") || "");
    let userId;

    if (cookie[COOKIE_NAME_ID] != null) {
      userId = cookie[COOKIE_NAME_ID];
    } else {
      userId = makeid(8);
    }

    const size = await redis.dbsize();
    console.log("current capacity:" + size);
    // there is enough capacity
    if (size < TOTAL_ACTIVE_USERS) {
      return getDefaultResponse(request, cookie, userId);
    } else {
      // site capacity is full
      const user = await redis.get(userId);
      if (user === "1") {
        // the user has already active session
        return getDefaultResponse(request, cookie, userId);
      } else {
        // capacity is full so the user is forwarded to waiting room
        return getWaitingRoomResponse(userId);
      }
    }
  } else {
    return fetch(request);
  }
}

async function getDefaultResponse(request, cookie, userId) {
  // uncomment below to test the function with a static html content
  // const newResponse = new Response(default_html)
  // newResponse.headers.append('content-type', 'text/html;charset=UTF-8')

  const response = await fetch(request);
  const newResponse = new Response(response.body, response);

  const now = Date.now();
  let lastUpdate = cookie[COOKIE_NAME_TIME];
  if (!lastUpdate) lastUpdate = 0;
  const diff = now - lastUpdate;
  const updateInterval = (SESSION_DURATION_SECONDS * 1000) / 2;
  if (diff > updateInterval) {
    await redis.setex(userId, SESSION_DURATION_SECONDS, 1);
    newResponse.headers.append(
      "Set-Cookie",
      `${COOKIE_NAME_TIME}=${now}; path=/`
    );
  }

  newResponse.headers.append(
    "Set-Cookie",
    `${COOKIE_NAME_ID}=${userId}; path=/`
  );
  return newResponse;
}

async function getWaitingRoomResponse(userId) {
  const newResponse = new Response(waiting_room_html);
  newResponse.headers.set("content-type", "text/html;charset=UTF-8");
  return newResponse;
}

function makeid(length) {
  let result = "";
  const characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  const charactersLength = characters.length;
  for (let i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}

const waiting_room_html = `
<title>Waiting Room</title>
<meta http-equiv='refresh' content='30' />

<style>*{box-sizing:border-box;margin:0;padding:0}body{line-height:1.4;font-size:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;padding:2rem;display:grid;place-items:center;min-height:100vh}.container{width:100%;max-width:800px}p{margin-top:.5rem}</style>

<div class='container'>
  <h1>
    <div>You are now in line.</div>
    <div>Thanks for your patience.</div>
  </h1>
  <p>We are experiencing a high volume of traffic. Please sit tight and we will let you in soon. </p>
  <p><b>This page will automatically refresh, please do not close your browser.</b></p>
</div>
`;

const default_html = `
<title>Waiting Room Demo</title>

<style>*{box-sizing:border-box;margin:0;padding:0}body{line-height:1.4;font-size:1rem;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;padding:2rem;display:grid;place-items:center;min-height:100vh}.container{width:100%;max-width:800px}p{margin-top:.5rem}</style>

<div class="container">
  <h1>
    <div>Waiting Room Demo</div>
  </h1>
    <p>
              Visit this site from a different browser, you will be forwarded to the waiting room when the capacity is full.
    </p>
  <p>  Check <a href={"https://github.com/upstash/waiting-room"} style={{"color": "blue"}}>this project </a> to set up a waiting room for your website.</p>
</div>
`;

위의 코드에서 waiting_room_html을 편집할 수 있습니다. 변하기 쉬운. 대기실 페이지의 정적 html입니다.

handleRequest 메소드는 웹사이트의 가용성에 따라 getDefaultResponse 또는 getWaitingRoomResponse를 반환합니다.

4 로컬에서 실행

대기실을 로컬에서 테스트하려면 수용 인원을 1로, 세션 기간을 30초로 설정하는 것이 합리적일 수 있습니다. 그런 다음 실행


wrangler dev

이제 Chrome에서 https://127.0.0.1:8787/을 열면 다음이 표시됩니다.

Cloudflare 작업자 및 서버리스 Redis를 사용하여 웹 사이트를 위한 자체 대기실 구축

그런 다음 Safari(또는 Chrome 시크릿 모드)에서 동일한 URL을 열면 다음이 표시됩니다.

Cloudflare 작업자 및 서버리스 Redis를 사용하여 웹 사이트를 위한 자체 대기실 구축

30초 이상 기다립니다. 대기실 페이지가 새로고침되고 웹사이트로 들어가는 것을 볼 수 있습니다.

우리 지역은 실제 웹사이트로 전달하지 않으므로 Cloudflare의 404 페이지가 표시됩니다. 괜찮습니다.

5 게시

다음을 사용하여 CF 작업자 기능을 배포하십시오.


wrangler publish

다음과 같은 URL을 제공합니다. https://waiting-room.upsdev.workers.dev/

이제 작업자를 웹사이트로 라우팅해 보겠습니다. 먼저 도메인의 네임서버가 Cloudflare를 가리켜야 합니다. 이것을 확인하십시오. 그런 다음 도메인을 경로로 추가하고 CF 작업자 대시보드에서 작업자 기능을 선택해야 합니다.

Cloudflare 작업자 및 서버리스 Redis를 사용하여 웹 사이트를 위한 자체 대기실 구축

결론

Cloudflare 작업자와 Upstash Redis 덕분에 애플리케이션 코드를 건드리지 않고 대기실을 성공적으로 구축했습니다. 이것이 Upstash와 함께 에지 기능이 얼마나 강력한지 보여주는 또 다른 지표라고 생각합니다.

개선해야 할 사항이 있습니다.

  • 예상 대기 시간:평균 대기 시간을 계산하여 표시할 수 있습니다.
  • 공정하고 질서정연한 대기열:현재 대기 중인 방문자가 여유가 있을 때 무작위로 사이트에 입장합니다. 대기열을 유지하고 방문자를 순서대로 안내할 수 있습니다.

위의 두 가지 개선 사항 모두 더 많은 상태를 유지하고 더 많은 원격 호출을 수행해야 합니다. 그래서 우리는 그것들을 건너 뛰는 것을 선호했습니다. 사용 사례에 엄격하게 필요한 경우 이 블로그에서 Cloudflare 팀의 엔터프라이즈 솔루션 작업에서 영감을 얻을 수 있습니다.

소스코드를 확인하세요.

Twitter나 Discord에서 여러분의 생각을 알려주세요.