<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          2萬字總結(jié)Redis常用面試題/知識(shí)點(diǎn)(2021最新)

          共 23612字,需瀏覽 48分鐘

           ·

          2021-08-05 10:43

          大家好,我是躍哥。上次和大家分享了關(guān)于MySQL的一點(diǎn)小知識(shí),那這次我們趁熱打鐵,學(xué)習(xí)下數(shù)據(jù)存儲(chǔ)中的Redis,在項(xiàng)目中也是超級(jí)實(shí)用的那種噢。

          本文是開源項(xiàng)目 JavaGuide 其中的一篇文章。項(xiàng)目地址:https://github.com/Snailclimb/JavaGuide

          另外,這篇文章之前發(fā)過,不過,我最近對(duì)其進(jìn)行了重構(gòu)完善并且修復(fù)了很多小問題。所以,在公號(hào)再同步一下!

          內(nèi)容很硬!強(qiáng)烈建議小伙伴們花 15 分鐘左右閱讀一遍!

          1. 簡(jiǎn)單介紹一下 Redis 唄!

          簡(jiǎn)單來說 Redis 就是一個(gè)使用 C 語言開發(fā)的數(shù)據(jù)庫,不過與傳統(tǒng)數(shù)據(jù)庫不同的是 Redis 的數(shù)據(jù)是存在內(nèi)存中的 ,也就是它是內(nèi)存數(shù)據(jù)庫,所以讀寫速度非常快,因此 Redis 被廣泛應(yīng)用于緩存方向。

          另外,Redis 除了做緩存之外,也經(jīng)常用來做分布式鎖,甚至是消息隊(duì)列。

          Redis 提供了多種數(shù)據(jù)類型來支持不同的業(yè)務(wù)場(chǎng)景。Redis 還支持事務(wù) 、持久化、Lua 腳本、多種集群方案。

          2. 分布式緩存常見的技術(shù)選型方案有哪些?

          分布式緩存的話,使用的比較多的主要是 MemcachedRedis。不過,現(xiàn)在基本沒有看過還有項(xiàng)目使用 Memcached 來做緩存,都是直接用 Redis

          Memcached 是分布式緩存最開始興起的那會(huì),比較常用的。后來,隨著 Redis 的發(fā)展,大家慢慢都轉(zhuǎn)而使用更加強(qiáng)大的 Redis 了。

          分布式緩存主要解決的是單機(jī)緩存的容量受服務(wù)器限制并且無法保存通用信息的問題。因?yàn)椋镜鼐彺嬷辉诋?dāng)前服務(wù)里有效,比如如果你部署了兩個(gè)相同的服務(wù),他們兩者之間的緩存數(shù)據(jù)是無法共同的。

          3. 說一下 Redis 和 Memcached 的區(qū)別和共同點(diǎn)

          現(xiàn)在公司一般都是用 Redis 來實(shí)現(xiàn)緩存,而且 Redis 自身也越來越強(qiáng)大了!不過,了解 Redis 和 Memcached 的區(qū)別和共同點(diǎn),有助于我們?cè)谧鱿鄳?yīng)的技術(shù)選型的時(shí)候,能夠做到有理有據(jù)!

          共同點(diǎn)

          1. 都是基于內(nèi)存的數(shù)據(jù)庫,一般都用來當(dāng)做緩存使用。
          2. 都有過期策略。
          3. 兩者的性能都非常高。

          區(qū)別

          1. Redis 支持更豐富的數(shù)據(jù)類型(支持更復(fù)雜的應(yīng)用場(chǎng)景)。Redis 不僅僅支持簡(jiǎn)單的 k/v 類型的數(shù)據(jù),同時(shí)還提供 list,set,zset,hash 等數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ)。Memcached 只支持最簡(jiǎn)單的 k/v 數(shù)據(jù)類型。
          2. Redis 支持?jǐn)?shù)據(jù)的持久化,可以將內(nèi)存中的數(shù)據(jù)保持在磁盤中,重啟的時(shí)候可以再次加載進(jìn)行使用,而 Memecache 把數(shù)據(jù)全部存在內(nèi)存之中。
          3. Redis 有災(zāi)難恢復(fù)機(jī)制。 因?yàn)榭梢园丫彺嬷械臄?shù)據(jù)持久化到磁盤上。
          4. Redis 在服務(wù)器內(nèi)存使用完之后,可以將不用的數(shù)據(jù)放到磁盤上。但是,Memcached 在服務(wù)器內(nèi)存使用完之后,就會(huì)直接報(bào)異常。
          5. Memcached 沒有原生的集群模式,需要依靠客戶端來實(shí)現(xiàn)往集群中分片寫入數(shù)據(jù);但是 Redis 目前是原生支持 cluster 模式的。
          6. Memcached 是多線程,非阻塞 IO 復(fù)用的網(wǎng)絡(luò)模型;Redis 使用單線程的多路 IO 復(fù)用模型。 (Redis 6.0 引入了多線程 IO )
          7. Redis 支持發(fā)布訂閱模型、Lua 腳本、事務(wù)等功能,而 Memcached 不支持。并且,Redis 支持更多的編程語言。
          8. Memcached 過期數(shù)據(jù)的刪除策略只用了惰性刪除,而 Redis 同時(shí)使用了惰性刪除與定期刪除。

          相信看了上面的對(duì)比之后,我們已經(jīng)沒有什么理由可以選擇使用 Memcached 來作為自己項(xiàng)目的分布式緩存了。

          4. 緩存數(shù)據(jù)的處理流程是怎樣的?

          作為暖男一號(hào),我給大家畫了一個(gè)草圖。

          正常緩存處理流程

          簡(jiǎn)單來說就是:

          1. 如果用戶請(qǐng)求的數(shù)據(jù)在緩存中就直接返回。
          2. 緩存中不存在的話就看數(shù)據(jù)庫中是否存在。
          3. 數(shù)據(jù)庫中存在的話就更新緩存中的數(shù)據(jù)。
          4. 數(shù)據(jù)庫中不存在的話就返回空數(shù)據(jù)。

          5. 為什么要用 Redis/為什么要用緩存?

          簡(jiǎn)單,來說使用緩存主要是為了提升用戶體驗(yàn)以及應(yīng)對(duì)更多的用戶。

          下面我們主要從“高性能”和“高并發(fā)”這兩點(diǎn)來看待這個(gè)問題。

          img

          高性能

          對(duì)照上面 ?? 我畫的圖。我們?cè)O(shè)想這樣的場(chǎng)景:

          假如用戶第一次訪問數(shù)據(jù)庫中的某些數(shù)據(jù)的話,這個(gè)過程是比較慢,畢竟是從硬盤中讀取的。但是,如果說,用戶訪問的數(shù)據(jù)屬于高頻數(shù)據(jù)并且不會(huì)經(jīng)常改變的話,那么我們就可以很放心地將該用戶訪問的數(shù)據(jù)存在緩存中。

          這樣有什么好處呢? 那就是保證用戶下一次再訪問這些數(shù)據(jù)的時(shí)候就可以直接從緩存中獲取了。操作緩存就是直接操作內(nèi)存,所以速度相當(dāng)快。

          不過,要保持?jǐn)?shù)據(jù)庫和緩存中的數(shù)據(jù)的一致性。如果數(shù)據(jù)庫中的對(duì)應(yīng)數(shù)據(jù)改變的之后,同步改變緩存中相應(yīng)的數(shù)據(jù)即可!

          高并發(fā):

          一般像 MySQL 這類的數(shù)據(jù)庫的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 緩存之后很容易達(dá)到 10w+,甚至最高能達(dá)到 30w+(就單機(jī) redis 的情況,redis 集群的話會(huì)更高)。

          QPS(Query Per Second):服務(wù)器每秒可以執(zhí)行的查詢次數(shù);

          由此可見,直接操作緩存能夠承受的數(shù)據(jù)庫請(qǐng)求數(shù)量是遠(yuǎn)遠(yuǎn)大于直接訪問數(shù)據(jù)庫的,所以我們可以考慮把數(shù)據(jù)庫中的部分?jǐn)?shù)據(jù)轉(zhuǎn)移到緩存中去,這樣用戶的一部分請(qǐng)求會(huì)直接到緩存這里而不用經(jīng)過數(shù)據(jù)庫。進(jìn)而,我們也就提高了系統(tǒng)整體的并發(fā)。

          6. Redis 常見數(shù)據(jù)結(jié)構(gòu)以及使用場(chǎng)景分析

          你可以自己本機(jī)安裝 redis 或者通過 redis 官網(wǎng)提供的在線 redis 環(huán)境。

          try-redis

          6.1. string

          1. 介紹 :string 數(shù)據(jù)結(jié)構(gòu)是簡(jiǎn)單的 key-value 類型。雖然 Redis 是用 C 語言寫的,但是 Redis 并沒有使用 C 的字符串表示,而是自己構(gòu)建了一種 簡(jiǎn)單動(dòng)態(tài)字符串(simple dynamic string,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本數(shù)據(jù)還可以保存二進(jìn)制數(shù)據(jù),并且獲取字符串長度復(fù)雜度為 O(1)(C 字符串為 O(N)),除此之外,Redis 的 SDS API 是安全的,不會(huì)造成緩沖區(qū)溢出。
          2. 常用命令: set,get,strlen,exists,decr,incr,setex 等等。
          3. 應(yīng)用場(chǎng)景: 一般常用在需要計(jì)數(shù)的場(chǎng)景,比如用戶的訪問次數(shù)、熱點(diǎn)文章的點(diǎn)贊轉(zhuǎn)發(fā)數(shù)量等等。

          下面我們簡(jiǎn)單看看它的使用!

          普通字符串的基本操作:

          127.0.0.1:6379> set key value #設(shè)置 key-value 類型的值
          OK
          127.0.0.1:6379> get key # 根據(jù) key 獲得對(duì)應(yīng)的 value
          "value"
          127.0.0.1:6379> exists key  # 判斷某個(gè) key 是否存在
          (integer) 1
          127.0.0.1:6379> strlen key # 返回 key 所儲(chǔ)存的字符串值的長度。
          (integer) 5
          127.0.0.1:6379> del key # 刪除某個(gè) key 對(duì)應(yīng)的值
          (integer) 1
          127.0.0.1:6379> get key
          (nil)Copy to clipboardErrorCopied

          批量設(shè)置 :

          127.0.0.1:6379> mset key1 value1 key2 value2 # 批量設(shè)置 key-value 類型的值
          OK
          127.0.0.1:6379> mget key1 key2 # 批量獲取多個(gè) key 對(duì)應(yīng)的 value
          1) "value1"
          2) "value2"Copy to clipboardErrorCopied

          計(jì)數(shù)器(字符串的內(nèi)容為整數(shù)的時(shí)候可以使用):

          127.0.0.1:6379> set number 1
          OK
          127.0.0.1:6379> incr number # 將 key 中儲(chǔ)存的數(shù)字值增一
          (integer) 2
          127.0.0.1:6379> get number
          "2"
          127.0.0.1:6379> decr number # 將 key 中儲(chǔ)存的數(shù)字值減一
          (integer) 1
          127.0.0.1:6379> get number
          "1"Copy to clipboardErrorCopied

          過期(默認(rèn)為永不過期)

          127.0.0.1:6379> expire key  60 # 數(shù)據(jù)在 60s 后過期
          (integer) 1
          127.0.0.1:6379> setex key 60 value # 數(shù)據(jù)在 60s 后過期 (setex:[set] + [ex]pire)
          OK
          127.0.0.1:6379> ttl key # 查看數(shù)據(jù)還有多久過期
          (integer) 56Copy to clipboardErrorCopied

          6.2. list

          1. 介紹list 即是 鏈表。鏈表是一種非常常見的數(shù)據(jù)結(jié)構(gòu),特點(diǎn)是易于數(shù)據(jù)元素的插入和刪除并且可以靈活調(diào)整鏈表長度,但是鏈表的隨機(jī)訪問困難。許多高級(jí)編程語言都內(nèi)置了鏈表的實(shí)現(xiàn)比如 Java 中的 LinkedList,但是 C 語言并沒有實(shí)現(xiàn)鏈表,所以 Redis 實(shí)現(xiàn)了自己的鏈表數(shù)據(jù)結(jié)構(gòu)。Redis 的 list 的實(shí)現(xiàn)為一個(gè) 雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷。
          2. 常用命令: rpush,lpop,lpush,rpop,lrange,llen 等。
          3. 應(yīng)用場(chǎng)景: 發(fā)布與訂閱或者說消息隊(duì)列、慢查詢。

          下面我們簡(jiǎn)單看看它的使用!

          通過 rpush/lpop 實(shí)現(xiàn)隊(duì)列:

          127.0.0.1:6379> rpush myList value1 # 向 list 的頭部(右邊)添加元素
          (integer) 1
          127.0.0.1:6379> rpush myList value2 value3 # 向list的頭部(最右邊)添加多個(gè)元素
          (integer) 3
          127.0.0.1:6379> lpop myList # 將 list的尾部(最左邊)元素取出
          "value1"
          127.0.0.1:6379> lrange myList 0 1 # 查看對(duì)應(yīng)下標(biāo)的list列表, 0 為 start,1為 end
          1) "value2"
          2) "value3"
          127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒數(shù)第一
          1) "value2"
          2) "value3"Copy to clipboardErrorCopied

          通過 rpush/rpop 實(shí)現(xiàn)棧:

          127.0.0.1:6379> rpush myList2 value1 value2 value3
          (integer) 3
          127.0.0.1:6379> rpop myList2 # 將 list的頭部(最右邊)元素取出
          "value3"Copy to clipboardErrorCopied

          我專門花了一個(gè)圖方便小伙伴們來理解:

          redis list

          通過 lrange 查看對(duì)應(yīng)下標(biāo)范圍的列表元素:

          127.0.0.1:6379> rpush myList value1 value2 value3
          (integer) 3
          127.0.0.1:6379> lrange myList 0 1 # 查看對(duì)應(yīng)下標(biāo)的list列表, 0 為 start,1為 end
          1) "value1"
          2) "value2"
          127.0.0.1:6379> lrange myList 0 -1 # 查看列表中的所有元素,-1表示倒數(shù)第一
          1) "value1"
          2) "value2"
          3) "value3"Copy to clipboardErrorCopied

          通過 lrange 命令,你可以基于 list 實(shí)現(xiàn)分頁查詢,性能非常高!

          通過 llen 查看鏈表長度:

          127.0.0.1:6379> llen myList
          (integer) 3Copy to clipboardErrorCopied

          6.3. hash

          1. 介紹 :hash 類似于 JDK1.8 前的 HashMap,內(nèi)部實(shí)現(xiàn)也差不多(數(shù)組 + 鏈表)。不過,Redis 的 hash 做了更多優(yōu)化。另外,hash 是一個(gè) string 類型的 field 和 value 的映射表,特別適合用于存儲(chǔ)對(duì)象,后續(xù)操作的時(shí)候,你可以直接僅僅修改這個(gè)對(duì)象中的某個(gè)字段的值。比如我們可以 hash 數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)用戶信息,商品信息等等。
          2. 常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
          3. 應(yīng)用場(chǎng)景: 系統(tǒng)中對(duì)象數(shù)據(jù)的存儲(chǔ)。

          下面我們簡(jiǎn)單看看它的使用!

          127.0.0.1:6379> hmset userInfoKey name "guide" description "dev" age "24"
          OK
          127.0.0.1:6379> hexists userInfoKey name # 查看 key 對(duì)應(yīng)的 value中指定的字段是否存在。
          (integer) 1
          127.0.0.1:6379> hget userInfoKey name # 獲取存儲(chǔ)在哈希表中指定字段的值。
          "guide"
          127.0.0.1:6379> hget userInfoKey age
          "24"
          127.0.0.1:6379> hgetall userInfoKey # 獲取在哈希表中指定 key 的所有字段和值
          1) "name"
          2) "guide"
          3) "description"
          4) "dev"
          5) "age"
          6) "24"
          127.0.0.1:6379> hkeys userInfoKey # 獲取 key 列表
          1) "name"
          2) "description"
          3) "age"
          127.0.0.1:6379> hvals userInfoKey # 獲取 value 列表
          1) "guide"
          2) "dev"
          3) "24"
          127.0.0.1:6379> hset userInfoKey name "GuideGeGe" # 修改某個(gè)字段對(duì)應(yīng)的值
          127.0.0.1:6379> hget userInfoKey name
          "GuideGeGe"Copy to clipboardErrorCopied

          6.4. set

          1. 介紹 : set 類似于 Java 中的 HashSet 。Redis 中的 set 類型是一種無序集合,集合中的元素沒有先后順序。當(dāng)你需要存儲(chǔ)一個(gè)列表數(shù)據(jù),又不希望出現(xiàn)重復(fù)數(shù)據(jù)時(shí),set 是一個(gè)很好的選擇,并且 set 提供了判斷某個(gè)成員是否在一個(gè) set 集合內(nèi)的重要接口,這個(gè)也是 list 所不能提供的。可以基于 set 輕易實(shí)現(xiàn)交集、并集、差集的操作。比如:你可以將一個(gè)用戶所有的關(guān)注人存在一個(gè)集合中,將其所有粉絲存在一個(gè)集合。Redis 可以非常方便的實(shí)現(xiàn)如共同關(guān)注、共同粉絲、共同喜好等功能。這個(gè)過程也就是求交集的過程。
          2. 常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。
          3. 應(yīng)用場(chǎng)景: 需要存放的數(shù)據(jù)不能重復(fù)以及需要獲取多個(gè)數(shù)據(jù)源交集和并集等場(chǎng)景

          下面我們簡(jiǎn)單看看它的使用!

          127.0.0.1:6379> sadd mySet value1 value2 # 添加元素進(jìn)去
          (integer) 2
          127.0.0.1:6379> sadd mySet value1 # 不允許有重復(fù)元素
          (integer) 0
          127.0.0.1:6379> smembers mySet # 查看 set 中所有的元素
          1) "value1"
          2) "value2"
          127.0.0.1:6379> scard mySet # 查看 set 的長度
          (integer) 2
          127.0.0.1:6379> sismember mySet value1 # 檢查某個(gè)元素是否存在set 中,只能接收單個(gè)元素
          (integer) 1
          127.0.0.1:6379> sadd mySet2 value2 value3
          (integer) 2
          127.0.0.1:6379> sinterstore mySet3 mySet mySet2 # 獲取 mySet 和 mySet2 的交集并存放在 mySet3 中
          (integer) 1
          127.0.0.1:6379> smembers mySet3
          1) "value2"Copy to clipboardErrorCopied

          6.5. sorted set

          1. 介紹: 和 set 相比,sorted set 增加了一個(gè)權(quán)重參數(shù) score,使得集合中的元素能夠按 score 進(jìn)行有序排列,還可以通過 score 的范圍來獲取元素的列表。有點(diǎn)像是 Java 中 HashMap 和 TreeSet 的結(jié)合體。
          2. 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
          3. 應(yīng)用場(chǎng)景: 需要對(duì)數(shù)據(jù)根據(jù)某個(gè)權(quán)重進(jìn)行排序的場(chǎng)景。比如在直播系統(tǒng)中,實(shí)時(shí)排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解為按消息維度的消息排行榜)等信息。
          127.0.0.1:6379> zadd myZset 3.0 value1 # 添加元素到 sorted set 中 3.0 為權(quán)重
          (integer) 1
          127.0.0.1:6379> zadd myZset 2.0 value2 1.0 value3 # 一次添加多個(gè)元素
          (integer) 2
          127.0.0.1:6379> zcard myZset # 查看 sorted set 中的元素?cái)?shù)量
          (integer) 3
          127.0.0.1:6379> zscore myZset value1 # 查看某個(gè) value 的權(quán)重
          "3"
          127.0.0.1:6379> zrange  myZset 0 -1 # 順序輸出某個(gè)范圍區(qū)間的元素,0 -1 表示輸出所有元素
          1) "value3"
          2) "value2"
          3) "value1"
          127.0.0.1:6379> zrange  myZset 0 1 # 順序輸出某個(gè)范圍區(qū)間的元素,0 為 start  1 為 stop
          1) "value3"
          2) "value2"
          127.0.0.1:6379> zrevrange  myZset 0 1 # 逆序輸出某個(gè)范圍區(qū)間的元素,0 為 start  1 為 stop
          1) "value1"
          2) "value2"Copy to clipboardErrorCopied

          6.6 bitmap

          1. 介紹: bitmap 存儲(chǔ)的是連續(xù)的二進(jìn)制數(shù)字(0 和 1),通過 bitmap, 只需要一個(gè) bit 位來表示某個(gè)元素對(duì)應(yīng)的值或者狀態(tài),key 就是對(duì)應(yīng)元素本身 。我們知道 8 個(gè) bit 可以組成一個(gè) byte,所以 bitmap 本身會(huì)極大的節(jié)省儲(chǔ)存空間。
          2. 常用命令: setbitgetbitbitcountbitop
          3. 應(yīng)用場(chǎng)景: 適合需要保存狀態(tài)信息(比如是否簽到、是否登錄...)并需要進(jìn)一步對(duì)這些信息進(jìn)行分析的場(chǎng)景。比如用戶簽到情況、活躍用戶情況、用戶行為統(tǒng)計(jì)(比如是否點(diǎn)贊過某個(gè)視頻)。
          # SETBIT 會(huì)返回之前位的值(默認(rèn)是 0)這里會(huì)生成 7 個(gè)位
          127.0.0.1:6379> setbit mykey 7 1
          (integer) 0
          127.0.0.1:6379> setbit mykey 7 0
          (integer) 1
          127.0.0.1:6379> getbit mykey 7
          (integer) 0
          127.0.0.1:6379> setbit mykey 6 1
          (integer) 0
          127.0.0.1:6379> setbit mykey 8 1
          (integer) 0
          # 通過 bitcount 統(tǒng)計(jì)被被設(shè)置為 1 的位的數(shù)量。
          127.0.0.1:6379> bitcount mykey
          (integer) 2Copy to clipboardErrorCopied

          針對(duì)上面提到的一些場(chǎng)景,這里進(jìn)行進(jìn)一步說明。

          使用場(chǎng)景一:用戶行為分析 很多網(wǎng)站為了分析你的喜好,需要研究你點(diǎn)贊過的內(nèi)容。

          # 記錄你喜歡過 001 號(hào)小姐姐
          127.0.0.1:6379> setbit beauty_girl_001 uid 1Copy to clipboardErrorCopied

          使用場(chǎng)景二:統(tǒng)計(jì)活躍用戶

          使用時(shí)間作為 key,然后用戶 ID 為 offset,如果當(dāng)日活躍過就設(shè)置為 1

          那么我該如何計(jì)算某幾天/月/年的活躍用戶呢(暫且約定,統(tǒng)計(jì)時(shí)間內(nèi)只要有一天在線就稱為活躍),有請(qǐng)下一個(gè) redis 的命令

          # 對(duì)一個(gè)或多個(gè)保存二進(jìn)制位的字符串 key 進(jìn)行位元操作,并將結(jié)果保存到 destkey 上。
          # BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 這四種操作中的任意一種參數(shù)
          BITOP operation destkey key [key ...]Copy to clipboardErrorCopied

          初始化數(shù)據(jù):

          127.0.0.1:6379> setbit 20210308 1 1
          (integer) 0
          127.0.0.1:6379> setbit 20210308 2 1
          (integer) 0
          127.0.0.1:6379> setbit 20210309 1 1
          (integer) 0Copy to clipboardErrorCopied

          統(tǒng)計(jì) 20210308~20210309 總活躍用戶數(shù): 1

          127.0.0.1:6379> bitop and desk1 20210308 20210309
          (integer) 1
          127.0.0.1:6379> bitcount desk1
          (integer) 1Copy to clipboardErrorCopied

          統(tǒng)計(jì) 20210308~20210309 在線活躍用戶數(shù): 2

          127.0.0.1:6379> bitop or desk2 20210308 20210309
          (integer) 1
          127.0.0.1:6379> bitcount desk2
          (integer) 2Copy to clipboardErrorCopied

          使用場(chǎng)景三:用戶在線狀態(tài)

          對(duì)于獲取或者統(tǒng)計(jì)用戶在線狀態(tài),使用 bitmap 是一個(gè)節(jié)約空間且效率又高的一種方法。

          只需要一個(gè) key,然后用戶 ID 為 offset,如果在線就設(shè)置為 1,不在線就設(shè)置為 0。

          7. Redis 單線程模型詳解

          Redis 基于 Reactor 模式來設(shè)計(jì)開發(fā)了自己的一套高效的事件處理模型 (Netty 的線程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),這套事件處理模型對(duì)應(yīng)的是 Redis 中的文件事件處理器(file event handler)。由于文件事件處理器(file event handler)是單線程方式運(yùn)行的,所以我們一般都說 Redis 是單線程模型。

          既然是單線程,那怎么監(jiān)聽大量的客戶端連接呢?

          Redis 通過IO 多路復(fù)用程序 來監(jiān)聽來自客戶端的大量連接(或者說是監(jiān)聽多個(gè) socket),它會(huì)將感興趣的事件及類型(讀、寫)注冊(cè)到內(nèi)核中并監(jiān)聽每個(gè)事件是否發(fā)生。

          這樣的好處非常明顯:I/O 多路復(fù)用技術(shù)的使用讓 Redis 不需要額外創(chuàng)建多余的線程來監(jiān)聽客戶端的大量連接,降低了資源的消耗(和 NIO 中的 Selector 組件很像)。

          另外, Redis 服務(wù)器是一個(gè)事件驅(qū)動(dòng)程序,服務(wù)器需要處理兩類事件:1. 文件事件; 2. 時(shí)間事件。

          時(shí)間事件不需要多花時(shí)間了解,我們接觸最多的還是 文件事件(客戶端進(jìn)行讀取寫入等操作,涉及一系列網(wǎng)絡(luò)通信)。

          《Redis 設(shè)計(jì)與實(shí)現(xiàn)》有一段話是如是介紹文件事件的,我覺得寫得挺不錯(cuò)。

          Redis 基于 Reactor 模式開發(fā)了自己的網(wǎng)絡(luò)事件處理器:這個(gè)處理器被稱為文件事件處理器(file event handler)。文件事件處理器使用 I/O 多路復(fù)用(multiplexing)程序來同時(shí)監(jiān)聽多個(gè)套接字,并根據(jù)套接字目前執(zhí)行的任務(wù)來為套接字關(guān)聯(lián)不同的事件處理器。

          當(dāng)被監(jiān)聽的套接字準(zhǔn)備好執(zhí)行連接應(yīng)答(accept)、讀取(read)、寫入(write)、關(guān) 閉(close)等操作時(shí),與操作相對(duì)應(yīng)的文件事件就會(huì)產(chǎn)生,這時(shí)文件事件處理器就會(huì)調(diào)用套接字之前關(guān)聯(lián)好的事件處理器來處理這些事件。

          雖然文件事件處理器以單線程方式運(yùn)行,但通過使用 I/O 多路復(fù)用程序來監(jiān)聽多個(gè)套接字,文件事件處理器既實(shí)現(xiàn)了高性能的網(wǎng)絡(luò)通信模型,又可以很好地與 Redis 服務(wù)器中其他同樣以單線程方式運(yùn)行的模塊進(jìn)行對(duì)接,這保持了 Redis 內(nèi)部單線程設(shè)計(jì)的簡(jiǎn)單性。

          可以看出,文件事件處理器(file event handler)主要是包含 4 個(gè)部分:

          • 多個(gè) socket(客戶端連接)
          • IO 多路復(fù)用程序(支持多個(gè)客戶端連接的關(guān)鍵)
          • 文件事件分派器(將 socket 關(guān)聯(lián)到相應(yīng)的事件處理器)
          • 事件處理器(連接應(yīng)答處理器、命令請(qǐng)求處理器、命令回復(fù)處理器)
          img

          《Redis設(shè)計(jì)與實(shí)現(xiàn):12章》

          8. Redis 沒有使用多線程?為什么不使用多線程?

          雖然說 Redis 是單線程模型,但是,實(shí)際上,Redis 在 4.0 之后的版本中就已經(jīng)加入了對(duì)多線程的支持。

          redis4.0 more thread

          不過,Redis 4.0 增加的多線程主要是針對(duì)一些大鍵值對(duì)的刪除操作的命令,使用這些命令就會(huì)使用主處理之外的其他線程來“異步處理”。

          大體上來說,Redis 6.0 之前主要還是單線程處理。

          那,Redis6.0 之前 為什么不使用多線程?

          我覺得主要原因有下面 3 個(gè):

          1. 單線程編程容易并且更容易維護(hù);
          2. Redis 的性能瓶頸不在 CPU ,主要在內(nèi)存和網(wǎng)絡(luò);
          3. 多線程就會(huì)存在死鎖、線程上下文切換等問題,甚至?xí)绊懶阅堋?/section>

          9. Redis6.0 之后為何引入了多線程?

          Redis6.0 引入多線程主要是為了提高網(wǎng)絡(luò) IO 讀寫性能,因?yàn)檫@個(gè)算是 Redis 中的一個(gè)性能瓶頸(Redis 的瓶頸主要受限于內(nèi)存和網(wǎng)絡(luò))。

          雖然,Redis6.0 引入了多線程,但是 Redis 的多線程只是在網(wǎng)絡(luò)數(shù)據(jù)的讀寫這類耗時(shí)操作上使用了,執(zhí)行命令仍然是單線程順序執(zhí)行。因此,你也不需要擔(dān)心線程安全問題。

          Redis6.0 的多線程默認(rèn)是禁用的,只使用主線程。如需開啟需要修改 redis 配置文件 redis.conf

          io-threads-do-reads yesCopy to clipboardErrorCopied

          開啟多線程后,還需要設(shè)置線程數(shù),否則是不生效的。同樣需要修改 redis 配置文件 redis.conf :

          io-threads 4 #官網(wǎng)建議4核的機(jī)器建議設(shè)置為2或3個(gè)線程,8核的建議設(shè)置為6個(gè)線程Copy to clipboardErrorCopied

          推薦閱讀:

          1. Redis 6.0 新特性-多線程連環(huán) 13 問!
          2. 為什么 Redis 選擇單線程模型

          10. Redis 給緩存數(shù)據(jù)設(shè)置過期時(shí)間有啥用?

          一般情況下,我們?cè)O(shè)置保存的緩存數(shù)據(jù)的時(shí)候都會(huì)設(shè)置一個(gè)過期時(shí)間。為什么呢?

          因?yàn)閮?nèi)存是有限的,如果緩存中的所有數(shù)據(jù)都是一直保存的話,分分鐘直接 Out of memory。

          Redis 自帶了給緩存數(shù)據(jù)設(shè)置過期時(shí)間的功能,比如:

          127.0.0.1:6379> exp key 60 # 數(shù)據(jù)在 60s 后過期
          (integer) 1
          127.0.0.1:6379> setex key 60 value # 數(shù)據(jù)在 60s 后過期 (setex:[set] + [ex]pire)
          OK
          127.0.0.1:6379> ttl key # 查看數(shù)據(jù)還有多久過期
          (integer) 56Copy to clipboardErrorCopied

          注意:**Redis 中除了字符串類型有自己獨(dú)有設(shè)置過期時(shí)間的命令 setex 外,其他方法都需要依靠 expire 命令來設(shè)置過期時(shí)間 。另外, persist 命令可以移除一個(gè)鍵的過期時(shí)間。**

          過期時(shí)間除了有助于緩解內(nèi)存的消耗,還有什么其他用么?

          很多時(shí)候,我們的業(yè)務(wù)場(chǎng)景就是需要某個(gè)數(shù)據(jù)只在某一時(shí)間段內(nèi)存在,比如我們的短信驗(yàn)證碼可能只在 1 分鐘內(nèi)有效,用戶登錄的 token 可能只在 1 天內(nèi)有效。

          如果使用傳統(tǒng)的數(shù)據(jù)庫來處理的話,一般都是自己判斷過期,這樣更麻煩并且性能要差很多。

          11. Redis 是如何判斷數(shù)據(jù)是否過期的呢?

          Redis 通過一個(gè)叫做過期字典(可以看作是 hash 表)來保存數(shù)據(jù)過期的時(shí)間。過期字典的鍵指向 Redis 數(shù)據(jù)庫中的某個(gè) key(鍵),過期字典的值是一個(gè) long long 類型的整數(shù),這個(gè)整數(shù)保存了 key 所指向的數(shù)據(jù)庫鍵的過期時(shí)間(毫秒精度的 UNIX 時(shí)間戳)。

          redis過期字典

          過期字典是存儲(chǔ)在 redisDb 這個(gè)結(jié)構(gòu)里的:

          typedef struct redisDb {
              ...

              dict *dict;     //數(shù)據(jù)庫鍵空間,保存著數(shù)據(jù)庫中所有鍵值對(duì)
              dict *expires   // 過期字典,保存著鍵的過期時(shí)間
              ...
          } redisDb;Copy to clipboardErrorCopied

          12. 過期的數(shù)據(jù)的刪除策略了解么?

          如果假設(shè)你設(shè)置了一批 key 只能存活 1 分鐘,那么 1 分鐘后,Redis 是怎么對(duì)這批 key 進(jìn)行刪除的呢?

          常用的過期數(shù)據(jù)的刪除策略就兩個(gè)(重要!自己造緩存輪子的時(shí)候需要格外考慮的東西):

          1. 惰性刪除 :只會(huì)在取出 key 的時(shí)候才對(duì)數(shù)據(jù)進(jìn)行過期檢查。這樣對(duì) CPU 最友好,但是可能會(huì)造成太多過期 key 沒有被刪除。
          2. 定期刪除 :每隔一段時(shí)間抽取一批 key 執(zhí)行刪除過期 key 操作。并且,Redis 底層會(huì)通過限制刪除操作執(zhí)行的時(shí)長和頻率來減少刪除操作對(duì) CPU 時(shí)間的影響。

          定期刪除對(duì)內(nèi)存更加友好,惰性刪除對(duì) CPU 更加友好。兩者各有千秋,所以 Redis 采用的是 定期刪除+惰性/懶漢式刪除

          但是,僅僅通過給 key 設(shè)置過期時(shí)間還是有問題的。因?yàn)檫€是可能存在定期刪除和惰性刪除漏掉了很多過期 key 的情況。這樣就導(dǎo)致大量過期 key 堆積在內(nèi)存里,然后就 Out of memory 了。

          怎么解決這個(gè)問題呢?答案就是:Redis 內(nèi)存淘汰機(jī)制。

          13. Redis 內(nèi)存淘汰機(jī)制了解么?

          相關(guān)問題:MySQL 里有 2000w 數(shù)據(jù),Redis 中只存 20w 的數(shù)據(jù),如何保證 Redis 中的數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)?

          Redis 提供 6 種數(shù)據(jù)淘汰策略:

          1. volatile-lru(least recently used):從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
          2. volatile-ttl:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
          3. volatile-random:從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
          4. allkeys-lru(least recently used):當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最近最少使用的 key(這個(gè)是最常用的)
          5. allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
          6. no-eviction:禁止驅(qū)逐數(shù)據(jù),也就是說當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),新寫入操作會(huì)報(bào)錯(cuò)。這個(gè)應(yīng)該沒人使用吧!

          4.0 版本后增加以下兩種:

          1. volatile-lfu(least frequently used):從已設(shè)置過期時(shí)間的數(shù)據(jù)集(server.db[i].expires)中挑選最不經(jīng)常使用的數(shù)據(jù)淘汰
          2. allkeys-lfu(least frequently used):當(dāng)內(nèi)存不足以容納新寫入數(shù)據(jù)時(shí),在鍵空間中,移除最不經(jīng)常使用的 key

          14. Redis 持久化機(jī)制(怎么保證 Redis 掛掉之后再重啟數(shù)據(jù)可以進(jìn)行恢復(fù))

          很多時(shí)候我們需要持久化數(shù)據(jù)也就是將內(nèi)存中的數(shù)據(jù)寫入到硬盤里面,大部分原因是為了之后重用數(shù)據(jù)(比如重啟機(jī)器、機(jī)器故障之后恢復(fù)數(shù)據(jù)),或者是為了防止系統(tǒng)故障而將數(shù)據(jù)備份到一個(gè)遠(yuǎn)程位置。

          Redis 不同于 Memcached 的很重要一點(diǎn)就是,Redis 支持持久化,而且支持兩種不同的持久化操作。Redis 的一種持久化方式叫快照(snapshotting,RDB),另一種方式是只追加文件(append-only file, AOF)。這兩種方法各有千秋,下面我會(huì)詳細(xì)這兩種持久化方法是什么,怎么用,如何選擇適合自己的持久化方法。

          快照(snapshotting)持久化(RDB)

          Redis 可以通過創(chuàng)建快照來獲得存儲(chǔ)在內(nèi)存里面的數(shù)據(jù)在某個(gè)時(shí)間點(diǎn)上的副本。Redis 創(chuàng)建快照之后,可以對(duì)快照進(jìn)行備份,可以將快照復(fù)制到其他服務(wù)器從而創(chuàng)建具有相同數(shù)據(jù)的服務(wù)器副本(Redis 主從結(jié)構(gòu),主要用來提高 Redis 性能),還可以將快照留在原地以便重啟服務(wù)器的時(shí)候使用。

          快照持久化是 Redis 默認(rèn)采用的持久化方式,在 Redis.conf 配置文件中默認(rèn)有此下配置:

          save 900 1           #在900秒(15分鐘)之后,如果至少有1個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。

          save 300 10 #在300秒(5分鐘)之后,如果至少有10個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。

          save 60 10000 #在60秒(1分鐘)之后,如果至少有10000個(gè)key發(fā)生變化,Redis就會(huì)自動(dòng)觸發(fā)BGSAVE命令創(chuàng)建快照。Copy to clipboardErrorCopied

          AOF(append-only file)持久化

          與快照持久化相比,AOF 持久化的實(shí)時(shí)性更好,因此已成為主流的持久化方案。默認(rèn)情況下 Redis 沒有開啟 AOF(append only file)方式的持久化,可以通過 appendonly 參數(shù)開啟:

          appendonly yesCopy to clipboardErrorCopied

          開啟 AOF 持久化后每執(zhí)行一條會(huì)更改 Redis 中的數(shù)據(jù)的命令,Redis 就會(huì)將該命令寫入硬盤中的 AOF 文件。AOF 文件的保存位置和 RDB 文件的位置相同,都是通過 dir 參數(shù)設(shè)置的,默認(rèn)的文件名是 appendonly.aof。

          在 Redis 的配置文件中存在三種不同的 AOF 持久化方式,它們分別是:

          appendfsync always    #每次有數(shù)據(jù)修改發(fā)生時(shí)都會(huì)寫入AOF文件,這樣會(huì)嚴(yán)重降低Redis的速度
          appendfsync everysec #每秒鐘同步一次,顯示地將多個(gè)寫命令同步到硬盤
          appendfsync no #讓操作系統(tǒng)決定何時(shí)進(jìn)行同步Copy to clipboardErrorCopied

          為了兼顧數(shù)據(jù)和寫入性能,用戶可以考慮 appendfsync everysec 選項(xiàng) ,讓 Redis 每秒同步一次 AOF 文件,Redis 性能幾乎沒受到任何影響。而且這樣即使出現(xiàn)系統(tǒng)崩潰,用戶最多只會(huì)丟失一秒之內(nèi)產(chǎn)生的數(shù)據(jù)。當(dāng)硬盤忙于執(zhí)行寫入操作的時(shí)候,Redis 還會(huì)優(yōu)雅的放慢自己的速度以便適應(yīng)硬盤的最大寫入速度。

          相關(guān) issue :783:Redis 的 AOF 方式

          拓展:Redis 4.0 對(duì)于持久化機(jī)制的優(yōu)化

          Redis 4.0 開始支持 RDB 和 AOF 的混合持久化(默認(rèn)關(guān)閉,可以通過配置項(xiàng) aof-use-rdb-preamble 開啟)。

          如果把混合持久化打開,AOF 重寫的時(shí)候就直接把 RDB 的內(nèi)容寫到 AOF 文件開頭。這樣做的好處是可以結(jié)合 RDB 和 AOF 的優(yōu)點(diǎn), 快速加載同時(shí)避免丟失過多的數(shù)據(jù)。當(dāng)然缺點(diǎn)也是有的, AOF 里面的 RDB 部分是壓縮格式不再是 AOF 格式,可讀性較差。

          補(bǔ)充內(nèi)容:AOF 重寫

          AOF 重寫可以產(chǎn)生一個(gè)新的 AOF 文件,這個(gè)新的 AOF 文件和原有的 AOF 文件所保存的數(shù)據(jù)庫狀態(tài)一樣,但體積更小。

          AOF 重寫是一個(gè)有歧義的名字,該功能是通過讀取數(shù)據(jù)庫中的鍵值對(duì)來實(shí)現(xiàn)的,程序無須對(duì)現(xiàn)有 AOF 文件進(jìn)行任何讀入、分析或者寫入操作。

          在執(zhí)行 BGREWRITEAOF 命令時(shí),Redis 服務(wù)器會(huì)維護(hù)一個(gè) AOF 重寫緩沖區(qū),該緩沖區(qū)會(huì)在子進(jìn)程創(chuàng)建新 AOF 文件期間,記錄服務(wù)器執(zhí)行的所有寫命令。當(dāng)子進(jìn)程完成創(chuàng)建新 AOF 文件的工作之后,服務(wù)器會(huì)將重寫緩沖區(qū)中的所有內(nèi)容追加到新 AOF 文件的末尾,使得新舊兩個(gè) AOF 文件所保存的數(shù)據(jù)庫狀態(tài)一致。最后,服務(wù)器用新的 AOF 文件替換舊的 AOF 文件,以此來完成 AOF 文件重寫操作。

          15. Redis 事務(wù)

          Redis 可以通過 MULTIEXECDISCARDWATCH 等命令來實(shí)現(xiàn)事務(wù)(transaction)功能。

          > MULTI
          OK
          > SET USER "Guide哥"
          QUEUED
          > GET USER
          QUEUED
          > EXEC
          1) OK
          2) "Guide哥"Copy to clipboardErrorCopied

          使用 MULTI 命令后可以輸入多個(gè)命令。Redis 不會(huì)立即執(zhí)行這些命令,而是將它們放到隊(duì)列,當(dāng)調(diào)用了 EXEC 命令將執(zhí)行所有命令。

          這個(gè)過程是這樣的:

          1. 開始事務(wù)(MULTI)。
          2. 命令入隊(duì)(批量操作 Redis 的命令,先進(jìn)先出(FIFO)的順序執(zhí)行)。
          3. 執(zhí)行事務(wù)(EXEC)。

          你也可以通過 DISCARD 命令取消一個(gè)事務(wù),它會(huì)清空事務(wù)隊(duì)列中保存的所有命令。

          > MULTI
          OK
          > SET USER "Guide哥"
          QUEUED
          > GET USER
          QUEUED
          > DISCARD
          OKCopy to clipboardErrorCopied

          WATCH 命令用于監(jiān)聽指定的鍵,當(dāng)調(diào)用 EXEC 命令執(zhí)行事務(wù)時(shí),如果一個(gè)被 WATCH 命令監(jiān)視的鍵被修改的話,整個(gè)事務(wù)都不會(huì)執(zhí)行,直接返回失敗。

          > WATCH USER
          OK
          > MULTI
          > SET USER "Guide哥"
          OK
          > GET USER
          Guide哥
          > EXEC
          ERR EXEC without MULTICopy to clipboardErrorCopied

          Redis 官網(wǎng)相關(guān)介紹 https://redis.io/topics/transactions 如下:

          redis事務(wù)

          但是,Redis 的事務(wù)和我們平時(shí)理解的關(guān)系型數(shù)據(jù)庫的事務(wù)不同。我們知道事務(wù)具有四大特性:1. 原子性2. 隔離性3. 持久性4. 一致性

          1. 原子性(Atomicity): 事務(wù)是最小的執(zhí)行單位,不允許分割。事務(wù)的原子性確保動(dòng)作要么全部完成,要么完全不起作用;
          2. 隔離性(Isolation): 并發(fā)訪問數(shù)據(jù)庫時(shí),一個(gè)用戶的事務(wù)不被其他事務(wù)所干擾,各并發(fā)事務(wù)之間數(shù)據(jù)庫是獨(dú)立的;
          3. 持久性(Durability): 一個(gè)事務(wù)被提交之后。它對(duì)數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對(duì)其有任何影響。
          4. 一致性(Consistency): 執(zhí)行事務(wù)前后,數(shù)據(jù)保持一致,多個(gè)事務(wù)對(duì)同一個(gè)數(shù)據(jù)讀取的結(jié)果是相同的;

          Redis 是不支持 roll back 的,因而不滿足原子性的(而且不滿足持久性)。

          Redis 官網(wǎng)也解釋了自己為啥不支持回滾。簡(jiǎn)單來說就是 Redis 開發(fā)者們覺得沒必要支持回滾,這樣更簡(jiǎn)單便捷并且性能更好。Redis 開發(fā)者覺得即使命令執(zhí)行錯(cuò)誤也應(yīng)該在開發(fā)過程中就被發(fā)現(xiàn)而不是生產(chǎn)過程中。

          redis roll back

          你可以將 Redis 中的事務(wù)就理解為 :Redis 事務(wù)提供了一種將多個(gè)命令請(qǐng)求打包的功能。然后,再按順序執(zhí)行打包的所有命令,并且不會(huì)被中途打斷。

          相關(guān) issue :

          • issue452: 關(guān)于 Redis 事務(wù)不滿足原子性的問題 。
          • Issue491:關(guān)于 redis 沒有事務(wù)回滾?

          16. 緩存穿透

          16.1. 什么是緩存穿透?

          緩存穿透說簡(jiǎn)單點(diǎn)就是大量請(qǐng)求的 key 根本不存在于緩存中,導(dǎo)致請(qǐng)求直接到了數(shù)據(jù)庫上,根本沒有經(jīng)過緩存這一層。舉個(gè)例子:某個(gè)黑客故意制造我們緩存中不存在的 key 發(fā)起大量請(qǐng)求,導(dǎo)致大量請(qǐng)求落到數(shù)據(jù)庫。

          16.2. 緩存穿透情況的處理流程是怎樣的?

          如下圖所示,用戶的請(qǐng)求最終都要跑到數(shù)據(jù)庫中查詢一遍。

          緩存穿透情況

          16.3. 有哪些解決辦法?

          最基本的就是首先做好參數(shù)校驗(yàn),一些不合法的參數(shù)請(qǐng)求直接拋出異常信息返回給客戶端。比如查詢的數(shù)據(jù)庫 id 不能小于 0、傳入的郵箱格式不對(duì)的時(shí)候直接返回錯(cuò)誤消息給客戶端等等。

          1)緩存無效 key

          如果緩存和數(shù)據(jù)庫都查不到某個(gè) key 的數(shù)據(jù)就寫一個(gè)到 Redis 中去并設(shè)置過期時(shí)間,具體命令如下:SET key value EX 10086 。這種方式可以解決請(qǐng)求的 key 變化不頻繁的情況,如果黑客惡意攻擊,每次構(gòu)建不同的請(qǐng)求 key,會(huì)導(dǎo)致 Redis 中緩存大量無效的 key 。很明顯,這種方案并不能從根本上解決此問題。如果非要用這種方式來解決穿透問題的話,盡量將無效的 key 的過期時(shí)間設(shè)置短一點(diǎn)比如 1 分鐘。

          另外,這里多說一嘴,一般情況下我們是這樣設(shè)計(jì) key 的:表名:列名:主鍵名:主鍵值

          如果用 Java 代碼展示的話,差不多是下面這樣的:

          public Object getObjectInclNullById(Integer id) {
              // 從緩存中獲取數(shù)據(jù)
              Object cacheValue = cache.get(id);
              // 緩存為空
              if (cacheValue == null) {
                  // 從數(shù)據(jù)庫中獲取
                  Object storageValue = storage.get(key);
                  // 緩存空對(duì)象
                  cache.set(key, storageValue);
                  // 如果存儲(chǔ)數(shù)據(jù)為空,需要設(shè)置一個(gè)過期時(shí)間(300秒)
                  if (storageValue == null) {
                      // 必須設(shè)置過期時(shí)間,否則有被攻擊的風(fēng)險(xiǎn)
                      cache.expire(key, 60 * 5);
                  }
                  return storageValue;
              }
              return cacheValue;
          }Copy to clipboardErrorCopied

          2)布隆過濾器

          布隆過濾器是一個(gè)非常神奇的數(shù)據(jù)結(jié)構(gòu),通過它我們可以非常方便地判斷一個(gè)給定數(shù)據(jù)是否存在于海量數(shù)據(jù)中。我們需要的就是判斷 key 是否合法,有沒有感覺布隆過濾器就是我們想要找的那個(gè)“人”。

          具體是這樣做的:把所有可能存在的請(qǐng)求的值都存放在布隆過濾器中,當(dāng)用戶請(qǐng)求過來,先判斷用戶發(fā)來的請(qǐng)求的值是否存在于布隆過濾器中。不存在的話,直接返回請(qǐng)求參數(shù)錯(cuò)誤信息給客戶端,存在的話才會(huì)走下面的流程。

          加入布隆過濾器之后的緩存處理流程圖如下。

          image

          但是,需要注意的是布隆過濾器可能會(huì)存在誤判的情況。總結(jié)來說就是:布隆過濾器說某個(gè)元素存在,小概率會(huì)誤判。布隆過濾器說某個(gè)元素不在,那么這個(gè)元素一定不在。

          為什么會(huì)出現(xiàn)誤判的情況呢? 我們還要從布隆過濾器的原理來說!

          我們先來看一下,當(dāng)一個(gè)元素加入布隆過濾器中的時(shí)候,會(huì)進(jìn)行哪些操作:

          1. 使用布隆過濾器中的哈希函數(shù)對(duì)元素值進(jìn)行計(jì)算,得到哈希值(有幾個(gè)哈希函數(shù)得到幾個(gè)哈希值)。
          2. 根據(jù)得到的哈希值,在位數(shù)組中把對(duì)應(yīng)下標(biāo)的值置為 1。

          我們?cè)賮砜匆幌拢?strong style="color: rgb(52, 152, 219);">當(dāng)我們需要判斷一個(gè)元素是否存在于布隆過濾器的時(shí)候,會(huì)進(jìn)行哪些操作:

          1. 對(duì)給定元素再次進(jìn)行相同的哈希計(jì)算;
          2. 得到值之后判斷位數(shù)組中的每個(gè)元素是否都為 1,如果值都為 1,那么說明這個(gè)值在布隆過濾器中,如果存在一個(gè)值不為 1,說明該元素不在布隆過濾器中。

          然后,一定會(huì)出現(xiàn)這樣一種情況:不同的字符串可能哈希出來的位置相同。 (可以適當(dāng)增加位數(shù)組大小或者調(diào)整我們的哈希函數(shù)來降低概率)

          更多關(guān)于布隆過濾器的內(nèi)容可以看我的這篇原創(chuàng):《不了解布隆過濾器?一文給你整的明明白白!》 ,強(qiáng)烈推薦,個(gè)人感覺網(wǎng)上應(yīng)該找不到總結(jié)的這么明明白白的文章了。

          17. 緩存雪崩

          17.1. 什么是緩存雪崩?

          我發(fā)現(xiàn)緩存雪崩這名字起的有點(diǎn)意思,哈哈。

          實(shí)際上,緩存雪崩描述的就是這樣一個(gè)簡(jiǎn)單的場(chǎng)景:緩存在同一時(shí)間大面積的失效,后面的請(qǐng)求都直接落到了數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時(shí)間內(nèi)承受大量請(qǐng)求。 這就好比雪崩一樣,摧枯拉朽之勢(shì),數(shù)據(jù)庫的壓力可想而知,可能直接就被這么多請(qǐng)求弄宕機(jī)了。

          舉個(gè)例子:系統(tǒng)的緩存模塊出了問題比如宕機(jī)導(dǎo)致不可用。造成系統(tǒng)的所有訪問,都要走數(shù)據(jù)庫。

          還有一種緩存雪崩的場(chǎng)景是:有一些被大量訪問數(shù)據(jù)(熱點(diǎn)緩存)在某一時(shí)刻大面積失效,導(dǎo)致對(duì)應(yīng)的請(qǐng)求直接落到了數(shù)據(jù)庫上。 這樣的情況,有下面幾種解決辦法:

          舉個(gè)例子 :秒殺開始 12 個(gè)小時(shí)之前,我們統(tǒng)一存放了一批商品到 Redis 中,設(shè)置的緩存過期時(shí)間也是 12 個(gè)小時(shí),那么秒殺開始的時(shí)候,這些秒殺的商品的訪問直接就失效了。導(dǎo)致的情況就是,相應(yīng)的請(qǐng)求直接就落到了數(shù)據(jù)庫上,就像雪崩一樣可怕。

          17.2. 有哪些解決辦法?

          針對(duì) Redis 服務(wù)不可用的情況:

          1. 采用 Redis 集群,避免單機(jī)出現(xiàn)問題整個(gè)緩存服務(wù)都沒辦法使用。
          2. 限流,避免同時(shí)處理大量的請(qǐng)求。

          針對(duì)熱點(diǎn)緩存失效的情況:

          1. 設(shè)置不同的失效時(shí)間比如隨機(jī)設(shè)置緩存的失效時(shí)間。
          2. 緩存永不失效。

          18. 如何保證緩存和數(shù)據(jù)庫數(shù)據(jù)的一致性?

          細(xì)說的話可以扯很多,但是我覺得其實(shí)沒太大必要(小聲 BB:很多解決方案我也沒太弄明白)。我個(gè)人覺得引入緩存之后,如果為了短時(shí)間的不一致性問題,選擇讓系統(tǒng)設(shè)計(jì)變得更加復(fù)雜的話,完全沒必要。

          下面單獨(dú)對(duì) Cache Aside Pattern(旁路緩存模式) 來聊聊。

          Cache Aside Pattern 中遇到寫請(qǐng)求是這樣的:更新 DB,然后直接刪除 cache 。

          如果更新數(shù)據(jù)庫成功,而刪除緩存這一步失敗的情況的話,簡(jiǎn)單說兩個(gè)解決方案:

          1. 緩存失效時(shí)間變短(不推薦,治標(biāo)不治本) :我們讓緩存數(shù)據(jù)的過期時(shí)間變短,這樣的話緩存就會(huì)從數(shù)據(jù)庫中加載數(shù)據(jù)。另外,這種解決辦法對(duì)于先操作緩存后操作數(shù)據(jù)庫的場(chǎng)景不適用。
          2. 增加 cache 更新重試機(jī)制(常用):如果 cache 服務(wù)當(dāng)前不可用導(dǎo)致緩存刪除失敗的話,我們就隔一段時(shí)間進(jìn)行重試,重試次數(shù)可以自己定。如果多次重試還是失敗的話,我們可以把當(dāng)前更新失敗的 key 存入隊(duì)列中,等緩存服務(wù)可用之后,再將緩存中對(duì)應(yīng)的 key 刪除即可。

          19. 參考

          • 《Redis 開發(fā)與運(yùn)維》
          • 《Redis 設(shè)計(jì)與實(shí)現(xiàn)》
          • Redis 命令總結(jié):http://Redisdoc.com/string/set.html
          • 通俗易懂的 Redis 數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)教程:https://juejin.im/post/5b53ee7e5188251aaa2d2e16
          • WHY Redis choose single thread (vs multi threads): https://medium.com/@jychen7/sharing-redis-single-thread-vs-multi-threads-5870bd44d153






          0、重磅!兩萬字長文總結(jié),梳理 Java 入門進(jìn)階哪些事(推薦收藏)

          1、講真的:我達(dá)成了一個(gè)優(yōu)秀的小目標(biāo)

          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美黑人大群交舔舔舔 | 特特级毛片 | 亚洲人成影视 | 欧美久久久久久久久久久 | 18禁黄网站 |