1、為什么要使用Redis做緩存
緩存的好處
使用緩存的目的就是提升讀寫性能。而實際業務場景下,更多的是為了提升讀性能,帶來更好的性
能,帶來更高的并發量。Redis 的讀寫性能比 Mysql 好的多,我們就可以把 Mysql 中的熱點數據緩
存到 Redis 中,提升讀取性能,同時也減輕了 Mysql 的讀取壓力。
**Redis的好處**
讀取速度快,因為數據存在內存中,所以數據獲取快,單機輕松10W+并發
支持多種數據結構,包括字符串、列表、集合、有序集合、哈希等
還擁有其他豐富的功能,主從復制、集群、數據持久化等
可以實現其他豐富的功能,消息隊列、分布式鎖等
2、為什么Redis單線程模型效率也能那么高?
**C語言實現,效率高**
C語言程序運行時要比其他語言編寫的程序快得多,因為它“離底層機器很近”
**單線程的優勢**
使用了單線程后,可維護性高。多線程模型雖然在某些方面表現優異,但是它卻引入了程序執行順序的不確定性,帶來了并發讀寫的一系列問題,增加了系統復雜度、同時可能存在線程切換、甚至加鎖解鎖、死鎖造成的性能損耗
**Pipeline**
Redis主要受限于內存和網絡,幾乎不會占用太多CPU。可以將命令和pipeline結合起來使用,減少命令在網絡上的傳輸時間,將多次網絡IO縮減為一次網絡IO,通過使用pipeline每秒可以處理100萬個請求。
**存儲實現優化**
Redis的基礎數據結構每一種至少有2種及2種以上的實現,在不同的大小或長度下選用適合的數據類型,達到極致的存儲效率,從而提高寫入和讀取速度。
?3、Redis6.0為什么要引入多線程呢?
**Redis多線程比單線程性能提升一倍:**
Redis 作者 antirez 在 RedisConf 2019分享時曾提到:Redis 6 引入的多線程 IO 特性對性能提升至少是一倍以上。國內也有大牛曾使用unstable版本在阿里云esc進行過測試,GET/SET 命令在4線程 IO時性能相比單線程是幾乎是翻倍了
**巨頭公司的需求**
Redis將所有數據放在內存中,內存的響應時長大約為100納秒,對于小數據包,Redis服務器可以處理80,000到100,000 QPS,這也是Redis處理的極限了,對于80%的公司來說,單線程的Redis已經足夠使用了。
但隨著越來越復雜的業務場景,有些公司動不動就上億的交易量,因此需要更大的QPS。
**集群方案的問題**
常見的解決方案是在分布式架構中對數據進行分區并采用多個服務器,但該方案有非常大的缺點,例如要管理的Redis服務器太多,維護代價大;
某些適用于單個Redis服務器的命令不適用于數據分區;
數據分區無法解決熱點讀/寫問題;數據偏斜,重新分配和放大/縮小變得更加復雜等等。
1.純內存KV操作
Redis的操作都是基于內存的,CPU不是 Redis性能瓶頸,,Redis的瓶頸是機器內存和網絡帶寬。
在計算機的世界中,CPU的速度是遠大于內存的速度的,同時內存的速度也是遠大于硬盤的速度。redis的操作都是基于內存的,絕大部分請求是純粹的內存操作,非常迅速。
2.單線程操作
使用單線程可以省去多線程時CPU上下文會切換的時間,也不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有死鎖問題導致的性能消耗。對于內存系統來說,多次讀寫都是在一個CPU上,沒有上下文切換效率就是最高的!既然單線程容易實現,而且 CPU 不會成為瓶頸,那就順理成章的采用單線程的方案了
Redis 單線程指的是網絡請求模塊使用了一個線程,即一個線程處理所有網絡請求,其他模塊該使用多線程,仍會使用了多個線程。
3.I/O 多路復用
為什么 Redis 中要使用 I/O 多路復用這種技術呢?
首先,Redis 是跑在單線程中的,所有的操作都是按照順序線性執行的,但是由于讀寫操作等待用戶輸入或輸出都是阻塞的,所以 I/O 操作在一般情況下往往不能直接返回,這會導致某一文件的 I/O 阻塞導致整個進程無法對其它客戶提供服務,而 I/O 多路復用就是為了解決這個問題而出現的
4.Reactor 設計模式
Redis基于Reactor模式開發了自己的網絡事件處理器,稱之為文件事件處理器(File Event Hanlder)。
?4、講一講Redis常見數據結構以及使用場景
字符串(String)
**適合場景**
**緩存功能**
Redis 作為緩存層,MySQL作為存儲層,絕大部分請求的數據都是從Redis中獲取。由于Redis具有支撐高并發的特性,所以緩存通常能起到加速讀寫和降低后端壓力的作用。
**計數**
使用Redis 作為計數的基礎工具,它可以實現快速計數、查詢緩存的功能,同時數據可以異步落地到其他數據源。
**共享Session**
一個分布式Web服務將用戶的Session信息(例如用戶登錄信息)保存在各自服務器中,這樣會造成一個問題,出于負載均衡的考慮,分布式服務會將用戶的訪問均衡到不同服務器上,用戶刷新一次訪問可能會發現需要重新登錄,這個問題是用戶無法容忍的。
為了解決這個問題,可以使用Redis將用戶的Session進行集中管理,,在這種模式下只要保證Redis是高可用和擴展性的,每次用戶更新或者查詢登錄信息都直接從Redis中集中獲取。
**限速**
比如,很多應用出于安全的考慮,會在每次進行登錄時,讓用戶輸入手機驗證碼,從而確定是否是用戶本人。但是為了短信接口不被頻繁訪問,會限制用戶每分鐘獲取驗證碼的頻率,例如一分鐘不能超過5次。一些網站限制一個IP地址不能在一秒鐘之內方問超過n次也可以采用類似的思路。
哈希(Hash)
Java里提供了HashMap,Redis中也有類似的數據結構,就是哈希類型。但是要注意,哈希類型中的映射關系叫作field-value,注意這里的value是指field對應的值,不是鍵對應的值。
適合場景
哈希類型比較適宜存放對象類型的數據,我們可以比較下,如果數據庫中表記錄user為:
| id | name ?| age |
| -- | ----- | --- |
| 1 ?| lijin | 18 ?|
| 2 ?| msb ? | 20 ?|
**1、使用String類型**
需要一條條去插入獲取。
set user:1:name lijin;
set user:1:age ?18;
set user:2:name msb;
set user:2:age ?20;
**優點:簡單直觀,每個鍵對應一個值**
**缺點:鍵數過多,占用內存多,用戶信息過于分散,不用于生產環境**
**2、使用hash類型**
hmset user:1 name lijin age 18
hmset user:2 name msb age 20
優點:簡單直觀,使用合理可減少內存空間消耗
列表(list)
列表( list)類型是用來存儲多個有序的字符串,a、b、c、c、b四個元素從左到右組成了一個有序的列表,列表中的每個字符串稱為元素(element),一個列表最多可以存儲(2^32-1)個元素(*4294967295*)。
**適合場景**
每個用戶有屬于自己的文章列表,現需要分頁展示文章列表。此時可以考慮使用列表,因為列表不但是有序的,同時支持按照索引范圍獲取元素。
消息隊列,Redis 的 lpush+brpop命令組合即可實現阻塞隊列,生產者客戶端使用lrpush從列表左側插入元素,多個消費者客戶端使用brpop命令阻塞式的“搶”列表尾部的元素,多個客戶端保證了消費的負載均衡和高可用性。
集合(set)
集合( set)類型也是用來保存多個的字符串元素,但和列表類型不一樣的是,集合中不允許有重復元素,并且集合中的元素是無序的,不能通過索引下標獲取元素。
**適合場景**
集合類型比較典型的使用場景是標簽(tag)。例如一個用戶可能對娛樂、體育比較感興趣,另一個用戶可能對歷史、新聞比較感興趣,這些興趣點就是標簽。有了這些數據就可以得到喜歡同一個標簽的人,以及用戶的共同喜好的標簽,這些數據對于用戶體驗以及增強用戶黏度比較重要。
例如一個電子商務的網站會對不同標簽的用戶做不同類型的推薦,比如對數碼產品比較感興趣的人,在各個頁面或者通過郵件的形式給他們推薦最新的數碼產品,通常會為網站帶來更多的利益。
除此之外,集合還可以通過生成隨機數進行比如抽獎活動,以及社交圖譜等等。
有序集合(ZSET)
有序集合相對于哈希、列表、集合來說會有一點點陌生,但既然叫有序集合,那么它和集合必然有著聯系,它保留了集合不能有重復成員的特性,但不同的是,有序集合中的元素可以排序。但是它和列表使用索引下標作為排序依據不同的是,它給每個元素設置一個分數( score)作為排序的依據。
有序集合中的元素不能重復,但是score可以重復,就和一個班里的同學學號不能重復,但是考試成績可以相同。
有序集合提供了獲取指定分數和元素范圍查詢、計算成員排名等功能,合理的利用有序集合,能幫助我們在實際開發中解決很多問題。
有序集合比較典型的使用場景就是排行榜系統。例如視頻網站需要對用戶上傳的視頻做排行榜,榜單的維度可能是多個方面的:按照時間、按照播放數量、按照獲得的贊數。
?5、pipeline有什么好處,為什么要用 pipeline?
Redis客戶端執行一條命令分為如下4個部分:1)發送命令2)命令排隊3)命令執行4)返回結果。
其中1和4花費的時間稱為Round Trip Time (RTT,往返時間),也就是數據在網絡上傳輸的時間。
Redis提供了批量操作命令(例如mget、mset等),有效地節約RTT。
但大部分命令是不支持批量操作的,例如要執行n次 hgetall命令,并沒有mhgetall命令存在,需要消耗n次RTT。
舉例:Redis的客戶端和服務端可能部署在不同的機器上。例如客戶端在本地,Redis服務器在阿里云的廣州,兩地直線距離約為800公里,那么1次RTT時間=800 x2/ ( 300000×2/3 ) =8毫秒,(光在真空中傳輸速度為每秒30萬公里,這里假設光纖為光速的2/3 )。而Redis命令真正執行的時間通常在微秒(1000微妙=1毫秒)級別,所以才會有Redis 性能瓶頸是網絡這樣的說法。
Pipeline(流水線)機制能改善上面這類問題,它能將一組 Redis命令進行組裝,通過一次RTT傳輸給Redis,再將這組Redis命令的執行結果按順序返回給客戶端,沒有使用Pipeline執行了n條命令,整個過程需要n次RTT。
使用Pipeline 執行了n次命令,整個過程需要1次RTT。
Pipeline并不是什么新的技術或機制,很多技術上都使用過。而且RTT在不同網絡環境下會有不同,例如同機房和同機器會比較快,跨機房跨地區會比較慢。
redis-cli的--pipe選項實際上就是使用Pipeline機制,但絕對部分情況下,我們使用Java語言的Redis客戶端中的Pipeline會更多一點。
代碼參見:
```
com.msb.redis.adv.RedisPipeline
```
總的來說,在不同網絡環境下非Pipeline和Pipeline執行10000次set操作的效果,在執行時間上的比對如下:
差距有100多倍,可以得到如下兩個結論:
1、Pipeline執行速度一般比逐條執行要快。
2、客戶端和服務端的網絡延時越大,Pipeline的效果越明顯。
6、Redis官方為什么不提供 Windows版本?
一個字懶,多一事不如少一事,Redis是開源軟件。
Redis的Windows版本是3.0,之前微軟維護,后續是tporadowski維護
[https://github.com/tporadowski/redis](https://github.com/tporadowski/redis)
目前Linux版本已經相當穩定,而且用戶量很大,無需開發windows版本,反而會帶來兼容性等問題。
7、Redis 持久化方式有哪些?以及有什么區別?
Redis 提供兩種持久化機制 RDB 和 AOF 機制:
RDB
RDB持久化是把當前進程數據生成快照保存到硬盤的過程。所謂內存快照,就是指內存中的數據在某一個時刻的狀態記錄。這就類似于照片,當你給朋友拍照時,一張照片就能把朋友一瞬間的形象完全記下來。RDB 就是Redis DataBase 的縮寫。
**優點:**
只有一個文件 dump.rdb ,方便持久化。
容災性好,一個文件可以保存到安全的磁盤。
性能最大化,fork 子進程來完成寫操作,讓主進程繼續處理命令,所以是 IO 最大化。
相對于數據集大時,比AOF的啟動效率更高。
**缺點:**
數據安全性低。 RDB是間隔一段時間進行持久化,如果持久化之間Redis發生故障,會發生數據丟失。所以這種方式更適合數據要求不嚴謹的時候。
AOF
AOF(append only file)持久化:以獨立日志的方式記錄每次寫命令,重啟時再重新執行AOF文件中的命令達到恢復數據的目的。AOF的主要作用是解決了數據持久化的實時性,目前已經是Redis持久化的主流方式。
**缺點:**
(1) AOF 文件比 RDB 文件大,且恢復速度慢。
(2)數據集大的時候,比 RDB 啟動效率低
8、什么是Redis事務?原理是什么?
Redis 中的事務是一組命令的集合,是 Redis 的最小執行單位。它可以保證一次執行多個命令,每
個事務是一個單獨的隔離操作,事務中的所有命令都會序列化、按順序地執行。服務端在執行事務
的過程中,不會被其他客戶端發送來的命令請求打斷。
它的原理是先將屬于一個事務的命令發送給 Redis,然后依次執行這些命令。
Redis 事務的注意點有哪些?
需要注意的點有:
Redis 事務是不支持回滾的,不像 MySQL 的事務一樣,要么都執行要么都不執行;
Redis 服務端在執行事務的過程中,不會被其他客戶端發送來的命令請求打斷。直到事務命令
全部執行完畢才會執行其他客戶端的命令。
Redis 事務為什么不支持回滾?
Redis 的事務不支持回滾,但是執行的命令有語法錯誤,Redis 會執行失敗,這些問題可以從程序層
面捕獲并解決。但是如果出現其他問題,則依然會繼續執行余下的命令。這樣做的原因是因為回滾
需要增加很多工作,而不支持回滾則可以保持簡單、快速的特性。
事務
大家應該對事務比較了解,簡單地說,事務表示一組動作,要么全部執行,要么全部不執行。
例如在社交網站上用戶A關注了用戶B,那么需要在用戶A的關注表中加入用戶B,并且在用戶B的粉絲表中添加用戶A,這兩個行為要么全部執行,要么全部不執行,否則會出現數據不一致的情況。
Redis提供了簡單的事務功能,將一組需要一起執行的命令放到multi和exec兩個命令之間。multi 命令代表事務開始,exec命令代表事務結束。另外discard命令是回滾。
一個客戶端
另外一個客戶端
在事務沒有提交的時查詢(查不到數據)
在事務提交后查詢(可以查到數據)
可以看到sadd命令此時的返回結果是QUEUED,代表命令并沒有真正執行,而是暫時保存在Redis中的一個緩存隊列(所以discard也只是丟棄這個緩存隊列中的未執行命令,并不會回滾已經操作過的數據,這一點要和關系型數據庫的Rollback操作區分開)。
只有當exec執行后,用戶A關注用戶B的行為才算完成,如下所示exec返回的兩個結果對應sadd命令。
**但是要注意Redis的事務功能很弱。在事務回滾機制上,Redis只能對基本的語法錯誤進行判斷。**
如果事務中的命令出現錯誤,Redis 的處理機制也不盡相同。
1、語法命令錯誤
例如下面操作錯將set寫成了sett,屬于語法錯誤,會造成整個事務無法執行,事務內的操作都沒有執行:
2、運行時錯誤
例如:事務內第一個命令簡單的設置一個string類型,第二個對這個key進行sadd命令,這種就是運行時命令錯誤,因為語法是正確的:
可以看到Redis并不支持回滾功能,第一個set命令已經執行成功,開發人員需要自己修復這類問題。
**Redis的事務原理**
事務是Redis實現在服務器端的行為,用戶執行MULTI命令時,服務器會將對應這個用戶的客戶端對象設置為一個特殊的狀態,在這個狀態下后續用戶執行的查詢命令不會被真的執行,而是被服務器緩存起來,直到用戶執行EXEC命令為止,服務器會將這個用戶對應的客戶端對象中緩存的命令按照提交的順序依次執行。
9、如何在100個億URL中快速判斷某URL是否存在?
傳統數據結構的不足
當然有人會想,我直接將網頁URL存入數據庫進行查找不就好了,或者建立一個哈希表進行查找不就OK了。
當數據量小的時候,這么思考是對的,
確實可以將值映射到 HashMap 的 Key,然后可以在 O(1) 的時間復雜度內返回結果,效率奇高。但是 HashMap 的實現也有缺點,例如存儲容量占比高,考慮到負載因子的存在,通常空間是不能被用滿的,舉個例子如果一個1000萬HashMap,Key=String(長度不超過16字符,且重復性極小),Value=Integer,會占據多少空間呢?1.2個G。實際上,1000萬個int型,只需要40M左右空間,占比3%,1000萬個Integer,需要161M左右空間,占比13.3%。可見一旦你的值很多例如上億的時候,那HashMap 占據的內存大小就變得很可觀了。
但如果整個網頁黑名單系統包含100億個網頁URL,在數據庫查找是很費時的,并且如果每個URL空間為64B,那么需要內存為640GB,一般的服務器很難達到這個需求。
?布隆過濾器
布隆過濾器簡介
**1970 年布隆提出了一種布隆過濾器的算法,用來判斷一個元素是否在一個集合中。
這種算法由一個二進制數組和一個 Hash 算法組成。**
本質上布隆過濾器是一種數據結構,比較巧妙的概率型數據結構(probabilistic data structure),特點是高效地插入和查詢,可以用來告訴你 “某樣東西一定不存在或者可能存在”。
相比于傳統的 List、Set、Map 等數據結構,它更高效、占用空間更少,但是缺點是其返回的結果是概率性的,而不是確切的。
實際上,布隆過濾器廣泛應用于網頁黑名單系統、垃圾郵件過濾系統、爬蟲網址判重系統等,Google 著名的分布式數據庫 Bigtable 使用了布隆過濾器來查找不存在的行或列,以減少磁盤查找的IO次數,Google Chrome瀏覽器使用了布隆過濾器加速安全瀏覽服務。
布隆過濾器的誤判問題
?通過hash計算在數組上不一定在集合
?本質是hash沖突
?通過hash計算不在數組的一定不在集合(誤判)
**優化方案**
增大數組(預估適合值)
增加hash函數
?