Django 面試準備 09-1:Redis vs Memcached
深入理解兩大緩存系統的差異與選擇策略
目錄
09-1. Redis vs Memcached
📌 為什麼需要緩存?
在 Web 應用中,數據庫往往是性能瓶頸:
用戶請求 → Django → 數據庫查詢(慢!)→ 返回數據引入緩存後:
用戶請求 → Django → 緩存(快!)→ 返回數據
↓ 未命中
數據庫 → 更新緩存 → 返回數據緩存的價值:
- ⚡ 減少數據庫壓力(從 100ms 降到 1ms)
- 🚀 提升響應速度
- 💰 降低服務器成本
🔍 Redis vs Memcached 核心差異
快速對比表
| 特性 | Redis | Memcached |
|---|---|---|
| 數據類型 | String, Hash, List, Set, Sorted Set | 僅支持 String |
| 持久化 | ✅ 支持(RDB、AOF) | ❌ 不支持 |
| 分布式 | ✅ 內建(Redis Cluster) | ⚠️ 需要客戶端實現 |
| 單線程/多線程 | 單線程(6.0+ 支持多線程 I/O) | 多線程 |
| 內存管理 | 更複雜(多種數據結構) | 簡單(Slab 分配) |
| 最大值大小 | 512MB | 1MB |
| 事務支持 | ✅ 支持 | ❌ 不支持 |
| 發布訂閱 | ✅ 支持 | ❌ 不支持 |
| 腳本支持 | ✅ Lua 腳本 | ❌ 不支持 |
🎯 Redis 詳解
1. 豐富的數據類型
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
# String:簡單鍵值對
r.set('user:1000:name', 'Alice')
r.get('user:1000:name') # b'Alice'
# Hash:用戶對象
r.hset('user:1000', mapping={
'name': 'Alice',
'email': 'alice@example.com',
'age': '25'
})
r.hgetall('user:1000')
# {b'name': b'Alice', b'email': b'alice@example.com', b'age': b'25'}
# List:消息隊列
r.lpush('task_queue', 'task1', 'task2', 'task3')
r.rpop('task_queue') # b'task1'
# Set:標籤系統
r.sadd('user:1000:tags', 'python', 'django', 'redis')
r.smembers('user:1000:tags')
# {b'python', b'django', b'redis'}
# Sorted Set:排行榜
r.zadd('leaderboard', {'player1': 100, 'player2': 200, 'player3': 150})
r.zrevrange('leaderboard', 0, 2, withscores=True)
# [(b'player2', 200.0), (b'player3', 150.0), (b'player1', 100.0)]2. 數據持久化
RDB(快照):
# redis.conf
save 900 1 # 900 秒內至少 1 次修改
save 300 10 # 300 秒內至少 10 次修改
save 60 10000 # 60 秒內至少 10000 次修改AOF(追加日誌):
# redis.conf
appendonly yes
appendfsync everysec # 每秒同步一次對比:
- RDB:快照,恢復快,但可能丟失數據
- AOF:日誌,數據更安全,但文件更大
3. 主從複製與高可用
# 主節點
redis-server --port 6379
# 從節點
redis-server --port 6380 --replicaof 127.0.0.1 6379Redis Sentinel(哨兵):
from redis.sentinel import Sentinel
sentinel = Sentinel([('localhost', 26379)], socket_timeout=0.1)
master = sentinel.master_for('mymaster', socket_timeout=0.1)
slave = sentinel.slave_for('mymaster', socket_timeout=0.1)
# 自動故障轉移
master.set('foo', 'bar')🎯 Memcached 詳解
1. 簡單高效
import memcache
mc = memcache.Client(['127.0.0.1:11211'])
# 設置值
mc.set('key', 'value')
mc.set('user:1000', {'name': 'Alice', 'age': 25})
# 獲取值
mc.get('key') # 'value'
mc.get('user:1000') # {'name': 'Alice', 'age': 25}
# 批量操作
mc.set_multi({
'key1': 'value1',
'key2': 'value2',
'key3': 'value3'
})
mc.get_multi(['key1', 'key2', 'key3'])2. 多線程優勢
Memcached 使用多線程處理請求:
線程 1 ←─┐
線程 2 ←─┤ 處理多個並發請求
線程 3 ←─┤
線程 4 ←─┘適合場景:
- 大量小對象緩存
- 高並發讀取
- 簡單的鍵值存儲
3. 一致性哈希分布
from pymemcache.client.hash import HashClient
# 多節點部署
client = HashClient([
('127.0.0.1', 11211),
('127.0.0.1', 11212),
('127.0.0.1', 11213)
])
# 自動分片
client.set('key1', 'value1') # 可能存在節點 1
client.set('key2', 'value2') # 可能存在節點 2
client.set('key3', 'value3') # 可能存在節點 3📊 性能對比
基準測試
import time
import redis
import memcache
def benchmark_redis():
r = redis.Redis()
start = time.time()
for i in range(10000):
r.set(f'key:{i}', f'value:{i}')
print(f"Redis SET: {time.time() - start:.2f}s")
start = time.time()
for i in range(10000):
r.get(f'key:{i}')
print(f"Redis GET: {time.time() - start:.2f}s")
def benchmark_memcached():
mc = memcache.Client(['127.0.0.1:11211'])
start = time.time()
for i in range(10000):
mc.set(f'key:{i}', f'value:{i}')
print(f"Memcached SET: {time.time() - start:.2f}s")
start = time.time()
for i in range(10000):
mc.get(f'key:{i}')
print(f"Memcached GET: {time.time() - start:.2f}s")典型結果:
Redis SET: 0.45s
Redis GET: 0.42s
Memcached SET: 0.38s
Memcached GET: 0.35s結論:
- 純鍵值操作:Memcached 略快
- 複雜數據結構:Redis 更適合
- 實際差異很小,場景選擇更重要
🤔 如何選擇?
選擇 Redis 的場景
✅ 需要複雜數據結構
# 購物車(Hash)
r.hset('cart:user123', 'product:456', 2) # 商品 ID → 數量
# 最近瀏覽(List)
r.lpush('recent:user123', 'product:789')
r.ltrim('recent:user123', 0, 9) # 只保留最近 10 個
# 在線用戶(Set)
r.sadd('online_users', 'user123', 'user456')
# 熱門文章排行(Sorted Set)
r.zincrby('hot_posts', 1, 'post:123')✅ 需要數據持久化
# 用戶會話(需要重啟後恢復)
r.setex('session:abc123', 3600, user_data)✅ 需要發布訂閱
# 實時通知
pubsub = r.pubsub()
pubsub.subscribe('notifications')
for message in pubsub.listen():
print(message)✅ 需要原子操作
# 秒殺庫存
r.watch('stock:product123')
pipeline = r.pipeline()
pipeline.multi()
pipeline.decr('stock:product123')
pipeline.execute()選擇 Memcached 的場景
✅ 簡單的鍵值緩存
# 數據庫查詢結果
mc.set('query:user_list', user_list, time=300)✅ 大規模分布式緩存
# 多個 Memcached 節點
client = HashClient([
('cache1.example.com', 11211),
('cache2.example.com', 11211),
('cache3.example.com', 11211),
('cache4.example.com', 11211)
])✅ 內存敏感場景
- Memcached 內存管理更高效
- Slab 分配減少碎片
✅ 純緩存(不需要持久化)
# 臨時數據
mc.set('temp:calculation', result, time=60)💡 Django 中的實踐
Redis 配置
# settings.py
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTION_POOL_KWARGS': {
'max_connections': 50,
'retry_on_timeout': True,
},
'PASSWORD': 'your-password',
'SOCKET_CONNECT_TIMEOUT': 5,
'SOCKET_TIMEOUT': 5,
}
}
}
# 使用
from django.core.cache import cache
cache.set('user:profile', user_data, timeout=300)
user_data = cache.get('user:profile')Memcached 配置
# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': [
'127.0.0.1:11211',
'127.0.0.1:11212',
],
'OPTIONS': {
'no_delay': True,
'ignore_exc': True,
'max_pool_size': 4,
'use_pooling': True,
}
}
}🎯 決策流程圖
需要緩存嗎?
├─ 否 → 直接訪問數據庫
└─ 是 ↓
需要複雜數據結構?
├─ 是 → 選擇 Redis
└─ 否 ↓
需要持久化?
├─ 是 → 選擇 Redis
└─ 否 ↓
需要分布式?
├─ 是 → Redis 或 Memcached(都支持)
└─ 否 ↓
追求極致性能?
├─ 是 → Memcached(簡單場景)
└─ 否 → Redis(功能更強大)💡 面試要點
Q1: Redis 和 Memcached 最大的區別是什麼?
答:
- 數據類型:Redis 支持多種數據結構,Memcached 只有字符串
- 持久化:Redis 支持持久化,Memcached 純內存
- 分布式:Redis 有 Cluster,Memcached 依賴客戶端
Q2: 為什麼 Redis 是單線程還能高性能?
答:
- 內存操作:主要是內存讀寫,非常快
- I/O 多路復用:epoll 處理並發連接
- 避免鎖競爭:單線程無需加鎖
- 優化的數據結構:針對性能優化
Q3: 什麼時候選擇 Memcached?
答:
- 簡單鍵值緩存:不需要複雜數據結構
- 大規模分布式:多節點橫向擴展
- 多線程優勢:CPU 密集型場景
- 純緩存場景:不需要持久化
Q4: Redis 的持久化會影響性能嗎?
答:
- RDB:子進程執行,影響較小
- AOF:everysec 模式影響可接受
- 建議:根據場景選擇,純緩存可關閉持久化
🎓 實戰建議
1. 混合使用
# settings.py
CACHES = {
# Redis:複雜數據、需要持久化
'redis': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
},
# Memcached:簡單緩存、高並發
'memcached': {
'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211',
}
}
# 使用
from django.core.cache import caches
redis_cache = caches['redis']
memcached_cache = caches['memcached']
# 購物車用 Redis
redis_cache.set('cart:user123', cart_data)
# 頁面片段用 Memcached
memcached_cache.set('page:home', html_content, 60)2. 監控與維護
# Redis 信息
r = redis.Redis()
info = r.info()
print(f"Used Memory: {info['used_memory_human']}")
print(f"Connected Clients: {info['connected_clients']}")
# Memcached 狀態
mc = memcache.Client(['127.0.0.1:11211'])
stats = mc.get_stats()
print(stats)🔗 下一篇
在下一篇文章中,我們將深入學習 Django 緩存框架,了解如何在 Django 中優雅地使用緩存。
閱讀時間:10 分鐘