您現在的位置是:首頁 > 動漫首頁動漫

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

由 itdebug 發表于 動漫2021-07-05
簡介然而,對於透過sinter獲取共同好友而言,Hash Tag則無能為力,例如,要用sinter去獲取使用者123456和456789兩個使用者的共同好友,除非我們將key定義為{ friend _ set }:123456和{ friend

redishash可以隨機彈出嗎

Redis是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體,

可持久化的日誌型

、高效能的Key-Value資料庫,

並提供多種語言的API

,同時也是key-value

儲存系統

,最主要的還支援

主從同步

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

巧計及理解redis的各類技術名詞

和Memcached類似,它支援儲存的value型別相對更多,包括string(字串)、list(連結串列)、set(集合)、zset(sorted set有序集合)和hash(雜湊型別)。這些資料型別都支援

push/pop

、add/remove以及

取交集,

並集和差集等多種豐富操作,而且這些操作都是原子性的。

01

使用redis有哪些好處?

1。速度快,因為資料存在記憶體中,類似於HashMap,HashMap的優勢就是查詢和操作的時間複雜度都是O(1)

2。支援豐

富資料型別

,支援string,list,set,sorted set,hash

1)String

常用命令:set、get、decr、incr、mget等。

應用場景:string型別的incr和decr命令的作用,是將key中儲存的數字值加、減,這兩個操作具有原子性,總能安全地進行加減操作,因此可以用string型別進行計數,如微博的

評論數

、點贊數、

分享數

,抖音作品的收藏數,京東商品的

銷售量

、評價數等。

實現方式:String在redis內部儲存,預設就是

一個字串

,被redisObject

所引用

,當遇到incr、decr等操作時,會轉成

數值型

進行計算,此時redisObject的encoding欄位為int。

2)Hash

常用命令:hget、hset、hgetall等。

應用場景:我們要儲存一個

使用者資訊物件資料

,其中包括使用者ID、使用者姓名、年齡和生日,透過

使用者ID,

我們希望

獲取該使用者的姓名

或者年齡或者生日。

如購物車

,以

使用者id為key

商品id

為field,

商品數量

為value,恰好構成了購物車的3個要素,如下圖所示。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

hash購物車

實現方式:Redis的Hash,實際是

內部儲存的Value為一個HashMap

,並提供了直接存取

這個Map成員

的介面。Key是使用者ID, value是一個Map。這個Map的key是成員的屬性名,value是屬性值。這樣

對資料的修改

和存取,都可以直接

透過其內部Map的Key

(Redis裡稱

內部Map的key

為field), 也就是透過 key(

使用者ID

) + field(屬

性標籤

) ,就可以操作

對應屬性

資料。

當前HashMap的實現有兩種方式:當HashMap的

成員比較少

時,Redis為了節省記憶體,會採用

類似一維陣列

的方式,來

緊湊儲存

,而

不會採用真正的HashMap

結構,這時

對應的value

redisObject的encoding

為zipmap;當

成員數量增大時,

會自動轉成真正的HashMap,此時encoding

為ht

3)List

常用命令:lpush、rpush、lpop、rpop、lrange等;

實現方式:Redis list的實現為一個

雙向連結串列

,即可以支援

反向查詢

和遍歷,更方便操作,不過帶來了

部分額外的記憶體開銷

,Redis內部的很多實現,包括

傳送緩衝佇列

等也都是用的這個資料結構。

應用場景:

1》訊息佇列

list型別的lpop和rpush(或者反過來,lpush和rpop),能

實現佇列

的功能,故而可以用Redis的list型別,實現

簡單的點對點的訊息佇列

。不過,

不推薦

在實戰中這麼使用,因為現在已經有Kafka、NSQ、RabbitMQ等成熟的訊息隊列了,它們的功能已經很完善了,除非是

為了更深入

地理解訊息佇列,不然沒必要去重複造輪子。

2》排行榜

list型別的lrange命令,可以

分頁檢視

佇列中的資料。可將

每隔一段時間,

計算一次的

排行榜儲存在list型別

中,如京東每日的

手機銷量排行

、學校每次

月考學生的成績排名

、鬥魚年終盛典主播排名等,酷狗音樂“K歌擂臺賽”的

打擂金曲排行榜

,每日計算一次,

儲存在list型別

中,介面訪問時,透過page和

size分頁

獲取打擂金曲。

但是,並不是所有的排行榜,都能

用list型別

實現,

只有定時計算

的排行榜,才適合

使用list型別儲存

,與定時計算的排行榜,相對應的是

實時計算的排行榜

,list型別

不能支援實時計算

的排行榜,之後在介紹

有序集合sorted set

的應用場景時會詳細介紹實時計算的排行榜的實現。

3》最新列表

list型別的lpush命令,和lrange命令

能實現最新列表

的功能,每次透過lpush命令,

往列表裡插入新的元素

,然後透過lrange命令,

讀取最新的元素列表

,如

朋友圈的點贊列表、評論列表。

4)Set

常用命令:sadd、spop、smembers、sunion等;

實現方式:set 的內部實現,

是一個 value永遠為null

的HashMap,實際就是透過

計算hash

的方式來

快速排重

的,這也是

set能提供判斷一個成員

是否

在集合內

的原因。

應用場景:

1》 好友、關注、粉絲、感興趣的人集合

set型別唯一的特點使得其適合用於儲存 好友、關注、粉絲、感興趣的人集合,集合中的元素數量

可能很多

,每次全部

取出來

成本不小,set型別提供了一些很實用的命令用於直接操作這些集合,如

a。

sinter

命令可以

獲得A和B兩個使用者

的共同好友

b。

sismember

命令可以判斷A是否

是B的好友

c。

scard

命令可以獲取好友數量

c。 關注時,

smove

命令可以將B從A的粉絲集合,

轉移到A

的好友集合

需要注意的是,如果你用的是Redis Cluster叢集,對於sinter、smove這種操作多個key的命令,要求這

兩個key必須儲存在同一個slot

(槽位)中,否則會報出 (error) CROSSSLOT Keys in request don‘t hash to the same slot 錯誤。

Redis Cluster一共有16384個slot,每個key都是透過雜湊演算法CRC 16 (key)獲取數值雜湊,再模16384來定位slot的。

要使得兩個key處於同一slot,除了兩個key一模一樣,還有沒有別的方法呢?

答案是肯定的,Redis提供了一種

Hash Tag的功能

,在key中使用

{}

括起key中的一部分,在進行 CRC 16 (key) mod 16384 的過程中,只會對

{}

內的字串計算,例如friend _ set :{123456}和fans _ set:{123456},分別表示使用者123456的好友集合和粉絲集合。

在定位slot時,只對{}內的123456進行計算,所以這兩個集合肯定是在同一個slot內的,當用戶123456

關注某個粉絲時

,就可以透過

smove命令,

將這個粉絲從使用者123456的粉絲集合,移動到好友集合。

相比於透過

srem命令,

先將這個粉絲,

從粉絲集合中刪除

,再透過

sadd命令

將這個粉絲加到好友集合,

smove命令

的優勢是它是

原子性的

,不會出現這個粉絲從粉絲集合中被刪除,卻沒有加到好友集合的情況。

然而,對於透過

sinter

獲取共同好友而言,Hash Tag則無能為力,例如,要用

sinter

去獲取使用者123456和456789兩個使用者的共同好友,除非我們將key定義為{ friend _ set }:123456和{ friend _ set}:456789,否則

不能保證兩個key會處於同一個slot

,但是如果真這樣做的話,所有

使用者的好友

集合,都會堆積在

同一個slot

中,資料分佈會

嚴重不均勻

,不可取,所以,在實戰中使用Redis Cluster時,

sinter這個命令,

其實是

不適合作用

兩個不同使用者

對應的集合的(同理其它

操作多個key

的命令)。

2》隨機展示

通常,app首頁的展示區域有限,但是

又不能總

是展示固定的內容,一種做法是

先確定一批

需要展示的內容,

再從中

隨機獲取。如下圖所示,酷狗音樂K歌擂臺賽當日的打擂歌曲共29首,首頁隨機展示5首;昨日打擂金曲共200首,首頁隨機展示30首。

set型別

適合存放

所有

需要展示的內容

,而s

rand

member命令則可以從中隨機獲取幾個。

3》黑名單/白名單

經常有業務

出於安全性

方面的考慮,需要設定

使用者黑名單

、ip黑名單、

裝置黑名單

等,set型別適合儲存這些黑名單資料,

sis

member命令可用於

判斷使用者

、ip、裝置是否處於黑名單之中。

5)Sorted Set

常用命令:zadd、zrange、zrem、zcard等。

擴充套件命令:

zrange

by

score key min max [withscores] [limit offset count]: 分頁排名,返回分數在[min,max]的成員,並按照分數

從低到高排序

。[withscores]:顯示分數;[limit offset count]:offset,表明從

腳標為offset的元素開始,

並返回count個成員。

z

incr

by key increment member:設定

指定成員

增加的分數

。返回值是更改後的分數。

z

count

key min max:獲取分數在[min,max]之間的成員。

z

rank

key member:返回成員

在集合中的排名

(從小到大)。

z

rev

rank key member:返回成員在集合中的排名(從大到小)。

實現方式:Redis sorted set的內部,

使用HashMap

和跳躍表(SkipList),來保證資料的儲存和有序,HashMap裡放的是

成員到score的對映

,而跳躍表裡,存放的是所有的成員,排序依據是HashMap裡存的score,

使用跳躍表的結構

,可以獲得比較高的查詢效率,並且在實現上比較簡單。

應用場景:Redis sorted set是自動有序且不重複的集合列表,透過使用者額外提供一個優先順序(score)的引數,來為成員排序,並且是

插入有序

的,即自動排序。

1》可以用於一個

大型線上遊戲

的積分排行榜。每當玩家的

分數發生變化

時,可以執行

ZADD命令

更新玩家的分數,此後再透過

ZRANGE命令,

獲取積分TOPTEN的使用者資訊。當然我們也可以利用ZRANK命令,透過username來獲取玩家的排行資訊。最後我們將組合使用ZRANGE和ZRANK命令,

快速的獲取

和某個玩家積分相近的其他使用者的資訊。

2》Sorted-Set型別還可用於

構建索引資料。

3》Sorted Sets,讓

重要的任務優先

執行,來

做帶權重的佇列

,比如普通訊息的score為1,

重要訊息的score為2

,然後工作執行緒可以選擇,按

score的倒序

來獲取工作任務。

02

string + json 也是儲存物件的一種方式,那麼儲存物件時,到底用 string + json還是用hash呢?

當物件的某個屬性

需要頻繁修改

時,

不適合

用string+json,因為它不夠靈活,每次修改,都需要重新

將整個物件序列化,

並賦值;如果使用hash型別,則可以

針對某個屬性單獨修改

,沒有序列化,也不需要

修改整個物件

。比如,商品的價格、銷量、關注數、評價數等

可能經常發生變化

的屬性,就

適合儲存

在hash型別裡。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

string + json與hash兩種儲存方式

當然,

不常變化

的屬性,儲存在hash型別裡,也沒有問題,比如商品名稱、商品描述、上市日期等。但是,當物件的

某個屬性

,不是

基本型別

或字串時,使用hash型別,就

必須手動

進行復雜序列化。比如,商品的標籤,是一個

標籤物件的列表

,商品可領取的優惠券,是一個

優惠券物件的列表

(如下圖所示)等,即使以coupons(優惠券)作為field,value

想儲存優惠券物件列表,

也還是要

使用json來序列化

,這樣的話,序列化工作就太繁瑣了,不如直接用string + json的方式儲存商品資訊來的簡單。

綜上,

一般物件

用string + json儲存,物件中

某些頻繁變化

的屬性抽出來用hash儲存。

03

對於排行榜和最新列表,兩種應用場景,是用list型別實現,還是有序集合sorted set型別實現?

並不是所有的

最新列表,

都能

用list型別

實現,因為

對於頻繁更新的列表

,list型別的分頁,可能導致

列表元素重複

或漏掉。舉個例子,當前列表裡由表頭到表尾,依次有(E,D,C,B,A)五個元素,每頁獲取3個元素,使用者

第一次獲取

到(E,D,C)三個元素,然後表頭

新增了一個元素F

,列表變成了(

F

,E,D,C,B,A),此時

使用者取第二頁拿到

(C,B,A),元素

C重複了

只有不需要分頁(比如每次都

只取列表的前5個元素

),或者

更新頻率低

(比如每天凌晨更新一次)的列表,才適合

用list型別實現

。對於需要分頁,並且

會頻繁更新

的列表,需用使用

有序集合sorted set

型別實現。另外,需要透過

時間範圍

查詢的最新列表,list型別也實現不了,也需要透過

有序集合sorted set

型別實現,如以成交時間範圍,作為條件來查詢的訂單列表。

那麼問題來了,對於

排行榜

最新列表

兩種應用場景,list型別能做到的,sorted set型別都能做到,list型別做不到的,sorted set型別也能做到,那為什麼

還要使用list型別,

去實現排行榜或最新列表呢,直接用sorted set型別不是更好嗎?

原因是sorted set型別

佔用的記憶體容量

是list型別的數倍之多,對於

列表數量

不多的情況,可以用sorted set型別來實現,比如上文中舉例的

打擂金曲

排行榜,每天全國只有一份,兩種資料型別的

記憶體容量

差距可以忽略不計,但是如果要實現

某首歌曲的翻唱作品地區排行榜

,數百萬的歌曲,300多個地區,會產生數量

龐大的榜單

,或者數量更加龐大的朋友圈點贊列表,就需要慎重地

考慮容量

的問題了。

04

什麼是快取穿透?

說白了,就是

外部

的請求,

直接跳過了資料的(Redis)快取層

,直接

將壓力壓在底層

的關係型資料庫身上,造成服務

響應緩慢

甚至崩潰

場景一:快取、

資料庫

都沒有。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

駭客攻擊,快取、資料庫都沒有這個資料

原因:如使用者

不斷的發起訪問

請求,

訪問ID

根本就不存在的資料, 導致快取層一直都查詢不到,不斷的把請求

壓在資料庫中

。通常情況下,業務中讀取不存在的資料的請求量並不會大,但是如果出現了

一些異常情況

,比如

駭客攻擊

故意大量訪問

某些

不存在的資料

,就很有可能會

將服務拖垮

停止服務

1)、介面層

增加

校驗,如

使用者鑑權

,非法資料校驗,如

ID要符合格式。

2)、將結果

為空的查詢

快取起來,

將從資料庫

沒有取到

,從

快取

也沒有取到的資料查詢,可以考慮將

key與空結果,

快取到Redis中,並

設定一個60s

有效期。那麼在這一分鐘裡,

所有同樣的訪問

,都將直接從快取中

獲得NULL(暫時為空的,儲存為空)。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

儲存空物件

3)、限流策略

,針對

同一使用者

同一IP

的大量訪問,

限制每秒

或每分鐘的訪問次數,從而

降低服務壓力

4)、採用

布隆過濾器

,將資料庫有的資料,放

一份副本,

足夠大

布隆過濾器

中,只有使用者訪問,先

把條件

布隆過濾器中

判斷一下,如果

儲存

則放行,如果

沒有

則直接攔截返回,從而

避免了對底層資料庫的訪問。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

布隆過濾器,避免了對底層資料庫的訪問

場景二:快取沒有,但

資料庫有

的資料。

若:資料庫

存在資料

,但是生成快取需要

耗費教長

的時間,或者

部分快取

時間過期而失效

,如果此時恰好,又大量請求進入,因為

快取還未完全生效

,會有部分資料的

請求壓倒

資料庫上。

如電商系統的

分頁

,因為電商

資料量巨大

不可能

把所有的資料

都快取起來

,所以有時候,只能按照

分頁快取

。但是業務上,難以預測使用者,到底

會訪問那些頁

,因此最簡答的方法,就是

每次點選頁數

時,

查庫並生成快取

。但是生成快取,也是

需要一定時間

,如果此時

有大量訪問

,很有可能就會在快取空窗期,

突破

到資料庫層。

解決方案:

1)、

加速快取

生成,不要讓生成快取

耗費太長時間

,儘量縮小

無快取空窗期

。或是

必須生成快取

後,才能對外提供服務。

2)、熱點資料

不過期

,將使用者

頻繁訪問

的熱點資料,設定為

永不過期

,這就可以保證訪問

不會落到資料庫

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

採用互斥鎖,只有一個請求落地到資料庫

3)、

採用

互斥鎖,

對於

快取過期

後,大量

併發訪問

同一資料的情況,可以考慮,

對向資料庫load資料的動作,

加以互斥鎖。不管你有

多少的併發請求

,來獲取該資料,

允許一個請求

從資料庫

load資料,寫入快取。其他的請求

會獲取鎖失敗

後,去快取獲取。這樣就可以保證

只有一個請求落地到資料庫層

,其他請求依然是訪問快取。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

互斥鎖控制訪問資料庫

05

什麼是快取雪崩?

說白了,就是

同一時刻

大批快取失效

,而

新快取又未跟上

外部訪問量又很大

。致使

大量的請求

,因快取

沒有資料而落到資料庫

上,導致

資料庫壓力峰值過大

,瞬間

宕機。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

訊息佇列等手段對資料庫的訪問削峰限流

場景一:壓垮資料庫,導致服務不可用

快取失效

,大量請求落到關係型資料庫。因

訪問量過大

,導致資料庫讀寫效能驟降,嚴重將導致資料庫宕機,從而引起

連鎖反應

,最終造成

系統崩潰

解決:

1)、

過期時間隨機化

,對快取資料的

過期時間

,根據一定的合理範圍,

設定隨機值

,防止同一時間

大量快取資料

過期的現象發生。

2)、熱點資料

不過期 ,對待

頻繁訪問的熱點資料

,我們可以使設定

其不會過期

3)、快取分散式

叢集化,將資料

分散儲存到不同的叢集節點

,降低

單節點的壓力

和風險值。

4)、多級快取

策略 , 建立

多級快取

,如

本地快取

作為一級快取,

外部儲存

作為二級快取。

二級快取掛

了,可以先去一級快取讀取。

場景二:壓垮Redis, Redis效能

急劇下降

因為同時

大量的Key

失效,會造成Redis執行緒,頻繁執行

定時刪除

任務,從而在一定程度上,

阻塞了Redis

其他的業務操作的。使得Redis

讀寫QPS

受到影響,效能降低。

多方案整合起來解決雪崩:

1)、

事前,預防快取雪崩

Redis叢集,

分散式化

,保證高可用,避免全盤崩潰(clusted or Sentinel),當一個Redis伺服器掛掉了之後,進行

故障轉移

Redis資料的過期時間,在一個

合理的範圍內隨機化

,儘量避免同一時間大量資料過期。

多級快取策略

,使用

guava

encache

作為

本地一級快取

外部儲存

Redis作為

二級快取。

2)、

事中,減小雪崩

造成的影響:

一旦發生雪崩,導致大量請求壓向資料庫,可以透過

訊息佇列

等手段,對資料庫的

訪問削峰限流

,避免MySQL

承擔不合理的請求

,保證一定的服務可用性。

連redis這些概念都不知道,誰敢相信您曾搞過軟體開發?

redis邏輯上的永不過期

06

redis提供6種資料淘汰策略

淘汰策略的原因:

在 redis 中,允許使用者設定

最大使用記憶體大小

server。maxmemory,在記憶體限定的情況下是很有用的。譬如,在一臺 8G 機子上部署了 4 個 redis 服務點,每一個服務點分配 1。5G 的記憶體大小,

減少記憶體緊張

的情況,由此獲取更為穩健的服務。

redis 記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰策略。

redis 提供 6種資料淘汰策略:

1)、volatile-lru:從設定了過期時間的資料集中,選擇

最近最久未使用

的資料釋放;

2)、volatile-random:從設定了過期時間的資料集中,

隨機選擇一個數據

進行釋放;

3)、volatile-ttl:從設定了過期時間的資料集中,選擇

馬上就要過期的資料

進行釋放操作;

4)、allkeys-lru:從資料集中(包括設定過期時間以及

未設定過期時間的資料集

中),選擇最近最久未使用的資料釋放;

5)、allkeys-random:從資料集中(包括了設定過期時間以及未設定過期時間)

隨機選擇一個數據

進行入釋放;

6)、noeviction:

不刪除任意資料

(但redis還會根據

引用計數器

,進行釋放),這時如果記憶體不夠時,會直接返回錯誤。

記憶方式:

單片語成:3個volatile開頭的,2個all_keys開頭。都是lru,random,只有volatile有ttl方式。最後加一個noeviction

volatile:指的都是快過期的資料集。

all_keys:是所有的資料集。

lrc:是選擇最近長時間不使用的,一般用作快取機制。

random:就是隨機選一個。

ttl:就是過期時間的設定

noeviction:不做任何設定

預設的記憶體策略是noeviction,在Redis中LRU演算法是一個近似演算法,預設情況下,Redis隨機挑選5個鍵,並且從中選取一個最近最久未使用的key進行淘汰,在配置檔案中可以透過maxmemory-samples的值,來設定redis需要檢查key的個數,但是檢查的越多,耗費的時間也就越久,但是結構越精確(也就是Redis從記憶體中,淘汰的物件未使用的時間,也就越久~),設定多少,綜合權衡。

一般來說,推薦使用的策略是volatile-lru,並

辨識Redis中儲存的資料

的重要性。對於那些重要的,絕對不能丟棄的資料(如配置類資料等),

應不設定有效期

,這樣Redis就

永遠不會淘汰這些資料

。對於那些相對不是那麼重要的,並且

能夠熱載入

的資料(比如

快取最近登入的使用者資訊

,當在Redis中找不到時,程式會去DB中讀取),可以設定上

有效期

,這樣在記憶體不夠時Redis就會淘汰這部分資料。