Redis適合哪些業務場景
常規業務系統的數據庫訪問中,讀寫操作的比例一般在7/3到9/1,也就是說讀操作遠多於寫操作,因此高並發系統設計里,通過NoSQL技術將熱點數據(短期內變動概率小的數據)放入內存以達到減輕DB壓力,提升數據訪問速度的目的,Redis和MongoDB是當下應用最廣泛的NoSQL產品,當然如果系統里的寫操作居多,也沒有必要使用緩存,因此Redis主要用於解決訪問性能和並發能力的問題。除了純數據緩存的作用之外,得益於其超高速的響應能力,Redis也常用於提供分布式鎖的解決方案。
哪些設計思路保證了Redis高性能
單個Redis server對請求的處理是基於單線程工作模型的,但由於是純內存操作,並且單線程的工作模式避免了線程上下文切換帶來的額外開銷,同時使用NIO多路復用機制(單線程維護多個I/O socket的狀態,socket event handler統一進行event分發,並通知到各個event listener),所以即使是單台Redis server的性能也是非常的快,可支持11萬次/秒的SET操作,8.1萬次/秒的GET操作。
高並發系統里有時候單台Redis server不能滿足性能需求,Redis 3.0之前的辦法是Redis Sentinel,多個節點同時提供服務,並且每個節點都保存全量數據,Redis 3.0之后引入了Redis Cluster,通過數據分片(Data Sharding)在每個節點上只保留部分數據(總共有16384個slot)來實現高可用。
Redis Cluster采用無中心節點方式實現,客戶端直接與redis集群的每個節點連接,客戶請求到達節點之后,使用統一的哈希算法,CRC16(key)%16384,計算出key對應的slot,然后從Redis Cluster定位出具體的server,具體的data sharding,最終將數據返回給用戶 。但由於Redis的事務僅能解決單台server上的ACID問題,對於多台server常見的問題是多個請求針對同一個key的操作一致性問題,需要結合Zookeeper使用分布式鎖的機制解決一致性問題和順序操作問題。
Redis支持動態添加和刪除節點,動態遷移和再平衡slot,動態重新選舉Leader並進行fail-over;每一個節點雖然只保存一部分的slot,但會保存一份相同的data sharding mapping table,這張表記錄所有16384個slot的host分布位置,並且節點之間會定期同步更新這張表的信息,這樣的設計可以保證Redis cluster內部節點之間只需要很少的信息就可以相互同步信息。客戶端訪問Redis Cluster的時候會指定集群內的一台host:port,如果操作的key不在當前的host上,則host會根據data sharding mapping table告訴客戶端正確的host:port;如果訪問的host:port下線了,則客戶端的鏈接自動轉移到對應的master或者slave節點。
Redis中各種數據類型的使用場景
Redis的數據存儲主要通過key/value實現,key都是string類型,value則分不同的應用場景有五種類型定義:
#1 string類型:可以包含任何數據(jps圖片或者經過序列化的對象,單個key最大可以存儲512M的數據),具有全局統計功能的數據,如全局ID生成器、集群配置信息等;
#2 hash類型:用於存儲對象結構的數據,多個field綁定到一個key上(對比使用string類型存儲對象的優勢在於hash類型可以直接update具體field的值而不影響其他field),如實現SSO,cookie為key,用戶信息為value,並有指定過期時間;
#3 list類型:用於存儲需要基於隊列或者棧操作的系列數據,如消息隊列;
#4 set類型:用於存儲需要維護一個全局不重復的集合,如服務注冊發現,可以實現全局去重的功能,如訪問網頁的獨立IP,共同好友等;
#5 zset類型:用於存儲需要維護一個全局不重復但有權重排序的列表可以使用SORTED SET,如積分排行榜、帶權重的消息隊列。
對上述的字段類型都可以進行的類似的操作,
設置一個值:[set|hmset|lpush|sadd] key value
獲取一個值:[get|hget] key
刪除一個值:[del|hdel] key
設置一個具有過期時間的值:[setex] key time value
如果值不存在就設置這個值:[setnx] key value
查找redis中的keys或者pattern:[keys/scan] key
判斷一個值是否存在:[exists|hexists] key
給指定值設置過期時間:[expire] key seconds
將指定key的value加1|減1:[incr|decr] key
將一個key\value遷移到指定server:[migrate] host port key dest-db timeout [copy] [replace]
HyperLogLog用於做基數統計(基於set類型的封裝,僅根據輸入的獨立元素個數進行統計,而不存儲元素本身),可以保證在輸入元素數量或者體積非常大的時候可以保證統計所需的空間固定為12kb(最大2^64個元素) 。
在指定的key中添加基數:[pfadd] key value
統計指定key中不同基數的個數:[pfcount] key
將sourceKey的技術合並到destKey的基數統計中:[pfmerge] destKey sourceKey
Pub/Sub用於做消息的發布訂閱(基於list類型的封裝,將消息封裝成list的節點)。
創建一個信息接收channel:[subscribe] channel
向指定的channel發送一個信息:[publish] channel message
單個redis命令的執行具有原子性,對於多個命令而言redis提供基礎事務機制,但是不保證多個命令執行的原子性,一個典型的redis事務如下:
開啟一個事務:[multi]
之后可以計划多條redis命令,但並不會執行
提交並執行之前的所有命令:[exec]
開始執行之前計划的redis命令,如果其中某條命令執行失敗並不會影響其他命令的執行
取消執行事務塊內所有計划的redis命令:[discard]
監視一個或者多個key:[watch] key
表示在執行exec之前如果key被事務之前的命令修改,則當前事務被discard。
Redis數據過期策略和內存回收策略
針對已經過期的數據Redis采用定期刪除和延遲刪除結合的策略,但是兩者都有缺陷;由於定期檢查所有的key是否過期會帶來性能問題,因此定期刪除策略使用的是隨機抽查,另外在操作Key前會判斷是否已經過期,如過期則立即刪除;這樣的策略會導致一些已經過期的key還堆積在內存里,使得redis server內存占用率居高不下,因此需要結合redis.conf中的maxmemory-policy配置使用,也就是當redis server的內存不足以寫入新數據時的內存回收策略,
#1 noeviction:表示直接報錯;
#2 allkeys-lur:表示在所有keys中根據LRU刪除key;
#3 allkeys-random:表示在所有keys中隨機刪除key;
#4 volatile-lru/volatile-random/volatile-ttl用於當redis server既充當cache又當DB的時候,表示在設置了expire date的keys中進行刪除,ttl表示刪除擁有更早過期時間的key。
解決Redis緩存穿透和緩存雪崩問題
緩存穿透和雪崩可以看做一個問題,只是嚴重程度不同;當一個請求到達redis之后發現沒有對應的緩存數據,然后向DB發送數據請求,如果能獲取到數據那問題就停留在了緩存穿透上,DB獲取到的數據會緩存到redis上;如果DB中也沒有對應的數據,並且當這樣的請求達到一定數量級並且耗用完所有的DB資源,最終導致DB連接異常就出現了緩存雪崩問題。
解決緩存穿透問題的思路有下述幾種,不管是否從DB中查找到對應的值(沒有值就為null),都在redis中記錄一條緩存記錄;在Dao層維護一張BitMap,用bit記錄對應的key是否有對應值,從而避免冗余的DB操作;后台線程專門用於更新即將過期的Redis數據,從而避免緩存穿透。
解決緩存雪崩問題的思路有下述幾種,在DB Connection上添加互斥鎖,這樣當大量緩存請求失效的時候需要排隊去DB請求數據;對設置了相同過期時間的數據設置一個隨機值,避免數據集體失效;使用雙緩存或者多層緩存策略,需要配合緩存預熱。