Redis詳解 單線程 基於內存設計 主從 持久化 與 memcached區別 及常見問題

1. Redis簡介

Redis本質上是一個Key-Value類型的內存數據庫,很像memcached,整個數據庫統統加載在內存當中進行操作,定期通過異步操作把數據庫數據flush到硬盤上進行保存。因爲是純內存操作,Redis的性能非常出色,每秒可以處理超過 10萬次讀寫操作,是已知性能最快的Key-Value DB。

Redis的出色之處不僅僅是性能,Redis最大的魅力是支持保存多種數據結構,此外單個value的最大限制是1GB,不像 memcached只能保存1MB的數據,因此Redis可以用來實現很多有用的功能,比方說用他的List來做FIFO雙向鏈表,實現一個輕量級的高性 能消息隊列服務,用他的Set可以做高性能的tag系統等等。
另外Redis也可以對存入的Key-Value設置expire時間,因此也可以被當作一 個功能加強版的memcached來用。

Redis的主要缺點是數據庫容量受到物理內存的限制,不能用作海量數據的高性能讀寫,因此Redis適合的場景主要侷限在較小數據量的高性能操作和運算上。

2. Redis支持的數據類型與適用場景

2.1 數據類型

Redis本質上是key-value對的內存數據庫,key(鍵)使用字符串存儲,但是key中不能出現空格或者換行符 \n,原因是空格和換行符都是Redis的特殊字符,但只限於key。value可以使用任何字符。

Redis以換行符 \n 作爲命令結束符,所以在key中不能存在 \n,否則就會報錯。此外,Redis以空格作爲命令和參數的分隔符,所以在key中也不能存在空格。
儘量使用較短的key,因爲較短的key可以節省內存和帶寬。

Redis通過Key-Value的單值不同類型來區分, 以下是支持的類型:

  • string

    string數據類型是二進制安全的,可以把圖片、css文件、視頻文件等保存在string中,爲了提供網站的運行速度,可以用string類型緩存一些靜態文件,如:圖片、css文件等。string類型支持增量操作,可用作統計計算,如統計網站訪問次數。

  • list

    list數據類型指key對應的value是一個雙向鏈表結構,所以list類型提供鏈表支持的所有操作。list類型在互聯網應用中非常有用,例如存放微博中“我的關注列表”或者論壇中所有的回帖ID。

    另外,使用list還可以實現消息隊列功能,減輕數據庫的壓力。消息隊列類似於現實生活中的排隊,每次有消息到達時就把消息放進隊列尾部,取出消息時就從隊列頭部取出。要用list實現消息隊列,先用rpush命令把消息放進隊列尾部,然後使用lpop命令把消息從隊列頭部取出。

  • set

    set數據類型是一種無序集合。優點是快速查找元素是否存在,用於記錄一些不能重複的數據。例如:網站中註冊的用戶名,如果要註冊的用戶名已經存在於集合中,就拒絕此用戶註冊。

    set類型通常用於記錄做過某些事情。例如:在投票系統中,每個用戶一天只能投票一次,那麼可以使用set類型來記錄某個用戶的投票情況,只需要以日期作爲key,將用戶ID作爲集合中的元素即可。要查看某個用戶今天是否投過票,只需以今天的日期作爲key去集合中查詢用戶ID是否存在。

  • zset

    即sorted set類型。zset類型和set類型很相似,都是string類型元素的集合,不同的是zset類型屬於有序集合,它通過一個double類型的整數score對集合中的元素進行排序。zset通過SkipList(跳躍表)和HashTable組合完成。SkipList負責排序,而HashTable負責保存數據。

    set類型能做的事情zset也可以做,而且zset還可以完成一些set不能做的事情,例如使用zset構建一個具有優先級的隊列,這也是list類型不能實現的。

    zset類型在Web應用中非常有用。例如,排行榜應用中按“頂貼”次數排序,方法是:將排序的值設置成zset的score值,將具體數據設置成相應的value,用戶每次按“頂貼”按鈕時,只需執行zadd命令修改score的值。

  • hash

    hash類型是每個key對應一個HashTable,hash類型適合存儲對象,例如用戶信息對象,把用戶ID作爲key,可以把用戶信息保存到hash類型中。

    新建一個hash類型對象時,爲了節省內存,Redis使用zipmap存儲數據。這個zipmap並不是真正的HashTable,但是相比普通HashTable,zipmap節省不少內存。如果field或value的大小超出一定限制,Redis在內部自動將zipmap替換成正常的HashTable存儲。修改配置文件的hash_max_zipmap_entries和hash_max_zipmap_value選項,可設置這兩個限制。

常用操作可以參見這裏

2.2 適用場景

  1. 會話緩存(Session Cache)

    最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如Memcached)的優勢在於:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?

    幸運的是,隨着 Redis 這些年的改進,很容易找到怎麼恰當的使用Redis來緩存會話的文檔。甚至廣爲人知的商業平臺Magento也提供Redis的插件。

  2. 全頁緩存(FPC)

    除基本的會話token之外,Redis還提供很簡便的FPC平臺。回到一致性問題,即使重啓了Redis實例,因爲有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。

    再次以Magento爲例,Magento提供一個插件來使用Redis作爲全頁緩存後端

    此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快速度加載你曾瀏覽過的頁面。

  3. 隊列

    Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作爲一個很好的消息隊列平臺來使用。Redis作爲隊列使用的操作,就類似於本地程序語言(如Python)對 list 的 push/pop 操作。

    如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是利用Redis創建非常好的後端工具,以滿足各種隊列需求。例如,Celery有一個後臺就是使用Redis作爲broker,你可以從這裏去查看。

  4. 排行榜/計數器

    Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之爲“user_scores”,我們只需要像下面一樣執行即可:

    當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執行:

    ZRANGE user_scores 0 10 WITHSCORES

  5. 發佈/訂閱

    發佈/訂閱的使用場景確實非常多。我已看見人們在社交網絡連接中使用,還可作爲基於發佈/訂閱的腳本觸發器,甚至用Redis的發佈/訂閱功能來建立聊天系統。

3. Redis設計

3.1 爲什麼redis需要把所有數據放到內存中?

Redis爲了達到最快的讀寫速度將數據都讀到內存中,並通過異步的方式將數據寫入磁盤。所以redis具有快速和數據持久化的特徵。如果不將數據放在內存中,磁盤I/O速度爲嚴重影響redis的性能。在內存越來越便宜的今天,redis將會越來越受歡迎。
如果設置了最大使用的內存,則數據已有記錄數達到內存限值後不能繼續插入新值。

3.2 Redis是單進程單線程的

redis利用隊列技術將併發訪問變爲串行訪問,消除了傳統數據庫串行控制的開銷

4. 虛擬內存

Redis的數據是保存在內存中的,可能出現物理內存不足的情況。物理內存不足時,Redis使用什麼方法解決問題呢?答案是使用“虛擬內存”(VM)。
redis的虛擬內存與操作系統的虛擬內存不是一回事,但思路和目的是相同的。就是暫時把不經常訪問的value從內存交換到磁盤中保存,同時,Redis會把value對應的key都放在內存中,當用戶訪問這些很少訪問的數據時,Redis纔會把key對應的value從磁盤導入到內存中。從而,騰出寶貴的內存空間用於其他需要經常訪問的數據。

對於redis這樣的內存數據庫,內存總是不夠用的。除了可以將數據分割到多個redis server,另外一個提高數據庫容量的辦法就是使用虛擬內存把那些不經常訪問的數據交換到磁盤上。

要想使用虛擬內存,需要在配置文件中開啓相關的配置項,如下:

vm-enabled yes                # 開啓vm功能   (默認是沒有使用虛擬內存的)
vm-swap-file /tmp/redis.swap        # 交換出來的value保存的文件路徑
vm-max-memory 268435456          # redis使用的最大內存,超過該上限後,Redis開始交換value到磁盤文件
vm-page-size 32                # 每個頁面的大小爲32字節
vm-pages 134217728            # 最多使用多少個頁面,即swap文件最多包含多少頁面
vm-max-threads 4            # 用於執行value對象換入換出的工作線程數量。0 表示不使用工作線程

Redis的虛擬內存只把value交換到磁盤中,而key依然存儲在內存中,目的是讓開啓虛擬內存的Redis和完全使用內存的Redis性能基本保持一致。如果由於太多key造成的內存不足的問題,Redis的虛擬內存並不能解決。

vm-max-threads 表示用於執行交換任務的工作線程的數量,建議不要將其設置爲0。因爲如果設置爲0,交換過程就會在主線程進行,從而阻塞其他用戶。但也不是設置越大越好,因爲太多的工作線程導致操作系統使用更多時間來切換線程,從而降低了效率。推薦將vm-max-threads 設置爲服務器的CPU核心數。

當key很小而value很大時,使用VM的效果會比較好.因爲這樣節約的內存比較大。

當key不小時,可以考慮使用一些非常方法將很大的key變成很大的value,比如可以考慮將key,value組合成一個新的value。

測試的時候發現用虛擬內存性能也不錯。如果數據量很大,可以考慮分佈式或者其他數據庫

5. 分佈式 / 主從複製

redis支持主從的模式。原則:Master會將數據同步到slave,而slave不會將數據同步到master。Slave啓動時會連接master來同步數據。

主從複製(也叫主從同步)可以防止主機壞掉導致的網站不能正常運作的問題。Redis支持主從複製,而且配置也很簡單。redis的主從複製可以讓多個從服務器(slave server)擁有和主服務器(master server)相同的數據庫副本。

這是一個典型的分佈式讀寫分離模型。我們可以利用master來插入數據,slave提供檢索服務。這樣可以有效減少單個機器的併發訪問數量

5.1 主從複製特點

  • 一個master可以擁有多個slave
  • 多個slave除了可以連接同一個master外,還可以連接其他的slave
  • 不會阻塞master,在slave同步數據時,master可以繼續處理客戶端的請求
  • 提高了系統的伸縮性,比如多個slave專門用於客戶端的讀操作
  • 可在master服務器上禁止數據持久化,而只在slave服務器上進行數據持久化操作

5.2 主從複製原理

主從複製設置很簡單,設置好slave服務器後,slave自動和master建立連接,發送SYNC命令。無論是第一次同步建立的連接還是連接斷開後重新建立的連接,master都會啓動一個後臺進程,將內存數據以快照方式寫入文件中,同時master主進程開始收集新的寫命令並且緩存起來。master後臺進程完成內存快照操作後,把數據文件發給slave,slave將文件保存到磁盤上,然後將數據加載到內存中。接着master把緩存的命令發給slave,後續master收到的寫命令都通過開始建立的連接發送給slave。當master與slave斷開連接,slave自動重新建立連接。如果master同時收到多個slave發來的同步請求,其只啓動一個進程寫數據庫鏡像,然後發送給所有的slave。

5.3 主從複製過程

分爲兩個階段,第一階段如下:

  1. slave服務器主動連接到master服務器。
  2. slave服務器發送SYNC命令到master服務器請求同步數據。
  3. master服務器備份數據庫到rdb文件。
  4. master服務器將該rdb文件傳輸給slave服務器。
  5. slave服務器清空數據庫數據,把rdb文件數據導入數據庫中。

第二階段:master服務器把用戶所有更改數據的操作(寫操作),通過命令的形式轉發給slave服務器,slave服務器只需執行master服務器發送過來的命令就可以實現後續的同步效果。

5.3 主從複製配置

相對MySQL的主從複製來說,Redis的主從複製配置很簡單,只需在slave服務器的配置文件中,添加下面的配置項:

slaveof 192.168.1.115 6379 #指定master(主服務器)的ip和端口
masterauth 密碼  #如果主服務器設置了安全密碼,還要加上這行代碼進行授權

配置完成後,重啓redis從服務器,就已經通過主從複製實現了數據的同步。

6. 常見Redis模型

6.1 讀寫分離模型

通過增加Slave DB的數量,讀的性能可以線性增長。爲了避免Master DB的單點故障,集羣一般都會採用兩臺Master DB做雙機熱備,所以整個集羣的讀和寫的可用性都非常高。

讀寫分離架構的缺陷在於,不管是Master還是Slave,每個節點都必須保存完整的數據,如果在數據量很大的情況下,集羣的擴展能力還是受限於單個節點的存儲能力,而且對於Write-intensive類型的應用,讀寫分離架構並不適合。

6.2 數據分片模型

爲了解決讀寫分離模型的缺陷,可以將數據分片模型應用進來。

可以將每個節點看成都是獨立的master,然後通過業務實現數據分片。

結合上面兩種模型,可以將每個master設計成由一個master和多個slave組成的模型。

7. 持久化

Redis是基於內存的數據庫,內存數據庫有個嚴重的弊端:突然宕機或者斷電時,內存中的數據就會丟失。爲了解決這個問題,redi提供了兩種持久化的方式:
snapshotting(內存快照),默認方式
append-only file(日誌追加,縮寫爲aof)
Redis是一個支持持久化的內存數據庫,也就是說redis需要經常將內存中的數據同步到硬盤來保證持久化。

7.1 內存快照

快照是默認的持久化方式。這種方式是將redis保存在內存中的數據以快照的方式寫入二進制文件,默認的文件名
dump.rdb。可以通過修改配置文件,來設置自動快照。

vi /usr/local/redis/etc/redis.conf
save 900 1                # 每900秒,數據更改1次,就發起快照保存
save 300 10              # 每300秒,數據更改10次,則發起快照保存
save 60  10000         # 每60秒,數據更改10000,則發起快照保存

上面設置了多個內存快照保存方案,只要其中一個條件成立,Redis都會進行一次內存快照操作。

Redis每隔一段時間進行一次內存快照操作,客戶端使用save或者bgsave命令,告訴Redis需要做一次內存快照操作。save命令在主線程中保存內存快照,Redis使用單線程處理所有請求,執行save命令可能阻塞其他客戶端請求,從而導致不能快速響應請求,所以建議不要使用save命令。另外要注意,內存快照每次都把內存數據完整地寫入硬盤,而不是隻寫入增量數據。所以如果數據量很大,寫入操作比較頻繁,就會嚴重影響性能。

由於快照方式是在一定間隔時間執行一次快照保存,所以如果redis出現問題,就會丟失最後一次快照後的所有修改。

7.2 日誌追加 AOF

日誌追加(aof)方式比快照方式有更好的持久化性,如果啓用了aof,Redis會將每一個收到的寫命令通過write函數追加到文件appendonly.aof中,當Redis重啓時,它會執行該文件中的所有命令,這樣就可以在內存中重建整個redis數據庫的內容。

另外,操作系統內核的 I/O 接口可能存在緩存,所以日誌追加方式不可能立即寫入文件,這樣就有可能丟失部分數據。幸運的是,Redis提供瞭解決方法,通過修改配置文件,告訴Redis應該在什麼時候使用fsync函數強制操作系統把緩存中的寫命令寫入磁盤中的日誌文件。有以下三種方法:

appendonly yes                      #啓用aof持久化方式
# appendfsync always            #每次收到寫命令就立即寫入磁盤,性能最差,持久化最好
appendfsync everysec            #每秒鐘寫入磁盤一次,在性能和持久化方面做了很好的折中
# appendfsync no                  #是否寫入磁盤完全依賴操作系統,性能最好,持久化沒保證

日誌追加方式有效地降低了數據丟失的風險,同時也帶來另一個問題,即持久化文件(appendonly.aof)不斷膨脹。例如調用 incr nums 命令100次,文件就會保存100條該命令,其實99條都是多餘的,因爲要恢復數據只需要set nums 100。

爲了壓縮日誌文件,Redis提供了bgrewriteaof命令。當Redis收到此命令時,就使用類似於內存快照的方式將內存中的數據以命令的方式保存到臨時文件中,最後替換原來的日誌文件。

內存快照和日誌追加,各有優缺點,選擇哪種持久化方式需要自己衡量。也可以把這兩種持久化方式都關閉,實現自己的持久化方式,如使用Berkeley DB或者Tokyo Cabinet。

8. Redis的回收策略

MySQL裏有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據?

redis 內存數據集大小上升到一定大小的時候,就會施行數據淘汰策略。redis 提供 6種數據淘汰策略:

  • volatile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

  • volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰

  • volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰

  • allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

  • allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

  • no-enviction(驅逐):禁止驅逐數據

9. 事務處理

redis對事務的支持目前還比較簡單。它只能保證一個client(客戶端)發起的事務中的命令可以連續的執行,中間不會插入其他的client的命令。當一個client在一個連接中發出 multi 命令時,這個連接會進入一個事務,後續的命令不會立即執行,而是先放到一個隊列中。當執行 exec 命令時,redis纔會順序執行隊列中的所有命令,之後退出事務;當執行 discard 命令時,redis會廢棄事務的命令隊列並退出事務。

一般情況下,Redis接收到一個客戶端連接發來的命令後,會立刻執行並返回結果。但是,當客戶端連接發出multi命令時,此連接便進入一個事務上下文,Redis把此連接發來的命令存入一個隊列中。當此連接發出exec命令時,Redis便開始按順序執行隊列中的所有命令,並將事務中所有命令的結果打包一起返回給客戶端,即提交事務後退出事務。如:

multi
set num 1
incr num
exec

從這個例子可以看出,set num 1 和 incr num 命令發出以後並沒有立刻執行,而是存放到事務的命令隊列中。當調用 exec 命令時,這兩個命令纔開始連續執行,最後返回這兩個命令執行後的結果。
可調用 discard 命令取消事務,例如:

multi
set count 100
incr count
discard
get count

從這個例子可以看出,set count 100 和 incr count 命令都沒有執行,discard 命令的作用是清空事務的命令隊列並退出事務上下文。

注意:Redis只能保證事務中的每個命令能夠連續執行,但是如果事務中有命令執行失敗,Redis無法進行回滾操作。也就是說事務中的命令要麼全部執行,要麼全部取消,我們無法從事務執行過程中的失敗處進行回滾。

10. 常見問題

10.1 使用Redis有哪些好處?

  1. 速度快,因爲數據存在內存中,類似於HashMap,HashMap的優勢就是查找和操作的時間複雜度都是O(1)

  2. 支持豐富數據類型,支持string,list,set,sorted set,hash

  3. 支持事務,操作都是原子性,所謂的原子性就是對數據的更改要麼全部執行,要麼全部不執行

  4. 豐富的特性:可用於緩存,消息,按key設置過期時間,過期後將會自動刪除

10.2 redis常見性能問題和解決方案

  • Master最好不要做任何持久化工作,如RDB內存快照和AOF日誌文件

    Master調用BGREWRITEAOF重寫AOF文件,AOF在重寫的時候會佔大量的CPU和內存資源,導致服務load過高,出現短暫服務暫停現象

    Master寫內存快照,save命令調度rdbSave函數,會阻塞主線程的工作,當快照比較大時對性能影響是非常大的,會間斷性暫停服務,所以Master最好不要寫內存快照。

  • 如果數據比較重要,某個Slave開啓AOF備份數據,策略設置爲每秒同步一次

  • 爲了主從複製的速度和連接的穩定性,Master和Slave最好在同一個局域網內

  • 儘量避免在壓力很大的主庫上增加從庫

  • 主從複製不要用圖狀結構,用單向鏈表結構更爲穩定,即:Master <- Slave1 <- Slave2 <- Slave3…

    這樣的結構方便解決單點故障問題,實現Slave對Master的替換。如果Master掛了,可以立刻啓用Slave1做Master,其他不變。

10.3 Memcache與Redis的區別都有哪些?

  1. 存儲方式

    Memecache把數據全部存在內存之中,斷電後會掛掉,數據不能超過內存大小。

    Redis有部份存在硬盤上,這樣能保證數據的持久性。

  2. 數據支持類型

    Memcache對數據類型支持相對簡單。

    Redis有複雜的數據類型。

  3. 使用底層模型不同

    它們之間底層實現方式 以及與客戶端之間通信的應用協議不一樣。

    Redis直接自己構建了VM 機制 ,因爲一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。

  4. value大小

    redis最大可以達到1GB,而memcache只有1MB

11. Ref

Redis簡介
https://blog.csdn.net/lamp_yang_3533/article/details/52560085
https://icefire.me/2018/06/15/Redis%E8%AF%A6%E8%A7%A3/

Redis vs memcache
https://www.cnblogs.com/aspirant/p/8883871.html
https://www.jianshu.com/p/e94fa7340923

Redis單線程爲什麼速度快
https://blog.csdn.net/xlgen157387/article/details/79470556

Redis 實現分佈式鎖
https://wudashan.cn/2017/10/23/Redis-Distributed-Lock-Implement/

Redis持久化
https://dbaplus.cn/news-158-2149-1.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章