redis緩存

緩存架構(gòu)

腦中的直觀反應(yīng)


計算機(jī)體系結(jié)構(gòu)中的緩存

多級緩存

  • SQLAlchemy起到一定的本地緩存作用
    在同一請求中多次相同的查詢只查詢數(shù)據(jù)庫一次缰趋,SQLAlchemy做了本地緩存(類似Django中的Queryset查詢結(jié)果集)
  • 使用Redis構(gòu)建一層緩存

緩存數(shù)據(jù)

緩存數(shù)據(jù)的類型
  • 一個數(shù)值
    例如
    • 驗證碼
    • 用戶狀態(tài)
      如:user:{user_id}: enable
  • 數(shù)據(jù)庫記錄
    • Caching at the object level
      以數(shù)據(jù)庫對象的角度考慮踪央, 應(yīng)用更普遍
      例如, 用戶的基本信息
    user = User.query.filter_by(id=1).first()
    user -> User對象
    {
    'user_id':1,
    'user_name': 'python',
    'age': 28,
    'introduction': ''
    }
    
    • Caching at the database query level
      以數(shù)據(jù)庫查詢的角度考慮丰包,應(yīng)用場景較特殊顶霞,一般僅針對較復(fù)雜的查詢進(jìn)行使用
    query_result = User.query.join(User.profile).filter_by(id=1).first() 
    -> sql = "select a.user_id, a.user_name, b.gender, b.birthday from tbl_user as a inner join tbl_profile as b on a.user_id=b.user_id where a.user_id=1;"
    # hash算法 md5
    query = md5(sql)  # 'fwoifhwoiehfiowy23982f92h929y3209hf209fh2'
    
    # redis 
    setex(query, expiry, json.dumps(query_result))
    
  • 一個視圖的響應(yīng)結(jié)果
@route('/articles')
@cache(exipry=30*60)
def get_articles():
    ch = request.args.get('ch')
    articles = Article.query.all()
    for article in articles:
       user = User.query.filter_by(id=article.user_id).first()
       comment = Comment.query.filter_by(article_id=article.id).all()
       results = {...} # 格式化輸出
    return results
# redis
# '/artciels?ch=1':  json.dumps(results)
  • 一個頁面
@route('/articles')
@cache(exipry=30*60)
def get_articles():
   ch = request.args.get('ch')
   articles = Article.query.all()
   for article in articles:
       user = User.query.filter_by(id=article.user_id).first()
       comment = Comment.query.all()
   results = {...}
   return render_template('article_temp', results)
#  redis
# '/artciels?ch=1':  html
緩存數(shù)據(jù)的保存方式
  • 序列化字符串
    # 序列化  json字符
    # setex('user:{user_id}:info')
    setex('user:1:info', expiry, json.dumps(user_dict))
    
    • 優(yōu)點(diǎn)
      • 存儲字符串節(jié)省空間
    • 缺點(diǎn)
      • 序列化有時間開銷
      • 更新不方便(一般直接刪除)
  • Redis的其他數(shù)據(jù)類型,如hash光绕、set、zset
    hmset('user:1:info', user_dict)
    
    • 優(yōu)點(diǎn)
      • 讀寫時不需要序列化轉(zhuǎn)換
      • 可以更新內(nèi)部數(shù)據(jù)
    • 缺點(diǎn)
      • 相比字符串畜份,采用復(fù)合結(jié)構(gòu)存儲空間占用大

緩存有效期與淘汰策略

有效期 TTL (Time to live)

設(shè)置有效期的作用:

1.節(jié)省空間
2.做到數(shù)據(jù)弱一致性诞帐,有效期失效后,可以保證數(shù)據(jù)的一致性

Redis的過期策略

過期策略通常有以下三種:

  • 定時過期
    每個設(shè)置過期時間的key都需要創(chuàng)建一個定時器爆雹,到過期時間就會立即清除停蕉。該策略可以立即清除過期的數(shù)據(jù),對內(nèi)存很友好钙态;但是會占用大量的CPU資源去處理過期的數(shù)據(jù)慧起,從而影響緩存的響應(yīng)時間和吞吐量。
     setex('a', 300, 'aval')
     setex('b', 600, 'bval')
    
  • 惰性過期
    只有當(dāng)訪問一個key時驯绎,才會判斷該key是否已過期完慧,過期則清除谋旦。該策略可以最大化地節(jié)省CPU資源剩失,卻對內(nèi)存非常不友好。極端情況可能出現(xiàn)大量的過期key沒有再次被訪問册着,從而不會被清除拴孤,占用大量內(nèi)存。
  • 定期過期
    每隔一定的時間甲捏,會掃描一定數(shù)量的數(shù)據(jù)庫的expires字典中一定數(shù)量的key演熟,并清除其中已過期的key。該策略是前兩者的一個折中方案。通過調(diào)整定時掃描的時間間隔和每次掃描的限定耗時芒粹,可以在不同情況下使得CPU和內(nèi)存資源達(dá)到最優(yōu)的平衡效果兄纺。

expires字典會保存所有設(shè)置了過期時間的key的過期時間數(shù)據(jù),其中化漆,key是指向鍵空間中的某個鍵的指針估脆,value是該鍵的毫秒精度的UNIX時間戳表示的過期時間。鍵空間是指該Redis集群中保存的所有鍵座云。

Redis中同時使用了惰性過期和定期過期兩種過期策略疙赠。

Redis過期刪除采用的是定期刪除,默認(rèn)是每100ms檢測一次朦拖,遇到過期的key則進(jìn)行刪除圃阳,這里的檢測并不是順序檢測,而是隨機(jī)檢測璧帝。那這樣會不會有漏網(wǎng)之魚捍岳?顯然Redis也考慮到了這一點(diǎn),當(dāng)我們?nèi)プx/寫一個已經(jīng)過期的key時睬隶,會觸發(fā)Redis的惰性刪除策略祟同,直接回干掉過期的key

為什么不用定時刪除策略?

定時刪除,用一個定時器來負(fù)責(zé)監(jiān)視key,過期則自動刪除。雖然內(nèi)存及時釋放理疙,但是十分消耗CPU資源晕城。在大并發(fā)請求下,CPU要將時間應(yīng)用在處理請求窖贤,而不是刪除key,因此沒有采用這一策略.

定期刪除+惰性刪除是如何工作的呢?

定期刪除砖顷,redis默認(rèn)每個100ms檢查,是否有過期的key,有過期key則刪除赃梧。需要說明的是滤蝠,redis不是每個100ms將所有的key檢查一次,而是隨機(jī)抽取進(jìn)行檢查(如果每隔100ms,全部key進(jìn)行檢查授嘀,redis豈不是卡死)物咳。因此,如果只采用定期刪除策略蹄皱,會導(dǎo)致很多key到時間沒有刪除览闰。
于是,惰性刪除派上用場巷折。也就是說在你獲取某個key的時候压鉴,redis會檢查一下,這個key如果設(shè)置了過期時間那么是否過期了锻拘?如果過期了此時就會刪除油吭。

采用定期刪除+惰性刪除就沒其他問題了么?

不是的击蹲,如果定期刪除沒刪除key。然后你也沒即時去請求key婉宰,也就是說惰性刪除也沒生效歌豺。這樣,redis的內(nèi)存會越來越高心包。那么就應(yīng)該采用內(nèi)存淘汰機(jī)制世曾。

緩存淘汰 eviction

Redis自身實現(xiàn)了緩存淘汰
Redis的內(nèi)存淘汰策略是指在Redis的用于緩存的內(nèi)存不足時,怎么處理需要新寫入且需要申請額外空間的數(shù)據(jù)谴咸。

  • noeviction:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時轮听,新寫入操作會報錯。
  • allkeys-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時岭佳,在鍵空間中血巍,移除最近最少使用的key。
  • allkeys-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時珊随,在鍵空間中述寡,隨機(jī)移除某個key。
  • volatile-lru:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時叶洞,在設(shè)置了過期時間的鍵空間中鲫凶,移除最近最少使用的key。
  • volatile-random:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時衩辟,在設(shè)置了過期時間的鍵空間中螟炫,隨機(jī)移除某個key。
  • volatile-ttl:當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時艺晴,在設(shè)置了過期時間的鍵空間中昼钻,有更早過期時間的key優(yōu)先移除。

redis 4.x 后支持LFU策略封寞,最少頻率使用

  • allkeys-lfu
  • volatile-lfu

LRU

LRU(Least recently used然评,最近最少使用)
LRU算法根據(jù)數(shù)據(jù)的歷史訪問記錄來進(jìn)行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問過狈究,那么將來被訪問的幾率也更高”碗淌。
基本思路
1.新數(shù)據(jù)插入到列表頭部;
2.每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問)抖锥,則將數(shù)據(jù)移到列表頭部亿眠;
3.當(dāng)列表滿的時候,將列表尾部的數(shù)據(jù)丟棄宁改。

LFU

LFU(Least Frequently Used 最近最少使用算法)
它是基于“如果一個數(shù)據(jù)在最近一段時間內(nèi)使用次數(shù)很少缕探,那么在將來一段時間內(nèi)被使用的可能性也很小”的思路。


LFU需要定期衰減还蹲。

Redis淘汰策略的配置
  • maxmemory 最大使用內(nèi)存數(shù)量
  • maxmemory-policy noeviction 淘汰策略

緩存模式

1.Cache Aside

更新方式

  • 先更新數(shù)據(jù)庫,再更新緩存。這種做法最大的問題就是兩個并發(fā)的寫操作導(dǎo)致臟數(shù)據(jù)谜喊。如下圖(以Redis和Mysql為例)潭兽,兩個并發(fā)更新操作,數(shù)據(jù)庫先更新的反而后更新緩存斗遏,數(shù)據(jù)庫后更新的反而先更新緩存山卦。這樣就會造成數(shù)據(jù)庫和緩存中的數(shù)據(jù)不一致,應(yīng)用程序中讀取的都是臟數(shù)據(jù)诵次。
  • 先刪除緩存账蓉,再更新數(shù)據(jù)庫。這個邏輯是錯誤的逾一,因為兩個并發(fā)的讀和寫操作導(dǎo)致臟數(shù)據(jù)铸本。如下圖(以Redis和Mysql為例)。假設(shè)更新操作先刪除了緩存遵堵,此時正好有一個并發(fā)的讀操作箱玷,沒有命中緩存后從數(shù)據(jù)庫中取出老數(shù)據(jù)并且更新回緩存,這個時候更新操作也完成了數(shù)據(jù)庫更新陌宿。此時锡足,數(shù)據(jù)庫和緩存中的數(shù)據(jù)不一致,應(yīng)用程序中讀取的都是原來的數(shù)據(jù)(臟數(shù)據(jù))壳坪。
  • 先更新數(shù)據(jù)庫舶得,再刪除緩存。這種做法其實不能算是坑爽蝴,在實際的系統(tǒng)中也推薦使用這種方式扩灯。但是這種方式理論上還是可能存在問題。如下圖(以Redis和Mysql為例)霜瘪,查詢操作沒有命中緩存珠插,然后查詢出數(shù)據(jù)庫的老數(shù)據(jù)。此時有一個并發(fā)的更新操作颖对,更新操作在讀操作之后更新了數(shù)據(jù)庫中的數(shù)據(jù)并且刪除了緩存中的數(shù)據(jù)捻撑。然而讀操作將從數(shù)據(jù)庫中讀取出的老數(shù)據(jù)更新回了緩存。這樣就會造成數(shù)據(jù)庫和緩存中的數(shù)據(jù)不一致缤底,應(yīng)用程序中讀取的都是原來的數(shù)據(jù)(臟數(shù)據(jù))顾患。

但是,仔細(xì)想一想个唧,這種并發(fā)的概率極低江解。因為這個條件需要發(fā)生在讀緩存時緩存失效,而且有一個并發(fā)的寫操作徙歼。實際上數(shù)據(jù)庫的寫操作會比讀操作慢得多犁河,而且還要加鎖鳖枕,而讀操作必需在寫操作前進(jìn)入數(shù)據(jù)庫操作,又要晚于寫操作更新緩存桨螺,所有這些條件都具備的概率并不大宾符。但是為了避免這種極端情況造成臟數(shù)據(jù)所產(chǎn)生的影響,我們還是要為緩存設(shè)置過期時間灭翔。

2.Read-through 通讀
3.Write-through 通寫
4.Write-behind caching
示例方案
  • 使用Read-throught + Cache aside
    • 構(gòu)建一層抽象出來的緩存操作層魏烫,負(fù)責(zé)數(shù)據(jù)庫查詢和Redis緩存存取,在Flask的視圖邏輯中直接操作緩存層工具肝箱。
  • 更新采用先更新數(shù)據(jù)庫哄褒,再刪除緩存

緩存問題

1緩存穿透

緩存只是為了緩解數(shù)據(jù)庫壓力而添加的一層保護(hù)層,當(dāng)從緩存中查詢不到我們需要的數(shù)據(jù)就要去數(shù)據(jù)庫中查詢了煌张。如果被黑客利用呐赡,頻繁去訪問緩存中沒有的數(shù)據(jù),那么緩存就失去了存在的意義唱矛,瞬間所有請求的壓力都落在了數(shù)據(jù)庫上罚舱,這樣會導(dǎo)致數(shù)據(jù)庫連接異常。

解決方案:
1.約定:對于返回為NULL的依然緩存绎谦,對于拋出異常的返回不進(jìn)行緩存,注意不要把拋異常的也給緩存了管闷。采用這種手段的會增加我們緩存的維護(hù)成本,需要在插入緩存的時候刪除這個空緩存窃肠,當(dāng)然我們可以通過設(shè)置較短的超時時間來解決這個問題包个。



2.制定一些規(guī)則過濾一些不可能存在的數(shù)據(jù),小數(shù)據(jù)用BitMap冤留,大數(shù)據(jù)可以用布隆過濾器碧囊,比如你的訂單ID 明顯是在一個范圍1-1000,如果不是1-1000之內(nèi)的數(shù)據(jù)那其實可以直接給過濾掉纤怒。


緩存雪崩

緩存雪崩是指緩存不可用或者大量緩存由于超時時間相同在同一時間段失效糯而,大量請求直接訪問數(shù)據(jù)庫,數(shù)據(jù)庫壓力過大導(dǎo)致系統(tǒng)雪崩泊窘。


解決方案:
1熄驼、給緩存加上一定區(qū)間內(nèi)的隨機(jī)生效時間,不同的key設(shè)置不同的失效時間烘豹,避免同一時間集體失效瓜贾。比如以前是設(shè)置10分鐘的超時時間,那每個Key都可以隨機(jī)8-13分鐘過期携悯,盡量讓不同Key的過期時間不同祭芦。
2、采用多級緩存憔鬼,不同級別緩存設(shè)置的超時時間不同龟劲,及時某個級別緩存都過期胃夏,也有其他級別緩存兜底。
3咸灿、利用加鎖或者隊列方式避免過多請求同時對服務(wù)器進(jìn)行讀寫操作构订。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侮叮,一起剝皮案震驚了整個濱河市避矢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囊榜,老刑警劉巖审胸,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異卸勺,居然都是意外死亡砂沛,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門曙求,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碍庵,“玉大人,你說我怎么就攤上這事悟狱【苍。” “怎么了?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵挤渐,是天一觀的道長苹享。 經(jīng)常有香客問我,道長浴麻,這世上最難降的妖魔是什么得问? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮软免,結(jié)果婚禮上宫纬,老公的妹妹穿的比我還像新娘。我一直安慰自己膏萧,他們只是感情好漓骚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著向抢,像睡著了一般认境。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挟鸠,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天叉信,我揣著相機(jī)與錄音,去河邊找鬼艘希。 笑死硼身,一個胖子當(dāng)著我的面吹牛硅急,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播佳遂,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼营袜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了丑罪?” 一聲冷哼從身側(cè)響起荚板,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吩屹,沒想到半個月后跪另,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煤搜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年免绿,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擦盾。...
    茶點(diǎn)故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡嘲驾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迹卢,到底是詐尸還是另有隱情辽故,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布婶希,位于F島的核電站榕暇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏喻杈。R本人自食惡果不足惜彤枢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望筒饰。 院中可真熱鬧缴啡,春花似錦、人聲如沸瓷们。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谬晕。三九已至碘裕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間攒钳,已是汗流浹背帮孔。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人文兢。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓晤斩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親姆坚。 傳聞我的和親對象是個殘疾皇子澳泵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,884評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 五種數(shù)據(jù)結(jié)構(gòu)簡介 Redis是使用C編寫的,內(nèi)部實現(xiàn)了一個struct結(jié)構(gòu)體redisObject對象兼呵,通過結(jié)構(gòu)體...
    彥幀閱讀 6,944評論 0 14
  • 使用緩存是系統(tǒng)性能優(yōu)化的第一黃金法則兔辅。 緩存的設(shè)計和使用對一個系統(tǒng)的性能至關(guān)重要,平時接觸到項目無論多少也都會在某...
    刀刃丿閱讀 1,342評論 0 6
  • 一萍程、Redis 1幢妄、概述 Redis是速度非惩醚觯快的非關(guān)系型內(nèi)存鍵值數(shù)據(jù)庫茫负,可以存儲鍵和物種不同類型的值之間的映射。...
    落地生涯閱讀 781評論 0 3
  • Redis是一個 Key-Value 存儲系統(tǒng)。和 Memcached 類似榕吼,它支持存儲的 value 類型相對更...
    vivi_wong閱讀 13,348評論 2 4
  • 作者:13GitHub:https://github.com/ZHENFENG13版權(quán)聲明:本文為原創(chuàng)文章饿序,未經(jīng)允...
    叫我十三吧閱讀 714評論 1 3