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