經常用Redis,這些坑你知道嗎?

2020-12-25 騰訊網

文章轉載自二馬讀書,作者二馬讀書

作者簡介:曾任職於阿里巴巴,每日優鮮等網際網路公司,任技術總監,15年電商網際網路經歷。

近些年,Redis憑藉在性能、穩定性和高可擴展性上的卓越表現,基本上已經成了網際網路行業緩存中間件的標配,甚至很多傳統行業也在使用Redis。那麼我們在使用Redis等緩存中間件時,要注意哪些問題呢?本文咱們就來聊聊,我們使用緩存中間件過程中曾經遇到的坑!

緩存穿透

先看一個常見的緩存使用方式。請求來了,先查緩存,緩存有值就直接返回;緩存沒值,查資料庫,然後把資料庫的值存到緩存,再返回。

假如緩存沒查到某個值,查資料庫也沒這個值,也就是說要查的值根本不存在,這樣就會導致每次對這個值的查詢請求都會穿透到資料庫。這就是所謂的「緩存穿透」。

如何避免緩存穿透?

如果從資料庫中沒查到值,可以在緩存中記錄一個空值,來避免「緩存穿透」。並且要給這個空值設置一個較短的過期時間。

比如說,我們經常會把用戶信息緩存到Redis。如果調用方傳了一個不存在的UserID,在緩存中就查不到這個用戶信息,然後去DB也查不到。這樣就會導致,每次根據這個UserID查用戶信息,都會穿透到資料庫,給資料庫造成了壓力。為了避免緩存穿透,當資料庫查不到時,我們可以在緩存中記錄一條空數據,比如userID做為key,空json做為值,如果程序獲得這個空json,就按用戶不存在處理。再給這個key設置一個很短的過期時間,比如30秒。

緩存雪崩

我們經常會遇到需要初始化緩存的情況。比如說用戶系統重構,表結構發生了變化,緩存信息也要變,上線前需要初始化緩存,將用戶信息批量存入緩存。假如我們給這些用戶信息設置相同的過期時間,到過期時間點所有用戶信息的緩存記錄就會同時集中失效,導致大量請求瞬間打到資料庫,資料庫很可能會被搞掛。這種緩存集中失效,導致大量請求同時穿透到資料庫的情況,就是所謂的「雪崩效應」。

所以,當我們向緩存初始化數據時,要保證每個緩存記錄過期時間的離散性。可以採用一個較大的固定值加上一個較小的隨機值。比如過期時間可以是:10小時 + 0到3600秒的隨機值。

緩存並發

當系統並發很高,緩存數據尤其是熱點數據過期後,可能會出現多個請求同時訪問資料庫並設置緩存的情況,不但給資料庫帶來壓力,而且會有緩存頻繁更新的問題。

我們可以通過加鎖來避免緩存並發問題。如果從緩存查不到數據,對查詢數據加分布式鎖,然後查資料庫並把資料庫查詢結果放入緩存。其他線程等待鎖釋放後,直接從緩存取值。

比如,電商系統會緩存商品SKU價格,一些熱點商品的並發訪問會非常高。當緩存過期失效後,訪問請求從緩存查不到記錄,此時可以用商品SKU ID為Key加分布式鎖,然後從資料庫查詢價格並把價格放入緩存,最後解鎖。解鎖後其他請求就可以從緩存直接取值了。從而避免了資料庫的壓力。

分布式鎖

以我們之前做過的5人拼團為例。如果有用戶參加團購,我們需要先校驗參團人數是否達到了上限5人。如果沒達到5人,用戶才可以參團。偽代碼如下:

//根據拼團ID獲取目前參團成員數量int numOfMembers = pinTuanService.getNumOfMembersById(pinTuanID);if(numOfMembers

高並發場景下,上面的代碼會有很嚴重的問題。如果某個團當前的參團人數是4,這時有兩個用戶同時參團,用戶A和用戶B的請求同時進入上面的代碼塊,A和B的請求同時執行到第2行代碼,獲取的numOfMembers都是4,表達式 numOfMembers

boolean aquired = distributedLock.aquireLock(pinTuanID, 3000);if(aquired == true) { try{ //根據拼團ID獲取目前參團成員數量 int numOfMembers = pinTuanService.getNumOfMembersById(pinTuanID); if(numOfMembers

這樣就好多啦!接下來我們看看基於Redis分布式鎖的實現,以及特別要注意的問題。一般我們會基於setnx實現Redis分布式鎖。setnx命令可以檢查key是否存在,如果key不存在,就在Redis中創建一個鍵值對(操作成功),如果key已經存在就放棄執行(操作失敗)。

先看一段基於Springboot實現的加鎖和釋放鎖的代碼:

@Componentpublic class DistributedLock { @Autowired private StringRedisTemplate redisTemplate; /** * 加鎖 * lockKey,redis的key * expireTime,過期時間,單位是毫秒 * 註:setIfAbsent方法就使用了redis的setnx */ public boolean aquireLock(String lockKey, long expireTime) { long waitTime = 0; boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, "distributedLock", expireTime, TimeUnit.MILLISECONDS); if(success == true){ return success; } else { //如果加鎖失敗,循環重試加鎖 while(success != true && waitTime

上面的代碼。乍一看,好像沒什麼問題!加鎖失敗有循環重試加鎖,過期時間設置了,而且也保證了創建Key-Value鍵值對和設置過期時間的原子性,這樣當程序沒有正常釋放鎖時,也能保證過期後鎖自動釋放(注意:redis較老的版本不支持 setnx 和設置過期時間的原子操作,不過可以利用Lua腳本來保證原子性)。

我們再仔細思考一下,一般場景我們會對Key設置一個很短的過期時間,當一次操作因為網絡等原因耗費了較長時間,操作還沒完成key就過期失效了。這樣會產生什麼問題呢?我們還是以拼團為例加以說明,先看看下面這張圖:

如上圖,用戶A和用戶B同時參加同一團,團ID為 001,我們以團ID作為分布式鎖的Key,"distributedLock" 作為固定的Value,過期時間是5秒。A先獲取分布式鎖,但是由於網絡等原因A的拼團操作在5秒內沒完成,這時Key過期並從Redis清除掉,A的分布式鎖失效。此時用戶B拿到分布式鎖,Key也同樣是團ID 001。在用戶B的拼團邏輯執行完之前,用戶A的邏輯先執行完了,緊接著A就把鎖給釋放了。不過A的鎖早已經過期失效了,B持有鎖的Key和A又完全一樣,所以此時A釋放的其實是B的鎖。這樣一來整個拼團還是有可能會超員。怎麼解決呢?

我們可以把分布式鎖的Value設成可以區分的值,比如拼團的場景Value可以設置為userID,在釋放鎖的時候根據key和value來判斷當前的鎖是不是自己的,只有Redis中userID和自己的userID相同才釋放鎖。

改進後的代碼如下:

@Componentpublic class DistributedLock { @Autowired private StringRedisTemplate redisTemplate; /** * 加鎖 * lockKey,redis的key * expireTime,過期時間,單位是毫秒 * 註:setIfAbsent方法就使用了redis的setnx */ public boolean aquireLock(String lockKey, String userID, long expireTime) { long waitTime = 0; boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, userID, expireTime, TimeUnit.MILLISECONDS); if(success == true){ return success; } else { //如果加鎖失敗,循環重試加鎖 while(success != true && waitTime

還有一種場景需要考慮。當Redis master發生故障,主備切換時往往會造成數據丟失,包括分布式鎖的Key-Value 也可能丟失。這樣就會導致操作還沒執行完,鎖就被其他請求拿到了。Redis官方提供了Redlock算法,以及相應的開源實現 Redisson。用到分布式鎖的場景,大家可以直接使用 Redisson,非常方便。如果系統對可靠性要求很高,如需用到分布式鎖,建議使用 Zookeeper,etcd 等。

OK,就分享到這。如果感覺本文對您有幫助,有勞點下在看,分享知識是美德哦

相關焦點

  • 基於Redis實現分布式鎖之前,這些坑你一定得知道
    開頭基於Redis的分布式鎖對大家來說並不陌生,可是你的分布式鎖有失敗的時候嗎?在失敗的時候可曾懷疑過你在用的分布式鎖真的靠譜嗎?以下是結合自己的踩坑經驗總結的一些經驗之談。你真的需要分布式鎖嗎?因為每次只有最大的token能寫,這樣storage的訪問就是線性的,在高並發場景下,這種方式會極大的限制吞吐量,而分布式鎖也大多是在這種場景下用的,很矛盾的設計。這是所有分布式鎖的問題。這個方案是一個通用的方案,可以和Redlock用,也可以和其他的lock用。所以我理解僅僅是一個和Redlock無關的解決方案。
  • 你知道為什麼要用Redis嗎
    沒用過redis,都不好意思當面試官。接下來,你來當面試官。看面試者對Redis了解多少。問題一:Redis為什麼用的人那麼多?有可能好多人會說因為速度快。沒錯哈,redis最顯著的特徵就在於它的速度問題二:到底有多快呢?
  • 伺服器安裝了redis之後,php怎麼使用redis,你知道嗎?
    既然你已經在伺服器中安裝了redis了,那你怎麼用PHP來操作redis呢?把數據保存到redis中。首先,你需要下載一個redis擴展,也就是stable版(穩定版)擴展。你可以到到pecl.php.net 搜索redis 下載擴展。1.下載完文件之後,解壓了它。
  • 你知道CPU結構也會影響Redis性能嗎?
    今天來分析下CPU結構對Redis性能會有影響嗎?在進行Redis性能分析的時候,通常我們會考慮下面這些方面,如:1. 縮短 key 的長度 2.禁止使用 keys *我們都知道 keys *, 在使用的時候 Redis 會處於阻塞狀態,導致其它任何命令在你的 Redis 實例中都無法執行。這個情況在 Redis 數據量大的時候就很明顯,嚴重影響系統的運行。(一般我們用 scan 來代替) 3.
  • 想要學習redis cluster?這些場景和問題你必須知道如何處理
    那就搭建主從複製的架構(redis replication),一個mater,多個slave,要幾個slave跟你要求的讀吞吐量有關係,然後自己搭建一個sentinal集群,去保證redis主從架構的高可用性,就可以了。
  • 手機維修的這些「暗語」你知道嗎?學會這些「行話」,避免被坑!
    手機維修的這些「暗語」你知道嗎?學會這些「行話」,避免被坑! 2020年06月16日 16:10作者:黃頁編輯:黃頁 手機維修的這些「暗語
  • 信用卡這些坑,你知道嗎?別再被銀行騙了!
    多數人在用信用卡時一般都只會注意帳單日和還款期限。 很少會有人關注銀行的息費計算,因為相對於用款本金來說,息費看似微乎其微,很容易被忽略。 但以下幾種「坑」你需要特別注意,當心越還越多哦~
  • 秋招衝刺,Redis、MySQL是你的坎嗎?阿里+華為+騰訊+百度+拼多多
    大廠永遠是程式設計師夢想的地方,俗話說的話「不想當將軍的士兵不是好士兵」,不想進大廠的程式設計師是沒有夢想的金九銀十即將到來相信有許多小夥伴們都已馬不停蹄的在準備各大廠的秋招提前批了吧,不知redis與mysql會不會成為你的坎?
  • 選購組裝機電腦防坑技巧:這些你知道嗎?
    組裝機電腦選購防坑有哪些技巧?下面聽李叔一一道來吧。你購買過某寶和某東的電腦主機嗎?整機和「洋垃圾」到底能不能買?當我看到那些低廉的價格,誇張唬人的廣告詞,也許你現在還在考慮,也許你有購買的衝動。但是請你繼續往下看。
  • 購買實木家具的這些坑你知道嗎
    很多人去家具市場或者在網上買實木家具,問的最多的就是「老闆,這是純實木家具嗎?得到的回答往往都是肯定的,「是的,放心買吧"。到拿回來一看,往往這些老闆們說的「純實木家具」原來只是貼皮的家具,中間都是人造板,或者是碾壓板。很顯,我們被坑了,那麼我們買實木家具都有哪些坑,如何那避免這些坑呢?
  • 你不知道的redis三-Redis的持久化機制
    一、持久化我們前兩章已經講了,redis是內存型的資料庫,他之所以快是因為數據存儲在內存。那麼數據存儲在內存會有什麼問題呢?當然就是當服務重啟或者伺服器宕機內存數據就被清除,我們就無法訪問之前存儲的數據了。那麼怎麼解決這個問題呢?
  • RedisDesktopManager 的備胎你知道嗎?
    Redis 可視化客戶端工具,大部分程式設計師使用的是 Redis Desktop Manager,確實非常好用。2、查看 Redisredis>3、Redis 增刪改查右鍵可新增 redis二、AnotherRedisDesktopManager對,你沒看錯
  • 緩存架構技術:Redis+MongDB,阿里P7面試必跳的坑
    億級流量Redis實戰看了這些個技術點,那麼我現在就來考考你一些Redis面試問題(準備好,接招吧):為什麼要用redis?/為什麼要用緩存?為什麼要用redis而不用map/guava做緩存?如何保證緩存與資料庫雙寫時的數據一致性?redis 常見數據結構以及使用場景分析?(String/Hash/List/Set/Sorted Set)redis如何設置過期時間?
  • 網易架構師心得:Springboot下使用redis踩過的坑
    本文分享網易架構師在spring boot下使用redis的心得。首先總結了redis服務端單線程工作模型,redis四種部署方式及使用場景,然後從源碼的角度上,分析springboot在jedis和lettuce客戶端下使用redis的一些坑~尤其是在集群模式下的一些不兼容問題!
  • redis是什麼?有什麼用
    redis是什麼redis是一個高性能的key-value資料庫,它是完全開源免費的,而且redis是一個NOSQL類型資料庫,是為了解決高並發、高擴展,大數據存儲等一系列的問題而產生的資料庫解決方案,是一個非關係型的資料庫。
  • Redis之父走了,但Redis可涼不了
    如果你能輕鬆搞定這些問題,大廠面試不過是小場面!1.Redis支持的數據類型?2.什麼是Redis持久化?Redis有哪幾種持久化方式?優缺點是什麼?3.Redis 有哪些架構模式?講講各自的特點4.使用過Redis分布式鎖麼,它是怎麼實現的?
  • 「面試」吃透了這些Redis知識點,面試官一定覺得你很NB
    這個多key命令的請求被發送到一個節點上,這裡有一個潛在的問題,不知道大家有沒有想到,就是這個命令裡的多個key一定都位於那同一個節點上嗎?就分為兩種情況了,如果多個key不在同一個節點上,此時節點只能返回重定向錯誤了,但是多個key完全可能位於多個不同的節點上,此時返回的重定向錯誤就會非常亂,所以redis集群選擇不支持此種情況。
  • Redis的那些事兒
    現在作為一個程式設計師,如果你不了解redis,或者沒有使用過redis,我估計你都不好意思出去找工作,即使你有臉去找工作,我估計面試官也沒臉讓你進入公司。Redis是什麼?為什麼幾乎100%的面試都會或多或少的提到它。為什麼很多大公司都要求你會使用redis。
  • Redis為什麼快?你只知道內存和單線程?抱歉我不能要你...
    但是,一場面試少說都是半小時起步上不封頂,你這樣一句話就回答了這麼重要的五個知識點,這個結果是你想要的麼?是面試官想要的麼?我再問你一個問題,你可能就懵逼了:String在Redis底層是怎麼存儲的?這些數據類型在Redis中是怎麼存放的?Redis快的原因就只有單線程和基於內存麼?
  • 職場中程式設計師必須知道的redis哈希槽原理
    分布式存儲系統中,如何將整個數據集按一定規則分配給多個節點的規則有很多,上一章介紹了簡單hash取模算法和一致性哈希算法,但是redis集群的分片路由規則卻沒有使用一致性hash策略,採用的是虛擬哈希槽策略那,就來聊聊redis集群的虛擬哈希槽 吧。