این سند یک مرور عملی و عمیق از Redis و الگوهای متداول استفاده از آن در Python است. علاوه بر معرفی ساختار دادهها و تفاوت متدها، نکات تولیدی (production-grade) و نسخهی اصلاحشدهی اسکریپت شما نیز ارائه شده است. همهی مثالها با کتابخانهی رسمی redis-py نوشته شدهاند.
-
مقدمه
-
نصب و راهاندازی
-
اتصال به Redis و تنظیمات کلیدی
-
ساختارهای داده و الگوهای رایج
- String
- List
- Counter
- Expire/TTL
- Set
- Hash
- Sorted Set
-
مدیریت کلیدها و جستوجو
-
حافظهنهان (Cache) و Memoization
-
صف، Pub/Sub و الگوهای پردازش
-
تراکنشها، اتمی بودن، Pipeline و Lua
-
پایداری داده، تکرار و خوشهبندی
-
سیاستهای حافظه و تخلیه (Eviction)
-
امنیت و تنظیمات تولیدی
-
نسخهی اصلاحشدهی اسکریپت شما
Redis یک پایگاهدادهی in-memory و key–value است که علاوه بر سرعت بالا، مجموعهای از ساختارهای دادهی سطحبالا مانند لیست، ست، هش و Sorted Set را فراهم میکند. موارد استفادهی پرتکرار:
- Cache برای نتایج محاسبات یا پاسخهای API
- Session Store برای وباپلیکیشنها
- صفها و پیامرسانی (Queue / Pub-Sub)
- Leaderboard با Sorted Set
- شمارندههای اتمی و ریتلیمیتینگ
- لینوکس/مک: از بستههای رسمی یا Docker استفاده کنید:
docker run -p 6379:6379 --name redis -d redis:7-alpinepip install redisبرای جلوگیری از نیاز به decode() روی مقادیر، از decode_responses=True استفاده کنید. همچنین Connection Pool و Timeoutها را مشخص کنید.
import redis
pool = redis.ConnectionPool(
host='localhost', port=6379, db=0,
decode_responses=True, # str بهجای bytes
socket_connect_timeout=2, socket_timeout=2
)
r = redis.Redis(connection_pool=pool)نکات:
decode_responses=Trueخوانایی کد را بالا میبرد.- Timeoutها را حتماً تنظیم کنید تا در شرایط شبکهای نامطمئن، بلاک نشوید.
- برای حجم بالای عملیات پشتسرهم از
pipelineاستفاده کنید.
کاربرد: کش ساده، فلگها، توکنها.
r.set("name", "Ali") # ایجاد/بازنویسی
r.set("name", "Ali2")
print(r.get("name")) # 'Ali2'متدهای مهم و تفاوتها:
SETبا گزینههایNX/XX: ایجاد فقط اگر کلید وجود ندارد/دارد.EX/PX: TTL بر حسب ثانیه/میلیثانیه.MGET/MSETبرای چند کلید همزمان (کاهش round-trip).
کاربرد: نگهداشت تاریخچه، صفهای ساده، لاگهای کوتاهمدت.
r.delete("history:name")
r.rpush("history:name", "Ali") # انتهای لیست
r.rpush("history:name", "Ali2")
r.lpush("history:name", "Reza") # ابتدای لیست
print(r.lrange("history:name", 0, -1)) # ['Reza','Ali','Ali2']
print(r.lindex("history:name", 0)) # 'Reza'
print(r.lindex("history:name", -1)) # 'Ali2'تفاوت متدها:
LPUSHدر ابتدای لیست درج میکند،RPUSHدر انتها.LPOP/RPOPاز ابتدا/انتها خارج میکند.BLPOP/BRPOPنسخهی بلاکینگ برای الگوی صف مصرفکننده/تولیدکننده.
کاربرد: شمارش بازدید، ریتلیمیتینگ، آمار.
r.delete("counter")
r.incr("counter") # 1
r.incrby("counter", 5) # 6
r.decr("counter") # 5
print(r.get("counter")) # '5'نکته: عملیات INCR/DECR اتمی هستند و نیازی به قفل اضافی ندارند.
کاربرد: کش موقت، کلیدهای خودپاکشونده.
r.set("temp", "hello", ex=3)
print(r.get("temp")) # 'hello'
import time; time.sleep(4)
print(r.get("temp")) # Noneمتدهای مرتبط:
EXPIRE key seconds،PEXPIRE key msTTL key/PTTL keyPERSIST keyبرای حذف TTL و دائمی کردن کلید
کاربرد: تگها، مجموعهی یکتا، آزمون عضویت.
r.delete("myset")
r.sadd("myset", "apple", "banana", "orange")
r.sadd("myset", "banana") # نادیده بهدلیل تکراری بودن
print(r.smembers("myset")) # {'banana','orange','apple'}متدهای مهم:
SISMEMBERبررسی عضویتSINTER،SUNION،SDIFFعملیات مجموعهایSPOP،SRANDMEMBERنمونهگیری تصادفی
کاربرد: پروفایل کاربر، تنظیمات.
r.delete("user:100")
r.hset("user:100", mapping={"name": "Sara", "age": 25, "city": "Tehran"})
print(r.hgetall("user:100")) # {'name':'Sara','age':'25','city':'Tehran'}
print(r.hget("user:100","name")) # 'Sara'تفاوت/نکات:
HSETاتمی روی یک فیلد یا چند فیلد.- برای افزایش عددی:
HINCRBY/HINCRBYFLOAT. HGETALLروی هشهای بزرگ سنگین است؛ ترجیحاًHMGETروی فیلدهای لازم.
کاربرد: امتیازدهی، اولویتبندی با عدد واقعی یا زمان.
r.delete("scores")
r.zadd("scores", {"Ali":100, "Sara":120, "Reza":90})
print(r.zrevrange("scores", 0, -1, withscores=True))
# [('Sara',120.0),('Ali',100.0),('Reza',90.0)]تفاوت متدها:
ZRANGE/ZREVRANGEباBYLEX/BYSCORE/WITHSCORESZINCRBYبرای افزایش تدریجی امتیاز- محدوده بر اساس نمره (
ZRANGEBYSCORE) یا رتبه (ZRANK)
- از
KEYS *در تولید استفاده نکنید؛ عملیات O(N) و مسدودکننده است. - از
SCANو دوستانش (SSCAN,HSCAN,ZSCAN) برای پیمایش تدریجی استفاده کنید.
for cursor, keys in r.scan_iter(count=1000):
pass # الگوی امن برای جستوجوی تدریجیالگو: قبل از محاسبه، کش را بررسی کنید؛ بعد از محاسبه، با TTL مناسب ذخیره کنید.
def cached_heavy_fn(n: int) -> int:
key = f"fib:{n}"
val = r.get(key)
if val is not None:
return int(val)
# محاسبهی پرهزینه (نمونه)
result = n if n <= 1 else cached_heavy_fn(n-1) + cached_heavy_fn(n-2)
# ذخیره با TTL برای جلوگیری از کهنگی داده
r.set(key, result, ex=3600)
return resultنکات:
- TTL را بر اساس ماهیت داده تنظیم کنید.
- برای تضمین «یکبار محاسبه»، میتوانید از قفل سبک با
SET lock NX EXاستفاده کنید.
- صف ساده:
LPUSHتولید،BRPOPمصرف. - Pub/Sub:
PUBLISHبرای انتشار وSUBSCRIBEبرای دریافت. مناسب برای اعلانها، نه ذخیره دائمی پیام. - برای صفهای پایدارتر و تحویل تضمینشده، Redis Streams مناسبتر است (در این سند پوشش داده نشده).
-
بسیاری از عملیات Redis اتمی هستند، اما برای چند دستور پشتسرهم:
MULTI/EXECباWATCHجهت CAS (Check-And-Set).pipeline()برای کاهش round-trip (افزایش کارایی شبکه).- اسکریپتهای Lua برای منطق اتمی پیچیده.
مثال Pipeline:
with r.pipeline() as pipe:
pipe.incr("counter")
pipe.expire("counter", 60)
results = pipe.execute()-
پایداری:
- RDB: اسنپشاتهای دورهای، سبک و سریع برای بکاپ.
- AOF: ثبت همه دستورات، بازیابی دقیقتر، حجم بیشتر.
- حالت ترکیبی نیز قابل تنظیم است.
-
تکرار (Replication) برای خواندن مقیاسپذیر و افزونگی.
-
Sentinel برای failover خودکار.
-
Cluster برای افقیسازی و شاردینگ خودکار کلیدها.
وقتی حافظه پر شود، Redis با توجه به maxmemory-policy شروع به حذف میکند:
- نمونهها:
allkeys-lru,volatile-ttl,noevictionو … - برای Cache از سیاستهای LRU/LFU استفاده کنید و TTL مناسب بگذارید.
- از قرار دادن Redis روی اینترنت عمومی خودداری کنید؛ پشت فایروال/شبکه خصوصی.
- در صورت نیاز
requirepassو ACLها را تنظیم کنید. - محدودیتهای اتصال و Timeoutها را ست کنید.
- مانیتورینگ:
INFO,LATENCY, متریکها و Exporter برای Prometheus.