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