redis的使用

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到底該如何選擇?

  1. 不要僅僅使用 RDB,因爲那樣會導致你丟失很多數據,因爲RDB是隔一段時間來備份數據
  2. 也不要僅僅使用 AOF,因爲那樣有兩個問題,第一,通過 AOF 做冷備沒有RDB恢復速度快; 第二,RDB 每次簡單粗暴生成數據快照,更加健壯,可以避免 AOF 這種複雜的備份和恢復機制的 bug
  3. 用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
  1. “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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章