Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Redis

สร้างห้องรอของคุณเองสำหรับเว็บไซต์ของคุณด้วย Cloudflare Workers และ Serverless Redis

ในบล็อกโพสต์นี้ เราจะใช้หน้าห้องรอสำหรับเว็บไซต์ของคุณ

ทำไม?

ผู้เข้าชมเว็บไซต์ของคุณเป็นจำนวนมากเป็นสิ่งที่ดีโดยทั่วไปแต่ไม่เสมอไป การรับส่งข้อมูลสูงอย่างกะทันหันอาจทำให้แอปพลิเคชันของคุณล้นหลามได้ง่าย ซึ่งอาจขัดขวางบริการของคุณโดยสิ้นเชิง ห้องรอเป็นโซลูชันที่ช่วยให้คุณควบคุมการรับส่งข้อมูลและปกป้องทรัพยากรของคุณในช่วงที่มีการจราจรหนาแน่น Cloudflare Waiting Room เป็นโซลูชันที่ดี แต่ใช้ได้เฉพาะกับบัญชีธุรกิจและบัญชีองค์กรเท่านั้น ไม่ต้องกังวล ในบล็อกนี้ เราจะสร้างห้องรอสำหรับเว็บไซต์ทุกประเภทโดยใช้ Cloudflare Workers และ Upstash Redis

อย่างไร

มีสองพารามิเตอร์ในการควบคุมห้องรอ

  • ระยะเวลาเซสชันสูงสุด :ผู้เยี่ยมชมสามารถอยู่เฉยๆบนเว็บไซต์ได้นานแค่ไหน?
  • ความจุเว็บไซต์สูงสุด :เว็บไซต์สามารถให้ผู้เข้าชมได้กี่คนพร้อมกัน?

เมื่อผู้เยี่ยมชมเข้าสู่เว็บไซต์ของเรา เราจะสร้างคีย์เฉพาะ (เช่น คีย์เซสชัน) และเก็บไว้ใน Redis เราจะใส่คีย์นี้ใน Redis โดยมีเวลาหมดอายุซึ่งก็คือ ระยะเวลาเซสชันสูงสุด . ก่อนให้ผู้เยี่ยมชมเข้าสู่เว็บไซต์ เราจะตรวจสอบ dbsize ของ Redis หากขนาดสูงกว่า **ความจุสูงสุดของเว็บไซต์ **เราจะส่งต่อผู้เยี่ยมชมไปยังห้องรอ ห้องรอเป็น html แบบคงที่ แต่จะรีเฟรชทุกๆ 30 วินาที นั่นคือทุกๆ 30 วินาที ผู้เข้าชมสามารถเข้าสู่เว็บไซต์ได้หากมีห้องว่าง

คีย์เฉพาะที่เราสร้างขึ้นยังถูกเขียนไปยังคำขอของผู้เข้าชมเป็นคุกกี้ ดังนั้นคำขอของผู้มาเยี่ยมคนเดียวกันจะมีคีย์เดียวกัน นี่เป็นสิ่งจำเป็นเพื่อตรวจสอบว่าผู้เข้าชมมีเซสชันภายในเว็บไซต์อยู่แล้วเมื่อความจุเต็มหรือไม่ ดังนั้นในฐานะผู้เยี่ยมชม คุณสามารถเข้าสู่เว็บไซต์ได้แม้ว่าความจุจะเต็มตราบเท่าที่คีย์ของคุณอยู่ในคีย์สเปซ Redis หากคุณไม่ได้ใช้งานนานกว่าระยะเวลาสูงสุดของเซสชัน คีย์ของคุณจะถูกลบออกจาก Redis และถ้าความจุเต็มเมื่อคุณส่งคำขอคุณจะถูกส่งต่อไปยังห้องรอ

เราจะนำตรรกะนี้ไปใช้ที่ Cloudflare Workers เราจะใช้ Upstash เป็นร้าน Redis ตอนนี้ เรามาอธิบายภูมิหลังของการตัดสินใจด้านเทคโนโลยีเหล่านี้กัน

ทำไมต้อง Cloudflare Workers

การใช้งานห้องรอจะสกัดกั้นคำขอทั้งหมดที่มายังเว็บไซต์ของคุณ ดังนั้นค่าใช้จ่ายด้านประสิทธิภาพจึงควรน้อยที่สุด Cloudflare Workers ใช้ประโยชน์จากโครงสร้างพื้นฐาน Edge ของ Cloudflare ดังนั้นจึงให้เวลาแฝงขั้นต่ำทั่วโลก ไม่เหมือนกับ AWS Lambda ตรงที่ไม่มีปัญหาการสตาร์ทแบบเย็น มันเป็นเทคโนโลยีแบบไร้เซิร์ฟเวอร์ ดังนั้นความสามารถในการปรับขนาดก็ไม่ใช่ปัญหาเช่นกัน

ทำไมต้อง Upstash Redis

คุณต้องตรวจสอบขนาดปัจจุบันของเว็บไซต์ก่อนที่จะอนุญาตให้ผู้เยี่ยมชมใหม่แต่ละคน Cloudflare Workers ไม่มีสัญชาติ ดังนั้นคุณต้องเก็บข้อมูลนี้ไว้ภายนอก Redis เป็นตัวเลือกที่ดีที่สุดโดยมีเวลาแฝงต่ำ แต่ข้อเสนอ Redis ต้องการการเชื่อมต่อแบบ TCP ซึ่ง Cloudflare Workers ไม่รองรับ Upstash เป็นข้อเสนอ Redis เดียวที่มี REST API ในตัว และต้องขอบคุณการจำลองแบบทั่วโลกที่ทำให้คุณมีเวลาแฝงต่ำไปทั่วโลก

การใช้งานทีละขั้นตอน

เราจะดำเนินโครงการทีละขั้นตอนด้านล่าง หากคุณต้องการโคลนโปรเจ็กต์และตั้งค่าห้องรอสำหรับเว็บไซต์ของคุณโดยตรง คุณสามารถทำตามขั้นตอนใน readme ของซอร์สโค้ดได้

1 ตั้งค่าโครงการ

สร้างโครงการโดยใช้ wrangler


wrangler generate waiting-room

จากนั้นติดตั้งการพึ่งพา:


npm install cookie upstash@redis

2 อัพเดท wrangler.toml

อัปเดตประเภท:

type = "webpack"

ตั้งค่า ID บัญชี Cloudflare ของคุณ เลือกตัวเลือกนี้เพื่อค้นหารหัสบัญชีของคุณ

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 Workers ดังนั้นเราจะใส่ตรรกะทั้งหมดไว้ข้างใน คัดลอกและวางโค้ดด้านล่างลงไป:

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

ตอนนี้เปิด https://127.0.0.1:8787/ ที่ Chrome คุณจะเห็น:

สร้างห้องรอของคุณเองสำหรับเว็บไซต์ของคุณด้วย Cloudflare Workers และ Serverless Redis

จากนั้นเปิด URL เดียวกันบน Safari (หรือ Chrome incognito) คุณจะเห็น:

สร้างห้องรอของคุณเองสำหรับเว็บไซต์ของคุณด้วย Cloudflare Workers และ Serverless Redis

รอนานกว่า 30 วินาที คุณควรเห็นหน้าในห้องรอจะรีเฟรชและเข้าสู่เว็บไซต์

ในพื้นที่ของเราไม่ส่งต่อไปยังเว็บไซต์จริง ดังนั้นเราจึงเห็นหน้า 404 ของ Cloudflare ไม่เป็นไร

5 เผยแพร่

ปรับใช้ฟังก์ชัน CF Workers ของคุณด้วย:


wrangler publish

มันจะให้ URL แก่คุณเช่น:https://waiting-room.upsdev.workers.dev/

ตอนนี้ ให้กำหนดเส้นทางพนักงานของเราไปยังเว็บไซต์ของคุณ อันดับแรก เนมเซิร์ฟเวอร์ของโดเมนควรชี้ไปที่ Cloudflare ตรวจสอบสิ่งนี้ จากนั้น คุณต้องเพิ่มโดเมนของคุณเป็นเส้นทางและเลือกฟังก์ชัน Workers ของคุณในแดชบอร์ด CF Workers

สร้างห้องรอของคุณเองสำหรับเว็บไซต์ของคุณด้วย Cloudflare Workers และ Serverless Redis

บทสรุป

เราสร้างห้องรอได้สำเร็จโดยไม่ต้องแตะรหัสแอปพลิเคชันของเราด้วย Cloudflare Workers และ Upstash Redis เราเชื่อว่านี่เป็นอีกตัวบ่งชี้ว่าฟังก์ชัน edge อันทรงพลังมารวมกับ Upstash ได้อย่างไร

มีสิ่งที่ต้องปรับปรุง:

  • เวลารอโดยประมาณ:เราสามารถคำนวณและแสดงเวลารอเฉลี่ยได้
  • คิวที่ยุติธรรมและเป็นระเบียบ:ขณะนี้ผู้เข้าชมที่รอจะสุ่มเข้าสู่ไซต์เมื่อมีห้องว่าง เราอาจเก็บคิวและพยายามให้ผู้มาเยี่ยมชมเป็นลำดับ

การปรับปรุงทั้งสองอย่างข้างต้นต้องการการรักษาสถานะให้มากขึ้นและโทรทางไกลมากขึ้น นั่นเป็นเหตุผลที่เราต้องการข้ามสิ่งเหล่านั้น หากกรณีการใช้งานของคุณจำเป็นต้องใช้อย่างเข้มงวด คุณสามารถตรวจสอบบล็อกนี้เพื่อรับแรงบันดาลใจจากงานของทีม Cloudflare ในโซลูชันระดับองค์กร

ตรวจสอบซอร์สโค้ด

แจ้งให้เราทราบความคิดเห็นของคุณบน Twitter หรือ Discord