Cache dan batas batas dengan redis dan next.js

 – Beragampengetahuan
17 mins read

Cache dan batas batas dengan redis dan next.js – Beragampengetahuan

Sebagai seseorang yang suka mengangkut produk dan alat untuk bereksperimen dan digunakan orang, itu hanya masalah waktu, itu hanya masalah skalabilitas yang saya miliki. Saya selalu tahu apa artinya penskalaan dan bagaimana skala secara teoritis. Saya akrab dengan penskalaan vertikal dan horizontal dan kompleksitas setiap penskalaan, tetapi secara teoritis. Sebenarnya, saya tidak perlu menerapkan salah satu dari mereka karena basis pengguna saya biasanya kecil.

Sampai saya membangun keran yang melebihi ekspektasi penggunaan. Namun, traksi besar ini telah menyebabkan serangkaian masalahnya sendiri. Masalahnya adalah bahwa meskipun saya membatasi frekuensi dana alamat dompet sekali sehari, pengguna dapat dengan mudah memutar alamat baru dan terus mengklaim. Ini menyebabkan keran saya sering mengalir.

Untuk memperbaikinya, saya harus memperkenalkan tingkat batas untuk membatasi alamat IP setiap penyalahgunaan pengguna dari keran. Sekali lagi, di platform LMS yang saya bangun, saya harus memperkenalkan cache untuk mengurangi jumlah panggilan ke database.

Dalam tutorial ini, saya akan menunjukkan kepada Anda cara mengimplementasikan pembatasan dan caching tingkat dalam aplikasi Anda menggunakan Redis.

Contents

Mengapa Redis?

Redis seperti teman yang dapat diandalkan itu, dia pandai dalam segala hal. Apakah Anda memerlukan pengambilan data petir? Redis. Apakah Anda perlu berkoordinasi antara beberapa server? Redis. Perlu membatasi seberapa sering pengguna dapat melakukan operasi? Anda dapat menebaknya dengan benar – Reddy.

Kami dapat mensembakan respons API, batas laju lacak, dan sering membuat aplikasi kami lebih tangguh. Bagian terbaik? Setelah Anda memahami polanya, implementasinya sebenarnya sangat sederhana. Setelah mengatakan itu, mari kita mulai membangun!

Prasyarat

  • Siapkan akun cloud redis dan dapatkan URL publik
  • Akrab dengan next.js

Apa yang kita bangun?

Langkah selanjutnya

Kami akan membuat aplikasi Next.js menggunakan metode berikut

  1. API Katalog Produk dengan Cache Repower
  2. Sistem pembatas laju laju fleksibel menggunakan redis
  3. Halaman demo interaktif untuk memvisualisasikan dua teknologi

Berikut adalah pratinjau penampilan struktur proyek kami:

/src
  /lib
    - redis.ts            # Redis client and caching utilities
    - edge-rate-limiter.ts # Edge-compatible rate limiting
    - product.ts          # Product data service

  /app
    /api
      /products           # Product data API endpoints
      /cache/invalidate   # Cache management endpoint
      /tests              # Test endpoints for rate limiting

    /demo                # Demo UI pages
      /caching           # Redis caching demo
      /rate-limiting     # Rate limiting demo

  - middleware.ts        # Rate limiting middleware

Langkah 1: Siapkan lingkungan kita

Pertama -tama mari kita buat proyek Next.js baru.

npx create-next-app redis-scaling-demo 
cd redis-scaling-demo

Untuk prompt yang menyertainya, pilih opsi default dan klik ENTER. Selanjutnya, kami menginstal dependensi dan jenis Redis.

npm install ioredis @upstash/ratelimit @upstash/redis @types/ioredis --save-dev

Jika Anda belum memiliki akun cloud redis, kunjungi Redisbuat akun baru dan pilih database gratis.

menghubungkan

Cari tombol di atas dan klik untuk memilih opsi CLI. Salin perintah yang berisi host dan port Anda.

redis://default:password@host:port

Ini harus terlihat seperti format di atas (Catatan: Abaikan “redis -cli -u” dalam perintah). Akhirnya, ganti kata sandi (tanda bintang dalam perintah) dengan kata sandi Anda yang sebenarnya, yang bisa Anda dapatkan dengan pergi ke bagian keamanan dan menyalin kata sandi.

Akhirnya, buat a .env File dalam file (dengan asumsi Anda telah membukanya di terminal kode) dan menempelkannya ke dalam url redis:

REDIS_URL=redis://default:password@host:port

Langkah 2: Buat Layanan

ada src Folder, buat folder baru yang disebut lib Lalu ada file yang dipanggil redis.ts. Tempel dalam kode di bawah ini.

import Redis from "ioredis";

let redisClient: Redis | null = null;

export function getRedisClient() 
  if (!redisClient) 
    redisClient = new Redis(process.env.REDIS_URL 

  return redisClient;


function createCacheKey(prefix: string, key: string): string 
  return `$prefix$key`;


async function getCachedData<T>(
  redis: Redis,
  prefix: string,
  key: string
): Promise<T | null> 
  const cachedData = await redis.get(createCacheKey(prefix, key));

  if (!cachedData) 
    return null;
  

  try 
    return JSON.parse(cachedData) as T;
   catch (error) 
    console.error(`Error parsing cached data for key $key:`, error);
    return null;
  


async function setCachedData<T>(
  redis: Redis,
  prefix: string,
  key: string,
  data: T,
  ttl: number
): Promise<void> 
  await redis.set(createCacheKey(prefix, key), JSON.stringify(data), "EX", ttl);


async function deleteCachedData(
  redis: Redis,
  prefix: string,
  key: string
): Promise<void> 
  await redis.del(createCacheKey(prefix, key));


async function invalidateCachePattern(
  redis: Redis,
  prefix: string,
  pattern: string
): Promise<number> 
  const keys = await redis.keys(`$prefix$pattern*`);

  if (keys.length === 0) 
    return 0;
  

  return await redis.del(...keys);


export function getCacheInstance(
  prefix: string = "cache:",
  defaultTTL: number = 60 * 5
) 
  const redis = getRedisClient();

  return  defaultTTL),
    delete: (key: string) => deleteCachedData(redis, prefix, key),
    invalidatePattern: (pattern: string) =>
      invalidateCachePattern(redis, prefix, pattern),
  ;

Next.js Middleware berjalan pada runtime Edge, dan memiliki keterbatasan dibandingkan dengan Node.js. Kita perlu membuat laju pembatas yang bekerja di lingkungan ini, jadi lib folder, buat yang lain dipanggil edge-rate-limiter.ts.

import  NextRequest, NextResponse  from "next/server";

// Simple in-memory store (note: this won't work in a multi-instance setup)
const ipRequests = new Map<string,  count: number; timestamp: number >();

export function createEdgeRateLimiter(options: 
  limit: number;
  timeWindow: number;
) 
  return function rateLimit(request: NextRequest)   count: 0, timestamp: now ;

    if (currentData.timestamp < windowStart) 
      currentData.count = 0;
      currentData.timestamp = now;
    

    currentData.count++;
    ipRequests.set(ip, currentData);

    const response = NextResponse.next();
    response.headers.set("X-RateLimit-Limit", options.limit.toString());
    response.headers.set(
      "X-RateLimit-Remaining",
      Math.max(0, options.limit - currentData.count).toString()
    );

    if (currentData.count > options.limit) 
      return NextResponse.json( error: "Too many requests" ,  status: 429 );
    

    return response;
  ;

Untuk membatasi tarif, kami membuat a rate-limiter.ts File dalam file lib Map.

import  NextRequest, NextResponse  from "next/server";
import  getRedisClient  from "./redis";
import Redis from "ioredis";

export interface RateLimitConfig 
  maxRequests: number;
  windowSizeInSeconds: number;
  prefix: string;


function getRedisKey(prefix: string, identifier: string): string 
  return `$prefix:$identifier`;


export async function checkRateLimit(
  redis: Redis,
  config: RateLimitConfig,
  identifier: string
): Promise<
  success: boolean;
  limit: number;
  remaining: number;
  reset: number;
> 

export function getClientIdentifier(req: NextRequest): string  forwardedFor 

export function createRateLimitMiddleware(config: RateLimitConfig) {
  return async function rateLimitMiddleware(
    req: NextRequest
  ): Promise<NextResponse> {
    try 
      const redis = getRedisClient();

      const identifier = getClientIdentifier(req);

      const  success, limit, remaining, reset  = await checkRateLimit(
        redis,
        config,
        identifier
      );

      const headers = 
        "X-RateLimit-Limit": limit.toString(),
        "X-RateLimit-Remaining": remaining.toString(),
        "X-RateLimit-Reset": reset.toString(),
      ;

      if (!success) 
        return NextResponse.json(
          
            error: "Too Many Requests",
            message: `Rate limit of $limit requests per $config.windowSizeInSeconds seconds exceeded.`,
          ,
          
            status: 429,
            headers: 
              ...headers,
              "Retry-After": Math.ceil(
                reset - Math.floor(Date.now() / 1000)
              ).toString(),
            ,
          
        );
      

      const response = NextResponse.next();

      Object.entries(headers).forEach(([key, value]) => 
        response.headers.set(key, value);
      );

      return response;
     catch (error) 
      console.error("Rate limit error:", error);
      return NextResponse.next();
    
  };
}

export const rateLimiters = 
  default: createRateLimitMiddleware(
    maxRequests: 60,
    windowSizeInSeconds: 60,
    prefix: "ratelimit:default",
  ),

  auth: createRateLimitMiddleware(
    maxRequests: 10,
    windowSizeInSeconds: 60,
    prefix: "ratelimit:auth",
  ),

  sensitive: createRateLimitMiddleware(
    maxRequests: 3,
    windowSizeInSeconds: 60,
    prefix: "ratelimit:sensitive",
  ),
;

Akhirnya, kami membuat layanan produk untuk mensimulasikan database. Jadi, buat a product.ts File dalam file lib Map.

export interface Product 
  id: string;
  name: string;
  description: string;
  price: number;
  category: string;
  image: string;
  stock: number;


const productData: Product[] = [
  
    id: "prod_001",
    name: "Ergonomic Office Chair",
    description: "A comfortable chair designed for long working hours",
    price: 299.99,
    category: "furniture",
    image: "/images/chair.jpg",
    stock: 25,
  ,
  
    id: "prod_002",
    name: "Mechanical Keyboard",
    description: "Mechanical keyboard with RGB lighting and Cherry MX switches",
    price: 129.99,
    category: "electronics",
    image: "/images/keyboard.jpg",
    stock: 40,
  ,
  
    id: "prod_003",
    name: "Wireless Mouse",
    description: "High-precision wireless mouse with long battery life",
    price: 49.99,
    category: "electronics",
    image: "/images/mouse.jpg",
    stock: 75,
  ,
  
    id: "prod_004",
    name: "Adjustable Standing Desk",
    description: "Electric adjustable standing desk for better ergonomics",
    price: 499.99,
    category: "furniture",
    image: "/images/desk.jpg",
    stock: 15,
  ,
  
    id: "prod_005",
    name: "Monitor Stand",
    description: "Adjustable monitor stand with cable management",
    price: 79.99,
    category: "accessories",
    image: "/images/monitor-stand.jpg",
    stock: 30,
  ,
  
    id: "prod_006",
    name: "USB-C Hub",
    description: "7-in-1 USB-C hub with HDMI, SD card reader, and USB ports",
    price: 39.99,
    category: "accessories",
    image: "/images/usb-hub.jpg",
    stock: 50,
  ,
  
    id: "prod_007",
    name: "Noise-Cancelling Headphones",
    description: "Over-ear headphones with active noise cancellation",
    price: 199.99,
    category: "electronics",
    image: "/images/headphones.jpg",
    stock: 20,
  ,
  
    id: "prod_008",
    name: "Ergonomic Mouse Pad",
    description: "Mouse pad with wrist support for ergonomic comfort",
    price: 19.99,
    category: "accessories",
    image: "/images/mousepad.jpg",
    stock: 100,
  ,
];

export async function getAllProducts(): Promise<Product[]> 
  await new Promise((resolve) => setTimeout(resolve, 300));
  return [...productData];


export async function getProductById(id: string): Promise<Product | null> 

export async function getProductsByCategory(
  category: string
): Promise<Product[]> 
  await new Promise((resolve) => setTimeout(resolve, 300));
  return productData.filter((product) => product.category === category);


export async function searchProducts(query: string): Promise<Product[]> 
  await new Promise((resolve) => setTimeout(resolve, 400));

  const searchTerm = query.toLowerCase();
  return productData.filter(
    (product) =>
      product.name.toLowerCase().includes(searchTerm) 

export const productService = 
  getAllProducts,
  getProductById,
  getProductsByCategory,
  searchProducts,
;

Langkah 3: Batas Batas – Middleware dan Tes API

Kami akan membuat middleware untuk pembatasan suku bunga, yang pada dasarnya akan melakukan tiga hal untuk kami:

  1. Buat tiga pembatas berbeda dengan batas yang berbeda
  2. Terapkan mereka sesuai dengan pola jalur URL
  3. Gunakan pencocokan untuk menjalankan rute API saja

Buat a middleware.ts mengajukan src folder dan tempel dalam kode di bawah ini.

import  NextRequest, NextResponse  from "next/server";
import  createEdgeRateLimiter  from "./lib/edge-rate-limiter";

// Create rate limiters with different settings
const rateLimiters = 
  default: createEdgeRateLimiter( limit: 60, timeWindow: 60 ), // 60 requests per minute
  auth: createEdgeRateLimiter( limit: 10, timeWindow: 60 ), // 10 requests per minute
  sensitive: createEdgeRateLimiter( limit: 3, timeWindow: 60 ), // 3 requests per minute
;

export function middleware(request: NextRequest) 
  const path = request.nextUrl.pathname;

  // Apply different rate limits based on path
  if (path.startsWith("/api/tests/admin")) 
    return rateLimiters.sensitive(request);
   else if (path.startsWith("/api/tests/auth")) 
    return rateLimiters.auth(request);
   else if (path.startsWith("/api/")) 
    return rateLimiters.default(request);
  

  return NextResponse.next();


export const config = 
  matcher: ["/api/:path*"],
;

Untuk titik akhir uji untuk menunjukkan batas laju yang berbeda, buat folder yang disebut api ada app Daftar isi. Kemudian buat folder lain yang disebut tests. Di sinilah titik akhir tes kami berada. Di folder tes, buat tiga subfolder: admin,,,,, auth Dan basic masing -masing. Untuk masing -masing subfolder ini, buat a route.ts dokumen.

ada app/api/tests/admin/route.ts File, tempel kode berikut:

import  NextRequest, NextResponse  from "next/server";

export function GET(request: NextRequest) 
  return NextResponse.json(
    message: "Admin rate limited endpoint (very strict)",
    timestamp: new Date().toISOString(),
    path: "/api/tests/admin",
  );

Demikian pula, di auth Daftar isi:

import  NextRequest, NextResponse  from "next/server";

export function GET(request: NextRequest) 
  return NextResponse.json(
    message: "Auth rate limited endpoint (moderate)",
    timestamp: new Date().toISOString(),
    path: "/api/tests/auth",
  );

Akhirnya, di basic Daftar isi:

import  NextRequest, NextResponse  from "next/server";

export function GET(request: NextRequest) 
  return NextResponse.json(
    message: "Default rate limited endpoint",
    timestamp: new Date().toISOString(),
    path: "/api/tests/basic",
  );

Langkah 4: Cache – Bangun API Produk

Sekarang, kita dapat terus membuat rute API untuk caching. ada app/api Folder, buat folder baru yang disebut products. Di folder ini, buat subfolder bernama categories dan file yang dipanggil route.ts. Di file routing, tempel dalam kode berikut

import  NextRequest, NextResponse  from "next/server";
import  productService  from "@/lib/product";
import  getCacheInstance  from "@/lib/redis";

// GET /api/products - List products with optional filtering
export async function GET(request: NextRequest) 
  try 
    // Parse query parameters
    const searchParams = request.nextUrl.searchParams;
    const category = searchParams.get("category");
    const query = searchParams.get("q");

    let cacheKey = "products";
    if (category) cacheKey += `:category:$category`;
    if (query) cacheKey += `:search:$query`;

    const cache = getCacheInstance("api:");

    const cachedData = await cache.get(cacheKey);
    if (cachedData) 
      console.log(`Cache hit for $cacheKey`);
      const response = NextResponse.json(cachedData);
      response.headers.set("X-Cache-Status", "HIT");
      return response;
    

    console.log(`Cache miss for $cacheKey, fetching from database...`);

    let products;
    if (query) 
      products = await productService.searchProducts(query);
     else if (category) 
      products = await productService.getProductsByCategory(category);
     else 
      products = await productService.getAllProducts();
    

    const responseData = 
      products,
      count: products.length,
      timestamp: new Date().toISOString(),
    ;

    await cache.set(cacheKey, responseData, 60 * 5);

    return NextResponse.json(responseData);
   catch (error) 
    console.error("Error fetching products:", error);
    return NextResponse.json(
       error: "Failed to fetch products" ,
       status: 500 
    );
  

Di subfolder categoriesbuat satu lagi route.ts File dan tempel berikut:

import  NextRequest, NextResponse  from "next/server";
import  productService  from "@/lib/product";
import  getCacheInstance  from "@/lib/redis";
import  Product  from "@/lib/product";

export async function GET(request: NextRequest) 
  try 
    const cacheKey = "product:categories";

    const cache = getCacheInstance("api:");

    const cachedCategories = await cache.get(cacheKey);
    if (cachedCategories) 
      console.log(`Cache hit for $cacheKey`);
      return NextResponse.json(cachedCategories);
    

    console.log(`Cache miss for $cacheKey, computing categories...`);

    const products = await productService.getAllProducts();

    const categories = Array.from(
      new Set(products.map((product: Product) => product.category))
    );

    const responseData = 
      categories,
      count: categories.length,
      timestamp: new Date().toISOString(),
    ;

    await cache.set(cacheKey, responseData, 60 * 60);

    return NextResponse.json(responseData);
   catch (error) 
    console.error("Error fetching categories:", error);
    return NextResponse.json(
       error: "Failed to fetch categories" ,
       status: 500 
    );
  

Selanjutnya, kami api Panggilan direktori cache Di dalamnya, subfolder disebut invalidate Dan route.ts dokumen.

import  NextRequest, NextResponse  from "next/server";
import  getCacheInstance  from "@/lib/redis";

export async function POST(request: NextRequest) 
  try 
    const  pattern  = await request.json();

    if (!pattern) 
      return NextResponse.json(
         error: "Pattern is required" ,
         status: 400 
      );
    

    const cache = getCacheInstance("api:");

    const deletedCount = await cache.invalidatePattern(pattern);

    return NextResponse.json(
      success: true,
      message: `Invalidated $deletedCount cache entries`,
      pattern,
    );
   catch (error) 
    console.error("Error invalidating cache:", error);
    return NextResponse.json(
       error: "Failed to invalidate cache" ,
       status: 500 
    );
  

Langkah 5: UI – Bangun demonstrasi tingkat batas UI

ada app Direktori, buat folder bernama demo dan dua subfolder caching Dan rate-limiting masing -masing. Di subfolder pembatas tingkat, buat a page.tsx File dan tempel di bawah ini.

"use client";

import  useState  from "react";
import Link from "next/link";

export default function RateLimitingDemo() {
  const [requests, setRequests] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const [requestCount, setRequestCount] = useState(5);
  const [endpoint, setEndpoint] = useState("/api/tests/basic");

  async function makeRequests() {
    setLoading(true);
    setRequests([]);

    const newRequests = [];

    const promises = Array.from( length: requestCount , (_, i) => {
      return fetch(endpoint)
        .then((response) => 
          const headers = 
            status: response.status,
            limit: response.headers.get("X-RateLimit-Limit"),
            remaining: response.headers.get("X-RateLimit-Remaining"),
            reset: response.headers.get("X-RateLimit-Reset"),
          ;

          return response
            .json()
            .then((data) => 
              return  id: i + 1, headers, data, success: response.ok ;
            )
            .catch(() => 
              return 
                id: i + 1,
                headers,
                data:  error: "Failed to parse JSON" ,
                success: false,
              ;
            );
        )
        .catch((error) => 
          return 
            id: i + 1,
            headers: ,
            data:  error: error.message ,
            success: false,
          ;
        );
    });

    const results = await Promise.all(promises);

    setRequests(results);
    setLoading(false);
  }

  return (
    <div className="min-h-screen p-8 bg-black text-black ">
      <div className="max-w-5xl mx-auto">
        <div className="mb-6">
          <Link href=" className="text-blue-600 hover:underline">
            ← Back to home
          </Link>
        </div>

        <h1 className="text-3xl font-bold mb-6">Redis Rate Limiting Demo</h1>

        <div className="bg-white p-6 rounded-lg shadow-md mb-8">
          <h2 className="text-xl font-semibold mb-4">How It Works</h2>
          <p className="mb-4">
            This demo shows Redis-based rate limiting in action. We've
            configured the API to limit requests based on client IP address.
          </p>
          <p>
            Try making multiple requests at once to see how the rate limiter
            kicks in after exceeding the allowed limit.
          </p>
        </div>

        <div className="bg-white p-6 rounded-lg shadow-md mb-8">
          <h2 className="text-xl font-semibold mb-4">Test Rate Limiting</h2>

          <div className="flex flex-col md:flex-row gap-4 mb-6">
            <div className="flex-1">
              <label
                htmlFor="requestCount"
                className="block text-sm font-medium text-gray-700 mb-1"
              >
                Number of Requests
              </label>
              <input
                type="number"
                id="requestCount"
                min="1"
                max="20"
                value=requestCount
                onChange=(e) => setRequestCount(parseInt(e.target.value))
                className="w-full p-2 border border-gray-300 rounded-md"
              />
            </div>

            <div className="flex-1">
              <label
                htmlFor="endpoint"
                className="block text-sm font-medium text-gray-700 mb-1"
              >
                API Endpoint
              </label>
              <select
                id="endpoint"
                value=endpoint
                onChange=(e) => setEndpoint(e.target.value)
                className="w-full p-2 border border-gray-300 rounded-md"
              >
                <option value="/api/tests/basic">
                  Default Rate Limit (60/min)
                </option>
                <option value="/api/tests/auth">
                  Auth Rate Limit (10/min)
                </option>
                <option value="/api/tests/admin">
                  Admin Rate Limit (3/min)
                </option>
              </select>
            </div>

            <div className="flex-1 flex items-end">
              <button
                onClick=makeRequests
                disabled=loading
                className="w-full bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 disabled:bg-blue-300"
              >
                loading ? "Sending Requests..." : "Send Requests"
              </button>
            </div>
          </div>
        </div>

        requests.length > 0 && (
          <div className="bg-white p-6 rounded-lg shadow-md">
            <h2 className="text-xl font-semibold mb-4">Request Results</h2>

            <div className="overflow-x-auto">
              <table className="min-w-full divide-y divide-gray-200">
                <thead className="bg-gray-50">
                  <tr>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      #
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Status
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Rate Limit
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Remaining
                    </th>
                    <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                      Response
                    </th>
                  </tr>
                </thead>
                <tbody className="bg-white divide-y divide-gray-200">
                  requests.map((request) => (
                    <tr key=request.id>
                      <td className="px-6 py-4 whitespace-nowrap">
                        request.id
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap">
                        <span
                          className=`px-2 inline-flex text-xs leading-5 font-semibold rounded-full $
                            request.success
                              ? "bg-green-100 text-green-800"
                              : "bg-red-100 text-red-800"
                          `
                        >
                          
                        </span>
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap">
                        
                      </td>
                      <td className="px-6 py-4 whitespace-nowrap">
                         "-"
                      </td>
                      <td className="px-6 py-4">
                        <pre className="text-xs bg-gray-50 p-2 rounded overflow-x-auto">
                          JSON.stringify(request.data, null, 2)
                        </pre>
                      </td>
                    </tr>
                  ))
                </tbody>
              </table>
            </div>
          </div>
        )
      </div>
    </div>
  );
}

Langkah 6: UI – Bangun Demo Cache UI

ada demo/caching Folder, buat a page.tsx dan salin kode berikut.

"use client";

import  useState, useEffect  from "react";
import Link from "next/link";
import  Product  from "@/lib/product";

export default function CachingDemo() 
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(false);
  const [responseTime, setResponseTime] = useState(0);
  const [cacheStatus, setCacheStatus] = useState("");
  const [category, setCategory] = useState("");
  const [categories, setCategories] = useState<string[]>([]);

  useEffect(() => 
    async function fetchCategories() 
      const response = await fetch("/api/products/categories");
      const data = await response.json();
      setCategories(data.categories);
    

    fetchCategories();
  , []);

  async function fetchProducts() 
    setLoading(true);
    setCacheStatus("");

    const startTime = performance.now();

    let url = "/api/products";
    if (category) 
      url += `?category=$category`;
    

    const response = await fetch(url);
    const data = await response.json();

    const endTime = performance.now();

    setProducts(data.products);
    setResponseTime(endTime - startTime);

    setCacheStatus(endTime - startTime < 500 ? "HIT" : "MISS");

    setLoading(false);
  

  async function invalidateCache() 
    setLoading(true);

    const response = await fetch("/api/cache/invalidate", 
      method: "POST",
      headers: 
        "Content-Type": "application/json",
      ,
      body: JSON.stringify(
        pattern: "products",
      ),
    );

    const data = await response.json();
    alert(`Cache invalidated: $data.message`);

    setLoading(false);

    fetchProducts();
  

  return (
    <div className="min-h-screen p-8 bg-black text-black">
      <div className="max-w-5xl mx-auto">
        <div className="mb-6">
          <Link href=" className="text-blue-600 hover:underline">
            ← Back to home
          </Link>
        </div>

        <h1 className="text-3xl font-bold mb-6">Redis Caching Demo</h1>

        <div className="bg-white p-6 rounded-lg shadow-md mb-8">
          <h2 className="text-xl font-semibold mb-4">How It Works</h2>
          <p className="mb-4">
            This demo shows Redis caching in action. When you fetch products,
            the first request will be slow as it goes to the database.
            Subsequent requests will be fast as they come from the Redis cache.
          </p>
          <p>
            Try fetching products multiple times to see the difference in
            response time between cache misses and hits.
          </p>
        </div>

        <div className="bg-white p-6 rounded-lg shadow-md mb-8">
          <h2 className="text-xl font-semibold mb-4">Test Caching</h2>

          <div className="flex flex-col md:flex-row gap-4 mb-6">
            <div className="flex-1">
              <label
                htmlFor="category"
                className="block text-sm font-medium text-gray-700 mb-1"
              >
                Filter by Category
              </label>
              <select
                id="category"
                value=category
                onChange=(e) => setCategory(e.target.value)
                className="w-full p-2 border border-gray-300 rounded-md"
              >
                <option value="">All Categories</option>
                categories.map((cat) => (
                  <option key=cat value=cat>
                    cat.charAt(0).toUpperCase() + cat.slice(1)
                  </option>
                ))
              </select>
            </div>

            <div className="flex-1 flex items-end">
              <button
                onClick=fetchProducts
                disabled=loading
                className="w-full bg-blue-600 text-white py-2 px-4 rounded hover:bg-blue-700 disabled:bg-blue-300"
              >
                loading ? "Loading..." : "Fetch Products"
              </button>
            </div>

            <div className="flex-1 flex items-end">
              <button
                onClick=invalidateCache
                disabled=loading
                className="w-full bg-red-600 text-white py-2 px-4 rounded hover:bg-red-700 disabled:bg-red-300"
              >
                Invalidate Cache
              </button>
            </div>
          </div>

          responseTime > 0 && (
            <div className="mb-4 p-4 bg-gray-100 rounded-md">
              <div className="flex justify-between items-center">
                <p className="text-lg">
                  Response Time:" "
                  <span className="font-bold">responseTime.toFixed(2)ms</span>
                </p>
                <p className="text-lg">
                  Cache:" "
                  <span
                    className=`font-bold $
                      cacheStatus === "HIT"
                        ? "text-green-600"
                        : "text-yellow-600"
                    `
                  >
                    cacheStatus
                  </span>
                </p>
              </div>
            </div>
          )
        </div>

        products.length > 0 && (
          <div className="bg-white p-6 rounded-lg shadow-md">
            <h2 className="text-xl font-semibold mb-4">
              Products (products.length)
            </h2>
            <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
              products.map((product) => (
                <div key=product.id className="border rounded-md p-4">
                  <h3 className="font-medium text-lg mb-1">product.name</h3>
                  <p className="text-gray-500 text-sm mb-2">
                    product.category
                  </p>
                  <p className="font-bold text-lg mb-2">
                    $product.price.toFixed(2)
                  </p>
                  <p className="text-sm text-gray-700">product.description</p>
                </div>
              ))
            </div>
          </div>
        )
      </div>
    </div>
  );

Uji aplikasi kami

PH! Itu banyak kode, tetapi kita bisa mengujinya pada akhirnya. Hal pertama adalah menjalankan server pengembangan kami.

Fungsi Uji:

1. Demo cache

  • Klik Demo Cache.
  • Pilih kategori dan klik dapatkan produk.
  • Perhatikan waktu respons.
  • Klik Dapatkan Produk lagi dan amati waktu respons yang lebih cepat, serta klik cache dan kehilangan.
  • Coba klik cache yang tidak valid dan dapatkan produk lagi.

Klik "Cache tidak valid" kemudian "Ekstrak produk"

Waktu respons awal adalah 2004,50 ms (tanpa cache).

Waktu respons awal adalah 2004,50 milidetik

Waktu respons cache – 285.50ms!

2. Demonstrasi batas kecepatan

  • Klik pada demo batas kecepatan.
  • Pilih titik akhir batas tingkat manajemen.
  • Tetapkan jumlah permintaan menjadi lima.
  • Klik Kirim Permintaan.
  • Amati bagaimana tiga permintaan pertama berhasil, tetapi dua permintaan berikutnya gagal dengan kesalahan 429.
  • Coba gunakan titik akhir yang berbeda dengan batas laju yang berbeda.

Coba gunakan titik akhir yang berbeda dengan batas laju yang berbeda

Batas pengeluaran administrasi hanya memungkinkan satu permintaan per menit. Anda dapat melihat bahwa tiga permintaan pertama berhasil, sementara dua lainnya gagal.

Kirim permintaan

Ketika kami membuat permintaan lagi dalam menit yang sama, kami menemukan bahwa semua permintaan gagal.

Bagaimana semuanya bekerja

Mari kita tinjau bagaimana komponen yang berbeda dari suatu aplikasi berinteraksi satu sama lain:

Aliran cache

  1. Pengguna memilih kategori dan mengklik “Dapatkan Produk” di UI.
  2. Klien mengirimkan permintaan ke /API /Produk? Kategori = xyz.
  3. Routing API menggunakan getCacheInstance () untuk memeriksa apakah Redis memiliki respons yang di -cache.
  4. Jika hit cache terjadi:
    • Data yang di-cache memiliki status x-cache: judul hit
    • Respons yang sangat cepat (biasanya di bawah 50ms)
  5. Jika kerusakan cache terjadi:
    • Data diperoleh dari layanan produk
    • Data disimpan di Redis dengan 5 menit TTL
    • Respons pengembalian (lebih lambat, biasanya 300-1000ms)
    • UI menampilkan produk dan menampilkan waktu respons

Aliran batas tingkat

  1. Pengguna memilih titik akhir dan jumlah permintaan dan klik Kirim Permintaan.
  2. Klien mengirimkan beberapa permintaan ke titik akhir yang dipilih
  3. Untuk setiap permintaan, middleware mencegat permintaan dan memanggil limiter tarif yang sesuai
  4. Tingkat limiter:
    • Melacak jumlah permintaan alamat IP klien
    • Tambahkan header batas tingkat ke respons
    • Jika batas terlampaui, itu akan mengembalikan kesalahan 429
  5. UI menampilkan hasil, menampilkan kode status, permintaan yang tersisa dan data respons

sebagai kesimpulan

Berikut ini adalah demonstrasi tentang cara menerapkan cache dan laju batas menggunakan Redis. Ini adalah tutorial yang panjang, tetapi tujuannya adalah menjadi sedetail mungkin, jadi mudah dimengerti. Anda juga dapat menemukan proyek lengkap di sini. Terima kasih telah membaca!

rencana pengembangan website



metode pengembangan website

jelaskan beberapa rencana untuk pengembangan website, proses pengembangan website, kekuatan dan kelemahan bisnis pengembangan website
, jasa pengembangan website, tahap pengembangan website, biaya pengembangan website

#Cache #dan #batas #batas #dengan #redis #dan #next.js

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *