کش کردن API قیمت ارز با Redis؛ سایت سریعتر و مصرف سهمیهٔ کمتر
کش کردن API قیمت ارز با Redis: TTL مناسب، الگوی cache-aside، جلوگیری از هجوم همزمان (stampede)، نگه داشتن آخرین مقدار معتبر و کد در Node.js، PHP و پایتون.

- نرخها را با کلید ثابت و TTL برابر تازگی پلن (مثلاً ۶۰ ثانیه) در Redis نگه دارید.
- یک کلید جدا بدون انقضا برای «آخرین مقدار معتبر» بسازید تا در قطعی API صفحه خالی نشود.
- با قفل کوتاه یا تازهسازی پسزمینه جلوی هجوم همزمان درخواستها هنگام انقضای کش را بگیرید.
فرض کنید سایت شما روزی ۵۰ هزار بازدید دارد و قیمت دلار در هدر همهٔ صفحهها نمایش داده میشود. اگر هر بازدید یک درخواست به API بفرستد، روزی ۵۰ هزار درخواست ثبت میشود؛ در حالی که نرخها فقط هر دقیقه عوض میشوند و ۱۴۴۰ درخواست کافی بود. کش کردن API قیمت ارز این شکاف را میبندد و Redis رایجترین ابزار آن است.
SET rates:all <json> EX 60 ذخیره کنید و هر صفحه اول از Redis بخواند. کلید دوم rates:last را بدون انقضا نگه دارید و در قطعی API از آن استفاده کنید. TTL را برابر meta.freshness_sec پلن خود بگذارید.این آموزش بخشی از راهنمای کامل API قیمت ارز و طلا است. اگر Redis ندارید، کش فایلی در آموزش PHP و لاراول هم برای سایتهای کوچک کافی است.
چرا Redis؟
| روش کش | سرعت خواندن | اشتراک بین چند سرور | مناسب برای |
|---|---|---|---|
| حافظهٔ برنامه (متغیر) | بسیار سریع | ندارد | یک پروسهٔ Node.js یا ربات |
| فایل | سریع | ندارد | هاست اشتراکی، سایت کوچک |
| Redis | بسیار سریع | دارد | سایت پربازدید، چند سرور، صف |
| پایگاه داده | متوسط | دارد | وقتی تاریخچه هم ذخیره میکنید |
الگوی پایه: cache-aside
منطق ساده است: اول از کش بخوان؛ اگر نبود از API بگیر و در کش بنویس. نسخهٔ Node.js با کتابخانهٔ ioredis:
import Redis from "ioredis"
const redis = new Redis(process.env.REDIS_URL)
const TTL = 60
export async function getRates() {
const cached = await redis.get("rates:all")
if (cached) return JSON.parse(cached)
try {
const r = await fetch("https://api.ezrate.me/v1/rates?category=fiat", {
headers: { "X-API-Key": process.env.EZRATE_KEY }, signal: AbortSignal.timeout(8000) })
if (!r.ok) throw new Error(String(r.status))
const data = (await r.json()).data
await redis.multi()
.set("rates:all", JSON.stringify(data), "EX", TTL)
.set("rates:last", JSON.stringify(data)) // آخرین مقدار معتبر، بدون انقضا
.exec()
return data
} catch {
const last = await redis.get("rates:last")
return last ? JSON.parse(last) : []
}
}مشکل هجوم همزمان (Cache Stampede)
در لحظهای که کش منقضی میشود، اگر صد درخواست همزمان برسد، هر صد تا کش را خالی میبینند و صد درخواست به API میفرستند. در سایتهای پربازدید این اتفاق هر دقیقه تکرار میشود و ممکن است به خطای 429 برسد. دو راه حل رایج:
راه حل ۱: قفل کوتاه
const got = await redis.set("rates:lock", "1", "NX", "EX", 10)
if (!got) {
// کس دیگری در حال تازهسازی است: آخرین مقدار را برگردان
return JSON.parse((await redis.get("rates:last")) ?? "[]")
}
// … دریافت از API و نوشتن در کش، سپس:
await redis.del("rates:lock")راه حل ۲: تازهسازی پسزمینه (پیشنهادی)
بهترین روش این است که صفحهها هرگز API را صدا نزنند. یک کار زمانبندیشده هر دقیقه کش را تازه میکند و صفحهها فقط از Redis میخوانند. اینطور تعداد درخواستها دقیقاً قابل پیشبینی است و هیچ کاربری منتظر API نمیماند.

همین الگو در PHP و پایتون
<?php
// PHP با افزونهٔ phpredis
$redis = new Redis();
$redis->connect('127.0.0.1');
$json = $redis->get('rates:all');
if ($json === false) {
$json = fetch_from_api(); // تابع آموزش PHP
$redis->setex('rates:all', 60, $json);
$redis->set('rates:last', $json);
}
$rates = json_decode($json, true);# پایتون با redis-py
import json, redis
r = redis.Redis()
data = r.get("rates:all")
if data is None:
data = json.dumps(fetch_rates("usd,eur,g18")) # تابع آموزش پایتون
r.set("rates:all", data, ex=60)
r.set("rates:last", data)
rates = json.loads(data)TTL مناسب چقدر است؟
TTL کمتر از تازگی پلن شما فایدهای ندارد؛ مقدار آن در فیلد meta.freshness_sec هر پاسخ آمده است. برای صفحههایی که فقط نمایش میدهند، ۶۰ ثانیه تعادل خوبی است. برای گزارشهای روزانه یا قیمتگذاری کالا، کش چند دقیقهای هم کاملاً مناسب است.
rates:last استفاده میکنید، این زمان به کاربر میگوید عدد ممکن است چند دقیقه قدیمی باشد.برای مدیریت خطاهای API هنگام تازهسازی کش، مدیریت خطا و Rate Limit در APIهای مالی را ببینید.
نامگذاری کلیدها
از همان ابتدا برای کلیدهای Redis یک قرارداد نامگذاری بگذارید تا بعداً قاطی نشوند. پیشنهاد ما: ezrate:{endpoint}:{params}. مثلاً ezrate:rates:fiat:IRT برای نرخ ارزها به تومان و ezrate:rates:gold:usd برای طلا بر حسب دلار. اگر پارامترها زیادند، از هش آنها استفاده کنید. نسخه را هم در نام بگذارید (ezrate:v1:…) تا اگر ساختار ذخیره را عوض کردید، کلیدهای قدیمی مزاحم نشوند.
مانیتورینگ کش
دو عدد را زیر نظر داشته باشید: نسبت برخورد به کش (hit ratio) و تعداد درخواست واقعی به API. اولی را با شمارندههای ساده در کد (INCR stats:hit و INCR stats:miss) و دومی را با مسیر GET /usage خود API میبینید؛ این مسیر از سهمیه کم نمیشود. اگر hit ratio زیر ۹۵ درصد است، احتمالاً TTL کوتاه است یا کلیدها بیش از حد متنوعاند.
هاست اشتراکی بدون Redis
روی بیشتر هاستهای اشتراکی Redis در دسترس نیست. خبر خوب این است که برای یک سایت با یک سرور، کش فایلی یا APCu کاملاً کافی است و تفاوت سرعتش برای این حجم داده محسوس نیست. Redis وقتی ضروری میشود که چند سرور یا چند پروسه باید کش مشترک ببینند.
کش چند لایه
در سایتهای خیلی پربازدید، کش چندلایه بهترین نتیجه را میدهد: Redis برای نرخها با TTL شصت ثانیه، کش صفحهٔ کامل یا CDN با TTL کوتاهتر برای صفحههایی که نرخ را نمایش میدهند، و کش مرورگر برای مسیر داخلی نرخها (Cache-Control: max-age=30). هر لایه بار لایهٔ پشتی را کم میکند و API تنها یک درخواست در دقیقه میبیند.

نرخهای لحظهای مرتبط با این مطلب: قیمت دلار، قیمت طلای ۱۸ عیار، قیمت سکه امامی.

نظرات (۰)
نظرها پس از تأیید نمایش داده میشوند؛ برای ارسال، ورود به حساب لازم است.


