대규모 프로젝트의 경우 일반적으로 Cloudflare Rate Limiting 또는 HAProxy와 같은 도구를 사용하는 것이 가장 좋습니다. 강력하고 안정적이며 무거운 작업을 대신 처리해 줍니다.
그러나 소규모 프로젝트의 경우 또는 작업 방식을 배우고 싶다면 코드에서 바로 속도 제한기를 만들 수 있습니다. 왜?
이 가이드가 끝나면 API가 과부하되지 않도록 보호하기 위해 TypeScript에서 기본 조절 장치를 구축하는 방법을 알게 될 것입니다. 우리가 다룰 내용은 다음과 같습니다:
이 가이드는 실용적인 출발점이 되도록 설계되었으며 불필요한 복잡성 없이 기본 사항을 배우고 싶은 개발자에게 적합합니다. 하지만 프로덕션 준비가 되어 있지 않습니다.
시작하기 전에 Lucia의 속도 제한 섹션에 올바른 크레딧을 제공하고 싶습니다.
Throttler 클래스를 정의해 보겠습니다.
export class Throttler { private storage = new Map(); constructor(private timeoutSeconds: number[]) {} }
Throttler 생성자는 시간 초과 기간(timeoutSeconds) 목록을 허용합니다. 사용자가 차단될 때마다 이 목록에 따라 기간이 점진적으로 늘어납니다. 결국 최종 제한 시간에 도달하면 콜백을 실행하여 사용자의 IP를 영구적으로 금지할 수도 있습니다. 하지만 이는 이 가이드의 범위를 벗어납니다.
다음은 간격을 늘려 사용자를 차단하는 조절 장치 인스턴스를 생성하는 예입니다.
const throttler = new Throttler([1, 2, 4, 8, 16]);
이 인스턴스는 처음으로 1초 동안 사용자를 차단합니다. 두 번째는 두 번째입니다.
우리는 지도를 사용하여 IP 주소와 해당 데이터를 저장합니다. 지도는 빈번한 추가 및 삭제를 효율적으로 처리하므로 이상적입니다.
프로 팁: 자주 변경되는 동적 데이터에는 지도를 사용하세요. 정적이고 변하지 않는 데이터의 경우 객체가 더 좋습니다. (토끼 구멍 1)
엔드포인트가 요청을 받으면 사용자의 IP 주소를 추출하고 Throttler에 문의하여 요청을 허용할지 여부를 결정합니다.
사례 A: 신규 또는 비활성 사용자
Throttler에서 IP를 찾을 수 없는 경우 사용자의 첫 번째 요청이거나 오랫동안 비활성 상태였던 것입니다. 이 경우:
사례 B: 활성 사용자
IP가 발견되면 사용자가 이전에 요청했다는 의미입니다. 여기:
후자의 경우에는 마지막 블록 이후 충분한 시간이 지났는지 확인해야 합니다. 우리는 인덱스 덕분에 어떤 timeoutSecond를 참조해야 하는지 알고 있습니다. 그렇지 않은 경우 간단히 되돌아오십시오. 그렇지 않으면 타임스탬프를 업데이트하세요.
export class Throttler { // ... public consume(key: string): boolean { const counter = this.storage.get(key) ?? null; const now = Date.now(); // Case A if (counter === null) { // At next request, will be found. // The index 0 of [1, 2, 4, 8, 16] returns 1. // That's the amount of seconds it will have to wait. this.storage.set(key, { index: 0, updatedAt: now }); return true; // allowed } // Case B const timeoutMs = this.timeoutSeconds[counter.index] * 1000; const allowed = now - counter.updatedAt >= timeoutMs; if (!allowed) { return false; // denied } // Allow the call, but increment timeout for following requests. counter.updatedAt = now; counter.index = Math.min(counter.index 1, this.timeoutSeconds.length - 1); this.storage.set(key, counter); return true; // allowed } }
인덱스를 업데이트할 때 timeoutSeconds의 마지막 인덱스로 제한됩니다. 이것이 없으면 counter.index 1이 이를 오버플로하고 다음에 this.timeoutSeconds[counter.index] 액세스로 인해 런타임 오류가 발생합니다.
이 예에서는 Throttler를 사용하여 사용자가 API를 호출할 수 있는 빈도를 제한하는 방법을 보여줍니다. 사용자가 너무 많은 요청을 하면 기본 로직을 실행하는 대신 오류가 발생합니다.
const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]); export async function GET({ getClientAddress }) { const IP = getClientAddress(); if (!throttler.consume(IP)) { throw error(429, { message: 'Too Many Requests' }); } // Read from DB, call OpenAI - do the thing. return new Response(null, { status: 200 }); }
로그인 시스템에서 속도 제한을 사용할 때 다음 문제가 발생할 수 있습니다.
이를 방지하려면 속도 제한에 IP 대신 사용자의 고유한 사용자 ID를 사용하세요. 또한 불필요한 블록을 방지하려면 로그인에 성공한 후 조절기 상태를 재설정해야 합니다.
Throttler 클래스에 재설정 메서드를 추가합니다.
export class Throttler { // ... public reset(key: string): void { this.storage.delete(key); } }
로그인 성공 후 사용하세요:
const user = db.get(email); if (!throttler.consume(user.ID)) { throw error(429); } const validPassword = verifyPassword(user.password, providedPassword); if (!validPassword) { throw error(401); } throttler.reset(user.id); // Clear throttling for the user
조절 장치가 IP 및 속도 제한을 추적하므로 더 이상 필요하지 않은 IP 레코드를 제거하는 방법과 시기를 생각하는 것이 중요합니다. 정리 메커니즘이 없으면 제한 장치가 레코드를 메모리에 계속 저장하므로 시간이 지남에 따라 데이터가 증가함에 따라 성능 문제가 발생할 수 있습니다.
이를 방지하기 위해 일정 기간 동안 활동이 없으면 오래된 기록을 주기적으로 삭제하는 정리 기능을 구현할 수 있습니다. 다음은 조절 장치에서 오래된 항목을 제거하기 위해 간단한 정리 방법을 추가하는 방법에 대한 예입니다.
export class Throttler { // ... public cleanup(): void { const now = Date.now() // Capture the keys first to avoid issues during iteration (we use .delete) const keys = Array.from(this.storage.keys()) for (const key of keys) { const counter = this.storage.get(key) if (!counter) { // Skip if the counter is already deleted (handles concurrency issues) return } // If the IP is at the first timeout, remove it from storage if (counter.index == 0) { this.storage.delete(key) continue } // Otherwise, reduce the timeout index and update the timestamp counter.index -= 1 counter.updatedAt = now this.storage.set(key, counter) } } }
정리 일정을 예약하는 매우 간단한 방법(아마도 최선은 아닐 수 있음)은 setInterval:
을 사용하는 것입니다.
const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]) const oneMinute = 60_000 setInterval(() => throttler.cleanup(), oneMinute)
이 정리 메커니즘은 스로틀러가 오래된 레코드를 무기한 보유하지 않도록 하여 애플리케이션 효율성을 유지하는 데 도움이 됩니다. 이 접근 방식은 간단하고 구현하기 쉽지만 보다 복잡한 사용 사례(예: 고급 예약 사용 또는 높은 동시성 처리)의 경우 추가 개선이 필요할 수 있습니다.
주기적인 정리를 통해 메모리 팽창을 방지하고 한동안 요청을 시도하지 않은 사용자가 더 이상 추적되지 않도록 할 수 있습니다. 이는 속도 제한 시스템을 확장 가능하고 리소스 효율적으로 만들기 위한 첫 번째 단계입니다.
모험을 느끼고 싶다면 속성이 어떻게 할당되고 어떻게 변경되는지 읽어보는 것이 흥미로울 수 있습니다. 또한 특히 단일형성이 선호하는 인라인 캐시와 같은 VM 최적화에 대해서도 알아보세요. 즐기다. ↩
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3