Redis چیست؟ آموزش کامل Cache، Queue، Session و Redis در Python
Redis یک دیتاستور سریع In-memory برای Cache، Session، Queue و پردازش رویداد است. در این آموزش، مفاهیم اصلی Redis و ساخت یک API خلاصهسازی مجهز به Cache با Python، FastAPI و درواره را یاد میگیرید.
هنگامی که تعداد کاربران و درخواستهای یک نرمافزار افزایش پیدا میکند، مراجعه مداوم به Database یا سرویسهای بیرونی میتواند سرعت برنامه را کاهش دهد و هزینه زیرساخت را افزایش دهد.
فرض کنید یک API هوش مصنوعی ساختهاید که کاربران متنهای مشابهی را برای خلاصهسازی ارسال میکنند. اگر برای هر درخواست تکراری دوباره مدل هوش مصنوعی فراخوانی شود، زمان پاسخ و هزینه مصرف API افزایش پیدا میکند.
یک راهکار این است که نتیجه درخواست قبلی را برای مدت مشخصی نگه داریم. اگر همان ورودی دوباره دریافت شد، پاسخ مستقیماً از Cache برگردانده شود.
Redis یکی از ابزارهای شناختهشده برای پیادهسازی چنین معماریهایی است. با این حال، کاربرد Redis فقط به Cache محدود نمیشود. از Redis میتوان برای Session، Counter، Rate Limit، Queue، Pub/Sub، Streams، Leaderboard و ذخیره دادههای موقت استفاده کرد.
در این مقاله یاد میگیرید:
- Redis یا ردیس چیست
- چرا Redis سرعت بالایی دارد
- Redis چه تفاوتی با Database معمولی دارد
- مهمترین Data Typeهای Redis چه هستند
- TTL و Expiration چگونه کار میکنند
- الگوهای مختلف Cache کداماند
- Redis چگونه برای Session و Rate Limit استفاده میشود
- تفاوت List، Pub/Sub و Redis Streams چیست
- چگونه Redis را با Docker نصب کنیم
- چگونه با Python و FastAPI به Redis متصل شویم
- چگونه یک API هوش مصنوعی مجهز به Cache بسازیم
- برای استفاده از Redis در Production چه نکاتی مهماند
Redis چیست؟
Redis یک Data Store سریع است که بخش اصلی دادههای فعال خود را در Memory نگه میدارد. Redis از ساختارهای داده مختلف، عملیات Atomic، Replication، Expiration و چند روش Persistence پشتیبانی میکند.
نام Redis در ابتدا از عبارت Remote Dictionary Server گرفته شده است.
در سادهترین حالت، Redis اطلاعات را به شکل Key و Value نگه میدارد:
Key: user:42:name
Value: Amir
ذخیره یک مقدار:
SET user:42:name "Amir"
دریافت آن:
GET user:42:name
اما Redis فقط یک Key-Value Store ساده نیست. Value میتواند ساختارهای مختلفی داشته باشد:
- String
- Hash
- List
- Set
- Sorted Set
- Stream
- Bitmap
- HyperLogLog
- Geospatial Index
- JSON در پیادهسازیهای پشتیبانیشده
- ساختارهای پیشرفتهتر متناسب با نسخه و قابلیتهای نصبشده
Redis چگونه تلفظ میشود؟
در فارسی معمولاً Redis را «ردیس» مینویسند. هر دو شکل زیر در جستوجوها و متنهای برنامهنویسی استفاده میشوند:
Redis
ردیس
در این مقاله بیشتر از شکل اصلی Redis استفاده میکنیم و در بخشهای فارسی نیز اصطلاح «ردیس» را بهطور طبیعی به کار میبریم.
چرا Redis سریع است؟
یکی از مهمترین دلایل سرعت Redis این است که دادههای فعال را در RAM نگه میدارد. دسترسی به Memory معمولاً بسیار سریعتر از خواندن مکرر اطلاعات از Disk است.
عوامل دیگری نیز در کارایی Redis نقش دارند:
- ساختارهای داده بهینه
- عملیات ساده و هدفمند
- کاهش رفتوبرگشت شبکه با Pipeline
- پشتیبانی از Connectionهای پایدار
- اجرای Atomic بسیاری از Commandها
- نبود سربار Queryهای پیچیده Relational
- Serialization محدود در سطح Server
با این حال، Redis همیشه سریعترین بخش سیستم نیست. Latency شبکه، اندازه Payload، عملکرد Client، تنظیمات Persistence و معماری Deployment نیز روی نتیجه اثر میگذارند.
آیا Redis یک Database است؟
Redis میتواند بهعنوان Database، Cache، Message Broker، Streaming Engine یا Data Structure Store استفاده شود. اما معمولاً نباید آن را جایگزین مستقیم PostgreSQL، MySQL یا سایر Databaseهای اصلی در تمام پروژهها دانست.
Redis برای این نوع دادهها مناسب است:
- دادههای موقت
- Cache پاسخها
- Session
- Counter
- Rate Limit
- صف پردازش
- وضعیت کوتاهمدت Job
- دادههای پرتکرار
- Leaderboard
- Event Stream
Database رابطهای برای این نوع نیازها مناسبتر است:
- دادههای دائمی Business
- روابط پیچیده
- Queryهای تحلیلی
- Constraintهای رابطهای
- گزارشگیری ساختاریافته
- تراکنشهای چندمرحلهای پیچیده
- نگهداری بلندمدت داده اصلی
در بسیاری از معماریها Redis در کنار Database اصلی استفاده میشود:
Client
↓
Application
↓
Redis Cache
↓ Cache Miss
PostgreSQL یا MySQL
در پروژه هوش مصنوعی:
Client
↓
Backend
↓
Redis Cache
↓ Cache Miss
API درواره
↓
مدل هوش مصنوعی
Redis چه کاربردهایی دارد؟
Cache
نتایج پرتکرار برای مدت محدود ذخیره میشوند:
خلاصه متن
نتیجه Query
اطلاعات محصول
تنظیمات عمومی
پاسخ یک API
Session
اطلاعات Session کاربر با TTL نگهداری میشود:
session:8f71c2
Rate Limiting
تعداد درخواست هر کاربر در یک بازه زمانی شمارش میشود:
rate:user_42:2026-08-06T10:30
Queue
Jobها در یک List یا Stream قرار میگیرند و Worker آنها را پردازش میکند.
Pub/Sub
یک Publisher پیام را روی Channel منتشر میکند و Subscriberهای فعال آن را دریافت میکنند.
Counter
عملیات INCR شمارنده را به شکل Atomic افزایش میدهد:
INCR article:120:views
Leaderboard
Sorted Set برای رتبهبندی کاربران بر اساس Score مناسب است.
قفل کوتاهمدت
برای هماهنگی محدود میان چند Process میتوان از الگوهای Lock استفاده کرد، اما پیادهسازی آن باید با دقت و متناسب با سطح اطمینان موردنیاز انجام شود.
ذخیره وضعیت Job
وضعیت پردازشهای غیرهمزمان میتواند در Redis ذخیره شود:
job:981:status = processing
مهمترین Data Typeهای Redis
انتخاب Data Type مناسب روی سادگی و عملکرد برنامه تأثیر مستقیم دارد.
String
String سادهترین Data Type در Redis است. این نوع میتواند متن، عدد، JSON Serializeشده یا داده باینری را نگه دارد.
SET greeting "سلام"
GET greeting
ذخیره با Expiration:
SET cache:article:101 "cached-content" EX 300
این Key پس از ۳۰۰ ثانیه منقضی میشود.
افزایش Counter:
INCR api:request:count
کاربردها:
- Cache
- Token موقت
- Counter
- Feature Flag
- JSON Serializeشده
- وضعیت ساده Job
Hash
Hash مجموعهای از Field و Value را زیر یک Key نگه میدارد.
HSET user:42 name "Amir" plan "pro" status "active"
دریافت یک Field:
HGET user:42 name
دریافت تمام Fieldها:
HGETALL user:42
کاربردها:
- پروفایل کاربر
- تنظیمات
- Metadata
- وضعیت Job
- Objectهای کوچک
نمونه:
user:42
name → Amir
plan → pro
status → active
List
List مجموعهای مرتب از Stringهاست و عملیات درج و حذف از ابتدا یا انتهای فهرست را ارائه میدهد.
افزودن Job:
LPUSH jobs:summary "job_101"
دریافت Job توسط Worker:
BRPOP jobs:summary 0
کاربردها:
- Queue ساده
- Stack
- فهرست آخرین Eventها
- پردازش Producer و Consumer
برای Queueهایی که به Consumer Group، ثبت تحویل یا بازیابی پیام پردازشنشده نیاز دارند، Redis Streams معمولاً قابلیتهای کاملتری ارائه میکند.
Set
Set مجموعهای بدون ترتیب از اعضای یکتا است.
SADD article:101:tags "redis" "python" "api"
بررسی عضویت:
SISMEMBER article:101:tags "redis"
کاربردها:
- اعضای یکتا
- Tag
- Permission
- کاربران Online
- حذف Duplicate
- اشتراک و اجتماع مجموعهها
Sorted Set
Sorted Set اعضای یکتا را همراه Score نگه میدارد.
ZADD leaderboard 1500 "user_42"
ZADD leaderboard 2100 "user_81"
دریافت رتبهبندی نزولی:
ZREVRANGE leaderboard 0 9 WITHSCORES
کاربردها:
- Leaderboard
- رتبهبندی
- صف اولویتدار
- زمانبندی
- داده مرتب بر اساس Timestamp یا Score
Stream
Redis Stream یک ساختار Append-only برای نگهداری Eventهاست.
افزودن Event:
XADD events:orders * type "order.created" order_id "ord_101"
خواندن Eventها:
XREAD COUNT 10 STREAMS events:orders 0
Streams از قابلیتهایی مانند موارد زیر پشتیبانی میکند:
- Consumer Group
- Pending Entries
- Acknowledgement
- بازخوانی پیام
- چند Consumer
- نگهداری Eventها
- شناسه ترتیبی
کاربردها:
- Event Processing
- Queue پایدارتر
- اتصال Producer و Worker
- پردازش Telemetry
- Workflowهای غیرهمزمان
TTL چیست؟
TTL مخفف Time To Live است و نشان میدهد یک Key چه مدت دیگر در Redis باقی میماند.
تنظیم Expiration:
SET verification:email:user_42 "739201" EX 120
این Key پس از ۱۲۰ ثانیه حذف میشود.
مشاهده TTL:
TTL verification:email:user_42
خروجی ممکن است چنین باشد:
85
یعنی ۸۵ ثانیه تا حذف Key باقی مانده است.
خروجیهای خاص:
-1
یعنی Key وجود دارد اما Expiration ندارد.
-2
یعنی Key وجود ندارد.
تنظیم TTL جداگانه:
EXPIRE session:abc123 3600
حذف Expiration:
PERSIST session:abc123
چرا TTL مهم است؟
بدون TTL، Cacheهای قدیمی ممکن است برای همیشه باقی بمانند و Memory را اشغال کنند.
TTL برای این موارد مهم است:
- Cache
- Session
- کد تأیید
- Token موقت
- Rate Limit
- Lock
- نتیجه موقت Job
- Deduplication کوتاهمدت
TTL باید بر اساس ماهیت داده انتخاب شود. یک مقدار ثابت برای تمام Cacheها معمولاً مناسب نیست.
انتخاب TTL مناسب
TTL بسیار کوتاه باعث Cache Miss زیاد میشود. TTL بسیار بلند ممکن است داده قدیمی به کاربر نشان دهد.
| نوع داده | TTL نمونه |
|---|---|
| تنظیمات نسبتاً ثابت | چند دقیقه تا چند ساعت |
| نتیجه Query پرتکرار | چند ثانیه تا چند دقیقه |
| Session | متناسب با سیاست Session |
| OTP | معمولاً چند دقیقه |
| Rate Limit | برابر پنجره محدودیت |
| پاسخ هوش مصنوعی تکراری | متناسب با کاربرد و احتمال تغییر |
| قیمت یا موجودی | کوتاه و همراه Invalidation |
این مقادیر نسخه عمومی و قطعی نیستند. TTL باید بر اساس نرخ تغییر داده، هزینه تولید مجدد و میزان تحمل Stale Data تعیین شود.
Cache چیست؟
Cache یک لایه ذخیرهسازی سریع برای دادههایی است که تولید یا دریافت مجدد آنها هزینه دارد.
مثلاً خلاصهسازی یک متن با مدل هوش مصنوعی ممکن است چند ثانیه زمان و مقداری هزینه مصرف داشته باشد. اگر ورودی، مدل و تنظیمات دقیقاً یکسان باشند، میتوان نتیجه قبلی را برای مدتی از Redis برگرداند.
چرخه Cache:
Request
↓
ساخت Cache Key
↓
بررسی Redis
↓
Cache Hit؟ بله → بازگرداندن پاسخ
↓ خیر
فراخوانی سرویس اصلی
↓
ذخیره نتیجه در Redis
↓
بازگرداندن پاسخ
Cache Hit و Cache Miss
Cache Hit
داده موردنظر در Cache پیدا میشود:
Redis → Response
Cache Miss
داده در Cache نیست و باید از منبع اصلی تولید یا دریافت شود:
Redis Miss → Database یا API → Redis → Response
نرخ Cache Hit یکی از Metricهای مهم سیستم است:
Cache Hit Rate = Cache Hits / Total Cache Lookups
برای مثال:
۸۰۰ Cache Hit از ۱۰۰۰ Lookup
Cache Hit Rate = 80%
برای سازگاری ساده با Ghost میتوان فرمول را به همین صورت متنی نمایش داد.
مهمترین الگوهای Cache
Cache-aside
رایجترین الگو است:
- برنامه Redis را بررسی میکند.
- اگر داده موجود بود، آن را برمیگرداند.
- اگر موجود نبود، منبع اصلی را فراخوانی میکند.
- نتیجه را در Redis ذخیره میکند.
مزایا:
- ساده و قابلکنترل
- فقط دادههای واقعاً مصرفشده Cache میشوند
- وابستگی محدود به Redis
معایب:
- Request اول کندتر است
- احتمال Stale Data وجود دارد
- Cache Invalidation باید مدیریت شود
Write-through
داده هنگام نوشتن همزمان در Database و Cache ثبت میشود.
مزیت:
- Cache معمولاً بهروز است
محدودیت:
- عملیات Write پیچیدهتر و کندتر میشود
- مدیریت شکست یکی از دو ذخیرهسازی اهمیت پیدا میکند
Write-behind
ابتدا داده در Cache نوشته میشود و بعداً به Database منتقل میشود.
این روش میتواند سرعت Write را افزایش دهد، اما ریسک از دست رفتن داده و پیچیدگی هماهنگی بیشتری دارد. برای دادههای مهم بدون طراحی دقیق مناسب نیست.
Refresh-ahead
پیش از منقضی شدن یک Key پرمصرف، سیستم آن را در پسزمینه Refresh میکند.
این روش برای دادههایی که باید سریع پاسخ داده شوند و تولیدشان زمانبر است مفید است.
Cache Key چگونه طراحی میشود؟
Cache Key باید:
- یکتا باشد
- قابل پیشبینی باشد
- نسخه داشته باشد
- تمام ورودیهای مؤثر را در نظر بگیرد
- بیش از حد طولانی نباشد
- اطلاعات محرمانه را مستقیماً نمایش ندهد
نمونه:
ai:summary:v1:model_123:hash_of_input
برای متنهای طولانی بهتر است از Hash استفاده شود:
import hashlib
digest = hashlib.sha256(text.encode("utf-8")).hexdigest()
cache_key = f"ai:summary:v1:{model_id}:{digest}"
اگر پارامتر max_sentences روی نتیجه اثر دارد، باید در Cache Key لحاظ شود:
cache_key = (
f"ai:summary:v1:"
f"{model_id}:"
f"{max_sentences}:"
f"{digest}"
)
در غیر این صورت، دو Request متفاوت ممکن است اشتباهاً یک پاسخ مشترک دریافت کنند.
Cache Invalidation
یکی از مهمترین چالشهای Cache حذف یا Refresh کردن داده قدیمی است.
روشها:
Expiration
Key پس از TTL حذف میشود.
حذف هنگام Update
بعد از تغییر منبع اصلی، Cache مربوط حذف میشود:
DEL product:42
Versioned Key
نسخه قرارداد یا داده در Key قرار میگیرد:
product:v2:42
Event-based Invalidation
وقتی داده تغییر میکند، Event منتشر میشود و Cacheهای مرتبط حذف میشوند.
انتخاب روش به میزان تغییر داده و حساسیت Stale Data بستگی دارد.
Cache Stampede چیست؟
فرض کنید یک Key پرترافیک منقضی شود و همزمان ۱۰۰ Request آن را بخواهند. تمام Requestها Cache Miss میگیرند و به Database یا API اصلی میروند.
این وضعیت Cache Stampede یا Thundering Herd نامیده میشود.
راهکارها:
- Lock کوتاهمدت برای تولیدکننده اول
- نگهداری Stale Data برای مدت محدود
- Refresh-ahead
- TTL Jitter
- Single-flight در سطح برنامه
- محدود کردن Concurrency
- Queue کردن محاسبات سنگین
TTL Jitter یعنی به TTL مقدار کمی تصادفی اضافه شود تا تعداد زیادی Key همزمان منقضی نشوند:
TTL پایه: ۳۶۰۰ ثانیه
Jitter: بین صفر تا ۳۰۰ ثانیه
TTL نهایی: بین ۳۶۰۰ تا ۳۹۰۰ ثانیه
تفاوت Redis List، Pub/Sub و Streams
این سه قابلیت گاهی با یکدیگر اشتباه گرفته میشوند.
| قابلیت | List | Pub/Sub | Streams |
|---|---|---|---|
| نگهداری پیام | بله | معمولاً خیر | بله |
| Consumer Group | محدود | خیر | بله |
| Acknowledgement | بهصورت داخلی کامل نیست | خیر | بله |
| دریافت توسط چند Subscriber | نیازمند طراحی | بله | بله |
| بازیابی پیام ازدسترفته | محدود | خیر | بله |
| مناسب Queue ساده | بله | خیر | بله |
| مناسب اعلان زنده | محدود | بله | بله |
| پیچیدگی | کم | کم | بیشتر |
List
برای Queue ساده با Producer و Worker مناسب است.
Pub/Sub
برای پیامهای زندهای مناسب است که فقط Subscriberهای فعال باید آنها را دریافت کنند. اگر Subscriber هنگام انتشار متصل نباشد، پیام را دریافت نمیکند.
Streams
برای Eventهایی مناسب است که باید نگهداری شوند و Consumer بتواند آنها را پردازش و Acknowledge کند.
Redis Pub/Sub چیست؟
Publisher پیام را روی یک Channel منتشر میکند:
PUBLISH notifications "new_message"
Subscriber به Channel گوش میدهد:
SUBSCRIBE notifications
Pub/Sub برای این موارد مناسب است:
- اعلان زنده
- Refresh تنظیمات
- اطلاعرسانی داخلی
- انتشار Eventهای غیرحساس و قابلچشمپوشی
Pub/Sub برای Jobهایی که نباید از دست بروند مناسب نیست؛ زیرا پیامهای معمول Pub/Sub برای Subscriber غیرفعال ذخیره نمیشوند.
Redis Streams چیست؟
Streams یک Log افزایشی از Eventها ایجاد میکند.
افزودن پیام:
XADD ai:jobs * job_id "job_101" status "queued"
ایجاد Consumer Group:
XGROUP CREATE ai:jobs workers 0 MKSTREAM
خواندن پیام با Consumer:
XREADGROUP GROUP workers worker_1 COUNT 10 BLOCK 5000 STREAMS ai:jobs >
تأیید پردازش:
XACK ai:jobs workers MESSAGE_ID
اگر Worker بعد از دریافت پیام متوقف شود، Event میتواند در Pending Entries باقی بماند و با سیاست مناسب بازیابی شود.
Session با Redis
اطلاعات Session میتواند در Hash ذخیره شود:
HSET session:abc123 user_id "42" plan "pro"
EXPIRE session:abc123 3600
در Request بعدی، Backend Session ID را دریافت و اطلاعات را از Redis میخواند:
HGETALL session:abc123
بهتر است Session ID:
- تصادفی و غیرقابل حدس باشد
- TTL مشخص داشته باشد
- بعد از خروج حذف شود
- در Log عمومی ثبت نشود
- اطلاعات ضروری و محدود نگه دارد
ساخت Rate Limit ساده با Redis
یکی از سادهترین الگوریتمها Fixed Window است.
برای هر کاربر و هر دقیقه یک Key میسازیم:
rate:user_42:202608061030
افزایش شمارنده:
INCR rate:user_42:202608061030
اگر اولین Request است، Expiration تنظیم میشود:
EXPIRE rate:user_42:202608061030 60
نمونه Python:
from datetime import datetime, timezone
async def check_rate_limit(redis_client, user_id: str):
current_minute = datetime.now(
timezone.utc
).strftime("%Y%m%d%H%M")
key = f"rate:{user_id}:{current_minute}"
async with redis_client.pipeline(
transaction=True
) as pipe:
pipe.incr(key)
pipe.expire(key, 60)
count, _ = await pipe.execute()
return count <= 60
این مثال برای آموزش مناسب است. در Production باید الگوریتم، محدودیت Burst، رفتار چند Region و Atomic بودن عملیات متناسب با نیاز پروژه بررسی شود.
Pipeline در Redis
اگر چند Command جداگانه ارسال کنید، برای هرکدام یک رفتوبرگشت شبکه انجام میشود.
بدون Pipeline:
Client → SET → Redis
Client ← OK ← Redis
Client → EXPIRE → Redis
Client ← 1 ← Redis
با Pipeline چند Command در یک Batch ارسال میشوند:
async with redis_client.pipeline() as pipe:
pipe.set("key:1", "value:1")
pipe.set("key:2", "value:2")
pipe.expire("key:1", 300)
results = await pipe.execute()
Pipeline میتواند تعداد Round Tripها را کاهش دهد، اما به معنی Transaction بودن تمام عملیات در تمام تنظیمات Client نیست. رفتار Client و گزینه transaction را بررسی کنید.
Transaction در Redis
Transactionهای Redis حول Commandهای زیر ساخته میشوند:
MULTI
EXEC
DISCARD
WATCH
نمونه CLI:
MULTI
INCR wallet:user_42
EXPIRE wallet:user_42 3600
EXEC
Redis Transaction با Transactionهای Database رابطهای دقیقاً یکسان نیست. برای مثال، مدل Rollback و مدیریت خطاها تفاوتهایی دارد.
از WATCH میتوان برای Optimistic Locking استفاده کرد؛ یعنی اگر Key پیش از اجرای Transaction تغییر کرده باشد، عملیات لغو و دوباره ارزیابی شود.
Persistence در Redis
با اینکه Redis دادههای فعال را در Memory نگه میدارد، میتواند دادهها را روی Disk نیز ثبت کند.
روشهای اصلی:
RDB
Snapshotهایی از Dataset در فاصلههای زمانی مشخص ساخته میشوند.
مزایا:
- فایل فشرده
- مناسب Backup
- Restart معمولاً سریع
- سربار کمتر در برخی Workloadها
محدودیت:
- ممکن است تغییرات بعد از آخرین Snapshot از دست بروند
AOF
Commandهای Write در Append Only File ثبت میشوند.
مزایا:
- امکان کاهش فاصله از دست رفتن داده
- بازسازی Dataset از Log عملیات
محدودیت:
- فایل بزرگتر
- سربار Write بیشتر
- نیاز به Rewrite دورهای
ترکیب RDB و AOF
بسته به اهمیت داده میتوان از ترکیب قابلیتها استفاده کرد.
اگر Redis فقط Cache است، از دست رفتن داده معمولاً با تولید دوباره قابل جبران است. اگر Redis نقش Queue یا Data Store اصلی دارد، سیاست Persistence و Recovery اهمیت بیشتری پیدا میکند.
Eviction Policy چیست؟
وقتی Memory به maxmemory برسد، Redis بر اساس Eviction Policy تصمیم میگیرد چه کاری انجام دهد.
نمونه Policyها:
noevictionallkeys-lruallkeys-lfuallkeys-randomvolatile-lruvolatile-lfuvolatile-ttl
انتخاب Policy به کاربرد بستگی دارد.
برای Cache عمومی، Policyهایی مانند LRU یا LFU میتوانند مناسب باشند. برای دادهای که نباید خودکار حذف شود، رفتار متفاوتی لازم است.
Redis را بدون محدودیت Memory رها نکنید. رشد کنترلنشده داده میتواند عملکرد سیستم را تحت تأثیر قرار دهد.
نصب Redis با Docker
برای محیط توسعه میتوانید Redis را با Docker اجرا کنید.
فایل docker-compose.yml:
services:
redis:
image: redis:8-alpine
container_name: redis-local
restart: unless-stopped
command:
- redis-server
- --appendonly
- "yes"
- --maxmemory
- 512mb
- --maxmemory-policy
- allkeys-lru
ports:
- "127.0.0.1:6379:6379"
volumes:
- redis_data:/data
volumes:
redis_data:
اجرا:
docker compose up -d
بررسی وضعیت:
docker compose ps
آزمایش اتصال:
docker compose exec redis redis-cli PING
پاسخ:
PONG
این تنظیم برای محیط توسعه Local است. در Production نباید Port Redis بدون کنترل در شبکه عمومی منتشر شود. نسخه Image را نیز پیش از Deployment بر اساس نسخه Stable مورد استفاده پروژه انتخاب و ثابت کنید.
اتصال Python به Redis
کتابخانه رسمی رایج Python برای Redis، پکیج redis است.
نصب:
pip install redis
نمونه همزمان:
import redis
client = redis.Redis(
host="localhost",
port=6379,
decode_responses=True,
)
client.set(
"greeting",
"سلام از Redis",
ex=300,
)
value = client.get("greeting")
print(value)
نمونه Async:
import asyncio
import redis.asyncio as redis
async def main():
client = redis.from_url(
"redis://localhost:6379/0",
decode_responses=True,
)
await client.set(
"greeting",
"سلام از Redis Async",
ex=300,
)
value = await client.get("greeting")
print(value)
await client.aclose()
asyncio.run(main())
پروژه عملی: Cache پاسخ API هوش مصنوعی با Redis
در این پروژه یک API خلاصهسازی با FastAPI میسازیم.
فرایند:
- کاربر متن را ارسال میکند.
- Backend از ورودی و تنظیمات یک Cache Key میسازد.
- Redis بررسی میشود.
- اگر پاسخ موجود باشد، همان نتیجه برگردانده میشود.
- اگر پاسخ موجود نباشد، API درواره فراخوانی میشود.
- نتیجه با TTL در Redis ذخیره میشود.
- پاسخ به کاربر برگردانده میشود.
ساختار پروژه:
redis-ai-cache/
├── app.py
├── requirements.txt
├── docker-compose.yml
└── .env
نصب وابستگیهای پروژه
فایل requirements.txt:
fastapi
uvicorn[standard]
httpx
redis
python-dotenv
نصب:
pip install -r requirements.txt
تنظیم Environment Variableها
فایل .env:
REDIS_URL=redis://localhost:6379/0
DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID
CACHE_TTL_SECONDS=3600
فایل .gitignore:
.env
.venv/
__pycache__/
کد کامل FastAPI
فایل app.py:
import hashlib
import json
import os
import random
from contextlib import asynccontextmanager
import httpx
import redis.asyncio as redis
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from redis.exceptions import RedisError
load_dotenv()
REDIS_URL = os.getenv(
"REDIS_URL",
"redis://localhost:6379/0",
)
DARVAREH_API_KEY = os.getenv("DARVAREH_API_KEY")
DARVAREH_MODEL_ID = os.getenv("DARVAREH_MODEL_ID")
CACHE_TTL_SECONDS = int(
os.getenv("CACHE_TTL_SECONDS", "3600")
)
DARVAREH_URL = (
"https://api.darvareh.ir/v1/chat/completions"
)
if not DARVAREH_API_KEY:
raise RuntimeError(
"متغیر DARVAREH_API_KEY تنظیم نشده است."
)
if not DARVAREH_MODEL_ID:
raise RuntimeError(
"متغیر DARVAREH_MODEL_ID تنظیم نشده است."
)
class SummaryRequest(BaseModel):
text: str = Field(
min_length=20,
max_length=20_000,
)
max_sentences: int = Field(
default=3,
ge=1,
le=10,
)
class SummaryResponse(BaseModel):
summary: str
model: str
cached: bool
def create_cache_key(
text: str,
max_sentences: int,
) -> str:
normalized_text = " ".join(
text.strip().split()
)
cache_input = json.dumps(
{
"version": "v1",
"model": DARVAREH_MODEL_ID,
"text": normalized_text,
"max_sentences": max_sentences,
},
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
)
digest = hashlib.sha256(
cache_input.encode("utf-8")
).hexdigest()
return f"ai:summary:v1:{digest}"
@asynccontextmanager
async def lifespan(app: FastAPI):
redis_client = redis.from_url(
REDIS_URL,
decode_responses=True,
socket_connect_timeout=3,
socket_timeout=3,
health_check_interval=30,
)
app.state.redis = redis_client
try:
await redis_client.ping()
print("Connected to Redis")
except RedisError:
print(
"Redis is unavailable; "
"the API will continue without cache."
)
yield
await redis_client.aclose()
app = FastAPI(
title="AI Summary API with Redis Cache",
version="1.0.0",
lifespan=lifespan,
)
@app.get("/health")
async def health_check():
redis_status = "unavailable"
try:
await app.state.redis.ping()
redis_status = "ok"
except RedisError:
pass
return {
"status": "ok",
"redis": redis_status,
}
@app.post(
"/v1/summaries",
response_model=SummaryResponse,
)
async def create_summary(
payload: SummaryRequest,
):
cache_key = create_cache_key(
payload.text,
payload.max_sentences,
)
try:
cached_value = await app.state.redis.get(
cache_key
)
except RedisError:
cached_value = None
if cached_value:
try:
cached_data = json.loads(cached_value)
return SummaryResponse(
summary=cached_data["summary"],
model=cached_data["model"],
cached=True,
)
except (
json.JSONDecodeError,
KeyError,
TypeError,
):
try:
await app.state.redis.delete(
cache_key
)
except RedisError:
pass
request_body = {
"model": DARVAREH_MODEL_ID,
"messages": [
{
"role": "system",
"content": (
"تو یک ویراستار فارسی هستی. "
"متن را دقیق و بدون اضافه کردن "
"اطلاعات جدید خلاصه کن."
),
},
{
"role": "user",
"content": (
f"متن زیر را حداکثر در "
f"{payload.max_sentences} جمله "
f"خلاصه کن:\n\n{payload.text}"
),
},
],
}
headers = {
"Authorization": (
f"Bearer {DARVAREH_API_KEY}"
),
"Content-Type": "application/json",
}
timeout = httpx.Timeout(
connect=10.0,
read=60.0,
write=20.0,
pool=10.0,
)
try:
async with httpx.AsyncClient(
timeout=timeout
) as http_client:
response = await http_client.post(
DARVAREH_URL,
headers=headers,
json=request_body,
)
if response.status_code == 429:
raise HTTPException(
status_code=503,
detail={
"code": "UPSTREAM_RATE_LIMIT",
"message": (
"سرویس هوش مصنوعی موقتاً "
"با محدودیت درخواست مواجه است."
),
},
)
if response.status_code >= 500:
raise HTTPException(
status_code=502,
detail={
"code": "UPSTREAM_ERROR",
"message": (
"سرویس هوش مصنوعی پاسخ "
"معتبری برنگرداند."
),
},
)
if response.status_code >= 400:
raise HTTPException(
status_code=502,
detail={
"code": "UPSTREAM_REJECTED",
"message": (
"درخواست توسط سرویس "
"بالادستی پذیرفته نشد."
),
},
)
result = response.json()
summary = (
result["choices"][0]["message"]["content"]
.strip()
)
except httpx.TimeoutException as exc:
raise HTTPException(
status_code=504,
detail={
"code": "UPSTREAM_TIMEOUT",
"message": (
"سرویس هوش مصنوعی در زمان "
"تعیینشده پاسخ نداد."
),
},
) from exc
except httpx.RequestError as exc:
raise HTTPException(
status_code=502,
detail={
"code": "UPSTREAM_CONNECTION_ERROR",
"message": (
"ارتباط با سرویس "
"هوش مصنوعی برقرار نشد."
),
},
) from exc
except (
ValueError,
KeyError,
IndexError,
TypeError,
) as exc:
raise HTTPException(
status_code=502,
detail={
"code": "INVALID_UPSTREAM_RESPONSE",
"message": (
"ساختار پاسخ سرویس "
"هوش مصنوعی معتبر نبود."
),
},
) from exc
if not summary:
raise HTTPException(
status_code=502,
detail={
"code": "EMPTY_UPSTREAM_RESPONSE",
"message": (
"سرویس هوش مصنوعی "
"پاسخ خالی برگرداند."
),
},
)
cache_value = json.dumps(
{
"summary": summary,
"model": DARVAREH_MODEL_ID,
},
ensure_ascii=False,
)
ttl_with_jitter = (
CACHE_TTL_SECONDS
+ random.randint(0, 300)
)
try:
await app.state.redis.set(
cache_key,
cache_value,
ex=ttl_with_jitter,
)
except RedisError:
pass
return SummaryResponse(
summary=summary,
model=DARVAREH_MODEL_ID,
cached=False,
)
اجرای پروژه
ابتدا Redis را اجرا کنید:
docker compose up -d
سپس FastAPI را اجرا کنید:
uvicorn app:app --reload
آدرس سرویس:
http://127.0.0.1:8000
مستندات تعاملی:
http://127.0.0.1:8000/docs
آزمایش Health Check
curl http://127.0.0.1:8000/health
پاسخ:
{
"status": "ok",
"redis": "ok"
}
ارسال درخواست خلاصهسازی
curl --request POST \
--url http://127.0.0.1:8000/v1/summaries \
--header "Content-Type: application/json" \
--data '{
"text": "Redis یک Data Store سریع مبتنی بر Memory است که برای Cache، Session، Queue، Counter و پردازش Event استفاده میشود. پشتیبانی از Data Typeهای مختلف و TTL باعث شده است Redis در معماری بسیاری از Backendها نقش مهمی داشته باشد.",
"max_sentences": 2
}'
پاسخ درخواست اول:
{
"summary": "Redis یک Data Store سریع مبتنی بر Memory است که برای Cache، Session، Queue و پردازش Event کاربرد دارد. Data Typeهای متنوع و قابلیت TTL آن را به ابزار مهمی در معماری Backend تبدیل کردهاند.",
"model": "YOUR_MODEL_ID",
"cached": false
}
اگر همان Request را دوباره ارسال کنید:
{
"summary": "Redis یک Data Store سریع مبتنی بر Memory است که برای Cache، Session، Queue و پردازش Event کاربرد دارد. Data Typeهای متنوع و قابلیت TTL آن را به ابزار مهمی در معماری Backend تبدیل کردهاند.",
"model": "YOUR_MODEL_ID",
"cached": true
}
در Request دوم، Backend پاسخ را از Redis دریافت کرده و مدل دوباره فراخوانی نشده است.
چرا Cache Key شامل Model ID است؟
دو مدل مختلف ممکن است برای یک متن پاسخ متفاوتی تولید کنند. اگر Model ID در Cache Key قرار نگیرد، پاسخ یک مدل ممکن است برای درخواست مدل دیگری بازگردانده شود.
همچنین این عوامل باید در Cache Key لحاظ شوند:
- متن نرمالشده
- Model ID
- نوع عملیات
- نسخه Prompt
- پارامترهای مؤثر
- زبان خروجی
- طول خروجی
- Tenant یا User در صورت شخصی بودن پاسخ
آیا پاسخ هوش مصنوعی همیشه باید Cache شود؟
خیر. Cache برای تمام Requestها مناسب نیست.
مناسبتر برای Cache:
- خلاصهسازی متن ثابت
- استخراج Keyword
- دستهبندی محتوای ثابت
- Embedding یک سند بدون تغییر
- پاسخهای عمومی با ورودی یکسان
- پردازشهایی که خروجی Deterministic یا قابل استفاده مجدد دارند
نامناسبتر برای Cache مشترک:
- پاسخ شخصیسازیشده
- اطلاعات حساس کاربر
- داده بلادرنگ
- وضعیت موجودی یا قیمت متغیر
- چت وابسته به تاریخچه
- Promptهایی با Context متفاوت
- خروجیهایی که باید هر بار تازه باشند
برای داده شخصی، Cache Key و Scope باید طوری طراحی شوند که اطلاعات یک کاربر به کاربر دیگر نمایش داده نشود.
Redis در دسترس نباشد چه اتفاقی میافتد؟
در کد این مقاله از الگوی Fail-open برای Cache استفاده شده است:
- اگر Redis در دسترس باشد، Cache استفاده میشود
- اگر Redis در دسترس نباشد، درخواست مستقیماً به درواره ارسال میشود
این رفتار برای Cache مناسب است؛ زیرا Cache یک لایه بهینهسازی است و نباید الزاماً کل API را متوقف کند.
اما اگر Redis نقش Queue، Session یا منبع ضروری هماهنگی را داشته باشد، Fail-open ممکن است مناسب نباشد. رفتار سیستم باید بر اساس نقش Redis تعیین شود.
اتصال Redis به API درواره چه مزیتی دارد؟
استفاده درست از Redis در کنار API هوش مصنوعی میتواند:
- Latency درخواستهای تکراری را کاهش دهد
- تعداد فراخوانیهای تکراری مدل را کم کند
- مصرف Token را کنترل کند
- هزینه پردازشهای تکراری را کاهش دهد
- Rate Limit داخلی ایجاد کند
- وضعیت Jobها را نگه دارد
- پردازشهای Async را مدیریت کند
- پاسخهای پرتکرار را سریعتر ارائه دهد
Cache نباید برای پنهان کردن معماری نامناسب استفاده شود. ابتدا باید مشخص شود کدام درخواستها واقعاً قابل استفاده مجدد هستند.
Base URL درواره:
https://api.darvareh.ir/v1
Endpoint مربوط به Chat Completions:
https://api.darvareh.ir/v1/chat/completions
برای مشاهده Model IDها و قیمت بهروز مدلها، صفحه مدلهای درواره را بررسی کنید.
Redis Sentinel چیست؟
Redis Sentinel برای مانیتور کردن Instanceها، تشخیص Failure و هماهنگی Failover در معماری Primary و Replica استفاده میشود.
کاربردهای اصلی:
- Monitoring
- Failure Detection
- Automatic Failover
- اعلام آدرس Primary جدید به Clientها
Sentinel جایگزین Backup یا Cluster نیست و نیازهای Scalability و Availability باید جداگانه بررسی شوند.
Redis Cluster چیست؟
Redis Cluster امکان توزیع Keyها بین چند Node را فراهم میکند. Keyspace با Hash Slotها تقسیم میشود و هر Node بخشی از داده را نگه میدارد.
Cluster زمانی مفید است که:
- Dataset از ظرفیت یک Node بزرگتر است
- Throughput بیشتری لازم است
- Sharding خودکار نیاز دارید
- Availability بالاتر میخواهید
Cluster پیچیدگی بیشتری دارد:
- عملیات چند Key ممکن است محدود شود
- Client باید Cluster-aware باشد
- Failover و Rebalancing نیازمند مانیتورینگ است
- طراحی Keyها و Hash Tagها اهمیت پیدا میکند
برای پروژه کوچک، شروع با یک Instance و برنامهریزی برای رشد معمولاً سادهتر است.
Naming Convention برای Keyها
یک قرارداد نامگذاری یکپارچه، مدیریت Redis را سادهتر میکند.
نمونه:
app:environment:resource:id:field
مثالها:
darvareh:prod:user:42:session
darvareh:prod:ai:summary:v1:HASH
darvareh:prod:rate:user_42:202608061030
darvareh:prod:job:981:status
اصول مناسب:
- Prefix برنامه داشته باشید
- Environment را جدا کنید
- نوع Resource مشخص باشد
- نسخه Schema را لحاظ کنید
- از Keyهای بسیار طولانی پرهیز کنید
- داده محرمانه را مستقیماً در Key ننویسید
چرا KEYS در Production مناسب نیست؟
Command زیر تمام Keyهای مطابق Pattern را جستوجو میکند:
KEYS ai:summary:*
روی Dataset بزرگ، این Command میتواند Redis را برای مدتی مشغول کند.
برای پیمایش تدریجی از SCAN استفاده کنید:
SCAN 0 MATCH ai:summary:* COUNT 100
SCAN نیز باید با دقت استفاده شود، اما برخلاف KEYS نتیجه را مرحلهای برمیگرداند.
برای حذف گروهی Cache بهتر است از Versioned Key، Tagging یا ساختار Invalidation هدفمند استفاده شود.
اشتباهات رایج در Redis
استفاده از Redis بهعنوان جایگزین تمام Databaseها
Redis برای همه مدلهای داده و Queryها مناسب نیست.
نداشتن TTL برای Cache
Cacheهای بدون Expiration میتوانند Memory را پر کنند.
انتخاب Cache Key ناقص
اگر یکی از پارامترهای مؤثر در Key نباشد، پاسخ اشتباه برگردانده میشود.
Cache کردن خطا
پاسخ Timeout یا خطای موقت را مانند نتیجه موفق ذخیره نکنید؛ مگر با سیاست مشخص و TTL بسیار کوتاه.
Cache کردن اطلاعات شخصی به شکل مشترک
Scope کاربر یا Tenant باید در Key و دسترسی لحاظ شود.
فرض کردن Pub/Sub بهعنوان Queue پایدار
Subscriber غیرفعال پیام معمول Pub/Sub را دریافت نمیکند.
نادیده گرفتن Duplicate در Queue
بعضی معماریها ممکن است پیام را بیش از یک بار تحویل دهند. Worker باید Idempotent باشد.
استفاده زیاد از KEYS
برای Dataset بزرگ از پیمایش کنترلشده استفاده کنید.
ساخت Connection جدید در هر Request
از Connection Pool کتابخانه Redis استفاده کنید و Client را با Lifecycle برنامه مدیریت کنید.
ذخیره Object بدون نسخه Schema
اگر ساختار JSON تغییر کند، داده قدیمی ممکن است قابل Parse نباشد. نسخه را در Key یا Value ذخیره کنید.
نداشتن محدودیت Memory
maxmemory و Eviction Policy را متناسب با کاربرد تنظیم کنید.
فرض کردن Persistence بهجای Backup
Persistence، Replication و Backup سه موضوع جدا هستند.
استفاده از Redis عمومی روی اینترنت
Redis باید در شبکه کنترلشده قرار گیرد و تنظیمات اتصال Production متناسب با زیرساخت انجام شود.
چکلیست Redis برای Production
پیش از انتشار این موارد را بررسی کنید:
- نقش Redis دقیقاً مشخص شده است
- Cache، Session و Queue از هم تفکیک شدهاند
- Key Naming Convention وجود دارد
- Cache Key تمام ورودیهای مؤثر را دارد
- TTL برای دادههای موقت تنظیم شده است
- TTLها متناسب با نوع دادهاند
- TTL Jitter در Cacheهای پرتعداد بررسی شده است
- Cache Stampede مدیریت میشود
- خطاها Cache نمیشوند
- اطلاعات کاربران Scope مناسب دارند
- Redis Client از Connection Pool استفاده میکند
- Timeout اتصال و Command مشخص است
- رفتار هنگام قطع Redis تعریف شده است
- Memory Limit تعیین شده است
- Eviction Policy مناسب انتخاب شده است
- Persistence متناسب با نقش Redis تنظیم شده است
- Backup جداگانه در صورت نیاز وجود دارد
- Queue Workerها Idempotent هستند
- Pub/Sub برای داده ضروری استفاده نشده است
- Streamهای قدیمی Trim میشوند
- Pending Messageها مانیتور میشوند
- Commandهای سنگین کنترل شدهاند
- Metricهای Memory و Latency ثبت میشوند
- Hit Rate و Miss Rate بررسی میشوند
- Connection Count مانیتور میشود
- Replication Lag در معماری Replica بررسی میشود
- سناریوی Recovery آزمایش شده است
- نسخه Redis و Client ثابت و کنترلشده است
- API Key در Cache یا Log عمومی ذخیره نمیشود
پرسشهای متداول
Redis چیست؟
Redis یک Data Store سریع مبتنی بر Memory است که برای Cache، Session، Queue، Counter، Pub/Sub، Streams و کاربردهای دیگر استفاده میشود.
تفاوت Redis و MySQL چیست؟
MySQL یک Database رابطهای برای دادههای ساختاریافته، Query و رابطه میان جدولهاست. Redis یک Data Structure Store سریع است که بیشتر برای دادههای موقت، پرتکرار و عملیات کمتأخیر استفاده میشود. آنها معمولاً مکمل یکدیگرند.
آیا Redis فقط Cache است؟
خیر. Redis میتواند برای Session، Queue، Pub/Sub، Streams، Counter، Leaderboard و حتی برخی کاربردهای Database استفاده شود.
آیا Redis دادهها را فقط در RAM نگه میدارد؟
داده فعال Redis در Memory نگهداری میشود، اما Redis از روشهای Persistence مانند RDB و AOF برای ثبت داده روی Disk نیز پشتیبانی میکند.
TTL در Redis چیست؟
TTL مدت باقیمانده تا Expire شدن یک Key است. با پایان TTL، Key حذف میشود.
تفاوت Redis Pub/Sub و Streams چیست؟
Pub/Sub پیام را برای Subscriberهای فعال ارسال میکند و معمولاً پیام ازدسترفته را نگه نمیدارد. Streams پیامها را ذخیره میکند و از Consumer Group و Acknowledgement پشتیبانی میکند.
آیا Redis برای Queue مناسب است؟
برای Queue ساده میتوان از List استفاده کرد. برای قابلیتهایی مانند Consumer Group و پیگیری پیامهای پردازشنشده، Streams یا یک Queue تخصصی میتواند مناسبتر باشد.
Redis برای API هوش مصنوعی چه کاربردی دارد؟
Redis میتواند پاسخهای تکراری را Cache کند، Rate Limit را مدیریت کند، وضعیت Jobها را نگه دارد و Queue پردازش ایجاد کند.
آیا Cache پاسخ هوش مصنوعی همیشه مجاز و مناسب است؟
خیر. باید نوع داده، شخصیسازی، Context، تازگی اطلاعات و سیاست نگهداری را بررسی کنید. پاسخهای شخصی یا متغیر نباید بدون Scope و TTL مناسب در Cache مشترک قرار گیرند.
آیا میتوان بدون Redis هم API هوش مصنوعی ساخت؟
بله. Redis یک ابزار اختیاری برای بهبود Performance و معماری است. پروژه ساده یا کمترافیک ممکن است در ابتدا به Redis نیاز نداشته باشد.
آدرس API درواره چیست؟
https://api.darvareh.ir/v1
Endpoint مربوط به Chat Completions:
https://api.darvareh.ir/v1/chat/completions
قیمت مدلهای درواره را از کجا ببینیم؟
برای مشاهده مدلها، Model ID و قیمت بهروز، صفحه مدلهای درواره را بررسی کنید.
جمعبندی
Redis یک Data Store سریع و انعطافپذیر است که در معماری Backend، API و سرویسهای هوش مصنوعی کاربردهای فراوانی دارد. Cache شناختهشدهترین کاربرد Redis است، اما قابلیتهایی مانند TTL، Hash، List، Set، Sorted Set، Pub/Sub و Streams امکان حل مسائل متنوعی را فراهم میکنند.
در پروژه عملی این مقاله، یک API خلاصهسازی با FastAPI ساختیم که ابتدا Redis را بررسی میکند و فقط هنگام Cache Miss به API درواره درخواست میفرستد. این معماری میتواند زمان پاسخ درخواستهای تکراری و تعداد فراخوانیهای غیرضروری مدل را کاهش دهد.
استفاده موفق از Redis فقط به اجرای SET و GET محدود نیست. طراحی Cache Key، انتخاب TTL، Invalidation، مدیریت Cache Stampede، محدودیت Memory، Persistence، Queue و رفتار سیستم هنگام قطع Redis باید از ابتدا مشخص شوند.
برای شروع استفاده از مدلهای هوش مصنوعی در پروژههای Python و Backend، به وبسایت درواره مراجعه کنید. فهرست مدلها، Model ID و قیمتهای بهروز نیز در صفحه مدلهای درواره در دسترس است.
منابع
- مستندات رسمی Redis
- راهنمای Data Typeهای Redis
- مستندات Redis Strings
- مستندات Redis Lists
- مستندات Redis Streams
- مستندات Redis Pub/Sub
- راهنمای Expiration و Command مربوط به EXPIRE
- راهنمای Redis Pipelining
- راهنمای Transaction در Redis
- راهنمای Persistence در Redis
مقالات مرتبط
- پردازش غیرهمزمان API هوش مصنوعی با Celery، Redis، Worker و Webhook
- ساخت حافظه AI Agent با PostgreSQL، Redis و Vector Database
- ساخت API هوش مصنوعی آماده محیط Production
- کاهش هزینه API هوش مصنوعی
- Prompt Caching چیست و چگونه هزینه API را کاهش میدهد؟
- محاسبه قیمت و هزینه API هوش مصنوعی
- ساخت AI Agent با Python، FastAPI و API درواره
برای مطالعه شرایط استفاده و محدودیتهای مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.