PHP 詳細面試總結 (三 Redis 基礎詳解)

A_aliane發表於2019-03-14

簡介

  • Redis是一個開源的key-value儲存系統。
  • 與Memcached類似,Redis將大部分資料儲存在記憶體中

redis-系統檔案說明

當mkae命令執行完成後,會在當前目錄生成多個可執行檔案,分別是

  • redis-server:redis伺服器的daemon啟動程式
  • redis-cli:redis命令列操作工具。當然,你也可以用telnet根據其純文字協議來操作
  • redis-benchmark:redis效能測試工具,測試redis在你的系統及你的配置下的讀寫功能
  • redis-stat:redis狀態檢測工具,可以檢測redis當前狀態引數及延遲狀況

資料型別

  • 支援的資料型別包括:字串、雜湊表、連結串列、集合、有序集合以及基於這些資料型別的相關操作。
  • 最為常用的資料型別主要由五種:String、Hash、List、Set和Sorted Set
  • 除了多種資料結構的支援,Redis相比Memcached還提供了許多額外的特性,比如Subscribe/publish命令,以支援釋出/訂閱模式這樣的通知機制等等,這些額外的特性同樣有助於擴充它的應用場景
  • redis通過Multi / Watch /Exec等命令可以支援事務的概念,原子性的執行一批命令。在2.6以後的版本中由於新增了對Script指令碼的支援,而指令碼固有的是以transaction事務的方式執行的,並且更加易於使用,所以不排除將來取消Multi等命令介面的可能性

String資料

  • 常用命令:set/get/decr/incr/mget等;
  • 應用場景:String是最常用的一種資料型別,普通的key/value儲存都可以歸為此類;
  • 實現方式:String在redis內部儲存預設就是一個字串,被redisObject所引用,當遇到incr、decr等操作時會轉成數值型進行計算,此時redisObject的encoding欄位為int。

Hash資料

  • 常用命令:hget/hset/hgetall等
  • 應用場景:我們要儲存一個使用者資訊物件資料,其中包括使用者ID、使用者姓名、年齡和生日,通過使用者ID我們希望獲取該使用者的姓名或者年齡或者生日;
  • 實現方式:Redis Hash對應Value內部實際就是一個HashMap,實際這裡會有2種不同實現,這個Hash的成員比較少時Redis為了節省記憶體會採用類似一維陣列的方式來緊湊儲存,而不會採用真正的HashMap結構,對應的value redisObject的encoding為zipmap,當成員數量增大時會自動轉成真正的HashMap,此時encoding為ht

List

  • 常用命令:lpush/rpush/lpop/rpop/lrange等;
  • 應用場景:Redis list的應用場景非常多,也是Redis最重要的資料結構之一,比如twitter的關注列表,粉絲列表等都可以用Redis的list結構來實現;
  • 實現方式:Redis list的實現為一個雙向連結串列,即可以支援反向查詢和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,Redis內部的很多實現,包括髮送緩衝佇列等也都是用的這個資料結構。

Set資料

  • 常用命令:sadd/spop/smembers/sunion等;
  • 應用場景:Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在於set是可以自動排重的,當你需要儲存一個列表資料,又不希望出現重複資料時,set是一個很好的選擇,並且set提供了判斷某個成員是否在一個set集合內的重要介面,這個也是list所不能提供的;
  • 實現方式:set 的內部實現是一個 value永遠為null的HashMap,實際就是通過計算hash的方式來快速排重的,這也是set能提供判斷一個成員是否在集合內的原因

Sorted Set資料

  • 常用命令:zadd/zrange/zrem/zcard等;
  • 應用場景:Redis sorted set的使用場景與set類似,區別是set不是自動有序的,而sorted set可以通過使用者額外提供一個優先順序(score)的引數來為成員排序,並且是插入有序的,即自動排序。當你需要一個有序的並且不重複的集合列表,那麼可以選擇sorted set資料結構,比如twitter 的public timeline可以以發表時間作為score來儲存,這樣獲取時就是自動按時間排好序的。
  • 實現方式:Redis sorted set的內部使用HashMap和跳躍表(SkipList)來保證資料的儲存和有序,HashMap裡放的是成員到score的對映,而跳躍表裡存放的 是所有的成員,排序依據是HashMap裡存的score,使用跳躍表的結構可以獲得比較高的查詢效率,並且在實現上比較簡單。

持久化

  • Redis可以以master-slave的方式配置伺服器,Slave節點對資料進行replica備份,Slave節點也可以充當Read only的節點分擔資料讀取的工作。
  • Redis雖然是基於記憶體的儲存系統,但是它本身是支援記憶體資料的持久化的,而且提供兩種主要的持久化策略:RDB快照和AOF日誌
  • Redis內建支援兩種持久化方案,snapshot快照和AOF 增量Log方式。快照顧名思義就是隔一段時間將完整的資料Dump下來儲存在檔案中。AOF增量Log則是記錄對資料的修改操作(實際上記錄的就是每個對資料產生修改的命令本身),兩種方案可以並存,也各有優缺點,具體參見
  • http://blog.chinaunix.net/uid-20682890-id-...
  • http://www.cnblogs.com/rollenholt/p/387444...

記憶體配置

/etc/sysctl.conf新增

vm.overcommit_memory=1

重新整理配置使之生效

sysctl vm.overcommit_memory=1

補充介紹:
如果記憶體情況比較緊張的話,需要設定核心引數:

echo 1 > /proc/sys/vm/overcommit_memory

核心引數說明如下:

  • overcommit_memory檔案指定了核心針對記憶體分配的策略,其值可以是0、1、2。
  • 0, 表示核心將檢查是否有足夠的可用記憶體供應用程式使用;如果有足夠的可用記憶體,記憶體申請允許;否則,記憶體申請失敗,並把錯誤返回給應用程式。
  • 1, 表示核心允許分配所有的實體記憶體,而不管當前的記憶體狀態如何。
  • 2, 表示核心允許分配超過所有實體記憶體和交換空間總和的記憶體

編輯redis.conf配置檔案(/etc/redis.conf),按需求做出適當調整,比如:

daemonize yes #轉為守護程式,否則啟動時會每隔5秒輸出一行監控資訊
save 60 1000 #減小改變次數,其實這個可以根據情況進行指定
maxmemory 256000000 #分配256M記憶體

Redis的7個應用場景

一:快取——熱資料

  • 熱點資料(經常會被查詢,但是不經常被修改或者刪除的資料),首選是使用redis快取,畢竟強大到冒泡的QPS和極強的穩定性不是所有類似工具都有的,而且相比於memcached還提供了豐富的資料型別可以使用,另外,記憶體中的資料也提供了AOF和RDB等持久化機制可以選擇,要冷、熱的還是忽冷忽熱的都可選。
  • 結合具體應用需要注意一下:很多人用spring的AOP來構建redis快取的自動生產和清除,過程可能如下:
  • Select 資料庫前查詢redis,有的話使用redis資料,放棄select 資料庫,沒有的話,select 資料庫,然後將資料插入redis
  • update或者delete資料庫前,查詢redis是否存在該資料,存在的話先刪除redis中資料,然後再update或者delete資料庫中的資料
  • 上面這種操作,如果併發量很小的情況下基本沒問題,但是高併發的情況請注意下面場景:
  • 為了update先刪掉了redis中的該資料,這時候另一個執行緒執行查詢,發現redis中沒有,瞬間執行了查詢SQL,並且插入到redis中一條資料,回到剛才那個update語句,這個悲催的執行緒壓根不知道剛才那個該死的select執行緒犯了一個彌天大錯!於是這個redis中的錯誤資料就永遠的存在了下去,直到下一個update或者delete。

二:計數器

  • 諸如統計點選數等應用。由於單執行緒,可以避免併發問題,保證不會出錯,而且100%毫秒級效能!爽。
  • 命令:INCRBY
  • 當然爽完了,別忘記持久化,畢竟是redis只是存了記憶體!

三:佇列

  • 相當於訊息系統,ActiveMQ,RocketMQ等工具類似,但是個人覺得簡單用一下還行,如果對於資料一致性要求高的話還是用RocketMQ等專業系統。
  • 由於redis把資料新增到佇列是返回新增元素在佇列的第幾位,所以可以做判斷使用者是第幾個訪問這種業務
  • 佇列不僅可以把併發請求變成序列,並且還可以做佇列或者棧使用

四:位操作(大資料處理)

  • 用於資料量上億的場景下,例如幾億使用者系統的簽到,去重登入次數統計,某使用者是否線上狀態等等。
  • 想想一下騰訊10億使用者,要幾個毫秒內查詢到某個使用者是否線上,你能怎麼做?千萬別說給每個使用者建立一個key,然後挨個記(你可以算一下需要的記憶體會很恐怖,而且這種類似的需求很多,騰訊光這個得多花多少錢。。)好吧。這裡要用到位操作——使用setbit、getbit、bitcount命令。
  • 原理是:redis內構建一個足夠長的陣列,每個陣列元素只能是0和1兩個值,然後這個陣列的下標index用來表示我們上面例子裡面的使用者id(必須是數字哈),那麼很顯然,這個幾億長的大陣列就能通過下標和元素值(0和1)來構建一個記憶系統,上面我說的幾個場景也就能夠實現。用到的命令是:setbit、getbit、bitcount

五:分散式鎖與單執行緒機制

  • 驗證前端的重複請求(可以自由擴充套件類似情況),可以通過redis進行過濾:每次請求將request Ip、引數、介面等hash作為key儲存redis(冪等性請求),設定多長時間有效期,然後下次請求過來的時候先在redis中檢索有沒有這個key,進而驗證是不是一定時間內過來的重複提交
  • 秒殺系統,基於redis是單執行緒特徵,防止出現資料庫“爆破”
  • 全域性增量ID生成,類似“秒殺”

六:最新列表

  • 例如新聞列表頁面最新的新聞列表,如果總數量很大的情況下,儘量不要使用select a from A limit 10這種low貨,嘗試redis的 LPUSH命令構建List,一個個順序都塞進去就可以啦。不過萬一記憶體清掉了咋辦?也簡單,查詢不到儲存key的話,用mysql查詢並且初始化一個List到redis中就好了。

七:排行榜

  • 誰得分高誰排名往上。命令:ZADD(有續集,sorted set)

其他web應用場景

1.在主頁中顯示最新的專案列表。

  • redis使用的是常駐記憶體的快取,速度非常快。lpush用來插入一個內容id,作為關鍵字儲存在列表頭部。ltrim用來限制列表中的專案數最多為5000。如果使用者需要的檢索的資料量超越這個快取容量,這時才需要把請求傳送到資料庫。

2.刪除和過濾。

  • 如果一篇文章被刪除,可以使用lrem從快取中徹底清除掉。

3.排行榜及相關問題。

  • 排行榜(leader board)按照得分進行排序。zadd命令可以直接實現這個功能,而zrevrange命令可以用來按照得分來獲取前100名的使用者,zrank可以用來獲取使用者排名,非常直接而且操作容易。

4.按照使用者投票和時間排序。

  • 這就像reddit的排行榜,得分會隨著時間變化。lpush和ltrim命令結合運用,把文章新增到一個列表中。一項後臺任務用來獲取列表,並重新計算列表的排序,zadd命令用來按照新的順序填充生成列表。列表可以實現非常快速的檢索,即使是負載很重的站點。

5.過期專案處理。

  • 使用unix時間作為關鍵字,用來保持列表能夠按時間排序。對current_time和time_to_live進行檢索,完成查詢過期專案的艱鉅任務。另一項後臺任務使用zrange...withscores進行查詢,刪除過期的條目。

6.特定時間內的特定專案。

  • 這是特定訪問者的問題,可以通過給每次頁面瀏覽使用sadd命令來解決。sadd不會將已經存在的成員新增到一個集合。

    7.實時分析正在發生的情況,用於資料統計與防止垃圾郵件等。

  • 使用redis原語命令,更容易實施垃圾郵件過濾系統或其他實時跟蹤系統。

    8.pub/sub

  • 在更新中保持使用者對資料的對映是系統中的一個普遍任務。redis的pub/sub功能使用了subscribe、unsubscribe和publish命令,讓這個變得更加容易。

Redis與Memcache對比

  • 網路IO模型 效能對比:(佔用的核數,執行緒數,網路模型)
  • 由於Redis只使用單核,而Md,所以平均每一個核上Redis在儲存小資料時比Memcached效能更 高。而在100k以上的資料中,Memcached效能要高於Redis,雖然Redis最近也在儲存大資料的效能上進行優化,但是比起 Memcached,還是稍有遜色。
  • Memcached是多執行緒,非阻塞IO複用的網路模型,分為監聽主執行緒和worker子執行緒,監聽執行緒監聽網路連線,接受請求後,將連線描述字pipe 傳遞給worker執行緒,進行讀寫IO, 網路層使用libevent封裝的事件庫,多執行緒模型可以發揮多核作用,但是引入了cache coherency和鎖的問題,比如,Memcached最常用的stats 命令,實際Memcached所有操作都要對這個全域性變數加鎖,進行計數等工作,帶來了效能損耗。
  • Redis使用單執行緒的IO複用網路模型,自己封裝了一個簡單的AeEvent事件處理框架,主要實現了epoll、kqueue和select,對於單純只有IO操作來說,單執行緒可以將速度優勢發揮到最大,但是Redis也提供了一些簡單的計算功能,比如排序、聚合等,對於這些操作,單執行緒模型實際會嚴重影響整體吞吐量,CPU計算過程中,整個IO排程都是被阻塞住的。

記憶體管理方面:

  • Memcached使用預分配的記憶體池的方式,使用slab和大小不同的chunk來管理記憶體,Item根據大小選擇合適的chunk儲存,記憶體池的方式可以省去申請/釋放記憶體的開銷,並且能減小記憶體碎片產生,但這種方式也會帶來一定程度上的空間浪費,並且在記憶體仍然有很大空間時,新的資料也可能會被剔除
  • Redis使用現場申請記憶體的方式來儲存資料,並且很少使用free-list等方式來優化記憶體分配,會在一定程度上存在記憶體碎片,Redis跟據儲存命令引數,會把帶過期時間的資料單獨存放在一起,並把它們稱為臨時資料,非臨時資料是永遠不會被剔除的,即便實體記憶體不夠,導致swap也不會剔除任何非臨時資料(但會嘗試剔除部分臨時資料),這點上Redis更適合作為儲存而不是cache。

資料一致性問題:

  • Memcached提供了cas命令,可以保證多個併發訪問操作同一份資料的一致性問題。 Redis沒有提供cas 命令,並不能保證這點,不過Redis提供了事務的功能,可以保證一串 命令的原子性,中間不會被任何操作打斷。由於需要使用cas方法,php的memcache客戶端不支援該方法,所以改用php的memcached客戶端 。這個東西還是很強大的,它所使用的libmemcached庫是各種語言都支援的。(需要額外安裝編譯,可以使用PECL裝,或手動編譯)
    如果採用CAS(check and set)方式協議,則是如下的情景。
  • 第一步,A取出資料物件X,並獲取到CAS-ID1;
  • 第二步,B取出資料物件X,並獲取到CAS-ID2;
  • 第三步,B修改資料物件X,在寫入快取前,檢查CAS-ID與快取空間中該資料的CAS-ID是否一致。結果是“一致”,就將修改後的帶有CAS-ID2的X寫入到快取。
  • 第四步,A修改資料物件Y,在寫入快取前,檢查CAS-ID與快取空間中該資料的CAS-ID是否一致。結果是“不一致”,則拒絕寫入,返回儲存失敗。

Redis資料型別:

> *  Redis相比Memcached來說,擁有更多的資料結構和並支援更豐富的資料操作。
> * 通常在Memcached 裡,你需要將資料拿到客戶端來進行類似的修改再set回去。

這大大增加了網路IO的次數和資料體積。在Redis中,這些複雜的操作通常和一般的 GET/SET一樣高效。所以,如果需要快取能夠支援更復雜的結構和操作,那麼Redis會是不錯的選擇

儲存方式及其它方面

> * Memcached基本只支援簡單的key-value儲存,不支援列舉,不支援持久化和複製等功能
> * Redis除key/value之外,還支援list,set,sorted set,hash等眾多資料結構,提供了KEYS

進行列舉操作,但不能線上上使用,如果需要列舉線上資料,Redis提供了工具可以直接掃描其dump檔案,列舉出所有資料,Redis還同時提供了持久化和複製等功能。

相關文章