wordpress的數(shù)據(jù)庫在那里關(guān)鍵詞優(yōu)化建議
深入理解Redis緩存穿透、擊穿、雪崩及解決方案
- 一、簡介
- Redis 簡介
- 緩存作用與優(yōu)化
- 二、緩存問題的分類
- 緩存穿透
- 緩存擊穿
- 緩存雪崩
- 三、緩存穿透的解決方案
- 布隆過濾器
- 緩存空對象
- 接口層校驗
- 四、緩存擊穿的解決方案
- 互斥鎖
- 熱點數(shù)據(jù)提前加載
- 五、緩存雪崩的解決方案
- 增加緩存容錯能力
- 數(shù)據(jù)預(yù)熱
- 六、Redis針對以上問題的解決方案
- 多級緩存策略
- 主從復(fù)制與持久化
一、簡介
Redis 簡介
Redis是一個基于內(nèi)存的數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),是一個支持鍵值對、發(fā)布/訂閱、存儲新聞資訊的高性能key-value存儲數(shù)據(jù)庫。
緩存作用與優(yōu)化
緩存技術(shù)在Web開發(fā)中是比較重要的組成部分,常用于增強Web應(yīng)用的性能和容錯性。緩存通過將計算過的數(shù)據(jù)或提前讀出數(shù)據(jù)放置在高速緩存中,當請求相同數(shù)據(jù)時直接從緩存中響應(yīng)。因此,緩存對于加速應(yīng)用響應(yīng)時間、節(jié)省處理器資源等方面有著非常顯著的作用。
為了更好的利用緩存,需要對緩存問題進行分類和解決方案。
二、緩存問題的分類
緩存穿透
緩存穿透是指查詢一個不存在的數(shù)據(jù),由于緩存沒有數(shù)據(jù),請求被透傳到數(shù)據(jù)庫,此時如果惡意用戶不斷發(fā)起不存在數(shù)據(jù)的查詢,緩存就無法發(fā)揮效果,請求最終壓垮數(shù)據(jù)庫。這種情況需要對不存在數(shù)據(jù)加以處理,如使用Nginx緩存;、使用異常機制處理。
緩存擊穿
緩存擊穿是指對于一個存在的key,由于并發(fā)量大,同時失效,導(dǎo)致多個線程都去查詢數(shù)據(jù)庫,造成緩存擊穿。為了避免此類情況,可以令所有線程等待第一個查詢后再進行操作;或者使用互斥鎖等機制限制并發(fā)訪問。
緩存雪崩
緩存雪崩是指緩存中大量的key在同一時刻失效,導(dǎo)致瞬間有大量的請求直接訪問數(shù)據(jù)庫,嚴重影響數(shù)據(jù)庫的性能和應(yīng)用的穩(wěn)定性。為了解決這個問題,可以引入緩存預(yù)熱、設(shè)置不同的過期時間等措施。
三、緩存穿透的解決方案
布隆過濾器
布隆過濾器可以快速判斷一個元素是否存在于一個集合中,因此可以用來驗證請求的參數(shù)或者ID在數(shù)據(jù)庫中是否存在,從而有效防止惡意攻擊導(dǎo)致的緩存穿透。
import redis
from bitarray import bitarrayclass BloomFilter:def __init__(self, capacity, error_rate):self.capacity = capacityself.error_rate = error_rateself.redis_client = redis.Redis()self.hash_count = int(-1 * (capacity * math.log(error_rate) / (math.log(2) ** 2)))self.bit_array_length = int(math.ceil((capacity * math.log(error_rate)) / math.log(1.0 / (2 ** math.log(2)))))self.redis_client.setbit('bloom_filter', self.bit_array_length, 0)def exists(self, key):for i in range(self.hash_count):hashed_index = hash(key + str(i)) % self.bit_array_lengthif not self.redis_client.getbit('bloom_filter', hashed_index):return Falsereturn Truedef add(self, key):for i in range(self.hash_count):hashed_index = hash(key + str(i)) % self.bit_array_lengthself.redis_client.setbit('bloom_filter', hashed_index, 1)
緩存空對象
當查詢結(jié)果為空時,我們也可以將其緩存到Redis中,并給它一個較短的生命周期。這樣,下次如果同樣的查詢請求再次到達時,就可以直接從緩存中返回空結(jié)果,而不會穿透到數(shù)據(jù)庫。
def get_user_info(user_id):user_key = f'user:{user_id}'user_info = redis.get(user_key)if not user_info:# 從數(shù)據(jù)庫中查詢用戶信息,如果查不到標記為空并將結(jié)果緩存到Redis中user_info = db.query_user_info(user_id)if not user_info:redis.set(user_key, '', ex=60)else:redis.set(user_key, user_info, ex=3600)return user_info
接口層校驗
在應(yīng)用層或者接口層增加驗證機制,對于非法請求進行攔截??梢愿鶕?jù)請求的參數(shù)特征、請求頻率等信息進行識別,從而避免類似SQL注入攻擊等請求穿透緩存。
四、緩存擊穿的解決方案
互斥鎖
在需要大量更新緩存的場景下,我們通常需要使用互斥鎖來避免緩存擊穿。比如,可以使用Redis的SETNX命令設(shè)置標記,當發(fā)現(xiàn)緩存過期時,先去獲取鎖,然后再去加載數(shù)據(jù)并更新緩存,同時釋放該鎖。
def get_user_info(user_id):user_key = f'user:{user_id}'user_info = redis.get(user_key)if not user_info:lock_key = f'{user_id}_lock'# 使用SETNX命令嘗試獲取鎖,如果獲取成功if redis.setnx(lock_key, 1):# 設(shè)置鎖的超時時間避免死鎖redis.expire(lock_key, 60)user_info = db.query_user_info(user_id)redis.set(user_key, user_info, ex=3600)# 解鎖,刪除鎖標記redis.delete(lock_key)return user_info
熱點數(shù)據(jù)提前加載
在緩存過期前提前加載數(shù)據(jù),避免并發(fā)請求穿透直接訪問數(shù)據(jù)庫導(dǎo)致緩存擊穿??梢栽O(shè)置緩存過期時間略長于預(yù)加載時間,保證數(shù)據(jù)一定能夠預(yù)先加載到緩存中。
def preload_hot_data():hot_data_key = 'hot_data'hot_data = db.query_hot_data()redis.set(hot_data_key, hot_data, ex=600)def get_hot_data():hot_data_key = 'hot_data'hot_data = redis.get(hot_data_key)if not hot_data:# 預(yù)加載熱點數(shù)據(jù)到緩存preload_hot_data()hot_data = redis.get(hot_data_key)return hot_data
五、緩存雪崩的解決方案
增加緩存容錯能力
當大量緩存同時失效時,可以通過應(yīng)用多級緩存、加入容錯機制等手段防止緩存雪崩。具體實現(xiàn)可以根據(jù)業(yè)務(wù)場景進行選擇和調(diào)整。
數(shù)據(jù)預(yù)熱
盡可能在業(yè)務(wù)低峰期前將緩存數(shù)據(jù)全部加載到緩存系統(tǒng)中,從而避免業(yè)務(wù)高峰期緩存穿透、緩存擊穿導(dǎo)致的緩存雪崩??梢允褂枚〞r任務(wù)或者異步加載方式,將慢查詢或熱點數(shù)據(jù)提前加載到緩存中。
def preload_cache():user_keys = db.query_all_user_keys()for user_key in user_keys:user_info = db.query_user_info(user_key)redis.set(user_key, user_info, ex=3600)# 定時任務(wù),每天凌晨1點執(zhí)行一次數(shù)據(jù)預(yù)熱
scheduler.add_job(preload_cache, 'cron', hour='1')
六、Redis針對以上問題的解決方案
Redis是一款高性能的內(nèi)存數(shù)據(jù)庫,為了解決以上問題,它提出了以下兩種解決方案:
多級緩存策略
緩存中間件作為緩存系統(tǒng)的一種,主要用來提高網(wǎng)站并發(fā)量、降低網(wǎng)站響應(yīng)時間、減輕源站數(shù)據(jù)庫的壓力等。多級緩存指的是使用兩種及以上的緩存技術(shù)來加速響應(yīng)速度。
"""
多級緩存策略示例:
1.使用 Redis 作為一級緩存,存儲頻繁訪問的熱數(shù)據(jù);
2.使用 Memcached 作為二級緩存,存儲冷數(shù)據(jù)或者業(yè)務(wù)數(shù)據(jù);
3.使用本地緩存作為三級緩存,存儲session,減少請求量。
"""
import redis
import memcacheclass Cache:def __init__(self):self.redis = redis.Redis(host='localhost', port=6379)self.memcache = memcache.Client(['127.0.0.1:11211'])def get(self, key):# 嘗試從 Redis 中獲取數(shù)據(jù)data = self.redis.get(key)if not data:# Redis 中沒有該數(shù)據(jù),嘗試從 Memcached 中獲取data = self.memcache.get(key)if not data:# Memcached 中也沒有,則從本地緩存中獲取data = self.local_cache.get(key)return datadef set(self, key, data):# 三個緩存都存儲數(shù)據(jù)self.redis.set(key, data)self.memcache.set(key, data)self.local_cache.set(key, data)
主從復(fù)制與持久化
為了提高 Redis 的穩(wěn)定性和可靠性,Redis 提供了主從復(fù)制和持久化機制。主從復(fù)制指的是數(shù)據(jù)備份,將 Redis 數(shù)據(jù)庫的數(shù)據(jù)復(fù)制到一臺或多臺 Redis 實例上,以實現(xiàn)讀寫分離及容災(zāi)恢復(fù);持久化指的是通過 RDB 和 AOF 兩種方式把內(nèi)存中的數(shù)據(jù)保存到磁盤中,保證數(shù)據(jù)在 Redis 重啟后依舊存在。
"""
主從復(fù)制示例:
1.創(chuàng)建一個 Redis 實例,將其設(shè)置為主服務(wù)器;
2.創(chuàng)建兩個 Redis 實例,將其分別設(shè)置為從服務(wù)器;
3.從主服務(wù)器同步數(shù)據(jù)到兩個從服務(wù)器。
"""import redis# 創(chuàng)建 Redis 實例,作為主服務(wù)器
redis_master = redis.Redis(host='192.168.1.100', port=6379)# 創(chuàng)建兩個 Redis 實例,作為從服務(wù)器
redis_slave1 = redis.Redis(host='192.168.1.101', port=6379)
redis_slave2 = redis.Redis(host='192.168.1.102', port=6379)# 將從服務(wù)器連接到主服務(wù)器上,實現(xiàn)主從復(fù)制
redis_slave1.slaveof(redis_master.host, redis_master.port)
redis_slave2.slaveof(redis_master.host, redis_master.port)# 可以通過以下命令查看主從狀態(tài)
# redis-cli info replication