在測試賬戶系統過程中遇到了線上大面積用戶登錄態失效的嚴重問題,事后對於其原因及測試盲點做了一些總結記錄以便以后查閱,總結分為以下7點,其中原理性的解釋有些摘自網絡。
1.賬戶系統token失效問題復盤
2.Redis 經典流程
3.Redis分片部署方式
4.Redis擴容導致緩存數據失效
5.Redis Sharding一致性hash算法
6.緩存失效,緩存擊穿,緩存穿透
7.Redis緩存測試總結
賬戶系統token失效問題復盤
現象:redis擴容后線上大量用戶登錄態失效,需要重新登錄。由於登錄態可以持續保持,部分用戶忘記密碼,需要修改密碼后再次登錄。在測試驗證中,由於切換環境、登錄登出導致這個問題難以發現和注意。
原因:sharded-redis-pool分片規則中有域名因子(框架源碼中),擴容修改了redis域名,導致redis中數據雖然存在,概率性獲取不到。
PS:失效問題復盤中有較多關於BUG修復前后代碼差異的片段由於保密未貼出,一般來說測試復盤過程中對於代碼的解析是很重要的一環。
Redis經典流程
前端測試盲點:
1.有些應用臨時數據都存儲在redis里,不存儲在DB里
2.上面流程中redis的數據不管有沒有生效,程序都可以正常進行,且功能正常
3.redis如果是集群的方式,緩存數據的讀取和寫入有沒有進入正確的分片
Redis分片部署方式
(1)在客戶端(jedis)做分片(Redis Sharding);這種方式在客戶端確定要連接的redis實例,然后直接訪問相應的redis實例(目前系統使用的方式)。
(2)在代理中做分片;這種方式中,客戶端並不直接訪問redis實例,它也不知道自己要訪問的具體是哪個redis實例,而是由代理轉發請求和結果;其工作過程為:客戶端先將請求發送給代理,代理通過分片算法確定要訪問的是哪個redis實例,然后將請求發送給相應的redis實例,redis實例將結果返回給代理,代理最后將結果返回給客戶端。
(3)在redis服務器端做分片(Redis Cluster);這種方式被稱為“查詢路由”,在這種方式中客戶端隨機選擇一個redis實例發送請求,如果所請求的內容不再當前redis實例中它會負責將請求轉交給正確的redis實例,也有的實現中,redis實例不會轉發請求,而是將正確redis的信息發給客戶端,由客戶端再去向正確的redis實例發送請求。
Redis擴容導致緩存數據失效
假設有三台緩存服務器,緩存tokenkey,希望tokenkey被均勻的緩存到這三台服務器上,原始的做法是對緩存項的鍵進行哈希,將哈希后的結果對緩存服務器的數量進行取模操作。
假設三台緩存服務器已經不能滿足業務緩存需求,需要增加機器,就會出現一些缺陷。假設增加一台服務器,緩存服務器的數量由三台變為四台,此時,如果仍用取模的方法對同一tokenkey進行緩存,那么這個tokenkey所在的服務器編號就肯定與原來三台服務器時所在的編號不同。這就導致了緩存在一定時間內是失效的,當應用無法從緩存中獲取數據,則會向后端服務請求數據,由於大量緩存同一時間失效,造成緩存的雪崩,可能導致系統被壓垮。
Redis Sharding一致性hash算法
一致性hash:一致性哈希算法也是使用取模的方法,只是一致性哈希算法是對232取模。
我們有ABC三台服務器,使用各自的IP地址進行哈希計算,使用哈希后的結果對232取模,計算結果映射到一個由232個點組成的哈希圓環上,可以得到如下的示意圖:
假設有4個tokenkey,1234需要緩存,根據hash(tokenkey)% 232得到的映射圖如下,tokenkey1、2存儲到A中,tokenkey3存儲到B中,tokenkey4存儲到C中。
假設機器B出現故障,需要移除服務器B,那么移除后的示意圖如下。
當服務器移除以后,按照之前的一致性哈希算法的規則,tokenkey3應該被緩存到服務器C中,tokenkey3的緩存位置發生了改變。但是tokenkey1、2仍被緩存到服務器A中,tokenkey4仍被緩存到服務器C中,這就是一致性哈希算法的優點,當服務器數量發生改變,並不是緩存都會失效,而是只有部分緩存會失效,前端的緩存仍能分擔整個系統的壓力,不至於所有壓力在同一時間集中到后端服務器上。
Hash環的偏斜及虛擬結點:
在實際的映射中,服務器可能會被映射成如下圖:
虛擬節點是實際節點在hash環上的復制品,一個實際節點可以對應多個虛擬節點。虛擬節點可以解決hash環的偏斜以及緩存雪崩的問題。
緩存失效、緩存擊穿、緩存穿透
緩存穿透
緩存穿透是指查詢一個一定不存在的數據,由於緩存是不命中時被動寫的,並且出於容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。在流量大時,可能DB就掛掉了,要是有人利用不存在的key頻繁攻擊我們的應用,這就是漏洞。
解決方案
有很多種方法可以有效地解決緩存穿透問題,最常見的則是采用布隆過濾器,將所有可能存在的數據哈希到一個足夠大的bitmap中,一個一定不存在的數據會被 這個bitmap攔截掉,從而避免了對底層存儲系統的查詢壓力。另外也有一個更為簡單粗暴的方法(我們采用的就是這種),如果一個查詢返回的數據為空(不管是數 據不存在,還是系統故障),我們仍然把這個空結果進行緩存,但它的過期時間會很短,最長不超過五分鍾。
緩存雪崩
緩存雪崩是指在我們設置緩存時采用了相同的過期時間或者其他情況,導致緩存在某一時刻同時失效,請求全部轉發到DB,DB瞬時壓力過重雪崩。
解決方案
緩存失效時的雪崩效應對底層系統的沖擊非常可怕。大多數系統設計者考慮用加鎖或者隊列的方式保證緩存的單線 程(進程)寫,從而避免失效時大量的並發請求落到底層存儲系統上。這里分享一個簡單方案就時講緩存失效時間分散開,比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鍾隨機,這樣每一個緩存的過期時間的重復率就會降低,就很難引發集體失效的事件。
緩存擊穿
對於一些設置了過期時間的key,如果這些key可能會在某些時間點被超高並發地訪問,是一種非常“熱點”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在於這里針對某一key緩存,前者則是很多key。
緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的並發請求過來,這些請求發現緩存過期一般都會從后端DB加載數據並回設到緩存,這個時候大並發的請求可能會瞬間把后端DB壓垮。
解決方案
使用互斥鎖(mutex key)
簡單地來說,就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db,而是先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一個mutex key,當操作返回成功時,再進行load db的操作並回設緩存;否則,就重試整個get緩存的方法。
Redis集群緩存測試總結
功能:
1.系統運行過程中,redis緩存數據生效。緩存的數據讀取正確、數據寫入落地正確,數據有效期設置合理。
2.redis集群分片策略驗證正確。
3.緩存與數據庫的數據一致性檢測。
4.DB事務性導致回滾,緩存是否回滾,有沒有產生臟數據。
5.注意測試環境與線上環境的區別,尤其是單例與集群分片、讀寫分離。盡量保持測試環境與線上一致或者是其縮小版。
自動化:
1.自動化用例中斷言部分設計緩存層斷言並且自動化框架本身對於斷層層次可配置。
性能及穩定性:
1.關注業務本身應用場景及緩存結構,是否使用緩存。
2.預防緩存穿透、緩存雪崩、緩存擊穿引發的系統風險。
擴容:
1.關注擴容方案設計、老數據備份策略、回滾方案
2.關注擴容后分片策略的變化
3.擴容后熱點數據失效率或命中率以及對后端DB帶來的壓力