redis的具體使用
redis是鍵值對的數據庫,常用的五種數據類型爲
- 字符串類型(string), 散列類型(hash), 列表類型(list), 集合類型(set), 有序集合類型(zset)
Redis用作緩存,主要兩個用途:高性能,高併發,因爲內存天然支持高併發
分佈式鎖(string)
setnx key value,當key不存在時,將 key 的值設爲 value ,返回1。若給定的 key 已經存在,則setnx不做任何動作,返回0。
當setnx返回1時,表示獲取鎖,做完操作以後del key,表示釋放鎖,如果setnx返回0表示獲取鎖失敗,整體思路大概就是這樣,細節還是比較多的,有時間單開一篇來講解
計數器(string)
如知乎每個問題的被瀏覽器次數
set key 0
incr key // incr readcount::{帖子id} 每閱讀一次
get key // get readcount::{帖子id} 獲取閱讀量
分佈式全局唯一id(string)
分佈式全局唯一id的實現方式有很多,這裏只介紹用redis實現
每次獲取userId的時候,對userId加1再獲取,可以改進爲如下形式
直接獲取一段userId的最大值,緩存到本地慢慢累加,快到了userId的最大值時,再去獲取一段,一個用戶服務宕機了,也頂多一小段userId沒有用到
set userId 0
incr usrId //返回1
incrby userId 1000 //返回10001
消息隊列(list)
在list裏面一邊進,一邊出即可
實現方式一
一直往list左邊放
lpush key value
#key這個list有元素時,直接彈出,沒有元素被阻塞,直到等待超時或發現可彈出元素爲止,上面例子超時時間爲10s
brpop key value 10
實現方式二
rpush key value
blpop key value 10
新浪/Twitter用戶消息列表(list)
假如說小編li關注了2個微博a和b,a發了一條微博(編號爲100)就執行如下命令
lpush msg::li 100
b發了一條微博(編號爲200)就執行如下命令:
lpush msg::li 200
假如想拿最近的10條消息就可以執行如下命令(最新的消息一定在list的最左邊):
下標從0開始,[start,stop]是閉區間,都包含
lrange msg::li 0 9
抽獎活動(set)
#參加抽獎活動
sadd key {userId}
獲取所有抽獎用戶,大輪盤轉起來
smembers key
抽取count名中獎者,並從抽獎活動中移除
spop key count
抽取count名中獎者,不從抽獎活動中移除
srandmember key count
實現點贊,簽到,like等功能(set)
#1001用戶給8001帖子點贊
sadd like::8001 1001
#取消點贊
srem like::8001 1001
#檢查用戶是否點過贊
sismember like::8001 1001
#獲取點讚的用戶列表
smembers like::8001
#獲取點贊用戶數
scard like::8001
實現關注模型,可能認識的人(set)
seven關注的人
sevenSub -> {qing, mic, james}
青山關注的人
qingSub->{seven,jack,mic,james}
Mic關注的人
MicSub->{seven,james,qing,jack,tom}
#返回sevenSub和qingSub的交集,即seven和青山的共同關注
sinter sevenSub qingSub -> {mic,james}
#我關注的人也關注他,下面例子中我是seven
#qing在micSub中返回1,否則返回0
sismember micSub qing
sismember jamesSub qing
#我可能認識的人,下面例子中我是seven
#求qingSub和sevenSub的差集,並存在sevenMayKnow集合中
sdiffstore sevenMayKnow qingSub sevenSub -> {seven,jack}
電商商品篩選(set)
每個商品入庫的時候即會建立他的靜態標籤列表如,品牌,尺寸,處理器,內存
#將拯救者y700P-001和ThinkPad-T480這兩個元素放到集合brand::lenovo
sadd brand::lenovo 拯救者y700P-001 ThinkPad-T480
sadd screenSize::15.6 拯救者y700P-001 機械革命Z2AIR
sadd processor::i7 拯救者y700P-001 機械革命X8TIPlus
#獲取品牌爲聯想,屏幕尺寸爲15.6,並且處理器爲i7的電腦品牌(sinter爲獲取集合的交集)
sinter brand::lenovo screenSize::15.6 processor::i7 -> 拯救者y700P-001
排行版(zset)
redis的zset天生是用來做排行榜的、好友列表, 去重, 歷史記錄等業務需求
#user1的用戶分數爲 10
zadd ranking 10 user1
zadd ranking 20 user2
#取分數最高的3個用戶
zrevrange ranking 0 2 withscores
過期策略
定期刪除
redis 會將每個設置了過期時間的 key 放入到一個獨立的字典中,以後會定期遍歷這個字典來刪除到期的 key。
定期刪除策略
Redis 默認會每秒進行十次過期掃描(100ms一次),過期掃描不會遍歷過期字典中所有的 key,而是採用了一種簡單的貪心策略。
從過期字典中隨機 20 個 key;
刪除這 20 個 key 中已經過期的 key;
如果過期的 key 比率超過 1/4,那就重複步驟 1;
惰性刪除
除了定期遍歷之外,它還會使用惰性策略來刪除過期的 key,所謂惰性策略就是在客戶端訪問這個 key 的時候,redis 對 key 的過期時間進行檢查,如果過期了就立即刪除,不會給你返回任何東西。
定期刪除是集中處理,惰性刪除是零散處理。
爲什麼要採用定期刪除+惰性刪除2種策略呢?
如果過期就刪除。假設redis裏放了10萬個key,都設置了過期時間,你每隔幾百毫秒,就檢查10萬個key,那redis基本上就死了,cpu負載會很高的,消耗在你的檢查過期key上了
但是問題是,定期刪除可能會導致很多過期key到了時間並沒有被刪除掉,那咋整呢?所以就是惰性刪除了。這就是說,在你獲取某個key的時候,redis會檢查一下 ,這個key如果設置了過期時間那麼是否過期了?如果過期了此時就會刪除,不會給你返回任何東西。
並不是key到時間就被刪除掉,而是你查詢這個key的時候,redis再懶惰的檢查一下
通過上述兩種手段結合起來,保證過期的key一定會被幹掉。
所以說用了上述2種策略後,下面這種現象就不難解釋了:數據明明都過期了,但是還佔有着內存
內存淘汰策略
這個問題可能有小夥伴們遇到過,放到Redis中的數據怎麼沒了?
因爲Redis將數據放到內存中,內存是有限的,比如redis就只能用10個G,你要是往裏面寫了20個G的數據,會咋辦?當然會幹掉10個G的數據,然後就保留10個G的數據了。那幹掉哪些數據?保留哪些數據?當然是幹掉不常用的數據,保留常用的數據了
Redis提供的內存淘汰策略有如下幾種:
noeviction 不會繼續服務寫請求 (DEL 請求可以繼續服務),讀請求可以繼續進行。這樣可以保證不會丟失數據,但是會讓線上的業務不能持續進行。這是默認的淘汰策略。
volatile-lru 嘗試淘汰設置了過期時間的 key,最少使用的 key 優先被淘汰。沒有設置過期時間的 key 不會被淘汰,這樣可以保證需要持久化的數據不會突然丟失。(這個是使用最多的)
volatile-ttl 跟上面一樣,除了淘汰的策略不是 LRU,而是 key 的剩餘壽命 ttl 的值,ttl 越小越優先被淘汰。
volatile-random 跟上面一樣,不過淘汰的 key 是過期 key 集合中隨機的 key。
allkeys-lru 區別於 volatile-lru,這個策略要淘汰的 key 對象是全體的 key 集合,而不只是過期的 key 集合。這意味着沒有設置過期時間的 key 也會被淘汰。
allkeys-random 跟上面一樣,不過淘汰的策略是隨機的 key。allkeys-random 跟上面一樣,不過淘汰的策略是隨機的 key。
持久化策略
Redis的數據是存在內存中的,如果Redis發生宕機,那麼數據會全部丟失,因此必須提供持久化機制。
Redis 的持久化機制有兩種,第一種是快照(RDB),第二種是 AOF 日誌。快照是一次全量備份,AOF 日誌是連續的增量備份。快照是內存數據的二進制序列化形式,在存儲上非常緊湊,而 AOF 日誌記錄的是內存數據修改的指令記錄文本。AOF 日誌在長期的運行過程中會變的無比龐大,數據庫重啓時需要加載 AOF 日誌進行指令重放,這個時間就會無比漫長。所以需要定期進行 AOF 重寫,給 AOF 日誌進行瘦身。
RDB是通過Redis主進程fork子進程,讓子進程執行磁盤 IO 操作來進行 RDB 持久化,AOF 日誌存儲的是 Redis 服務器的順序指令序列,AOF 日誌只記錄對內存進行修改的指令記錄。即RDB記錄的是數據,AOF記錄的是指令
RDB和AOF到底該如何選擇?
- 不要僅僅使用 RDB,因爲那樣會導致你丟失很多數據,因爲RDB是隔一段時間來備份數據
- 也不要僅僅使用 AOF,因爲那樣有兩個問題,第一,通過 AOF 做冷備沒有RDB恢復速度快; 第二,RDB 每次簡單粗暴生成數據快照,更加健壯,可以避免 AOF 這種複雜的備份和恢復機制的 bug
- 用RDB恢復內存狀態會丟失很多數據,重放AOP日誌又很慢。Redis4.0推出了混合持久化來解決這個問題。將 rdb 文件的內容和增量的 AOF 日誌文件存在一起。這裏的 AOF 日誌不再是全量的日誌,而是自持久化開始到持久化結束的這段時間發生的增量 AOF 日誌,通常這部分 AOF 日誌很小。於是在 Redis 重啓的時候,可以先加載 rdb 的內容,然後再重放增量 AOF 日誌就可以完全替代之前的 AOF 全量文件重放,重啓效率因此大幅得到提升。
緩存雪崩和緩存穿透
緩存雪崩是什麼?
假設有如下一個系統,高峯期請求爲5000次/秒,4000次走了緩存,只有1000次落到了數據庫上,數據庫每秒1000的併發是一個正常的指標,完全可以正常工作,但如果緩存宕機了,每秒5000次的請求會全部落到數據庫上,數據庫立馬就死掉了,因爲數據庫一秒最多抗2000個請求,如果DBA重啓數據庫,立馬又會被新的請求打死了,這就是緩存雪崩。
如何解決緩存雪崩
事前:redis高可用,主從+哨兵,redis cluster,避免全盤崩潰
事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL被打死
事後:redis持久化,快速恢復緩存數據
緩存穿透是什麼?
假如客戶端每秒發送5000個請求,其中4000個爲黑客的惡意攻擊,即在數據庫中也查不到。舉個例子,用戶id爲正數,黑客構造的用戶id爲負數,
如果黑客每秒一直髮送這4000個請求,緩存就不起作用,數據庫也很快被打死。
如何解決緩存穿透
查詢不到的數據也放到緩存,value爲空,如set -999 “”
總而言之,緩存雪崩就是緩存失效,請求全部全部打到數據庫,數據庫瞬間被打死。緩存穿透就是查詢了一個一定不存在的數據,並且從存儲層查不到的數據沒有寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義
redis常使用的一些命令
1.sadd
向名稱爲 key 的 set 中添加元素
redis 127.0.0.1:6379> sadd myset "hello"
(integer) 1
redis 127.0.0.1:6379> sadd myset "world"
(integer) 1
redis 127.0.0.1:6379> sadd myset "world"
(integer) 0
redis 127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
redis 127.0.0.1:6379>
本例中,我們向 myset 中添加了三個元素,但由於第三個元素跟第二個元素是相同的,所以
第三個元素沒有添加成功,最後我們用 smembers 來查看 myset 中的所有元素。
2.srem
刪除名稱爲 key 的 set 中的元素 member
redis 127.0.0.1:6379> sadd myset2 "one"
(integer) 1
redis 127.0.0.1:6379> sadd myset2 "two"
(integer) 1
redis 127.0.0.1:6379> sadd myset2 "three"
(integer) 1
redis 127.0.0.1:6379> srem myset2 "one"
(integer) 1
redis 127.0.0.1:6379> srem myset2 "four"
(integer) 0
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379>
本例中,我們向 myset2 中添加了三個元素後,再調用 srem 來刪除 one 和 four,但由於元素
中沒有 four 所以,此條 srem 命令執行失敗。
3.spop
隨機返回並刪除名稱爲 key 的 set 中一個元素
redis 127.0.0.1:6379> sadd myset3 "one"
(integer) 1
redis 127.0.0.1:6379> sadd myset3 "two"
(integer) 1
redis 127.0.0.1:6379> sadd myset3 "three"
(integer) 1
redis 127.0.0.1:6379> spop myset3
"three"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379>
本例中,我們向 myset3 中添加了三個元素後,再調用 spop 來隨機刪除一個元素,可以看到
three 元素被刪除了。
4.sdiff
返回所有給定 key 與第一個 key 的差集
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> sdiff myset2 myset3
1) "three"
redis 127.0.0.1:6379>
本例中,我們可以看到 myset2 中的元素與 myset3 中不同的只是 three,所以只有 three 被查
出來了,而不是 three 和 one,因爲 one 是 myset3 的元素。
我們也可以將 myset2 和 myset3 換個順序來看一下結果:
redis 127.0.0.1:6379> sdiff myset3 myset2
- “one”
redis 127.0.0.1:6379>
這個結果中只顯示了, myset3 中的元素與 myset2 中不同的元素。
5.sdiffstore
返回所有給定 key 與第一個 key 的差集,並將結果存爲另一個 key
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> sdiffstore myset4 myset2 myset3
(integer) 1
redis 127.0.0.1:6379> smembers myset4
1) "three"
redis 127.0.0.1:6379>
6.sinter
返回所有給定 key 的交集
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> sinter myset2 myset3
1) "two"
redis 127.0.0.1:6379>
通過本例的結果可以看出, myset2 和 myset3 的交集 two 被查出來了
7.sinterstore
返回所有給定 key 的交集,並將結果存爲另一個 key
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> sinterstore myset5 myset2 myset3
(integer) 1
redis 127.0.0.1:6379> smembers myset5
1) "two"
redis 127.0.0.1:6379>
通過本例的結果可以看出, myset2 和 myset3 的交集被保存到 myset5 中了
8.sunion
返回所有給定 key 的並集
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> sunion myset2 myset3
1) "three"
2) "one"
3) "two"
redis 127.0.0.1:6379>
通過本例的結果可以看出, myset2 和 myset3 的並集被查出來了
9.sunionstore
返回所有給定 key 的並集,並將結果存爲另一個 key
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> sunionstore myset6 myset2 myset3
(integer) 3
redis 127.0.0.1:6379> smembers myset6
1) "three"
2) "one"
3) "two"
redis 127.0.0.1:6379>
通過本例的結果可以看出, myset2 和 myset3 的並集被保存到 myset6 中了
10、smove
從第一個 key 對應的 set 中移除 member 並添加到第二個對應 set 中
redis 127.0.0.1:6379> smembers myset2
1) "three"
2) "two"
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> smove myset2 myset7 three
(integer) 1
redis 127.0.0.1:6379> smembers myset7
1) "three"
redis 127.0.0.1:6379>
通過本例可以看到, myset2 的 three 被移到 myset7 中了
11.scard
返回名稱爲 key 的 set 的元素個數
redis 127.0.0.1:6379> scard myset2
(integer) 1
redis 127.0.0.1:6379>
通過本例可以看到, myset2 的成員數量爲 1
12.sismember
測試 member 是否是名稱爲 key 的 set 的元素
redis 127.0.0.1:6379> smembers myset2
1) "two"
redis 127.0.0.1:6379> sismember myset2 two
(integer) 1
redis 127.0.0.1:6379> sismember myset2 one
(integer) 0
redis 127.0.0.1:6379>
通過本例可以看到, two 是 myset2 的成員,而 one 不是。
13.srandmember
隨機返回名稱爲 key 的 set 的一個元素,但是不刪除元素
redis 127.0.0.1:6379> smembers myset3
1) "two"
2) "one"
redis 127.0.0.1:6379> srandmember myset3
"two"
redis 127.0.0.1:6379> srandmember myset3
"one"
redis 127.0.0.1:6379>