距離上次寫博客有兩三個月了,這段時間去了新公司上班,忙了很多。接手了一個項目,剛好用到redis,先總結下遇到的問題(跟redis相關的問題):
1、列表問題
舉例:展示商品列表,但是要先展示運營置頂的數據,如果排序的序號一樣,則按照id降序排序,就是需要按照sort asc, id desc 來排序;用redis怎么處理?
【分析】
首先這個問題可能本身有點問題,因為如果限定了redis,那么處理的方法就給限定死了,當時由於一股勁想着用redis處理,而忘了去想下redis是否適合處理這種問題;
第二,講下一開始是怎么用redis處理的:
1)商品列表存儲起來在有序集合,按照sort字段排序比如有序集合goods-by-sort中數據:
商品id (member) , value(score)
1 1
2 1
3 3
4 2
2)每當創建新商品,加入此隊列,sort值是默認值;
3)每當運營在管理后台修改sort值,則修改此有序集合中對應商品的score值;
4)刪除商品,或者設置不可見,則從這個有序集合中刪掉該商品id的數據;
5)用戶獲取商品列表時,因為需要按照sort和id排序,所以我當時再新增一個有序集合: goods_list_data,首先從goods_by_sort取出數據,在程序里面重新排序,然后寫到goods_temp有序集合,然后rename為goods_list_data有序集合,然后給這個集合設置一個過期時間,比如2分鍾。
6)針對上面第2)到第4)步可能會對goods_by_sort有序集合的數據進行調整,比如修改、新增和刪除,那么goods_list_data數據也需要更新,否則用戶會一直看到被刪除的商品。所以上面這三種情況,我會去更新一個string類型的refresh_goods_data 的key,每次去incr。用一個定時任務,每分鍾一次去檢查這個值,如果不為0,那么就去更新goods_list_data,然后設置refresh_goods_data的值為0,。否則則不處理,因為goods_list_data有序集合沒改動。
【上面這么做的問題】
1)耦合很深,不好維護;搞了2個有序集合,還有定時任務;
2)會有無底洞問題:有序集合存儲的數據會越來越多,當然這個可以根據業務處理,比如裁剪,但是維護這個有序集合也是個問題,增刪改都要做相應維護。
【比較好的處理方法】
1)還是回歸到查詢數據庫,根據sort asc,id desc 排序來分頁獲取,但是基礎數據就從redis中獲取;這個要根據數據量,還有sql語句復雜度來評估,如果聯表,或者是已經被告知數據庫出現這個慢查詢sql,那就肯定不能用這個方法。
2)同事建議這種用sphinx來處理,有道理,不過還沒嘗試。
2、redis防雪崩、防穿透、無底洞問題
【分析和解決方法】
防止雪崩問題的有效方法:
1)不設置過期時間,只要數據實時更新到redis,那么給用戶的數據就是實時的,不影響后端數據庫;
2)熱數據和冷數據的區分,每天定時刷新熱數據;
防穿透問題:
1)由於在redis中找不到數據,所以會去數據庫讀取,但是數據庫也沒有,所以不會寫回緩存,導致並發訪問時每次兜圈數據庫讀取;
2)可以給這類數據寫一個null或false到redis,設置一個過期時間,比如2分鍾;
3)要注意的是,這種key不能存儲太長時間,key的量多起來,內存占用也會多的。
無底洞問題:
1)key如果不過期,那么會一直保存在內存,內存會越來越不夠用;
2)key如果過期,那么過期后,怎么處理,高並發訪問數據,數據庫會不會掛掉?網上有不少代碼用setnx加鎖方式,獲取到鎖的就去db查詢然后寫回redis,而其他的請求沒有獲取到鎖,則等待一段時間(比如10毫秒)然后再去redis讀取,取到就返回,取不到數據的話可以根據業務看要不要直接返回空結果,還是再去獲取鎖,直到讀取到數據或者嘗試的次數到達指定次數。
3、刷緩存問題
舉例:由於是在一個老項目上做優化,之前是沒有做redis緩存(嚴格來說還是有緩存,但是僅僅是把api接口的結果緩存起來設置個過期時間),所以新的優化上線后,如果訪問舊的數據,緩存中沒有,那么如果不刷數據,就會所有請求到緩存都是空命中,此時要么直接返回說沒數據(用戶體驗非常差),或者去數據庫查詢,此時是類似雪崩的情況,並發訪問數據庫,數據庫可能會掛掉,那么比較好的處理方式就是先把數據刷到緩存。這里該如何處理?
【分析】
1)根據業務,分析哪些數據需要提前刷新到緩存;
2)增加鎖機制,如果獲取不到數據,則先去獲取鎖,獲取到鎖的則去db查詢然后寫回redis,db無數據則寫null或false到redis並設置過期時間;
3)根據數據庫表中數據量和業務,分析是否可以先刷一部分數據。比如商品有1000萬條數據,用戶在網站首頁可以分頁慢慢地看1000萬條數據,但是大部分用戶可能只會看前面的幾十頁數據,比如一頁20條數據,那么准備2000條最新數據或者熱門數據即可。其他的商品,等待用戶訪問的時候,從redis獲取,讀取不到從db獲取寫回redis即可。
4、商品之間根據標簽進行關聯,比如:
商品A: tag1, tag2 ,tag3
商品B: tag2, tag4
商品C: tag1, tag5
商品D: tag4
所以,商品A和商品B、商品C關聯;商品B和商品D關聯;商品C和商品A關聯;商品D和商品B關聯。
每當要獲取商品A關聯的商品時,當然可以從db去獲取A的標簽,然后計算出管理的商品,那么如何用redis處理?
【分析】
如果非要用redis處理,那么就是需要提前把關系計算好,存放到集合/有序集合,那么一旦需要獲取數據的時候,不需要再去計算,而是直接從緩存讀出這些關聯的id。
1)新增商品時,會附帶標簽。計算關聯的商品,是比較耗時的操作,可以放在隊列,由后台腳本定時處理;
2)刪除商品時,也是需要放入隊列,由后台腳本去處理;
3)修改標簽(給商品新增一個或多個標簽、刪除一個或多個標簽和修改某些標簽),也是需要放入隊列,由后台腳本去處理;
【上面這么做的問題】
1)不好維護;增刪改,都需要去維護這個有序集合。好處就是需要獲取關聯數據時直接從集合/有序集合獲取id,基礎信息也從redis獲取;
2)請教了同事,說用sphinx也可以處理,還沒嘗試。
5、用redis能否解決mysql like 的問題
答案自然是解決不了,只能用sphinx或es這些全文搜索系統。
6、商品列表有個邏輯是,允許展示用戶自己發布的商品(不論審核狀態)+其他用戶發布的商品(只能審核通過狀態)的這些數據。當時用redis處理,搞了兩個有序集合,一個是存儲網站上所有審核通過的商品;第二個有序集合是存儲用戶自己發布的商品(不論審核狀態)。當需要獲取商品列表時,合並兩個集合,然后分頁取數據。
【問題】
並發情況下,合並有序集合的代價是很高的,可能造成阻塞;
【解決方法】
1)可以的話直接mysql查詢。
2)使用sphinx:又是同事的建議。
總結:
1、redis不是萬能的,也有自己的優勢和劣勢;redis不適合處理sql這種關系型的業務;
2、redis的性能是很好的,但是人為的操作,使用不當,可能造成阻塞;
3、除了redis,還有其他的方法可以處理,不能限定死了。處理問題的時候,不僅要考慮能不能處理,還要考慮是否合理。