نتیجهی هر ارزیابی میتونه allow، deny، یا یه زنجیرهی پیچیدهتر شامل داور LLM یا تأیید انسانی (مثلاً از طریق Slack) باشه؛ یعنی برای عملیات پرریسک مثل kubectl delete pod، میشه درخواست رو متوقف کرد تا یه انسان تأییدش کنه.
مدیریت credential ها
یکی از نکات کلیدی طراحی اینه که credential های واقعی هیچوقت رو خود agent نمیشینن. Agent فقط یه placeholder میفرسته، و این gateway است که موقع عبور دادن ترافیک، توکن واقعی رو جایگزینش میکنه. یعنی حتی اگه agent کامل کنترل بشه یا لاگش لو بره، خود secret ها هیچوقت در معرض دید agent قرار نمیگیرن.
سه مدل استقرار
طبق ریپازیتوری رسمی گیتهاب، Claw Patrol سه شکل مختلف استقرار داره:
clawpatrol run: یه تونل مخصوص هر پروسه باز میکنه (رو لینوکس با netns، رو مک با NetworkExtension)؛ فقط ترافیک همون دستور wrapشده از gateway عبور میکنه.
clawpatrol join: یه تونل WireGuard کامل بالا میاره که کل ترافیک host رو مسیر میده.
clawpatrol gateway: خود proxy است؛ یه باینری تکی که HCL config رو لود میکنه و کلاینتها رو میپذیره.
میتونی هرکدوم از این سه شکل رو بسته به نیازت انتخاب کنی؛ لازم نیست همیشه کل host رو مسیر بدی.
چطور همین الان امتحانش کنیم
پروژه زیر لایسنس MIT منتشر شده و از طریق اسکریپت نصب خود پروژه یا از سورس (که نیاز به Go و Node.js داره) قابل بیلده. برای شروع:
# نصب سریع
curl -fsSL https://clawpatrol.dev/install.sh | sh
# یا از سورس (نیاز به Go و Node.js)
git clone https://github.com/denoland/clawpatrol
cd clawpatrol && make
بعد از نصب، یه فایل HCL برای قوانینت مینویسی (مثلاً بلاک کردن DROP TABLE رو یه دیتابیس خاص، یا نیاز به تأیید انسانی برای حذف پاد رو Kubernetes production)، و agent ت رو طوری تنظیم میکنی که ترافیکش از یکی از سه مسیر بالا عبور کنه. ابزار همچنین یه دستور clawpatrol test هم داره که میتونی قوانینت رو در برابر یه سری fixture (رکورد شده از دشبورد یا نوشتهشدهی دستی) تو CI تست کنی؛ اگه یه تغییر قانون، نتیجهی یه fixture رو عوض کنه، تست fail میشه و diff نشونت میده.
نکتهی مهم: هنوز alpha است
خود Deno صراحتاً اعلام کرده که Claw Patrol فعلاً alpha software است. پشتیبانی پروتکلها دقیقاً به اندازهی نیاز خود Deno گسترشه، نه بیشتر؛ اگه پروتکل خاصی میخوای که پشتیبانی نمیشه، باید یا خودت کدش رو اضافه کنی (مستندات کافی برای این کار هست)، یا از تیم Deno درخواست کنی. شرکت هم صراحتاً از جامعه خواسته که الگوهای قانوننویسی واقعی، پروتکلهای موردنیاز بعدی، و باگهای مسیر نصب رو گزارش بدن.
جمعبندی
اگه داری agent هایی میسازی که دسترسی نوشتنی به زیرساخت واقعی (دیتابیس، کلاستر، سرویسهای ابری) دارن، Claw Patrol یه لایهی امنیتی جدی و متنباز ارائه میده که فراتر از یه HTTP proxy ساده عمل میکنه؛ مخصوصاً به این خاطر که پروتکلهای غیر-HTTP رو هم میفهمه و credential های واقعی رو از دید خود agent مخفی نگه میداره. ولی چون هنوز alpha است، برای استفادهی production جدی، بهتره اول رو یه محیط تست یا staging امتحانش کنی و منتظر بلوغ بیشتر پروژه بمونی.
یه مثال ساده
فرض کن یه تابع محاسباتی سنگین به Rust داری:
#[no_mangle]
pub fn fibonacci(n: u32) -> u64 {
if n <= 1 {
return n as u64;
}
let mut a = 0u64;
let mut b = 1u64;
for _ in 2..=n {
let temp = a + b;
a = b;
b = temp;
}
b
}
با ابزاری مثل wasm-pack، این کد به یه فایل .wasm compile میشه که میشه مستقیم تو جاوااسکریپت import و صداش زد:
import init, { fibonacci } from './pkg/my_wasm_module.js';
async function run() {
await init();
console.log(fibonacci(50)); // اجرا با سرعت نزدیک به native
}
run();
از دید جاوااسکریپت، fibonacci فقط یه تابع معمولیه، ولی پشت صحنه داره یه باینری کامپایلشده و بهینه اجرا میشه، نه کد تفسیرشدهی جاوااسکریپت.
کجاها واقعاً استفاده میشه
ابزارهای سنگین که به مرورگر آوردن: Figma بخش زیادی از موتور رندرش رو با WebAssembly پیاده کرده تا بتونه performance شبیه یه اپلیکیشن دسکتاپ رو تو مرورگر ارائه بده. AutoCAD نسخهی وب خودش رو با پورت کردن دههها کد C++ به Wasm ساخته.
بازیها و موتورهای گرافیکی: موتورهایی مثل Unity و Unreal Engine امکان export کردن بازی به WebAssembly رو دارن، که یعنی بازیهای نسبتاً سنگین میتونن مستقیم تو مرورگر، بدون نصب هیچ پلاگینی، اجرا بشن.
پردازش تصویر و ویدیو: ابزارهایی مثل ffmpeg.wasm امکان encode/decode کردن ویدیو مستقیم تو مرورگر رو میدن، بدون اینکه فایل به سرور آپلود بشه؛ هم سریعتره، هم حریم خصوصی کاربر حفظ میشه.
اجرای زبونهای دیگه تو مرورگر: پروژههایی مثل Pyodide، پایتون رو کامل به WebAssembly compile کردن، یعنی میتونی کد پایتون واقعی رو مستقیم تو مرورگر، بدون سرور، اجرا کنی.
فراتر از مرورگر: WebAssembly دیگه محدود به مرورگر نیست. با استانداردی به اسم WASI (WebAssembly System Interface)، میشه Wasm رو مستقل از مرورگر، رو سرور یا edge computing هم اجرا کرد. کلادفلر Workers و Fastly Compute دقیقاً از همین قابلیت استفاده میکنن تا کد رو تو صدها دیتاسنتر مختلف، با startup time خیلی کمتر از یه کانتینر معمولی، اجرا کنن.
چیزی که WebAssembly نیست
یه سوءتفاهم رایج اینه که فکر کنیم Wasm قراره جای جاوااسکریپت رو بگیره. اینطور نیست؛ Wasm نمیتونه مستقیم به DOM دسترسی داشته باشه یا event handler ثبت کنه. برای هر تعامل با صفحه (کلیک، تغییر استایل، دستکاری DOM)، هنوز باید از جاوااسکریپت بهعنوان واسط استفاده کنی. Wasm بیشتر مکمل جاوااسکریپته تا جایگزینش؛ جاهایی که محاسبات سنگین لازمه، کار رو به Wasm میسپاری و بقیهی تعامل با کاربر همچنان با جاوااسکریپت انجام میشه.
کِی واقعاً بهش نیاز داری
اگه داری یه اپلیکیشن وب معمولی میسازی (فرم، داشبورد، فروشگاه آنلاین)، به احتمال زیاد هیچوقت مستقیم با WebAssembly سروکار پیدا نمیکنی؛ جاوااسکریپت (یا فریمورکهای روش) کاملاً کافیه. Wasm زمانی وارد بازی میشه که یه بخش خاص از اپلیکیشنت واقعاً محاسبات سنگین داره (پردازش تصویر/صوت/ویدیو، رمزنگاری، فیزیک، parsing فایلهای حجیم) و همون بخش داره performance اپ رو محسوس کند میکنه. تو اون حالت، میتونی فقط همون بخش خاص رو به Rust یا C++ بنویسی و به Wasm compile کنی، بدون اینکه مجبور بشی کل اپلیکیشن رو از جاوااسکریپت خارج کنی.
همین عدم قطعیت باعث شده خیلی از توزیعهای لینوکس بزرگ (مثل Debian و Fedora) و کلود پرووایدرهای بزرگ (AWS با Amazon ElastiCache و Google Cloud با Memorystor) به سمت پشتیبانی از Valkey حرکت کنن یا حداقل گزینهی Valkey رو کنار Redis ارائه بدن.
کدوم رو باید انتخاب کنی
اگه یه پروژهی جدید رو از صفر شروع میکنی و لایسنس متنباز کاملاً آزاد (BSD) برات اهمیت داره، یا میخوای مطمئن باشی که هیچ تغییر لایسنسی تو آینده گریبانت رو نمیگیره، Valkey انتخاب منطقیتریه؛ مخصوصاً که پشتوانهی چند تا از بزرگترین شرکتهای زیرساخت دنیا رو داره و توسعهش هم فعاله.
اگه از قبل با Redis کار میکنی و به ابزارها، افزونهها یا سرویسهای خاصی که مستقیم از Redis Ltd. میان وابستهای (مثل Redis Enterprise یا بعضی ماژولهای اختصاصی)، یا اصلاً لایسنس براتون مسئلهی حادی نیست، ادامه دادن با Redis همچنان گزینهی معتبریه؛ خصوصاً که با AGPL نسخهی ۸، دوباره به یه مسیر متنباز برگشته.
نکتهی عملی آخر: چون این دو پروژه از یه نقطهی مشترک شروع شدن، مهاجرت بینشون (تو هر جهتی) هنوز نسبتاً کمدردسره، پس تصمیم امروز لزوماً برای همیشه قفل نمیشه. اگه مطمئن نیستی، شروع با Valkey ریسک کمتری داره؛ چون برگشتن به Redis همیشه یه گزینهی باز باقی میمونه.
// pseudo-code ساده برای fixed window
const requests = {}; // { userId: { count, windowStart } }
function isAllowed(userId, limit = 100, windowMs = 60000) {
const now = Date.now();
const entry = requests[userId];
if (!entry || now - entry.windowStart > windowMs) {
requests[userId] = { count: 1, windowStart: now };
return true;
}
if (entry.count >= limit) return false;
entry.count++;
return true;
}
مشکل این روش یک نکتهی ظریفه: چون بازهها ثابت و جدا از همن، یک کلاینت میتونه دقیقاً لحظهی آخر یک بازه ۱۰۰ درخواست بفرسته و دقیقاً لحظهی اول بازهی بعدی، ۱۰۰ درخواست دیگه — یعنی توی یک بازهی دو ثانیهای واقعی، ۲۰۰ درخواست رد شده، درحالیکه سقف روی کاغذ ۱۰۰ در دقیقه بود. به این پدیده burst at boundary میگن.
Sliding Window
این روش با نگهداشتن timestamp دقیق هر درخواست (یا یک تقریب وزندار از دو بازهی متوالی)، مشکل burst مرزی رو حل میکنه. دقیقتره ولی حافظه و محاسبات بیشتری میخواد، چون باید یا timestamp های تکتک درخواستها رو نگه داری، یا یک فرمول ترکیبی حساب کنی.
Token Bucket
این یکی از محبوبترین الگوریتمهاست، چون هم دقیقه، هم به کلاینت اجازهی burst کنترلشده میده. تصورش اینطوریه: یک سطل با ظرفیت مشخص (مثلاً ۱۰ توکن) داری که با نرخ ثابتی (مثلاً یک توکن در ثانیه) پر میشه. هر درخواست یک توکن مصرف میکنه؛ اگه سطل خالی باشه، درخواست رد میشه. مزیتش اینه که اگه کلاینت مدتی درخواستی نفرستاده باشه، سطلش پر مونده و میتونه یک burst کوتاه (تا سقف ظرفیت سطل) بفرسته، ولی نمیتونه برای مدت طولانی بالاتر از نرخ میانگین ادامه بده.
مشابه token bucket ولی از زاویهی دیگه: درخواستها توی یک صف (سطل) قرار میگیرن و با نرخ ثابتی از صف خارج و پردازش میشن، مثل یک سطل که از یک سوراخ ثابت آب نشت میکنه. اگه صف پر بشه، درخواستهای جدید رد میشن. این روش خروجی رو کاملاً یکنواخت نگه میداره (بدون burst)، برخلاف token bucket که burst محدود رو مجاز میدونه — انتخاب بین این دو به این بستگی داره که آیا میخوای نرخ خروجی کاملاً ثابت باشه، یا کمی انعطاف برای burst داشته باشه.
کجا پیادهسازیش کنیم
rate limiting رو میشه در چند لایه پیاده کرد. توی سطح Nginx یا یک reverse proxy، با ماژول limit_req میشه بدون تغییر کد اپلیکیشن، محدودیت گذاشت:
توی سطح اپلیکیشن، معمولاً از Redis برای نگهداشتن شمارندهها استفاده میشه، چون Redis سریع و atomic عمل میکنه و اگه چند instance از سرور اپلیکیشن داشته باشی، همه بتونن یک شمارندهی مشترک رو ببینن (که با یک شمارندهی in-memory جدا روی هر instance، امکانش نیست).
توی سطح API Gateway (مثل Kong، AWS API Gateway، یا Cloudflare)، rate limiting معمولاً بهصورت آماده و قابلتنظیم از طریق پنل مدیریت وجود داره، بدون نیاز به نوشتن کد.
پاسخ درست به کلاینت رد شده
وقتی یک درخواست بهخاطر rate limit رد میشه، باید کد وضعیت HTTP استاندارد 429 Too Many Requests رو برگردونی، و بهتره هدر Retry-After رو هم اضافه کنی تا کلاینت بدونه دقیقاً چند ثانیه دیگه باید صبر کنه قبل از تلاش مجدد. این جزئیات کوچیک، تفاوت بین یک API قابلاعتماد و یکی که کلاینتهاش رو گیج میکنه رو میسازه.
چطوری بهتره استفاده بشه ؟
Rate limiting را میتوان در چند لایه مختلف پیادهسازی کرد؛ از لایه زیرساخت مثل Nginx و API Gateway گرفته تا داخل خود اپلیکیشن. انتخاب محل مناسب بستگی دارد به اینکه هدف اصلی، محافظت از منابع سیستم است یا کنترل رفتار کاربران و قوانین کسبوکار.
Rate Limiting در Nginx / Reverse Proxy
Nginx در لایه ورودی سیستم قرار میگیرد و قبل از رسیدن درخواست به اپلیکیشن تصمیم میگیرد که درخواست اجازه عبور دارد یا نه. مزیت اصلی این روش این است که درخواستهای اضافی اصلاً وارد برنامه نمیشوند و منابعی مثل CPU، حافظه و اتصالهای دیتابیس مصرف نمیشوند.
این روش برای جلوگیری از فشار زیاد، حملات حجمی، crawlerها و درخواستهای غیرعادی بسیار مناسب است.
مزایا:
عملکرد بسیار بالا
کاهش فشار روی اپلیکیشن
عدم نیاز به تغییر کد برنامه
محدودیت اصلی آن این است که معمولاً فقط به اطلاعاتی مثل IP و Header دسترسی دارد و نمیتواند تفاوت بین کاربران، پلنها یا سطح دسترسی آنها را تشخیص دهد.
مثلاً نمیتواند بهتنهایی تصمیم بگیرد که یک کاربر رایگان فقط ۱۰۰ درخواست در دقیقه داشته باشد و یک کاربر حرفهای ۱۰۰۰۰ درخواست.
Rate Limiting در Middleware اپلیکیشن
در این روش محدودیت داخل خود برنامه اعمال میشود. چون اپلیکیشن اطلاعات بیشتری درباره کاربر دارد، میتواند قوانین دقیقتری بر اساس User ID، API Key، نقش کاربر یا نوع اشتراک اعمال کند.
برای مثال، یک سرویس هوش مصنوعی میتواند برای کاربران رایگان محدودیت ۱۰ درخواست در روز و برای کاربران پولی محدودیت ۱۰۰۰ درخواست در روز قرار دهد.
مزایا:
دسترسی به اطلاعات کامل کاربر
مناسب برای قوانین کسبوکار
انعطافپذیری بالا
اما مشکل این روش این است که درخواست ابتدا وارد اپلیکیشن میشود و سپس محدود میشود. همچنین در معماریهای چند سروره، نگهداری شمارنده در حافظه داخلی هر سرور باعث ایجاد خطا میشود.
برای مثال اگر یک کاربر ۱۰۰ درخواست ارسال کند و Load Balancer آن را بین دو سرور تقسیم کند، هر سرور ممکن است فقط ۵۰ درخواست ببیند و محدودیت بهدرستی اعمال نشود.
به همین دلیل معمولاً از Redis یا یک ذخیرهساز مشترک برای نگهداری وضعیت rate limit استفاده میشود.
Rate Limiting در API Gateway
API Gateway یک لایه مدیریتی بین کلاینت و سرویسهای مختلف قرار میگیرد و معمولاً برای معماریهای بزرگ و چندسرویسی استفاده میشود.
این لایه میتواند محدودیتهای مشترکی برای تمام APIها اعمال کند، API Key مدیریت کند، quota تعریف کند و سیاستهای امنیتی را بهصورت متمرکز اجرا کند.
مزایا:
مناسب برای Microserviceها
مدیریت متمرکز APIها
پشتیبانی از API Key و quota
در مقابل، پیچیدگی و هزینه بیشتری نسبت به Nginx یا middleware ساده دارد و برای پروژههای کوچک ممکن است بیش از نیاز باشد.
بهترین رویکرد: ترکیبی از چند لایه
در سیستمهای واقعی معمولاً از یک روش واحد استفاده نمیشود، بلکه چند لایه با هم کار میکنند.
یک معماری رایج:
Client
|
v
Cloudflare / WAF
|
v
Nginx
(IP Rate Limit)
|
v
Application
(User/API Key Limit)
|
v
Redis
(Shared Counter)
|
v
Database
در این معماری:
Nginx جلوی فشار غیرعادی روی زیرساخت را میگیرد.
Middleware اپلیکیشن محدودیتهای مربوط به کاربران و منطق کسبوکار را اجرا میکند.
Redis وضعیت rate limit را بین چند سرور هماهنگ نگه میدارد.
API Gateway در سیستمهای بزرگ مدیریت متمرکز APIها را انجام میدهد.
مثال:
برای یک وبسایت بلاگی متوسط، یک نقطه شروع منطقی میتواند این باشد:
محدودیت عمومی در Nginx: حدود 50 تا 100 درخواست در ثانیه برای هر IP
محدودیت در سطح کاربر: حدود 1000 تا 3000 درخواست در ساعت که مثلا از ۱۰۰۰ تا برای مهمان و ۳۰۰۰ برای بالاترین سطح دسترسی قبل از ادمین
endpointهای حساس: بین 3 تا 10 درخواست در دقیقه
اما این اعداد قطعی نیستند. یک وبسایت خبری با میلیونها بازدید روزانه، یک وبلاگ شخصی و یک SaaS با API عمومی، نیازهای کاملاً متفاوتی دارند.
عدد صحیح زمانی مشخص میشود که رفتار واقعی کاربران، میانگین درخواستها، زمان پاسخ API، ظرفیت سرور و مصرف منابع بررسی شود. Rate limiting باید بر اساس ظرفیت سیستم تنظیم شود، نه صرفاً بر اساس یک عدد از پیش تعیینشده.
جمعبندی
rate limiting یکی از اون تصمیمهای زیرساختیه که تا نیازش رو حس نکنی، شاید حس کنی زیادیه، ولی وقتی یک باگ توی یک کلاینت یا یک حملهی ساده بدون این لایه سرورت رو از پا دربیاره، میفهمی چرا این یکی از اولین چیزهاییه که هر API عمومی باید از روز اول داشته باشه، نه چیزی که بعد از اولین حادثه اضافهاش میکنی.
async function getTopProducts() {
const cached = await redis.get('top_products');
if (cached) return JSON.parse(cached);
const products = await db.query('SELECT ... ORDER BY sales DESC LIMIT 10');
await redis.set('top_products', JSON.stringify(products), 'EX', 300); // 5 دقیقه
return products;
}
این الگو رو cache-aside میگن: اول کش رو چک میکنی، اگه خالی بود از منبع اصلی میگیری و توی کش میذاری. رایجترین الگوی کش توی دنیای بکاند همینه، چون منطق سادهست و کنترل کامل دستته.
کش توی دیتابیس
خود دیتابیسها هم لایههای کش داخلی دارن که معمولاً کمتر بهشون فکر میکنیم. مثلاً PostgreSQL یک بخش به اسم shared buffers داره که صفحات پرکاربرد دیتابیس رو توی رم نگه میداره تا لازم نباشه هر بار از دیسک بخونه. MySQL هم مکانیزمهای مشابهی داره. این یعنی حتی وقتی فکر میکنی داری «مستقیم از دیتابیس» میخونی، احتمالاً بخشی از اون داده از قبل توی حافظهی خود دیتابیس کش شده و دیسک اصلاً درگیر نشده.
کش توی اپلیکیشنهای موبایل
توی اپهای موبایل هم همین منطق تکرار میشه، فقط با اسمهای دیگه. مثلاً یک اپلیکیشن ممکنه لیست محصولات یا فید خبری رو یک بار از سرور بگیره و توی حافظهی محلی گوشی (یا یک دیتابیس سبک مثل SQLite) نگه داره، تا کاربر بدون اینترنت هم بتونه محتوای قبلی رو ببینه، و تا وقتی دادهی جدیدی نیومده، دوباره از سرور درخواست نده. این هم برای تجربهی کاربری بهتره (لود سریعتر) و هم برای مصرف داده و باتری صرفهجویی میکنه.
چیزی که واقعاً برای یک مهندس نرمافزار جذابه: invalidation
جملهی معروفی توی دنیای برنامهنویسی هست که میگه فقط دو چیز سخت توی علوم کامپیوتر وجود داره: invalidate کردن کش، نامگذاری متغیرها، و off-by-one errors. این جمله شوخیه، ولی نکتهی جدی پشتش اینه: نگهداشتن داده توی کش کار سختی نیست؛ سخت اینه که بفهمی دقیقاً کِی اون داده دیگه معتبر نیست و باید پاکش کنی یا آپدیتش کنی.
فرض کن قیمت یک محصول توی دیتابیس عوض بشه، ولی نسخهی کششدهاش هنوز قیمت قدیمی رو نشون بده. اگه TTL طولانی گذاشته باشی، این عدمتطابق میتونه دقیقهها یا حتی ساعتها طول بکشه. راهحلهای رایج برای این مشکل چندتاست: یکی اینکه هر وقت داده توی دیتابیس آپدیت شد، مستقیماً همون کلید رو از کش پاک کنی (invalidate on write). یکی دیگه اینه که از TTL کوتاهتر استفاده کنی تا حداکثر «قدیمیبودن» داده محدود باشه. روش سوم که پیچیدهتره ولی قویتره، استفاده از event-based invalidation است: وقتی یک تغییر توی سیستم اتفاق میفته، یک event منتشر میشه و هر سرویسی که کش مرتبط داره، خودش رو آپدیت میکنه.
یک مشکل کمتر شناختهشده: cache stampede
این یکی از اون چیزهاییه که وقتی برای اولین بار باهاش برخورد میکنی، واقعاً غافلگیرت میکنه. فرض کن یک کلید کش با TTL پنج دقیقهای منقضی بشه، و دقیقاً همون لحظه هزار درخواست همزمان برسه. چون کش خالیه، هر هزار درخواست میرن سراغ منبع اصلی (مثلاً دیتابیس) تا همون مقدار رو دوباره محاسبه کنن — درحالیکه فقط یکی از این محاسبات کافی بود. این پدیده رو cache stampede یا thundering herd میگن، و میتونه یک دیتابیس سالم رو در عرض چند ثانیه از پا دربیاره.
راهحل رایج برای این مشکل یک قفل موقته: اولین درخواستی که میبینه کش خالیه، یک lock میگیره و شروع میکنه به محاسبه؛ بقیهی درخواستها یا منتظر میمونن تا اون قفل آزاد بشه، یا موقتاً همون مقدار قدیمی (حتی اگه منقضی شده) رو برمیگردونن تا محاسبهی جدید تموم بشه. به این تکنیک دوم گاهی stale-while-revalidate میگن، و توی خیلی از CDN ها و کتابخانههای کش هم پیادهسازی شده.
سیاستهای حذف: چه چیزی رو نگه داریم، چه چیزی رو دور بریزیم
چون حافظهای که کش توش نگهداری میشه (چه RAM چه هر چیز دیگه) محدوده، وقتی کش پر میشه باید یک چیزی رو حذف کنی تا جا برای دادهی جدید باز بشه. رایجترین سیاست LRU (Least Recently Used) است: چیزی که مدت طولانیتری استفاده نشده، اول حذف میشه. یک سیاست دیگه LFU (Least Frequently Used) است که بهجای «آخرین بار کِی استفاده شده» به «چند بار استفاده شده» نگاه میکنه. انتخاب بین این دو به رفتار واقعی ترافیکت بستگی داره — مثلاً برای یک فید خبری که محتوای پرطرفدار مدام عوض میشه، LRU معمولاً منطقیتره؛ برای دادههایی که یک زیرمجموعهی ثابت همیشه پرتکرارن، LFU میتونه بهتر عمل کنه.
جمعبندی
نکتهی جالب کش اینه که هیچوقت یک تصمیم یکباره نیست. هر لایهای از سیستم —از مرورگر تا CDN تا اپلیکیشن تا دیتابیس— نسخهی خودش رو از همین ایدهی ساده پیاده میکنه: یک بار محاسبه کن، سریع نگه دار، و مراقب باش که کِی این نسخهی سریع دیگه با واقعیت همخونی نداره. بخش سخت کش هیچوقت «نگهداشتن» نبوده؛ همیشه «دونستن کِی باید فراموشش کنی» بوده.
/api
/static
الگوریتمهای تقسیم ترافیک
خود لود بالانسر باید یک قانون داشته باشه که تصمیم بگیره کدوم درخواست بره کدوم سرور. رایجترینش round robin است: درخواستها بهترتیب و دورهای بین سرورها پخش میشن، بدون توجه به اینکه هر سرور الان چقدر مشغوله. سادهست ولی همیشه منصفانه نیست — اگه یک درخواست سنگینتر باشه و روی یک سرور طول بکشه، اون سرور همچنان درخواست بعدی رو هم میگیره.
الگوریتم least connections این مشکل رو حل میکنه: درخواست جدید میره سراغ سروری که الان کمترین تعداد کانکشن فعال رو داره. برای ترافیکی که مدت زمان پردازش درخواستهاش خیلی متفاوته (مثلاً بعضی درخواستها سریع جواب داده میشن، بعضی طول میکشن)، این روش عادلانهتره.
یک روش دیگه IP hash است، جایی که بر اساس IP کاربر یک هش محاسبه میشه و همیشه همون کاربر به همون سرور فرستاده میشه. این برای موقعیتهایی مفیده که session کاربر روی یک سرور خاص ذخیره شده و باید همیشه همون کاربر به همون سرور برسه — البته این خودش یک ضعف داره، چون اگه توزیع IP ها نامتوازن باشه، بعضی سرورها بیشتر از بقیه بار میگیرن.
ابزارهای واقعی
اگه بخوای خودت یک لود بالانسر راه بندازی، دو تا از رایجترین گزینهها Nginx و HAProxy هستن. یک تنظیم سادهی Nginx برای پخش ترافیک بین سه سرور backend اینشکلیه:
upstream backend_servers {
least_conn;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
listen 80;
location / {
proxy_pass http://backend_servers;
}
}
اینجا least_conn مشخص میکنه از الگوریتم least connections استفاده بشه، و هر درخواستی که به این سرور برسه بین این سه backend پخش میشه. HAProxy هم همین کار رو میکنه، ولی معمولاً برای سناریوهای پیچیدهتر و ترافیک بسیار بالا انتخاب اول خیلی از تیمهاست، چون از ابتدا برای همین کار طراحی شده و قابلیتهای health check و مانیتورینگش قویتره.
اگه نخوای خودت این لایه رو مدیریت کنی، سرویسهای ابری مثل Cloudflare یک لود بالانسر مدیریتشده ارائه میدن که هم روی لایهی DNS کار میکنه (یعنی حتی قبل از رسیدن ترافیک به سرورهات، بین چند دیتاسنتر جغرافیایی پخشش میکنه) و هم health check خودکار داره؛ اگه یک سرور جواب نده، خودش بهصورت خودکار از چرخه خارجش میکنه تا برگرده.
چند لود بالانسر مرجع که ارزش دیدن دارن
تا اینجا از Nginx و HAProxy و Cloudflare گفتم، ولی چند تا نمونهی دیگه هست که دیدنشون برای یک مهندس نرمافزار واقعاً جالبه، چون هرکدوم یک مسئلهی متفاوت رو حل کردن.
اولیش Maglev گوگله. این یک لود بالانسر لایهی ۴ نرمافزاریه که گوگل خودش ساخته و یک مقالهی مهندسی دربارش منتشر کرده. نکتهی جالب Maglev اینه که بهجای اینکه از هش معمولی استفاده کنه، از یک روش به اسم consistent hashing با یک جدول lookup بزرگ استفاده میکنه. چرا این مهمه؟ چون وقتی یک backend اضافه یا حذف میشه، اکثر connection های موجود همچنان به همون backend قبلیشون میرسن — فقط بخش کوچیکی جابهجا میشه. توی یک لود بالانسر ساده با mod hash، اضافهکردن یا حذف یک سرور میتونه تقریباً همهی mapping ها رو بههم بریزه، که برای سرویسهایی که connection طولانی دارن فاجعهست.
دومی Envoy Proxy است، که اولین بار توی Lyft ساخته شد و بعد به یکی از پروژههای اصلی CNCF تبدیل شد. چیزی که Envoy رو خاص میکنه اینه که configuration اش رو میشه بهصورت داینامیک و از راه دور، از طریق یک API به اسم xDS، تغییر داد، بدون اینکه پروسه ریاستارت بشه. این دقیقاً همون چیزیه که service mesh هایی مثل Istio بهش نیاز دارن: هزاران sidecar proxy که هر کدوم کنار یک سرویس نشستن، و باید بتونن بدون downtime روتها و policy هاشون رو آپدیت کنن.
سومی AWS است، که سه نوع لود بالانسر جدا ارائه میده و این خودش نشونهی خوبیه که چرا تفکیک لایهها مهمه: Network Load Balancer (NLB) برای لایهی ۴ با تأخیر خیلی پایین، Application Load Balancer (ALB) برای لایهی ۷ با قابلیتهایی مثل روتینگ بر اساس path یا host header، و Classic Load Balancer که نسخهی قدیمیتر و امروز کمتر توصیهشدهست. اینکه AWS این سه رو جدا از هم نگه داشته، نشون میده هیچ لود بالانسر «همهکاره» ای وجود نداره؛ انتخاب درست کاملاً به نوع ترافیک بستگی داره.
چهارمی که شاید برای خیلی از دولوپرها آشناتر باشه، خود Kubernetes است. سرویسهای Kubernetes از یک کامپوننت به اسم kube-proxy استفاده میکنن که روی هر node اجرا میشه و ترافیک بین Pod ها رو با استفاده از iptables یا IPVS پخش میکنه. جالبیش اینه که این لود بالانسینگ کاملاً decentralized اتفاق میفته — هیچ نقطهی مرکزی وجود نداره که همهی ترافیک از اون رد بشه، برخلاف مدل سنتی که یک لود بالانسر جلوی همهچیز میشینه.
اگه بخوای یک قدم عمیقتر بری، سراغ Linux Virtual Server (LVS) و IPVS برو — این تکنولوژی مستقیم توی kernel لینوکس پیادهسازی شده و پایهی خیلی از لود بالانسرهای بزرگمقیاس دنیا از جمله همون kube-proxy است. چیزی که این رو برای یک مهندس جالب میکنه اینه که کل تصمیمگیری لود بالانسینگ توی kernel space اتفاق میفته، نه توی user space، که یعنی سربار (overhead) بهشدت پایینتر از یک reverse proxy معمولیه.
نکتهای که معمولاً فراموش میشه
چیزی که خیلی از تیمها موقع راهاندازی اولین لود بالانسرشون فراموش میکنن، مدیریت session است. اگه اپلیکیشنت session رو توی حافظهی خود سرور نگه میداره (نه توی یک دیتابیس یا Redis مشترک)، وقتی کاربر بار دوم درخواست بده و لود بالانسر اون رو به یک سرور دیگه بفرسته، session گم میشه و کاربر عملاً logout میشه. راهحل درست این نیست که مجبور کنی کاربر همیشه به همون سرور برسه (چون این کار انعطاف لود بالانسر رو از بین میبره)؛ راهحل درست اینه که session رو از حافظهی محلی سرور جدا کنی و توی یک storage مشترک بین همهی سرورها نگه داری. این یکی از اون نکاتیه که تا خودت باهاش برخورد نکنی، احتمالاً موقع طراحی اولیه بهش فکر نمیکنی.
مشکل فقط تاخیر نیست. اگر هزاران کاربر همزمان از یک سرور واحد درخواست کنند، آن سرور به سرعت اشباع شده و سایت کند یا حتی از دسترس خارج میشود. CDN این دو مشکل را همزمان حل میکند:
کاهش فاصله فیزیکی: محتوا از نزدیکترین نقطه جغرافیایی به کاربر تحویل داده میشود.
توزیع بار ترافیکی: درخواستها بین صدها سرور پخش میشوند و هیچ سرور واحدی تحت فشار قرار نمیگیرد.
CDN چگونه کار میکند؟
معماری CDN بر پایه چند مفهوم کلیدی ساخته شده است:
سرورهای لبه (Edge Servers)
اینها سرورهای کوچکی هستند که در نقاط مختلف جغرافیایی (به نام PoP یا Point of Presence) قرار دارند. وظیفه آنها نگهداری نسخه کش شده از محتوای سایت اصلی است.
فرآیند تحویل محتوا
وقتی کاربری برای اولین بار صفحهای را درخواست میکند، سرور لبه آن را از سرور اصلی (Origin Server) میگیرد، ذخیره میکند و به کاربر تحویل میدهد. درخواستهای بعدی همان صفحه، مستقیماً از سرور لبه پاسخ داده میشوند و دیگر نیازی به ارتباط با سرور اصلی نیست.
قوانین کش (Cache Rules)
مدیر سایت میتواند تعیین کند چه محتوایی کش شود و برای چه مدت. فایلهای استاتیک مثل تصاویر، CSS و جاوا اسکریپت معمولاً برای مدت طولانی کش میشوند، در حالی که محتوای پویا ممکن است اصلاً کش نشود.
انواع CDN بر اساس روش ذخیرهسازی
دو روش اصلی برای تحویل محتوا در CDN وجود دارد:
Pull CDN
در این روش، سرور لبه فقط زمانی محتوا را از سرور اصلی میگیرد که کاربر آن را درخواست کند. یعنی اولین کاربری که از یک منطقه جغرافیایی خاص صفحهای را باز میکند، کمی تاخیر را تجربه میکند، اما کاربران بعدی از همان منطقه سرعت بالایی خواهند داشت. این روش برای سایتهایی با محتوای متنوع و پویا مناسب است.
Push CDN
در این روش، صاحب سایت خودش محتوا را به صورت دستی یا خودکار روی سرورهای لبه آپلود میکند. این روش برای سایتهایی که محتوای ثابت و مشخصی دارند (مثل فایلهای دانلودی یا ویدیوهای از پیش تعیین شده) بهتر عمل میکند و اولین کاربر هم سرعت بالایی را تجربه میکند.
مزایا و معایب استفاده از CDN
مزایا:
افزایش چشمگیر سرعت: کاهش Latency و بارگذاری سریعتر برای کاربران در سراسر جهان.
کاهش هزینه پهنای باند: با کش کردن محتوا، ترافیک سرور اصلی به شدت کاهش مییابد.
مقیاسپذیری بالا: سایت میتواند ترافیکهای ناگهانی و سنگین را بدون مشکل مدیریت کند.
امنیت بیشتر: اکثر CDNهای مدرن دارای فایروال، محافظت در برابر حملات DDoS و گواهینامه SSL رایگان هستند.
بهبود سئو: سرعت بارگذاری یکی از فاکتورهای مهم رتبهبندی گوگل است.
معایب:
هزینه اضافی: برای سایتهای بزرگ با ترافیک بالا، هزینه CDN میتواند قابل توجه باشد.
پیچیدگی پیکربندی: تنظیمات نادرست کش میتواند باعث نمایش محتوای قدیمی به کاربران شود.
وابستگی به سرویسدهنده: در صورت قطعی سراسری CDN، سایت شما نیز از دسترس خارج میشود.
محدودیت در محتوای پویا: صفحاتی که برای هر کاربر متفاوت هستند (مثل داشبورد کاربری) نمیتوانند به راحتی کش شوند.
مقایسه CDN با میزبانی سنتی
در میزبانی سنتی، تمام کاربران دنیا به یک سرور واحد متصل میشوند. این روش برای سایتهای کوچک و محلی کاملاً مناسب است، اما برای سایتهایی با مخاطبان جهانی، با مشکلات سرعت و پایداری مواجه میشود.
CDN در واقع یک لایه اضافی بین کاربر و سرور اصلی است. این دو تکنولوژی رقیب یکدیگر نیستند، بلکه مکمل هماند. شما همچنان به یک سرور اصلی نیاز دارید، اما CDN بار آن را کاهش میدهد و تجربه کاربری را بهبود میبخشد.
ارائهدهندگان معروف CDN
در بازار امروز، چند بازیگر اصلی وجود دارند:
Cloudflare: محبوبترین گزینه برای سایتهای کوچک و متوسط. پلن رایگان آن بسیار قدرتمند است و راهاندازی آن تنها با تغییر DNS امکانپذیر است.
Akamai: یکی از قدیمیترین و بزرگترین ارائهدهندگان CDN که بیشتر برای سازمانهای بزرگ و شرکتهای Enterprise مناسب است.
Fastly: معروف به CDN نسل جدید با قابلیت Real-time Purging. بسیاری از سایتهای بزرگ خبری و استریم از آن استفاده میکنند.
AWS CloudFront: برای کسانی که از زیرساخت آمازون استفاده میکنند، یکپارچگی بینظیری دارد.
BunnyCDN: یک گزینه اقتصادی با کیفیت بالا که در سالهای اخیر بسیار محبوب شده است.
جمعبندی نهایی و نگاه شخصی
به عنوان کسی که سالها با سایتهای مختلف کار کردهام، باید صادقانه بگویم که استفاده از CDN برای هر سایتی که مخاطبان جدی دارد، دیگر یک انتخاب نیست، بلکه یک ضرورت است. خودم شخصاً بعد از فعالسازی Cloudflare روی یکی از سایتهایم، کاهش ۴۰ درصدی زمان بارگذاری را تجربه کردم و این عدد اصلاً کوچک نیست.
با این حال، یک هشدار دوستانه بدهم: CDN جادو نمیکند. اگر سایت شما به دلیل کدهای بهینهنشده، تصاویر سنگین یا کوئریهای دیتابیس کند باشد، CDN فقط بخشی از مشکل را حل میکند. اول سایت خود را بهینه کنید، سپس از CDN به عنوان یک تقویتکننده استفاده کنید.
در نهایت، اگر تازه کار هستید، پیشنهاد میکنم با پلن رایگان Cloudflare شروع کنید. هم ساده است، هم امن و هم تاثیر آن را خیلی سریع مشاهده خواهید کرد.
سوالات متداول
آیا CDN برای سایتهای کوچک هم لازم است؟ برای سایتهای کوچک با مخاطبان محلی، CDN ضروری نیست اما میتواند مزایای امنیتی و سرعتی خوبی ارائه دهد. با پلنهای رایگان مثل Cloudflare، امتحان کردن آن هیچ هزینهای ندارد.
آیا CDN باعث میشود سایت همیشه در دسترس باشد؟ CDN به شدت در برابر حملات DDoS مقاومت میکند و اگر سرور اصلی شما برای لحظاتی از دسترس خارج شود، میتواند محتوای کش شده را تحویل دهد. اما اگر سرور اصلی برای مدت طولانی قطع باشد و محتوای پویا نیاز به دیتابیس داشته باشد، سایت با مشکل مواجه میشود.
آیا CDN روی سئو تاثیر مثبت دارد؟ بله. سرعت بارگذاری یکی از فاکتورهای رسمی رتبهبندی گوگل است و CDN با کاهش Latency و بهبود Core Web Vitals، میتواند به بهبود رتبه سایت کمک کند. همچنین SSL رایگان و محافظت در برابر حملات از دیگر مزایای غیرمستقیم آن برای سئو هستند.
آیا استفاده از CDN باعث میشود کاربران IP ما را نبینند؟ بله، وقتی از CDN استفاده میکنید، کاربران با IP سرورهای لبه CDN ارتباط برقرار میکنند و IP واقعی سرور اصلی شما پنهان میشود. این موضوع یک لایه امنیتی اضافی ایجاد میکند.
سیستم DNS چیست؟
سیستم نام دامنه یا DNS، دقیقاً برای حل همین مشکل طراحی شد. به زبان ساده، DNS دفترچه تلفن اینترنت است.
همانطور که شما شماره تلفن دوستتان را حفظ نیستید و نام او را در دفترچه تلفن جستجو میکنید تا شمارهاش را پیدا کنید، کامپیوتر شما هم نام دامنه (مثل codoloper.com) را به DNS میدهد تا آدرس IP معادل آن را دریافت کند.
این سیستم یک پایگاه داده عظیم، توزیعشده و سلسلهمراتبی در سراسر جهان است. هیچ سرور مرکزی و واحدی وجود ندارد که همه اطلاعات را نگه دارد. این توزیعشدگی باعث میشود اینترنت هرگز به دلیل خرابی یک سرور متوقف نشود.
DNS چگونه کار میکند؟
وقتی آدرسی را در مرورگر وارد میکنید، یک فرآیند چندمرحلهای و بسیار سریع آغاز میشود. بیایید این مسیر را قدم به قدم بررسی کنیم.
مراحل یک جستجوی DNS
۱. بررسی حافظه نهان (Cache): قبل از هر چیز، مرورگر و سیستم عامل شما بررسی میکنند که آیا آدرس IP این دامنه را قبلاً ذخیره کردهاند یا خیر. اگر پیدا شد، کار در همان لحظه تمام میشود.
۲. ارسال درخواست به Resolver: اگر در کش محلی نبود، درخواست به یک سرور DNS Resolver (معمولاً متعلق به ارائهدهنده اینترنت شما یا ISP) ارسال میشود. این سرور وظیفه دارد کل فرآیند جستجو را برای شما انجام دهد.
۳. مراجعه به سرور ریشه (Root Server): اگر Resolver هم آدرس را نداشت، به یکی از سرورهای ریشه در جهان مراجعه میکند. سرور ریشه آدرس را نمیداند، اما میداند که برای پیدا کردن پسوند com. باید به کجا مراجعه کند.
۴. مراجعه به سرور TLD: Resolver حالا به سرور TLD (Top-Level Domain) مخصوص com. میرود. سرور TLD هم آدرس دقیق را نمیداند، اما آدرس سرور مرجعی که مسئول دامنه شماست را در اختیار Resolver قرار میدهد.
۵. مراجعه به سرور مرجع (Authoritative Server): در مرحله آخر، Resolver به سرور مرجع دامنه شما متصل میشود. این سرور دقیقاً میداند که IP معادل نام دامنه شما چیست. آن را به Resolver برمیگرداند.
۶. ذخیره و تحویل: Resolver آدرس IP را در حافظه کش خود ذخیره میکند (برای درخواستهای بعدی) و آن را به مرورگر شما تحویل میدهد.
بعد از DNS: وقتی آدرس را پیدا کردیم
بسیاری از افراد فکر میکنند با پیدا شدن IP، کار تمام است. اما DNS فقط خط شروع مسابقه است. بعد از اینکه مرورگر IP سرور مقصد را به دست آورد، اتفاقات زیر رخ میدهد:
۱. ارتباط TCP: مرورگر یک اتصال TCP با سرور مقصد برقرار میکند تا از رسیدن صحیح دادهها اطمینان حاصل کند. ۲. دستدهی امنیتی (TLS/SSL): اگر سایت از پروتکل HTTPS استفاده کند، یک رمزنگاری امن بین مرورگر و سرور برقرار میشود. ۳. ارسال درخواست HTTP: مرورگر درخواست صفحه وب را به سرور میفرستد. ۴. دریافت پاسخ: سرور فایلهای HTML، CSS و جاوا اسکریپت را برمیگرداند و مرورگر آنها را رندر میکند.
بنابراین، DNS فقط وظیفه ترجمه نام به عدد را دارد و بقیه مسیر بر عهده پروتکلهای دیگر شبکه است.
مقایسه DNS سنتی با روشهای مدرن
DNS سنتی که در پورت ۵۳ کار میکند، سالهاست استفاده میشود. اما یک مشکل بزرگ دارد: ترافیک آن رمزنگاری نشده و به صورت متن ساده (Plain Text) ارسال میشود. این موضوع باعث مشکلات امنیتی و حریم خصوصی میشود.
به همین دلیل، پروتکلهای جدیدی معرفی شدهاند:
DoT (DNS over TLS): ترافیک DNS را از طریق پروتکل امن TLS رمزنگاری میکند.
DoH (DNS over HTTPS): ترافیک DNS را درون ترافیک HTTPS پنهان میکند. این روش باعث میشود ارائهدهندگان اینترنت نتوانند به راحتی درخواستهای DNS شما را رصد یا مسدود کنند.
در حالی که DNS سنتی سرعت بالاتری دارد (به دلیل سربار کمتر)، روشهای مدرن مثل DoH امنیت و حریم خصوصی بسیار بهتری را ارائه میدهند.
مزایا و چالشهای سیستم DNS
مزایا:
سهولت استفاده: نیاز به حفظ کردن اعداد پیچیده را از بین میبرد.
مقیاسپذیری: به دلیل ساختار توزیعشده، با رشد اینترنت هیچ مشکلی پیدا نمیکند.
انعطافپذیری: میتوانید IP سرور خود را تغییر دهید بدون اینکه کاربران نیاز باشد آدرس دامنه را تغییر دهند.
چالشها:
آسیبپذیری امنیتی: حملاتی مانند DNS Spoofing یا DNS Cache Poisoning میتوانند کاربران را به سایتهای جعلی هدایت کنند.
تاخیر (Latency): اگر سرور DNS Resolver کند باشد یا کش خالی باشد، چند میلیثانیه تاخیر در بارگذاری سایت ایجاد میشود.
حملات DDoS: سرورهای ریشه و TLD همواره در معرض حملات منع سرویس هستند.
جمعبندی نهایی و نگاه شخصی
به عنوان یک توسعهدهنده، همیشه وقتی به معماری DNS فکر میکنم، شگفتزده میشوم. ما هر روز دهها بار از آن استفاده میکنیم، اما تا زمانی که خراب نشود، متوجه حضورش نمیشویم. DNS یک شاهکار مهندسی است که توانست هرج و مرج روزهای اولیه اینترنت را به یک سیستم منظم و جهانی تبدیل کند.
صادقانه بگویم، اگرچه پروتکلهای جدید مثل DoH امنیت را بالا بردهاند، اما هنوز هم پیچیدگیهای مدیریت DNS برای کسبوکارهای بزرگ چالشبرانگیز است. با این حال، درک عمیق از نحوه کار DNS، نه تنها دید شما را به شبکه باز میکند، بلکه در عیبیابی مشکلات شبکه و سرور به شدت به شما کمک خواهد کرد. امیدوارم این مقاله توانسته باشد تصویر روشنی از این سیستم حیاتی در ذهن شما ایجاد کند.
سوالات متداول
چرا باید DNS پیشفرض خود را به 8.8.8.8 یا 1.1.1.1 تغییر دهیم؟ تغییر DNS به سرورهای عمومی مثل گوگل (8.8.8.8) یا کلادفلر (1.1.1.1) معمولاً به دو دلیل انجام میشود: اول، افزایش سرعت به دلیل زیرساخت قویتر این شرکتها، و دوم، دور زدن محدودیتها و جلوگیری از ردیابی درخواستهای DNS توسط ارائهدهنده اینترنت محلی.
منظور از DNS Propagation یا انتشار DNS چیست؟ وقتی رکوردهای DNS یک دامنه را تغییر میدهید، این تغییر بلافاصله در تمام سرورهای جهان اعمال نمیشود. سرورهای مختلف در سراسر جهان بر اساس زمان TTL (Time to Live) کش خود را آپدیت میکنند. این پروسه که به آن انتشار DNS میگویند، ممکن است از چند دقیقه تا ۴۸ ساعت طول بکشد.
آیا DNSSEC همان رمزنگاری DNS است؟ خیر. DNSSEC (Domain Name System Security Extensions) برای رمزنگاری ترافیک طراحی نشده است، بلکه برای امضای دیجیتال دادههای DNS استفاده میشود. DNSSEC تضمین میکند که آدرس IP دریافتی واقعاً متعلق به همان دامنه است و در مسیر دستکاری نشده است. برای رمزنگاری خود ترافیک باید از DoH یا DoT استفاده کنید.
چرا ریورس پراکسی اینقدر مهم است؟
در یک محیط پروداکشن واقعی، ریورس پراکسی وظایف زیادی به عهده میگیرد:
SSL Termination: مدیریت گواهینامه HTTPS در یک جا متمرکز میشود. سرویسهای پشتی میتوانند بدون SSL روی شبکه داخلی کار کنند.
Load Balancing: ترافیک بین چند نمونه از یک سرویس تقسیم میشود تا هیچ کدام اشباع نشوند.
Caching: پاسخهای تکراری ذخیره میشوند تا سرور اصلی بار کمتری داشته باشد.
Static File Serving: فایلهای CSS، JS و تصاویر مستقیم از ریورس پراکسی سرو میشوند بدون اینکه سرور برنامه اصلی درگیر شود.
Security: آدرس و پورت سرورهای اصلی از کاربر پنهان میماند. میتوان rate limiting و فیلتر IP را در این لایه اعمال کرد.
Logging: تمام درخواستها در یک نقطه لاگ میشوند.
چرا Nginx؟
Nginx (تلفظ: انجین-ایکس) یک وبسرور با کارایی بالاست که به عنوان ریورس پراکسی هم استفاده میشود. در سال ۲۰۰۴ توسط Igor Sysoev ساخته شد و امروز بیش از ۳۴ درصد از وبسرورهای دنیا از آن استفاده میکنند.
دلیل محبوبیتش ساختار event-driven آن است. برخلاف Apache که برای هر درخواست یک thread جدید باز میکند، Nginx میتواند هزاران اتصال همزمان را با منابع کم مدیریت کند.
گزینههای دیگری مثل Apache، Caddy، Traefik و HAProxy هم وجود دارند که هرکدام کاربرد خودشان را دارند. اما Nginx به دلیل پرفورمنس بالا، مستندات غنی و جامعه بزرگ، رایجترین انتخاب برای محیطهای پروداکشن است.
راهاندازی عملی: از صفر تا پروداکشن
محیط ما
فرض میکنیم یک سرور Ubuntu 22.04 داریم و میخواهیم:
یک اپ Node.js روی پورت 3000 داریم
یک اپ Python/FastAPI روی پورت 8000 داریم
دامنه example.com را داریم
میخواهیم SSL داشته باشیم
میخواهیم استاتیک فایلها بهینه سرو شوند
مرحله اول: نصب Nginx
sudo apt update
sudo apt install nginx -y
# بررسی وضعیت سرویس
sudo systemctl status nginx
# اگر اجرا نیست:
sudo systemctl start nginx
sudo systemctl enable nginx
بعد از نصب، با رفتن به آدرس IP سرور باید صفحه پیشفرض Nginx را ببینید.
مرحله دوم: آشنایی با ساختار فایلهای Nginx
/etc/nginx/
├── nginx.conf ← فایل اصلی تنظیمات
├── sites-available/ ← تنظیمات سایتها (غیرفعال)
├── sites-enabled/ ← تنظیمات فعال (لینک به sites-available)
├── conf.d/ ← فایلهای تنظیمات اضافی
└── snippets/ ← قطعههای قابل استفاده مجدد
فلسفه کار این است که تنظیمات را در sites-available مینویسید و با یک symlink در sites-enabled فعالشان میکنید. این باعث میشود بتوانید سایتها را راحت فعال و غیرفعال کنید.
مرحله سوم: اولین تنظیم ریورس پراکسی
یک فایل تنظیمات برای اپ Node.js بسازیم:
sudo nano /etc/nginx/sites-available/myapp
server {
listen80;
server_name example.com www.example.com;
# لاگهای اختصاصی این سایتaccess_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log;
location / {
proxy_pass http://localhost:3000;
proxy_http_version1.1;
# هدرهای ضروری برای ریورس پراکسیproxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# برای WebSocket اگر استفاده میکنیدproxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
}
حالا آن را فعال میکنیم:
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
# بررسی صحت تنظیمات - این دستور را قبل از هر reload اجرا کنید
sudo nginx -t
# اعمال تغییرات
sudo systemctl reload nginx
چرا آن هدرها ضروری هستند؟
proxy_set_header Host $host: بدون این، سرور اصلی نمیداند درخواست برای کدام دامنه آمده.
X-Real-IP و X-Forwarded-For: چون ریورس پراکسی جلوی سرور است، IP کلاینت در سرور اصلی به درستی نمایش داده نشده است. این هدرها IP واقعی کاربر را انتقال میدهند.
X-Forwarded-Proto: به سرور اصلی میگوید کلاینت با HTTP آمده یا HTTPS. بدون این، اگر HTTPS داشته باشید و redirect تولید کنید، ممکن است loop ایجاد شود.
مرحله چهارم: مدیریت چند سرویس
حالا فرض کنید میخواهید هم اپ Node.js و هم اپ Python داشته باشید، هر کدام زیر یک مسیر مختلف:
یک نکته مهم: توجه به slash آخر در proxy_pass http://localhost:8000/ مهم است. وقتی slash دارد، مسیر /api/users به /users تبدیل میشود. اگر slash نباشد، به /api/users ارسال میشود.
تجدید خودکار گواهینامه هم از قبل تنظیم شده. میتوانید با این دستور بررسی کنید:
sudo certbot renew --dry-run
تنظیمات پروداکشن که اغلب نادیده گرفته میشوند
بهینهسازی فایل اصلی nginx.conf
# /etc/nginx/nginx.confuser www-data;
# معمولاً برابر با تعداد هستههای CPUworker_processes auto;
events {
# حداکثر اتصال همزمان هر workerworker_connections1024;
useepoll;
multi_accepton;
}
http {
# پنهان کردن نسخه Nginx در هدرهاserver_tokensoff;
# بهینهسازی ارسال فایلsendfileon;
tcp_nopushon;
tcp_nodelayon;
# فشردهسازی پاسخهاgzipon;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length1000;
gzip_varyon;
# timeoutهای مناسبkeepalive_timeout65;
client_max_body_size10M;
# هدرهای امنیتیadd_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Rate Limiting
جلوگیری از درخواستهای بیش از حد یک کلاینت:
http {
# تعریف zone برای rate limiting# zone=api: اسم zone# 10m: ۱۰ مگابایت حافظه برای ذخیره IPها# rate=10r/s: حداکثر ۱۰ درخواست در ثانیهlimit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:3000;
}
location /login {
limit_req zone=login burst=3;
proxy_pass http://localhost:3000;
}
}
}
burst=20 یعنی اگر ناگهان ۲۰ درخواست آمد قبول میشود، اما بعدش باید به نرخ عادی برگردد.
Load Balancing واقعی
http {
# تعریف upstream با چند سرورupstream nodejs_backend {
# روش پیشفرض: round robinserver localhost:3000;
server localhost:3001;
server localhost:3002;
# keepalive برای بهبود پرفورمنسkeepalive32;
}
# یا با وزندهی (اگر یک سرور قویتر است)upstream weighted_backend {
server server1.internal weight=3;
server server2.internal weight=1;
}
# ip_hash: هر کاربر همیشه به یک سرور میرود (مفید برای session)upstream sticky_backend {
ip_hash;
server server1.internal;
server server2.internal;
}
server {
location / {
proxy_pass http://nodejs_backend;
proxy_http_version1.1;
proxy_set_header Connection "";
}
}
}
Caching برای API
http {
# تعریف cache zoneproxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
max_size=1g inactive=60m use_temp_path=off;
server {
location /api/public/ {
proxy_cache api_cache;
proxy_cache_valid20010m;
proxy_cache_valid4041m;
# هدری که نشان میدهد پاسخ از cache آمده یا نهadd_header X-Cache-Status $upstream_cache_status;
proxy_pass http://localhost:3000;
}
}
}
یک سناریوی کامل پروداکشن
بگذارید همه چیز را در یک تنظیم واقعی کنار هم بگذاریم. فرض میکنیم یک استارتاپ داریم با:
# بررسی صحت تنظیمات
sudo nginx -t
# reload بدون قطع اتصالات فعال
sudo systemctl reload nginx
# بررسی لاگهای خطا به صورت real-time
sudo tail -f /var/log/nginx/error.log
# بررسی اتصالات فعال
sudo nginx -s status
# نمایش تنظیمات کامل بعد از include
sudo nginx -T
# بررسی اینکه کدام فایل تنظیمات اعمال شده
sudo nginx -T | grep server_name
خطاهای رایج
502 Bad Gateway: سرور اصلی در دسترس نیست یا crash کرده. ابتدا بررسی کنید آیا سرویس روی پورت مورد نظر اجرا است:
504 Gateway Timeout: درخواست خیلی طول کشیده. proxy_read_timeout را افزایش دهید یا مشکل سرور اصلی را پیدا کنید.
413 Request Entity Too Large:client_max_body_size را افزایش دهید.
WebSocket کار نمیکند: هدرهای Upgrade و Connection را بررسی کنید.
Nginx در برابر رقبا
Nginx در برابر Apache
Apache مدل process-based دارد و برای هر اتصال یک thread باز میکند. این در ترافیک بالا منابع زیادی مصرف میکند. Nginx با مدل event-driven چندین هزار اتصال را با یک worker process مدیریت میکند. اما Apache مزیتهایی هم دارد: .htaccess که اجازه میدهد تنظیمات در سطح دایرکتوری تعریف شوند، و پشتیبانی بهتر از برخی فناوریهای قدیمی مثل mod_php.
Nginx در برابر Caddy
Caddy یک رقیب جدیدتر است که SSL را به صورت خودکار مدیریت میکند. تنظیمات آن سادهتر است اما کانفیگ Nginx انعطاف و کنترل بیشتری میدهد. برای تیمهای کوچک که میخواهند سریع راه بیافتند، Caddy گزینه خوبی است.
Nginx در برابر Traefik
Traefik برای محیطهای container-native (Kubernetes، Docker Swarm) طراحی شده و کشف خودکار سرویسها را پشتیبانی میکند. اگر با Kubernetes کار میکنید، Traefik یا ingress-nginx انتخابهای بهتری هستند.
مزایا و معایب این رویکرد
مزایا
ریورس پراکسی لایهای از انتزاع بین کاربر و زیرساخت ایجاد میکند. میتوانید سرورهای پشتی را بدون اطلاع کاربر تغییر دهید. SSL در یک جا مدیریت میشود. پرفورمنس بهتری برای فایلهای استاتیک دارید. امنیت بیشتری دارید چون سرورهای اصلی مستقیم در معرض اینترنت نیستند.
معایب
یک لایه پیچیدگی اضافه میشود. دیباگ کردن مشکلات شبکه کمی سختتر میشود چون باید مطمئن شوید مشکل از Nginx است یا سرویس پشتی. اگر Nginx crash کند، همه چیز از کار میافتد (که با high availability میتوان حل کرد).
جمعبندی
ریورس پراکسی دیگر یک گزینه اختیاری نیست. هر اپلیکیشنی که میخواهد به شکل جدی در پروداکشن باشد، به آن نیاز دارد.
Nginx به خاطر پرفورمنس، انعطاف و جامعه بزرگ استانداردترین انتخاب برای این کار است. تنظیمهایی که در این مقاله دیدیم، پایهای هستند که در اکثر محیطهای واقعی استفاده میشوند.
نظر شخصی: اگر تازه شروع میکنید، با یک تنظیم ساده شروع کنید و کمکم گسترش دهید. سعی نکنید از ابتدا همه چیز را کامل کنید. اما حتماً از همان ابتدا SSL داشته باشید و هدرهای پراکسی را درست تنظیم کنید، این دو مورد را نمیتوانید بعداً راحت اضافه کنید بدون اینکه کارها خراب شود.
سوالات متداول
آیا میتوانم بدون Nginx هم اپلیکیشن را deploy کنم؟ بله، میتوانید اپلیکیشن را مستقیم روی پورت 80 یا 443 اجرا کنید. اما در عمل این کار مشکلاتی دارد: SSL مدیریت کردن سختتر است، نمیتوانید چند سرویس روی یک IP داشته باشید، و پرفورمنس سرو فایلهای استاتیک کمتر است.
تفاوت proxy_pass http://localhost:3000 و proxy_pass http://localhost:3000/ چیست؟ وقتی slash آخر هست، مسیر از URL حذف میشود. مثلاً location /api/ با proxy_pass http://localhost:3000/ یعنی /api/users به /users تبدیل میشود. بدون slash، مسیر کامل منتقل میشود.
چطور بفهمم Nginx درست کانفیگ شده؟ با دستور sudo nginx -t صحت syntax را بررسی کنید. بعد با ابزاری مثل curl -v هدرهای پاسخ را بررسی کنید و ببینید هدر Server چه میگوید و هدرهایی که تنظیم کردهاید وجود دارند یا نه.
آیا Nginx برای WebSocket مناسب است؟ بله، اما باید هدرهای Upgrade و Connection را تنظیم کنید و proxy_read_timeout را بالا ببرید چون اتصالات WebSocket طولانیمدت هستند.
چطور یک سرور Nginx را بدون downtime ریستارت کنم؟ از sudo systemctl reload nginx یا sudo nginx -s reload استفاده کنید. این دستورها کانفیگ جدید را بارگذاری میکنند بدون اینکه اتصالات فعال قطع شوند.
آیا Nginx میتواند جایگزین یک load balancer سختافزاری شود؟ برای اکثر پروژهها بله. Nginx در محیطهایی با صدها هزار درخواست در ثانیه هم به خوبی کار میکند. تنها در مقیاسهای بسیار بزرگ (میلیونها درخواست در ثانیه) ممکن است به راهحلهای تخصصیتر نیاز باشد.
مشاهده وضعیت کانتینرهای در حال اجرا
خواندن لاگهای هر سرویس مستقیم از مرورگر
ورود به Shell کانتینر بدون نیاز به exec -it
ساخت، حذف و ریاستارت کانتینرها
مدیریت Volume ها و Network ها
اجرای Docker Compose Stack ها
مدیریت چندین سرور Docker از یک پنل واحد
مدیریت Image ها و پاکسازی فضا
رایگان است یا پولی؟
Portainer Community Edition (CE) کاملاً رایگان و متنباز است و برای اکثر توسعهدهندگان بیش از کافی است.
نسخه
هزینه
مناسب برای
Community Edition (CE)
رایگان
توسعهدهندگان، VPS، پروژههای شخصی
Business Edition (BE)
رایگان تا ۳ Node، بعد پولی
تیمها و شرکتها با نیاز به RBAC، SSO، GitOps
اگر میخواهی کانتینر اجرا کنی، لاگ بخوانی، Docker Compose مدیریت کنی و اپلیکیشن Deploy کنی، نسخه CE کاملاً کافی است.
نصب روی VPS در کمتر از یک دقیقه
اگر Docker روی سرورت نصب است، با همین دستورات Portainer را راهاندازی کن:
نکته امنیتی: هرگز Portainer را مستقیم روی پورت عمومی اجرا نکن. حتماً پشت Nginx با HTTPS قرار بده و پورت ۹۴۴۳ را از Firewall مسدود کن.
برای چه کسانی مناسب است؟
توسعهدهندگانی که VPS یا سرور شخصی دارند
کسانی که سرویسهایی مثل PostgreSQL، Redis، MinIO یا Grafana را Self-host میکنند
تازهکارهایی که دارند Docker یاد میگیرند و میخواهند وضعیت کانتینرها را بصری ببینند
فریلنسرها و تیمهای کوچکی که باید چند پروژه روی یک سرور مدیریت کنند
Portainer جایگزین CLI میشود؟
نه، و نباید باشد. Portainer یک ابزار مکمل است، نه جایگزین. برای مدیریت روزمره و مانیتورینگ عالی است، اما برای کارهای پیشرفتهتر مثل دیباگ مشکلات شبکه، نوشتن Compose فایلهای پیچیده یا Scripting، همچنان به CLI نیاز داری.
توصیه: هر دو را یاد بگیر. CLI برای تسلط واقعی ضروری است، Portainer برای مدیریت راحتتر روزانه.
جمعبندی
Portainer یک ابزار رایگان، سریع و کاربرپسند برای مدیریت Docker است. اگر سرور یا VPS داری، نصب Portainer یکی از اولین کارهایی است که باید انجام دهی. در کمتر از یک دقیقه نصب میشود و از همان لحظه مدیریت کانتینرهایت را متحول میکند.
{
"name": "Ali",
"age": 30,
"married": true
}
۲. آرایه (array)
یک فهرست مرتب از مقادیر که بین [ ] قرار گرفتهاند.
["apple", "banana", "cherry"]
نوعهای داده در JSON
یک ارزش (value) در JSON میتواند یکی از موارد زیر باشد:
رشته (string): متنی بین "..." با یونیکد و پشتیبانی escape.
عدد (number): مشابه C یا Java، فقط به شکل اعشاری.
بولین (boolean):true یا false.
تهی (null): نشاندهنده نبود مقدار.
شیء یا آرایه: میتوانند بهصورت تو در تو باشند.
فضای خالی (whitespace) بین نشانهها مجاز است و تأثیری بر ساختار ندارد.
قواعد نگارش و سینتکس (Syntax)
یک object با { (آکولاد باز) شروع، و با } (آکولاد بسته) تمام میشود. در انتهای هر نام یک : (دو نقطه) قرار میگیرد و زوجهای نام/ارزش با , (ویرگول) جدا میشوند.
{ "a": 1, "b": 2 }
یک array با [ (براکت باز) شروع، و با ] (براکت بسته) تمام میشود. ارزشها توسط , (ویرگول) از هم جدا میشوند.