Redis 緩存穿透是指在緩存系統(如 Redis)中,當客戶端請求的數據既不在緩存中,也不在數據庫中時,每次請求都會直接穿透緩存訪問數據庫,從而給數據庫帶來巨大壓力,甚至可能導致數據庫崩潰。下面為你詳細介紹其產生原因、解決方案以及示例代碼。
產生原因
- 非法請求:惡意攻擊者可能會故意發送大量不存在于數據庫中的請求,使緩存失去作用,請求全部落到數據庫上。
- 業務邏輯錯誤:在業務開發過程中,如果對數據的判斷邏輯有誤,可能會導致程序請求不存在的數據。
解決方案
1. 緩存空對象
當請求的數據在數據庫中不存在時,將一個空對象(如null
、""
)存入緩存,并設置一個較短的過期時間。這樣下次相同的請求就會直接從緩存中獲取空對象,而不會再次訪問數據庫。
2. 布隆過濾器
布隆過濾器是一種空間效率極高的概率型數據結構,用于判斷一個元素是否存在于一個集合中。在請求訪問緩存之前,先通過布隆過濾器判斷該請求的數據是否可能存在。如果布隆過濾器判斷數據不存在,那么就直接返回,避免訪問數據庫。
示例代碼(Python + Redis)
以下是使用 Python 和 Redis 實現緩存空對象的示例代碼:
import redis# 連接Redis
r = redis.Redis(host='localhost', port=6379, db=0)def get_data(key):# 先從緩存中獲取數據data = r.get(key)if data is not None:# 如果緩存中有數據,直接返回if data == b'':return Nonereturn data.decode('utf-8')else:# 緩存中沒有數據,從數據庫中獲取(這里用模擬函數代替)data = get_data_from_db(key)if data is None:# 如果數據庫中也沒有數據,緩存空對象r.setex(key, 60, '') # 設置過期時間為60秒else:# 數據庫中有數據,存入緩存r.setex(key, 3600, data) # 設置過期時間為3600秒return datadef get_data_from_db(key):# 模擬從數據庫中獲取數據# 這里可以替換為實際的數據庫查詢操作if key == 'existing_key':return 'some data'return None# 測試
print(get_data('existing_key'))
print(get_data('non_existing_key'))
上述代碼實現了一個簡單的緩存空對象的機制,當請求的數據在數據庫中不存在時,會將空對象存入緩存,避免下次請求再次訪問數據庫。
布隆過濾器示例(Python + RedisBloom)
如果你使用的是 RedisBloom 模塊,可以使用布隆過濾器來解決緩存穿透問題:
from redisbloom.client import Client# 連接RedisBloom
rb = Client()# 初始化布隆過濾器
rb.bfCreate('mybloom', 0.01, 1000) # 錯誤率為0.01,預計插入1000個元素# 向布隆過濾器中添加元素
rb.bfAdd('mybloom', 'existing_key')def get_data_with_bloom(key):# 先通過布隆過濾器判斷元素是否可能存在if not rb.bfExists('mybloom', key):return None# 再從緩存中獲取數據data = r.get(key)if data is not None:if data == b'':return Nonereturn data.decode('utf-8')else:data = get_data_from_db(key)if data is None:r.setex(key, 60, '')else:r.setex(key, 3600, data)return data# 測試
print(get_data_with_bloom('existing_key'))
print(get_data_with_bloom('non_existing_key'))
上述代碼使用了 RedisBloom 模塊的布隆過濾器,在請求訪問緩存之前,先通過布隆過濾器判斷元素是否可能存在,從而減少不必要的數據庫訪問。