Gönderi

Redis ile Önbellekleme ve Oturum Yönetimi: Performansı Maksimuma Çıkarın

Redis ile yüksek performanslı önbellekleme ve oturum yönetimi. Caching stratejileri, pub/sub, data structures ve production best practices.

Redis ile Önbellekleme ve Oturum Yönetimi: Performansı Maksimuma Çıkarın

Redis (REmote DIctionary Server), yüksek performanslı, in-memory key-value veri deposudur. Mikroservis mimarilerinde önbellekleme, oturum yönetimi, message queue ve real-time analytics gibi birçok kritik görevde kullanılır. Bu yazıda Redis’in temellerinden production-ready kullanımına kadar detaylı bir rehber sunacağız.

Redis Neden Bu Kadar Popüler?

Redis’in popülaritesinin arkasında birçok teknik avantaj bulunur:

Performans

  • In-Memory Storage: Tüm veriler RAM’de tutulur, disk I/O yok
  • Single-Threaded: Lock mekanizmasına gerek yok, atomic operasyonlar
  • 10-100x Hız Artışı: Database sorgularına göre mikrosaniye mertebesinde yanıt süreleri
  • Sub-millisecond Latency: Ortalama < 1ms okuma/yazma süresi

Veri Yapıları

Redis sadece string değil, birçok gelişmiş veri yapısı sunar:

  • Strings: Basit key-value çiftleri
  • Hashes: Object benzeri yapılar
  • Lists: FIFO/LIFO kuyruklar
  • Sets: Benzersiz değer koleksiyonları
  • Sorted Sets: Sıralı kümeler (leaderboard için ideal)
  • Bitmaps: Bit-level operasyonlar
  • HyperLogLogs: Kardinalite tahmini
  • Streams: Log benzeri veri yapısı

Kullanım Alanları

  • Caching: Database query sonuçlarını önbellekleme
  • Session Store: Kullanıcı oturumlarını merkezi yönetim
  • Rate Limiting: API rate limiter implementasyonu
  • Leaderboards: Gaming skorlar, ranking sistemleri
  • Real-time Analytics: Sayaçlar, metrikler
  • Pub/Sub: Message broker, event streaming
  • Geospatial: Lokasyon bazlı sorgular

Redis Kurulumu

Ubuntu/Debian için Kurulum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Redis resmi repository'yi ekle
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list

# Redis'i kur
sudo apt update
sudo apt install redis-server redis-tools

# Redis'i başlat
sudo systemctl start redis-server
sudo systemctl enable redis-server

# Kurulumu test et
redis-cli ping
# Yanıt: PONG

Docker ile Kurulum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Redis container çalıştır
docker run -d \
  --name redis \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7-alpine redis-server --appendonly yes

# Persistent data ile
docker run -d \
  --name redis-persistent \
  -p 6379:6379 \
  -v /my/redis/data:/data \
  -v /my/redis/conf:/usr/local/etc/redis \
  redis:7-alpine redis-server /usr/local/etc/redis/redis.conf

# Redis CLI'ya bağlan
docker exec -it redis redis-cli

Docker Compose ile Production Setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
version: '3.8'

services:
  redis:
    image: redis:7-alpine
    container_name: redis-server
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 512mb --maxmemory-policy allkeys-lru
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
      - ./redis.conf:/usr/local/etc/redis/redis.conf
    environment:
      - REDIS_REPLICATION_MODE=master
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3
    restart: unless-stopped

  redis-commander:
    image: rediscommander/redis-commander:latest
    container_name: redis-ui
    environment:
      - REDIS_HOSTS=local:redis:6379:0:${REDIS_PASSWORD}
    ports:
      - "8081:8081"
    depends_on:
      - redis
    networks:
      - app-network

volumes:
  redis-data:

networks:
  app-network:
    driver: bridge

Redis Temel Komutlar

String Operasyonları

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# SET ve GET
SET user:1000:name "John Doe"
GET user:1000:name

# EX ile expiration (saniye)
SET session:abc123 "user_data" EX 3600

# PX ile expiration (milisaniye)
SET token:xyz789 "auth_token" PX 300000

# NX: Sadece key yoksa set et
SET product:100:stock 50 NX

# XX: Sadece key varsa update et
SET product:100:stock 45 XX

# GETSET: Get edip yeni değer set et
GETSET counter:page_views 0

# MSET ve MGET (Multiple)
MSET user:1:name "Alice" user:1:age "30" user:1:city "Istanbul"
MGET user:1:name user:1:age user:1:city

# INCR ve DECR (Atomic counter)
INCR page:views
INCRBY page:views 10
DECR product:stock
DECRBY product:stock 5

# APPEND
SET msg "Hello"
APPEND msg " World"
GET msg  # "Hello World"

# STRLEN
STRLEN msg  # 11

# Key expire yönetimi
EXPIRE user:session 3600
TTL user:session
PERSIST user:session  # Expiration'ı kaldır

Redis Caching Pattern Redis ile Caching Stratejileri

Hash Operasyonları

Hash’ler, object-like yapılar oluşturmak için idealdir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# HSET ve HGET
HSET user:1000 name "John Doe" email "[email protected]" age 30
HGET user:1000 name

# HMSET (Multiple field set - deprecated, HSET kullan)
HSET user:1000 name "John" email "[email protected]" age 30

# HMGET (Multiple field get)
HMGET user:1000 name email age

# HGETALL (Tüm field'ları getir)
HGETALL user:1000

# HEXISTS (Field var mı kontrol et)
HEXISTS user:1000 name

# HDEL (Field sil)
HDEL user:1000 age

# HINCRBY (Numeric field artır)
HINCRBY user:1000 login_count 1

# HKEYS ve HVALS
HKEYS user:1000  # Tüm field isimlerini getir
HVALS user:1000  # Tüm değerleri getir

# HLEN (Field sayısı)
HLEN user:1000

List Operasyonları

List’ler FIFO/LIFO queue implementasyonu için kullanılır:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# LPUSH ve RPUSH (Başa/Sona ekle)
LPUSH queue:tasks "task1"
RPUSH queue:tasks "task2" "task3"

# LPOP ve RPOP (Baştan/Sondan çıkar)
LPOP queue:tasks
RPOP queue:tasks

# LRANGE (Range query)
LRANGE queue:tasks 0 -1  # Tüm elemanları getir
LRANGE queue:tasks 0 10  # İlk 10 elemanı getir

# LLEN (List uzunluğu)
LLEN queue:tasks

# LINDEX (Index'e göre eleman)
LINDEX queue:tasks 0

# LSET (Index'e set et)
LSET queue:tasks 0 "updated_task"

# LTRIM (Listeyi trim et)
LTRIM queue:tasks 0 99  # İlk 100 elemanı tut

# BLPOP ve BRPOP (Blocking pop - queue için ideal)
BLPOP queue:tasks 30  # 30 saniye bekle

Set Operasyonları

Set’ler benzersiz değerler içindir:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# SADD (Set'e ekle)
SADD tags:post:100 "python" "redis" "cache"

# SMEMBERS (Tüm elemanları getir)
SMEMBERS tags:post:100

# SISMEMBER (Eleman var mı kontrol)
SISMEMBER tags:post:100 "python"

# SREM (Eleman sil)
SREM tags:post:100 "cache"

# SCARD (Set boyutu)
SCARD tags:post:100

# Set operasyonları
SADD set1 "a" "b" "c"
SADD set2 "b" "c" "d"

SINTER set1 set2  # Kesişim: ["b", "c"]
SUNION set1 set2  # Birleşim: ["a", "b", "c", "d"]
SDIFF set1 set2   # Fark: ["a"]

# SPOP (Random eleman çıkar)
SPOP tags:post:100

# SRANDMEMBER (Random eleman getir, silme)
SRANDMEMBER tags:post:100 2

Sorted Set Operasyonları

Sorted set’ler score ile sıralanmış kümelerdir (leaderboard için mükemmel):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# ZADD (Score ile ekle)
ZADD leaderboard:game1 1000 "player1"
ZADD leaderboard:game1 1500 "player2" 1200 "player3"

# ZRANGE (Score'a göre sıralı getir)
ZRANGE leaderboard:game1 0 -1 WITHSCORES
ZRANGE leaderboard:game1 0 9 WITHSCORES  # Top 10

# ZREVRANGE (Ters sıralı - en yüksekten başla)
ZREVRANGE leaderboard:game1 0 9 WITHSCORES

# ZSCORE (Belirli member'ın score'u)
ZSCORE leaderboard:game1 "player1"

# ZINCRBY (Score artır)
ZINCRBY leaderboard:game1 50 "player1"

# ZRANK ve ZREVRANK (Sıralama)
ZRANK leaderboard:game1 "player1"      # 0'dan başlayan rank
ZREVRANK leaderboard:game1 "player1"   # Ters sıralı rank

# ZCOUNT (Score aralığındaki eleman sayısı)
ZCOUNT leaderboard:game1 1000 2000

# ZRANGEBYSCORE (Score aralığına göre getir)
ZRANGEBYSCORE leaderboard:game1 1000 1500 WITHSCORES

# ZREM (Member sil)
ZREM leaderboard:game1 "player1"

# ZREMRANGEBYRANK (Rank aralığını sil)
ZREMRANGEBYRANK leaderboard:game1 10 -1  # 11. sıradan sonrakileri sil

Redis Data Structures Redis Veri Yapıları: String, Hash, List, Set, Sorted Set

Python ile Redis Kullanımı

Kurulum ve Bağlantı

1
pip install redis redis-py-cluster
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import redis
from redis import Redis, ConnectionPool
from typing import Optional, Dict, List
import json

# Basit bağlantı
r = redis.Redis(
    host='localhost',
    port=6379,
    db=0,
    decode_responses=True  # String olarak decode et
)

# Connection pool ile (production için önerilen)
pool = ConnectionPool(
    host='localhost',
    port=6379,
    db=0,
    max_connections=50,
    decode_responses=True
)
r = Redis(connection_pool=pool)

# Authentication ile
r = redis.Redis(
    host='localhost',
    port=6379,
    password='your_password',
    db=0,
    decode_responses=True
)

# Redis URL ile
r = redis.from_url('redis://:password@localhost:6379/0')

# Bağlantıyı test et
try:
    r.ping()
    print("Redis bağlantısı başarılı!")
except redis.ConnectionError:
    print("Redis'e bağlanılamıyor!")

Cache Wrapper Sınıfı

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from functools import wraps
import hashlib
import pickle
from typing import Any, Callable

class RedisCache:
    """Redis cache wrapper sınıfı"""
    
    def __init__(self, redis_client: Redis, default_ttl: int = 3600):
        self.redis = redis_client
        self.default_ttl = default_ttl
    
    def _make_key(self, prefix: str, *args, **kwargs) -> str:
        """Cache key oluştur"""
        key_parts = [prefix] + [str(arg) for arg in args]
        if kwargs:
            key_parts.append(str(sorted(kwargs.items())))
        key_string = ":".join(key_parts)
        return f"cache:{hashlib.md5(key_string.encode()).hexdigest()}"
    
    def get(self, key: str) -> Optional[Any]:
        """Cache'den veri al"""
        data = self.redis.get(key)
        if data:
            return pickle.loads(data)
        return None
    
    def set(self, key: str, value: Any, ttl: Optional[int] = None) -> bool:
        """Cache'e veri kaydet"""
        ttl = ttl or self.default_ttl
        serialized = pickle.dumps(value)
        return self.redis.setex(key, ttl, serialized)
    
    def delete(self, key: str) -> bool:
        """Cache'den sil"""
        return bool(self.redis.delete(key))
    
    def exists(self, key: str) -> bool:
        """Key var mı kontrol et"""
        return bool(self.redis.exists(key))
    
    def clear_pattern(self, pattern: str) -> int:
        """Pattern'e uyan tüm key'leri sil"""
        keys = self.redis.keys(pattern)
        if keys:
            return self.redis.delete(*keys)
        return 0
    
    def cache_function(self, ttl: Optional[int] = None, prefix: str = "func"):
        """Decorator: Function sonucunu cache'le"""
        def decorator(func: Callable) -> Callable:
            @wraps(func)
            def wrapper(*args, **kwargs):
                # Cache key oluştur
                cache_key = self._make_key(
                    f"{prefix}:{func.__name__}",
                    *args,
                    **kwargs
                )
                
                # Cache'den dene
                cached_result = self.get(cache_key)
                if cached_result is not None:
                    print(f"Cache HIT: {cache_key}")
                    return cached_result
                
                # Cache MISS: Fonksiyonu çalıştır
                print(f"Cache MISS: {cache_key}")
                result = func(*args, **kwargs)
                
                # Sonucu cache'le
                self.set(cache_key, result, ttl or self.default_ttl)
                
                return result
            
            return wrapper
        return decorator


# Kullanım örneği
cache = RedisCache(r, default_ttl=300)

@cache.cache_function(ttl=600, prefix="user")
def get_user_from_db(user_id: int) -> Dict:
    """Simulated database query"""
    import time
    time.sleep(2)  # Yavaş DB sorgusu simülasyonu
    return {
        "id": user_id,
        "name": "John Doe",
        "email": "[email protected]"
    }

# İlk çağrı: DB'den gelir (2 saniye)
user = get_user_from_db(1)

# İkinci çağrı: Cache'den gelir (milisaniyeler)
user = get_user_from_db(1)

FastAPI ile Cache Entegrasyonu

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
from fastapi import FastAPI, Depends
from pydantic import BaseModel
import redis.asyncio as aioredis
from typing import Optional
import json

app = FastAPI()

# Async Redis client
redis_client: Optional[aioredis.Redis] = None

@app.on_event("startup")
async def startup():
    global redis_client
    redis_client = await aioredis.from_url(
        "redis://localhost:6379",
        encoding="utf-8",
        decode_responses=True
    )

@app.on_event("shutdown")
async def shutdown():
    if redis_client:
        await redis_client.close()

class Product(BaseModel):
    id: int
    name: str
    price: float
    stock: int

async def get_redis() -> aioredis.Redis:
    """Redis dependency"""
    return redis_client

@app.get("/products/{product_id}")
async def get_product(
    product_id: int,
    redis: aioredis.Redis = Depends(get_redis)
):
    """Product getir - Cache stratejisi ile"""
    cache_key = f"product:{product_id}"
    
    # Cache'den dene
    cached = await redis.get(cache_key)
    if cached:
        return {"source": "cache", "data": json.loads(cached)}
    
    # DB'den çek (simulated)
    product = Product(
        id=product_id,
        name=f"Product {product_id}",
        price=99.99,
        stock=100
    )
    
    # Cache'e kaydet (1 saat)
    await redis.setex(
        cache_key,
        3600,
        product.model_dump_json()
    )
    
    return {"source": "database", "data": product}

@app.put("/products/{product_id}")
async def update_product(
    product_id: int,
    product: Product,
    redis: aioredis.Redis = Depends(get_redis)
):
    """Product güncelle ve cache'i invalidate et"""
    # DB'yi güncelle (simulated)
    
    # Cache'i invalidate et
    await redis.delete(f"product:{product_id}")
    
    # İlgili cache pattern'lerini temizle
    keys = await redis.keys(f"product:list:*")
    if keys:
        await redis.delete(*keys)
    
    return {"message": "Product updated", "invalidated_cache": len(keys) + 1}

@app.post("/cache/flush")
async def flush_cache(redis: aioredis.Redis = Depends(get_redis)):
    """Tüm cache'i temizle (dikkatli kullan!)"""
    await redis.flushdb()
    return {"message": "Cache flushed"}

Redis Session Management Redis ile Mikroservislerde Session Yönetimi

Session Management

Flask ile Session Store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from flask import Flask, session
from flask_session import Session
import redis

app = Flask(__name__)

# Session configuration
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True
app.config['SESSION_KEY_PREFIX'] = 'session:'
app.config['SESSION_REDIS'] = redis.Redis(
    host='localhost',
    port=6379,
    db=0
)

Session(app)

@app.route('/login', methods=['POST'])
def login():
    # User authentication
    user_id = 12345
    
    # Session'a kaydet
    session['user_id'] = user_id
    session['username'] = 'johndoe'
    session['roles'] = ['user', 'admin']
    
    return {'message': 'Logged in successfully'}

@app.route('/profile')
def profile():
    # Session'dan oku
    user_id = session.get('user_id')
    if not user_id:
        return {'error': 'Not authenticated'}, 401
    
    return {
        'user_id': user_id,
        'username': session.get('username'),
        'roles': session.get('roles')
    }

@app.route('/logout', methods=['POST'])
def logout():
    session.clear()
    return {'message': 'Logged out successfully'}

Custom Session Manager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import uuid
import json
from datetime import timedelta
from typing import Optional, Dict, Any

class SessionManager:
    """Redis-based session manager"""
    
    def __init__(self, redis_client: Redis, prefix: str = "session"):
        self.redis = redis_client
        self.prefix = prefix
        self.default_ttl = timedelta(hours=24)
    
    def create_session(self, user_id: int, data: Dict[str, Any]) -> str:
        """Yeni session oluştur"""
        session_id = str(uuid.uuid4())
        session_key = f"{self.prefix}:{session_id}"
        
        session_data = {
            'user_id': user_id,
            'created_at': datetime.utcnow().isoformat(),
            **data
        }
        
        self.redis.setex(
            session_key,
            self.default_ttl,
            json.dumps(session_data)
        )
        
        return session_id
    
    def get_session(self, session_id: str) -> Optional[Dict]:
        """Session verisini getir"""
        session_key = f"{self.prefix}:{session_id}"
        data = self.redis.get(session_key)
        
        if data:
            # TTL'i yenile (sliding expiration)
            self.redis.expire(session_key, self.default_ttl)
            return json.loads(data)
        
        return None
    
    def update_session(self, session_id: str, data: Dict[str, Any]) -> bool:
        """Session'ı güncelle"""
        session_key = f"{self.prefix}:{session_id}"
        current_data = self.get_session(session_id)
        
        if not current_data:
            return False
        
        current_data.update(data)
        current_data['updated_at'] = datetime.utcnow().isoformat()
        
        self.redis.setex(
            session_key,
            self.default_ttl,
            json.dumps(current_data)
        )
        
        return True
    
    def delete_session(self, session_id: str) -> bool:
        """Session'ı sil (logout)"""
        session_key = f"{self.prefix}:{session_id}"
        return bool(self.redis.delete(session_key))
    
    def get_user_sessions(self, user_id: int) -> List[str]:
        """User'ın tüm session'larını getir"""
        pattern = f"{self.prefix}:*"
        sessions = []
        
        for key in self.redis.scan_iter(match=pattern, count=100):
            data = self.redis.get(key)
            if data:
                session_data = json.loads(data)
                if session_data.get('user_id') == user_id:
                    sessions.append(key.split(':')[-1])
        
        return sessions
    
    def invalidate_user_sessions(self, user_id: int) -> int:
        """User'ın tüm session'larını sil"""
        sessions = self.get_user_sessions(user_id)
        count = 0
        
        for session_id in sessions:
            if self.delete_session(session_id):
                count += 1
        
        return count

Rate Limiting

Token Bucket Algorithm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
import time
from typing import Tuple

class RateLimiter:
    """Redis-based rate limiter (Token Bucket)"""
    
    def __init__(self, redis_client: Redis):
        self.redis = redis_client
    
    def is_allowed(
        self,
        identifier: str,
        max_requests: int = 100,
        window_seconds: int = 60
    ) -> Tuple[bool, Dict]:
        """Rate limit kontrolü"""
        key = f"ratelimit:{identifier}"
        current_time = int(time.time())
        
        # Lua script (atomic operation)
        lua_script = """
        local key = KEYS[1]
        local max_requests = tonumber(ARGV[1])
        local window = tonumber(ARGV[2])
        local current_time = tonumber(ARGV[3])
        
        local count = redis.call('GET', key)
        
        if not count then
            redis.call('SETEX', key, window, 1)
            return {1, max_requests - 1, window}
        end
        
        count = tonumber(count)
        
        if count < max_requests then
            redis.call('INCR', key)
            local ttl = redis.call('TTL', key)
            return {1, max_requests - count - 1, ttl}
        end
        
        local ttl = redis.call('TTL', key)
        return {0, 0, ttl}
        """
        
        allowed, remaining, reset = self.redis.eval(
            lua_script,
            1,
            key,
            max_requests,
            window_seconds,
            current_time
        )
        
        return bool(allowed), {
            'limit': max_requests,
            'remaining': int(remaining),
            'reset': int(reset)
        }

# FastAPI middleware örneği
from fastapi import Request, HTTPException
from starlette.middleware.base import BaseHTTPMiddleware

class RateLimitMiddleware(BaseHTTPMiddleware):
    def __init__(self, app, redis_client: Redis):
        super().__init__(app)
        self.limiter = RateLimiter(redis_client)
    
    async def dispatch(self, request: Request, call_next):
        # Client IP'ye göre rate limit
        client_ip = request.client.host
        
        allowed, info = self.limiter.is_allowed(
            identifier=client_ip,
            max_requests=100,
            window_seconds=60
        )
        
        if not allowed:
            raise HTTPException(
                status_code=429,
                detail=f"Rate limit exceeded. Try again in {info['reset']} seconds",
                headers={
                    'X-RateLimit-Limit': str(info['limit']),
                    'X-RateLimit-Remaining': '0',
                    'X-RateLimit-Reset': str(info['reset'])
                }
            )
        
        response = await call_next(request)
        
        # Rate limit headers ekle
        response.headers['X-RateLimit-Limit'] = str(info['limit'])
        response.headers['X-RateLimit-Remaining'] = str(info['remaining'])
        response.headers['X-RateLimit-Reset'] = str(info['reset'])
        
        return response

# Kullanım
app.add_middleware(RateLimitMiddleware, redis_client=r)

Production Best Practices

1. Redis Configuration (redis.conf)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Memory management
maxmemory 2gb
maxmemory-policy allkeys-lru

# Persistence (RDB + AOF)
save 900 1
save 300 10
save 60 10000
appendonly yes
appendfsync everysec

# Security
requirepass your_strong_password_here
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command CONFIG "CONFIG_abc123"

# Performance
tcp-backlog 511
timeout 300
tcp-keepalive 60
databases 16

# Logging
loglevel notice
logfile /var/log/redis/redis-server.log

2. Monitoring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def get_redis_info(redis_client: Redis) -> Dict:
    """Redis metrics"""
    info = redis_client.info()
    
    return {
        'version': info['redis_version'],
        'uptime_days': info['uptime_in_days'],
        'connected_clients': info['connected_clients'],
        'used_memory': f"{info['used_memory_human']}",
        'used_memory_peak': f"{info['used_memory_peak_human']}",
        'total_commands_processed': info['total_commands_processed'],
        'instantaneous_ops_per_sec': info['instantaneous_ops_per_sec'],
        'keyspace_hits': info['keyspace_hits'],
        'keyspace_misses': info['keyspace_misses'],
        'hit_rate': round(
            info['keyspace_hits'] / 
            (info['keyspace_hits'] + info['keyspace_misses']) * 100, 
            2
        ) if (info['keyspace_hits'] + info['keyspace_misses']) > 0 else 0
    }

3. Connection Pooling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Connection pool best practices
pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    password='your_password',
    db=0,
    max_connections=50,
    socket_timeout=5,
    socket_connect_timeout=5,
    socket_keepalive=True,
    health_check_interval=30,
    decode_responses=True
)

r = redis.Redis(connection_pool=pool)

4. Error Handling

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from redis.exceptions import (
    ConnectionError,
    TimeoutError,
    RedisError
)

def safe_redis_operation(func):
    """Redis operation wrapper with error handling"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except ConnectionError:
            logger.error("Redis connection error")
            # Fallback logic
            return None
        except TimeoutError:
            logger.error("Redis timeout")
            return None
        except RedisError as e:
            logger.error(f"Redis error: {e}")
            return None
    return wrapper

@safe_redis_operation
def get_from_cache(key: str):
    return r.get(key)

Sonuç

Redis, modern web uygulamalarında vazgeçilmez bir bileşen haline gelmiştir. Önbellekleme ve oturum yönetimi gibi temel kullanımların ötesinde, rate limiting, leaderboard, pub/sub ve real-time analytics gibi birçok advanced kullanım senaryosu sunar.

Bu yazıda öğrendikleriniz:

  • Redis kurulumu ve temel komutlar
  • Tüm veri yapıları (String, Hash, List, Set, Sorted Set)
  • Python ile Redis entegrasyonu
  • Cache stratejileri ve function decorator
  • Session management implementasyonu
  • Rate limiting algoritmaları
  • Production best practices

Önerilen Kaynaklar

Bir sonraki yazımızda Traefik ile reverse proxy ve SSL yönetimini inceleyeceğiz. Takipte kalın!

Bu gönderi CC BY 4.0 lisansı altındadır.