Redis的基礎知識和應用場景
什么是redis?
Redis 是互聯網技術領域使用最為廣泛的存儲中間件,它是「Remote Dictionary Service」的首字母縮寫,也就是「遠程字典服務」。Redis 以其超高的性能、完美的文檔、簡潔易懂的源碼和豐富的客戶端庫支持在開源中間件領域廣受好評。國內外很多大型互聯網公司都在使用 Redis,比如 Twitter、YouPorn、暴雪娛樂、Github、StackOverflow、騰訊、阿里、京東、華為、新浪微博等等,很多中小型公司也都有應用。也可以說,對 Redis 的了解和應用實踐已成為當下中高級后端開發者繞不開的必備技能。
Redis 可以做什么
- 記錄帖子的點贊數、評論數和點擊數 (hash)。
- 記錄用戶的帖子 ID 列表 (排序),便于快速顯示用戶的帖子列表 (zset)。
- 記錄帖子的標題、摘要、作者和封面信息,用于列表頁展示 (hash)。
- 記錄帖子的點贊用戶 ID 列表,評論 ID 列表,用于顯示和去重計數 (zset)。
- 緩存近期熱帖內容 (帖子內容空間占用比較大),減少數據庫壓力 (hash)。
- 記錄帖子的相關文章 ID,根據內容推薦相關帖子 (list)。
- 如果帖子 ID 是整數自增的,可以使用 Redis 來分配帖子 ID(計數器)。
- 收藏集和帖子之間的關系 (zset)。
- 記錄熱榜帖子 ID 列表,總熱榜和分類熱榜 (zset)。
- 緩存用戶行為歷史,進行惡意行為過濾 (zset,hash)。
- .........等等
redis 基礎數據結構及相關應用介紹
關于基礎數據結構也可以看我之前的文章《換一種存儲方式,居然能節約這么多內存?》。
string
實現方式:
動態字符串;內部結構類似java 的ArrayList;采用與預分配冗余空間的方式來減少內存的頻繁分配。 當字符長度<1M,擴容時加倍現有空間 當字符串>1M,擴容每次加1M,字符串最大長度為512M 字符串由多個字節組成,每個字節又由8個bit組成,一個字符串看做bit 組合,這便是bitmap(位圖)數據結構。
命令
- set key;get key
list
實現方式:
鏈表實現類似java 里面的LinkList。
在數據量少的情況下,使用的是快速列表,數據較少的情況下是用一塊連續的內存,ziplist 數據結構。
數據多的時候使用linkedlist,鏈表結合ziplist,這樣的優點:既滿足了快速的插入刪除的性能,又不會出現太大的空間冗余。
經典應用:
異步隊列 需要注意的是 lindex 是慢操作時間復雜度O(n),慎用。
隊列空了可能造成 cpu空轉,qps也可能被拉高??梢圆捎米枞x:blpop/brpop
命令
- rpush rpop
hash
類似java 里面的HashMap,數組+鏈表的二維結構。在數據量少的時候是ziplist。
和HashMap對比:redis key只能是字符串,而且rehash 方式不同。
set
類似java HashSet。
應用場景:
保存中獎用戶的id,有去重功能
命令
- sadd
- smembers key 獲取所有的key
- sismember key setv 相當于contains(s)
- scard key 返回key 的數量
- spop keys:Redis Spop 命令用于移除集合中的指定 key 的一個或多個隨機元素。
zset
類似java SortedSet 和HashMap 結合體;concurrentskipmap
內部實現 “跳躍鏈表”的數據結構。數據少的時候使用ziplist。數據多的時候用skiplist.
注意:關于過期時間,以對象為單位,如果設置了過期時間,然后調用set 修改了對象,過期時間會消失。
經典應用
- 粉絲列表:value ID, score 關注時間
- 分布式鎖:
redis2.8 加入了set指令的擴展參數,使得 setnx 和 expire 指令可以一起執行 。
jredis 命令:
- StringUtils.equals("OK", redis.set("seemoonup", "false", "NX", "EX", 5))
這個命令的完整意思就是 如果“seemoonup“這個key不存在設置為”false“并且設置過期時間5秒,該實現缺點:沒有ack(消息確認機制) 保證。
位圖
最小單位bit(比特)只能是0,1
bitfield
有三個子指令,分別是 get/set/incrby,它們都可以對指定位片段進行讀寫,但是最多只能處理 64 個連續的位,如果 超過 64 位,就得使用多個子指令,bitfield 可以一次執行多個子指令
bitmap
- setbit
- bitcount
redis 高級一些的應用
HyperLogLog
這是一種高級數據結構,提供不準確的去重計數方案,誤差率在0.81%。
應用場景
高訪問量頁面的UV, 不適合單用戶的存儲。
實現方式:
Redis 對 HyperLogLog 的存儲進行了優化,在計數比較小時,它的存儲空間采用稀疏矩陣存儲,空間占用很小,僅僅在計數慢慢變大,稀疏矩陣占用空間漸漸超過了閾值時才會一次性轉變成稠密矩陣,才會占用 12k 的空間。為什么是12k?
實現是16384個桶,也就是2的14次方,每個桶的maxbtis需要6個字節存儲,也就是 2的14次方*6/8=12k
布隆過濾器(bloom filter)
優點:
在去重的同時,還能節省90%的空間。
注意:
有誤判率,判斷沒有,肯定沒有;有,很可能有。只會誤判那些沒有見過的,誤判率可以通過參數來調整。
原理:
位圖+hash表,一個大型數組(位圖)+幾個無偏的haash函數,位圖越稀疏,正確率越高。
redis4.0 以后才有;我們平時用到的 HBase、Cassandra 還有 LevelDB、RocksDB 內部都有布隆過濾器結構,布隆過濾器可以顯著降低數據庫的 IO請求數量。當用戶來查詢某個 row 時,可以先通過內存中的布隆過濾器過濾掉大量不存在的 row 請求,然后再去磁盤進行查詢
hbase、cassandra levelDB RocksDB 都有布隆過濾器結構
Google 開發著名的 Guava 庫就提供了布隆過濾器(Bloom Filter)的實現 。
簡單限流
使用zset 實現。
運用原理:
zset;key=用戶+行為,vaule=時間戳,score=時間戳
使用 pipline :一次發多條命令,
Zremrangebyscore 命令用于移除有序集中,指定分數(score)區間內的所有成員
zremrangebyscore(key,0,時間窗口*1000)
缺點:
量大不適合,會占用大量存儲空間,比如限定60s 內操作不能超過100萬次。
漏斗限流
redis 4.0 提供了一個限流模塊,Redis-Cell,該模塊使用了漏斗算法
該模塊是用Rust 語言寫的
Redis 是用C 寫的
該模塊只有1條指令
- cl.throttle
重試時間都幫你算好
例如:key 每60s 只能回復30次
- cl.throttle key 15 30 60 1
附近的人GeoHash
這是Redis 在3.2版本以后增加了Geo模塊。
原理:
在redis 里面,經緯度使用52位整數進行編碼,放zset 里面;zset value 是key,score 是GeoHash 的52位整數值,
命令
- geoadd company 116.481 39.996 xiaomi
算兩點之間的距離
- geodis company juejin xiaomi km
獲取元素位置
- geopos conpany xiaomi
獲取元素hash 值
- geohash company xiaomi
附近的公司
- georadiusbymember company xiaomi 20 km count 3
范圍20km 以內最多3個元素 按距離正排,不會排除自身
- georadius company 116.514 39.9 20 km withdis count 3 asc
注意:
集群環境中單個key對應的數據不要超過1M,否則導致集群遷移出現卡頓現象。建議Geo的數據庫使用單獨Redis 實例部署,如果數據量過億,甚至更大,需要進行拆分,可以按國家、省市區拆分,降低單個zset 集合的大小。
redis大海撈針-key的遍歷
keys
沒有 offset limit 參數
keys 算法是遍歷,時間復雜度為O(n) 如果實例中有千萬級以上的key,keys 指令會造成卡頓。
redis 是單線程數據
redis 2.8 版本中加入了scan
scan 優點
復雜度雖然也是O(n) 但它是通過游標分步進行的,不會阻塞 線程。
提供limit 參數,控制每次返回結果的最大條數
和keys 一樣,也有模式匹配功能
服務器不需要為游標保存狀態
注意
返回的結果可能會有重復,需要客戶端去重
遍歷過程中如果數據有修改,可能返回不正確的數據
單次結果返回為空并不意味著遍歷結束,要看返回的游標值是否為0
limit 不是限定返回結果的數量,而是限定服務器單次遍歷的字典槽位數量(約等于)
- scan 0 match key99* count 10
字典的結構
Redis 中所有的key 都存在一個很大的字典中
數據結構 類似java HashMap
數據結構
一維數組+二維鏈表
第一維 數組大小總是2的N次方,擴容一次,大小空間加倍,2的N+1 次方
scan 指令返回的就是 第一維數組的位置索引,這個就是槽(slot)
scan 返回之所以是空的,因為有些槽是空的
scan 遍歷
高位進位加減法 來遍歷
考慮到字典擴縮容時 避免槽位的重復和遺漏
從左邊開始加 和普通加法相反
采用【高位進位加減法】 rehash 后的槽位 在遍歷順序上是相鄰的
漸進式的 rehash
擴容
同時保存新舊數組
定時任務后續對hash 的指令操作 將舊數組掛接的元素遷移到新數組上
scan 也是同時掃面新舊槽位,將結果融合后返回客戶端
大key 掃描
大key 的缺點
擴容和回收 占用內存大 造成卡頓
scan 來判讀key 的大小 太麻煩
redis-cli 指令中提供了掃描功能
- redis-cli -h 127.0.0.1 -p 7001 --bigkeys
防止大幅度抬升 redis 的ops 導致報警,可以加一個修改參數
- redis-cli -h 127.0.0.1 -p 7001 --bigkeys -i 0.1