Redis數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)系統(tǒng):第三章:Redis在項(xiàng)目中如何使用?

簡單介紹一個(gè)redis?

redis是一個(gè)key-value類型的非關(guān)系型數(shù)據(jù)庫,基于內(nèi)存也可持久化的數(shù)據(jù)庫,相對于關(guān)系型數(shù)據(jù)庫(數(shù)據(jù)主要存在硬盤中),性能高,因此我們一般用redis來做緩存使用;并且redis支持豐富的數(shù)據(jù)類型,比較容易解決各種問題
Redis的Value支持5種數(shù)據(jù)類型,string、hash、list、set、zset(sorted set);

  String類型是最簡單的類型,一個(gè)key對應(yīng)一個(gè)value,項(xiàng)目中主要利用單點(diǎn)登錄中的token用string類型來存儲(chǔ);

 Hash類型中的key是string類型,value又是一個(gè)map(key-value),針對這種數(shù)據(jù)特性,比較適合存儲(chǔ)對象,在項(xiàng)目中由于購物車是用redis來存儲(chǔ)的,因?yàn)檫x擇redis的散列(hash)來存儲(chǔ);

 List類型是按照插入順序的字符串鏈表(雙向鏈表),主要命令是LPUSH和RPUSH,能夠支持反向查找和遍歷,如果使用的話主要存儲(chǔ)商品評論列表,key是該商品的ID,value是商品評論信息列表;

 Set類型是用哈希表類型的字符串序列,沒有順序,集合成員是唯一的,沒有重復(fù)數(shù)據(jù),底層主要是由一個(gè)value永遠(yuǎn)為null的hashmap來實(shí)現(xiàn)的。

我們的電商項(xiàng)目中沒有用到這個(gè)數(shù)據(jù)類型。這個(gè)應(yīng)用場景一般存儲(chǔ)一個(gè)列表數(shù)據(jù),但列表里面又不希望出現(xiàn)重復(fù)數(shù)據(jù),比如微博應(yīng)用中,可以將一個(gè)用戶所有關(guān)注的對象放在一個(gè)集合中,將其所有粉絲存在一個(gè)集合,這樣我們就可以實(shí)現(xiàn)兩個(gè)人的共同好友、共同關(guān)注等需求;

zset(sorted set)類型和set類型基本是一致的,不同的是zset這種類型會(huì)給每個(gè)元素關(guān)聯(lián)一個(gè)double類型的分?jǐn)?shù)(score),這樣就可以為成員排序,并且插入是有序的。這種數(shù)據(jù)類型如果使用的話主要用來統(tǒng)計(jì)商品的銷售排行榜,比如:items:sellsort 10 1001 20 1002 這個(gè)代表編號(hào)是1001的商品銷售數(shù)量為10,編號(hào)為1002的商品銷售數(shù)量為20。

(3)我們項(xiàng)目中主要用redis的java客戶端Jedis來操作redis數(shù)據(jù)庫,用來緩存各種操作頻繁,不經(jīng)常修改的數(shù)據(jù),這樣就減輕了數(shù)據(jù)庫的訪問壓力,提高了查詢效率

你還用過其他的緩存嗎?這些緩存有什么區(qū)別?都在什么場景下去用?

 對于緩存了解過redis和memcache,redis我們在項(xiàng)目中用的比較多,memcache沒用過,但是了解過一點(diǎn);

 Memcache和redis的區(qū)別:

    數(shù)據(jù)支持的類型:

    存儲(chǔ)方式:redis不僅僅支持簡單的k/v類型的數(shù)據(jù),同時(shí)還支持list、set、zset、hash等數(shù)據(jù)結(jié)構(gòu)的存儲(chǔ);memcache只支持簡單的k/v類型的數(shù)據(jù),key和value都是string類型

    可靠性:memcache不支持?jǐn)?shù)據(jù)持久化,斷電或重啟后數(shù)據(jù)消失,但其穩(wěn)定性是有保證的;redis支持?jǐn)?shù)據(jù)持久化和數(shù)據(jù)恢復(fù),允許單點(diǎn)故障,但是同時(shí)也會(huì)付出性能的代價(jià)

    性能上:對于存儲(chǔ)大數(shù)據(jù),memcache的性能要高于redis

應(yīng)用場景:

Memcache:適合多讀少寫,大數(shù)據(jù)量的情況(一些官網(wǎng)的文章信息等)

Redis:適用于對讀寫效率要求高、數(shù)據(jù)處理業(yè)務(wù)復(fù)雜、安全性要求較高的系統(tǒng)

Redis在你們項(xiàng)目中是怎么用的?

門戶系統(tǒng)中的首頁內(nèi)容信息的展示。(商品類目、廣告、熱門商品等信息)門戶系統(tǒng)的首頁是用戶訪問量最大的,而且這些數(shù)據(jù)一般不會(huì)經(jīng)常修改,因此為了提高用戶的體驗(yàn),我們選擇將這些內(nèi)容放在緩存中;
單點(diǎn)登錄系統(tǒng)中也用到了redis。因?yàn)槲覀兪欠植际较到y(tǒng),存在session之間的共享問題,因此在做單點(diǎn)登錄的時(shí)候,我們利用redis來模擬了session的共享,來存儲(chǔ)用戶的信息,實(shí)現(xiàn)不同系統(tǒng)的session共享;
我們項(xiàng)目中同時(shí)也將購物車的信息設(shè)計(jì)存儲(chǔ)在redis中,購物車在數(shù)據(jù)庫中沒有對應(yīng)的表,用戶登錄之后將商品添加到購物車后存儲(chǔ)到redis中,key是用戶id,value是購物車對象;
因?yàn)獒槍υu論這塊,我們需要一個(gè)商品對應(yīng)多個(gè)用戶評論,并且按照時(shí)間順序顯示評論,為了提高查詢效率,因此我們選擇了redis的list類型將商品評論放在緩存中;
在統(tǒng)計(jì)模塊中,我們有個(gè)功能是做商品銷售的排行榜,因此選擇redis的zset結(jié)構(gòu)來實(shí)現(xiàn);

還有一些其他的應(yīng)用場景,主要就是用來作為緩存使用。

對redis的持久化了解不?

   Redis是內(nèi)存型數(shù)據(jù)庫,同時(shí)它也可以持久化到硬盤中,redis的持久化方式有兩種:

RDB(半持久化方式):

按照配置不定期的通過異步的方式、快照的形式直接把內(nèi)存中的數(shù)據(jù)持久化到磁盤的一個(gè)dump.rdb文件(二進(jìn)制文件)中;

這種方式是redis默認(rèn)的持久化方式,它在配置文件(redis.conf)中的格式是:save N M,表示的是在N秒之內(nèi)發(fā)生M次修改,則redis抓快照到磁盤中;

原理:當(dāng)redis需要持久化的時(shí)候,redis會(huì)fork一個(gè)子進(jìn)程,這個(gè)子進(jìn)程會(huì)將數(shù)據(jù)寫到一個(gè)臨時(shí)文件中;當(dāng)子進(jìn)程完成寫臨時(shí)文件后,會(huì)將原來的.rdb文件替換掉,這樣的好處是寫時(shí)拷貝技術(shù)(copy-on-write),可以參考下面的流程圖;
在這里插入圖片描述












優(yōu)點(diǎn):只包含一個(gè)文件,對于文件備份、災(zāi)難恢復(fù)而言,比較實(shí)用。因?yàn)槲覀兛梢暂p松的將一個(gè)單獨(dú)的文件轉(zhuǎn)移到其他存儲(chǔ)媒介上;性能最大化,因?yàn)閷τ谶@種半持久化方式,使用的是寫時(shí)拷貝技術(shù),可以極大的避免服務(wù)進(jìn)程執(zhí)行IO操作;相對于AOF來說,如果數(shù)據(jù)集很大,RDB的啟動(dòng)效率就會(huì)很高 缺點(diǎn):如果想保證數(shù)據(jù)的高可用(最大限度的包裝數(shù)據(jù)丟失),那么RDB這種半持久化方式不是一個(gè)很好的選擇,因?yàn)橄到y(tǒng)一旦在持久化策略之前出現(xiàn)宕機(jī)現(xiàn)象,此前沒有來得及持久化的數(shù)據(jù)將會(huì)產(chǎn)生丟失;rdb是通過fork進(jìn)程來協(xié)助完成持久化的,因此當(dāng)數(shù)據(jù)集較大的時(shí)候,我們就需要等待服務(wù)器停止幾百毫秒甚至一秒;

AOF(全持久化的方式)

把每一次數(shù)據(jù)變化都通過write()函數(shù)將你所執(zhí)行的命令追加到一個(gè)appendonly.aof文件里面;

事實(shí)上,不會(huì)立即將命令寫入硬盤文件中,而是寫入硬盤緩存,可以配置策略,配置多久從硬盤緩存寫入到硬盤文件中。

Appendfsync always

Appendfsync everysec 默認(rèn)的

Appendfsync no 不主動(dòng),默認(rèn)30秒一次

Redis默認(rèn)是不支持這種全持久化方式的,需要將no改成yes

在這里插入圖片描述

















實(shí)現(xiàn)文件刷新的三種方式:

在這里插入圖片描述










no:不會(huì)自動(dòng)同步到磁盤上,需要依靠OS(操作系統(tǒng))進(jìn)行刷新,效率快,但是安全性就比較差;

always:每提交一個(gè)命令都調(diào)用fsync刷新到aof文件,非常慢,但是安全;

everysec:每秒鐘都調(diào)用fsync刷新到aof文件中,很快,但是可能丟失一秒內(nèi)的數(shù)據(jù),推薦使用,兼顧了速度和安全;

原理:redis需要持久化的時(shí)候,fork出一個(gè)子進(jìn)程,子進(jìn)程根據(jù)內(nèi)存中的數(shù)據(jù)庫快照,往臨時(shí)文件中寫入重建數(shù)據(jù)庫狀態(tài)的命令;父進(jìn)程會(huì)繼續(xù)處理客戶端的請求,除了把寫命令寫到原來的aof中,同時(shí)把收到的寫命令緩存起來,這樣包裝如果子進(jìn)程重寫失敗的話不會(huì)出問題;當(dāng)子進(jìn)程把快照內(nèi)容以命令方式寫入臨時(shí)文件中后,子進(jìn)程會(huì)發(fā)送信號(hào)給父進(jìn)程,父進(jìn)程會(huì)把緩存的寫命令寫入到臨時(shí)文件中;接下來父進(jìn)程可以使用臨時(shí)的aof文件替換原來的aof文件,并重命名,后面收到的寫命令也開始往新的aof文件中追加。下面的圖為最簡單的方式,其實(shí)也是利用寫時(shí)復(fù)制原則。
在這里插入圖片描述








優(yōu)點(diǎn):

數(shù)據(jù)安全性高

該機(jī)制對日志文件的寫入操作采用的是append模式,因此在寫入過程中即使出現(xiàn)宕機(jī)問題,也不會(huì)破壞日志文件中已經(jīng)存在的內(nèi)容;

缺點(diǎn):

對于數(shù)量相同的數(shù)據(jù)集來說,aof文件通常要比rdb文件大,因此rdb在恢復(fù)大數(shù)據(jù)集時(shí)的速度大于AOF;

根據(jù)同步策略的不同,AOF在運(yùn)行效率上往往慢于RDB,每秒同步策略的效率是比較高的,同步禁用策略的效率和RDB一樣高效;

針對以上兩種不同的持久化方式,如果緩存數(shù)據(jù)安全性要求比較高的話,用aof這種持久化方式(比如項(xiàng)目中的購物車);如果對于大數(shù)據(jù)集要求效率高的話,就可以使用默認(rèn)的。而且這兩種持久化方式可以同時(shí)使用。  

做過redis的集群嗎?你們做集群的時(shí)候搭建了幾臺(tái),都是怎么搭建的?

針對這類問題,我們首先考慮的是為什么要搭建集群?(這個(gè)需要針對我們的項(xiàng)目來說)

Redis的數(shù)據(jù)是存放在內(nèi)存中的,這就意味著redis不適合存儲(chǔ)大數(shù)據(jù),大數(shù)據(jù)存儲(chǔ)一般公司常用hadoop中的Hbase或者M(jìn)ogoDB。因此redis主要用來處理高并發(fā)的,用我們的項(xiàng)目來說,電商項(xiàng)目如果并發(fā)大的話,一臺(tái)單獨(dú)的redis是不能足夠支持我們的并發(fā),這就需要我們擴(kuò)展多臺(tái)設(shè)備協(xié)同合作,即用到集群。

Redis搭建集群的方式有多種,例如:客戶端分片、Twemproxy、Codis等,但是redis3.0之后就支持redis-cluster集群,這種方式采用的是無中心結(jié)構(gòu),每個(gè)節(jié)點(diǎn)保存數(shù)據(jù)和整個(gè)集群的狀態(tài),每個(gè)節(jié)點(diǎn)都和其他所有節(jié)點(diǎn)連接。如果使用的話就用redis-cluster集群。

集群這塊直接說是公司運(yùn)維搭建的,小公司的話也有可能由我們自己搭建,開發(fā)環(huán)境我們也可以直接用單機(jī)版的。但是可以了解一下redis的集群版。搭建redis集群的時(shí)候,對于用到多少臺(tái)服務(wù)器,每家公司都不一樣,大家針對自己項(xiàng)目的大小去衡量。舉個(gè)簡單的例子:

我們項(xiàng)目中redis集群主要搭建了6臺(tái),3主(為了保證redis的投票機(jī)制)3從(高可用),每個(gè)主服務(wù)器都有一個(gè)從服務(wù)器,作為備份機(jī)。

架構(gòu)圖如下:
在這里插入圖片描述













所有的節(jié)點(diǎn)都通過PING-PONG機(jī)制彼此互相連接;
每個(gè)節(jié)點(diǎn)的fail是通過集群中超過半數(shù)的節(jié)點(diǎn)檢測失效時(shí)才生效;
客戶端與redis集群連接,只需要連接集群中的任何一個(gè)節(jié)點(diǎn)即可;
Redis-cluster把所有的物理節(jié)點(diǎn)映射到【0-16383】slot上,負(fù)責(zé)維護(hù)

2、容錯(cuò)機(jī)制(投票機(jī)制)

(1)選舉過程是集群中的所有master都參與,如果半數(shù)以上master節(jié)點(diǎn)與故障節(jié)點(diǎn)連接超過時(shí)間,則認(rèn)為該節(jié)點(diǎn)故障,自動(dòng)會(huì)觸發(fā)故障轉(zhuǎn)移操作;

(2)集群不可用?

 a:如果集群任意master掛掉,并且當(dāng)前的master沒有slave,集群就會(huì)fail;

 b:如果集群超過半數(shù)以上master掛掉,無論是否有slave,整個(gè)集群都會(huì)fail;

6、redis有事務(wù)嗎?

Redis是有事務(wù)的,redis中的事務(wù)是一組命令的集合,這組命令要么都執(zhí)行,要不都不執(zhí)行,redis事務(wù)的實(shí)現(xiàn),需要用到MULTI(事務(wù)的開始)和EXEC(事務(wù)的結(jié)束)命令 ;
在這里插入圖片描述











當(dāng)輸入MULTI命令后,服務(wù)器返回OK表示事務(wù)開始成功,然后依次輸入需要在本次事務(wù)中執(zhí)行的所有命令,每次輸入一個(gè)命令服務(wù)器并不會(huì)馬上執(zhí)行,而是返回”QUEUED”,這表示命令已經(jīng)被服務(wù)器接受并且暫時(shí)保存起來,最后輸入EXEC命令后,本次事務(wù)中的所有命令才會(huì)被依次執(zhí)行,可以看到最后服務(wù)器一次性返回了兩個(gè)OK,這里返回的結(jié)果與發(fā)送的命令是按順序一一對應(yīng)的,這說明這次事務(wù)中的命令全都執(zhí)行成功了。

Redis的事務(wù)除了保證所有命令要不全部執(zhí)行,要不全部不執(zhí)行外,還能保證一個(gè)事務(wù)中的命令依次執(zhí)行而不被其他命令插入。同時(shí),redis的事務(wù)是不支持回滾操作的。

【擴(kuò)展】

Redis的事務(wù)中存在一個(gè)問題,如果一個(gè)事務(wù)中的B命令依賴上一個(gè)命令A(yù)怎么辦?

這會(huì)涉及到redis中的WATCH命令:可以監(jiān)控一個(gè)或多個(gè)鍵,一旦其中有一個(gè)鍵被修改(或刪除),之后的事務(wù)就不會(huì)執(zhí)行,監(jiān)控一直持續(xù)到EXEC命令(事務(wù)中的命令是在EXEC之后才執(zhí)行的,EXEC命令執(zhí)行完之后被監(jiān)控的鍵會(huì)自動(dòng)被UNWATCH)。

應(yīng)用場景:待定
【擴(kuò)展】

redis的安全機(jī)制(你們公司redis的安全這方面怎么考慮的?)

漏洞介紹:redis默認(rèn)情況下,會(huì)綁定在bind 0.0.0.0:6379,這樣就會(huì)將redis的服務(wù)暴露到公網(wǎng)上,如果在沒有開啟認(rèn)證的情況下,可以導(dǎo)致任意用戶在訪問目標(biāo)服務(wù)器的情況下未授權(quán)訪問redis以及讀取redis的數(shù)據(jù),攻擊者就可以在未授權(quán)訪問redis的情況下可以利用redis的相關(guān)方法,成功在redis服務(wù)器上寫入公鑰,進(jìn)而可以直接使用私鑰進(jìn)行直接登錄目標(biāo)主機(jī);

比如:可以使用FLUSHALL方法,整個(gè)redis數(shù)據(jù)庫將被清空

解決方案:

禁止一些高危命令。修改redis.conf文件,用來禁止遠(yuǎn)程修改DB文件地址,比如 rename-command FLUSHALL "" 、rename-command CONFIG"" 、rename-command EVAL “”等;
以低權(quán)限運(yùn)行redis服務(wù)。為redis服務(wù)創(chuàng)建單獨(dú)的用戶和根目錄,并且配置禁止登錄;
為redis添加密碼驗(yàn)證。修改redis.conf文件,添加

requirepass mypassword;

禁止外網(wǎng)訪問redis。修改redis.conf文件,添加或修改 bind 127.0.0.1,使得redis服務(wù)只在當(dāng)前主機(jī)使用;
做log監(jiān)控,及時(shí)發(fā)現(xiàn)攻擊;

redis的哨兵機(jī)制(redis2.6以后出現(xiàn)的)

哨兵機(jī)制:

監(jiān)控:監(jiān)控主數(shù)據(jù)庫和從數(shù)據(jù)庫是否正常運(yùn)行;

    提醒:當(dāng)被監(jiān)控的某個(gè)redis出現(xiàn)問題的時(shí)候,哨兵可以通過API向管理員或者其他應(yīng)用程序發(fā)送通知;

    自動(dòng)故障遷移:主數(shù)據(jù)庫出現(xiàn)故障時(shí),可以自動(dòng)將從數(shù)據(jù)庫轉(zhuǎn)化為主數(shù)據(jù)庫,實(shí)現(xiàn)自動(dòng)切換;

具體的配置步驟面試中可以說參考的網(wǎng)上的文檔。要注意的是,如果master主服務(wù)器設(shè)置了密碼,記得在哨兵的配置文件(sentinel.conf)里面配置訪問密碼

3、緩存穿透

緩存查詢一般都是通過key去查找value,如果不存在對應(yīng)的value,就要去數(shù)據(jù)庫中查找。如果這個(gè)key對應(yīng)的value在數(shù)據(jù)庫中也不存在,并且對該key并發(fā)請求很大,就會(huì)對數(shù)據(jù)庫產(chǎn)生很大的壓力,這就叫緩存穿透

解決方案:

    對所有可能查詢的參數(shù)以hash形式存儲(chǔ),在控制層先進(jìn)行校驗(yàn),不符合則丟棄。還有最常見的則是采用布隆過濾器,將所有可能存在的數(shù)據(jù)哈希到一個(gè)足夠大的bitmap中,一個(gè)一定不存在的數(shù)據(jù)會(huì)被這個(gè)bitmap攔截掉,從而避免了對底層存儲(chǔ)系統(tǒng)的查詢壓力。
    也可以采用一個(gè)更為簡單粗暴的方法,如果一個(gè)查詢返回的數(shù)據(jù)為空(不管是數(shù) 據(jù)不存在,還是系統(tǒng)故障),我們?nèi)匀话堰@個(gè)空結(jié)果進(jìn)行緩存,但它的過期時(shí)間會(huì)很短,最長不超過五分鐘。

4、緩存雪崩

當(dāng)緩存服務(wù)器重啟或者大量緩存集中在一段時(shí)間內(nèi)失效,發(fā)生大量的緩存穿透,這樣在失效的瞬間對數(shù)據(jù)庫的訪問壓力就比較大,所有的查詢都落在數(shù)據(jù)庫上,造成了緩存雪崩。

這個(gè)沒有完美解決辦法,但可以分析用戶行為,盡量讓失效時(shí)間點(diǎn)均勻分布。大多數(shù)系統(tǒng)設(shè)計(jì)者考慮用加鎖或者隊(duì)列的方式保證緩存的單線程(進(jìn)程)寫,從而避免失效時(shí)大量的并發(fā)請求落到底層存儲(chǔ)系統(tǒng)上。

解決方案:

    在緩存失效后,通過加鎖或者隊(duì)列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個(gè)key只允許一個(gè)線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
    可以通過緩存reload機(jī)制,預(yù)先去更新緩存,再即將發(fā)生大并發(fā)訪問前手動(dòng)觸發(fā)加載緩存
    不同的key,設(shè)置不同的過期時(shí)間,讓緩存失效的時(shí)間點(diǎn)盡量均勻
    做二級(jí)緩存,或者雙緩存策略。A1為原始緩存,A2為拷貝緩存,A1失效時(shí),可以訪問A2,A1緩存失效時(shí)間設(shè)置為短期,A2設(shè)置為長期。

redis中對于生存時(shí)間的應(yīng)用

Redis中可以使用expire命令設(shè)置一個(gè)鍵的生存時(shí)間,到時(shí)間后redis會(huì)自動(dòng)刪除;

應(yīng)用場景:

設(shè)置限制的優(yōu)惠活動(dòng)的信息;
一些及時(shí)需要更新的數(shù)據(jù),積分排行榜;
手機(jī)驗(yàn)證碼的時(shí)間;
限制網(wǎng)站訪客訪問頻率;