1. 程式人生 > 其它 >NoSQL 介紹之Redis

NoSQL 介紹之Redis

一切工業技術的革新都是為了解決當時遇到的瓶頸,那資料庫也不例外。

在最早期Web1.0(是以編輯為特徵,網站提供給使用者的內容是網站編輯進行編輯處理後提供的,使用者閱讀網站提供的內容。這個過程是網站到使用者的單向行為,web1.0時代的代表站點為新浪,搜狐,網易三大門戶。),由於使用者的訪問量很少,設計和實現程式主要是為了解決功能問題,用一夫當關的高效能的單點伺服器(小型機,典型的Oracle DB2)可以解決大部分問題,此階段用到的主流的技術:Java、Jsp、RDBMS、Tomcat、HTML、Linux、Jdbc、SVN ...雖然效率不高,但是能完成功能沒問題。

隨著Web2.0(更注重使用者的互動作用,使用者既是網站內容的瀏覽者,也是網站內容的製造者,加強了網站與使用者的互動,如微博、天涯社群、人人網、自媒體)時代到來,產生了大量的使用者資料,加上後來智慧移動裝置的普及,所有的網際網路平臺都面臨著效能挑戰。要解決功能的擴充套件性的問題

,此時更多的考慮是程式的設計原則,比如設計模式、框架等,常用程式的流行的框架有:Structs、Spring、SpringMVC、Hibernate、Mybatis ... (這些框架只是更好的解決了擴充套件性的問題,沒有他們照樣也是能完成功能的)。

但是隨著使用者量、訪問量的海量增長,程式開始遇到了效能的問題,像計算密集型應用,如統計分析的應用,單點的CPU可能無法承受;像IO密集型應用,單點伺服器的硬碟也無法儲存,上面普通的技術無法解決效能的問題,此時出現了:NoSQL、Java多執行緒、Hadoop、Nginx、MQ、Kafka。

那麼問題就圍繞著如何解決CPU以及記憶體的壓力、IO壓力來開展。

  • 解決CPU、記憶體壓力:負載均衡+快取資料庫
  • 解決IO壓力:打破以業務邏輯為依據的儲存,針對不同的資料結構型別改為以效能為最優先的儲存方式。

NoSQL 資料庫概述:

  • NoSQL(Not Only SQL),意思為"不僅僅是SQL",泛指非關係型資料庫。
  • NoSQL不依賴業務邏輯方式儲存,而以簡單的key-value模式儲存,因此大大增加了資料庫的擴充套件能力。
  • 不遵循SQL標準 // 增加了學習成本 不能通用
  • 不支援ACID
  • 遠超於SQL的效能。

NoSQL適用場景:

  • 對資料高併發的讀寫
  • 海量資料的讀寫
  • 對資料高可擴充套件性的

NoSQL不適用場景:

  • 需要事務支援
  • 基於sql的結構化查詢儲存,處理複雜的關係,需要即席查詢

什麼是Redis?

redis 是一個基於記憶體的高效能key-value資料庫。

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適合的場景主要侷限在較小資料量的高效能操作和運算上。

Redis的應用場景:

  • 配合關係型資料庫做快取記憶體
  1. 高頻次、熱門訪問的資料(熱點資料),降低資料庫IO
  2. 分散式架構做session共享
  • 由於其具有持久化能力(可以將資料存入磁碟中),可以利用其多樣性的資料結構儲存特定的資料,如:
  1. 最新的N個數據 -- 通過List實現按自然時間排序的資料
  2. 排行榜,TOP N -- 利用zset(有序集合)
  3. 時效性的資料,比如手機驗證碼 -- Expire 過期時間設定
  4. 計數器,秒殺 -- 原子性,自增方法 INCR 、DECR
  5. 去除大量資料中的重複資料 -- 利用Set集合
  6. 構建佇列 -- 利用 List 集合
  7. 釋出訂閱訊息系統 -- pub/sub模式

Redis支援的資料型別?

  • Binary-safe strings
  • Lists: collections of string elements sorted according to the order of insertion. They are basicallylinked lists.
  • Sets: collections ofunique,unsortedstring elements.
  • Sorted sets: similar to Sets but where every string element is associated to a floating number value, calledscore. The elements are always taken sorted by their score, so unlike Sets it is possible to retrieve a range of elements (for example you may ask: give me the top 10, or the bottom 10).
  • Hashes: which aremapscomposed of fields associated with values. Both the field and the value are strings. This is very similar to Ruby or Python hashes.
  • Bit arrays (or simply bitmaps): it is possible, using special commands, to handle String values like an array of bits, you can set and clear individual bits, count all the bits set to 1, find the first set or unset bit, and so forth.
  • HyperLogLogs:this is a probabilistic data structure which is used in order to estimate the cardinality of a set. Don't be scared, it is simpler than it seems... See later in the HyperLogLog section of this tutorial.
  • Streams: append-only collections of map-like entries that provide an abstract log data type. They are covered in depth in the

Introduction to Redis Streams​redis.io

Redis為什麼快?

  • 基於記憶體 // 基於記憶體的並不一定比基於檔案的快,比如kafka的隨機記憶體處理,但是大多數情況下是這樣的。
  • 單執行緒:單執行緒的模型簡單,不易出錯,沒有額外的效能處理;多執行緒會引入很多問題:共享資源、硬體受限(硬體切換等)。
  • 使用了作業系統中的多路IO複用:與底層硬體有關。
  1. BIO
  2. NIO:非阻塞式IO
  3. AIO:非同步IO處理

// 廚師+灶臺+老闆

為什麼redis需要把所有資料放到記憶體中?

Redis為了達到最快的讀寫速度將資料都讀到記憶體中,並通過非同步的方式將資料寫入磁碟。所以redis具有快速和資料持久化的特徵。如果不將資料放在記憶體中,磁碟I/O速度為嚴重影響redis的效能。在記憶體越來越便宜的今天,redis將會越來越受歡迎。

如果設定了最大使用的記憶體,則資料已有記錄數達到記憶體限值後不能繼續插入新值。

Redis是單程序單執行緒的

redis利用佇列技術將併發訪問變為序列訪問,消除了傳統資料庫序列控制的開銷

分散式

Redis支援主從的模式。原則:Master會將資料同步到slave,而slave不會將資料同步到master。Slave啟動時會連線master來同步資料。

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

讀寫分離,基本的原理是讓主資料庫處理事務性增、改、刪操作(INSERT、UPDATE、DELETE),而從資料庫處理SELECT查詢操作。資料庫複製被用來把事務性操作導致的變更同步到叢集中的從資料庫。

因為資料庫的“寫”(寫10000條資料到oracle可能要3分鐘)操作是比較耗時的。但是資料庫的“讀”(從oracle讀10000條資料可能只要5秒鐘)。所以讀寫分離,解決的是,資料庫的寫入,影響了查詢的效率。

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

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

資料分片模型

為了解決讀寫分離模型的缺陷,可以將資料分片模型應用進來。可以將每個QQ號碼買號地圖節點看成都是獨立的master,然後通過業務實現資料分片。結合上面兩種模型,可以將每個master設計成由一個master和多個slave組成的模型。

Redis的回收策略

  • 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(驅逐):禁止驅逐資料

使用Redis有哪些好處?

  • 速度快,因為資料存在記憶體中,類似於HashMap,HashMap的優勢就是查詢和操作的時間複雜度都是O(1)
  • 支援豐富資料型別,支援string,list,set,sorted set,hash
  • 支援事務,操作都是原子性,所謂的原子性就是對資料的更改要麼全部執行,要麼全部不執行
  • 豐富的特性:可用於快取,訊息,按key設定過期時間,過期後將會自動刪除

Redis相比memcached有哪些優勢?

  • memcached所有的值均是簡單的字串,redis作為其替代者,支援更為豐富的資料型別
  • redis的速度比memcached快很多? // 為什麼?
  • redis可以持久化其資料

Redis常見效能問題和解決方案?

  • Master最好不要做任何持久化工作,如RDB記憶體快照和AOF日誌檔案。
  1. 寫記憶體快照時,save命令排程rdbSave函式,會阻塞主執行緒的工作;
  2. AOF 持久化是通過儲存被執行的寫命令來記錄資料庫狀態的,所以AOF檔案的大小隨著時間的流逝一定會越來越大;影響包括但不限於:對於Redis伺服器,計算機的儲存壓力;AOF還原出資料庫狀態的時間增加;
  3. 為了解決AOF檔案體積膨脹的問題,Redis提供了AOF重寫功能:Redis伺服器可以建立一個新的AOF檔案來替代現有的AOF檔案,新舊兩個檔案所儲存的資料庫狀態是相同的,但是新的AOF檔案不會包含任何浪費空間的冗餘命令,通常體積會較舊AOF檔案小很多。
  4. AOF在重寫的時候會佔大量的CPU和記憶體資源。如果不重寫AOF檔案,這個持久化方式對效能的影響是最小的,但是AOF檔案會不斷增大,AOF檔案過大會影響Master重啟的恢復速度。
  • 如果資料比較重要,某個Slave開啟AOF備份資料,策略設定為每秒同步一次
  • 為了主從複製的速度和連線的穩定性,Master和Slave最好在同一個區域網內
  • 儘量避免在壓力很大的主庫上增加從庫
  • 主從複製不要用圖狀結構,用單向連結串列結構更為穩定,即:Master <- Slave1 <- Slave2 <- Slave3...
  • 這樣的結構方便解決單點故障問題,實現Slave對Master的替換。如果Master掛了,可以立刻啟用Slave1做Master,其他不變。

MySQL裡有2000w資料,redis中只存20w的資料,如何保證redis中的資料都是熱點資料?

redis 記憶體資料集大小上升到一定大小的時候,就會施行資料淘汰策略。redis 提供 6種,前文已經介紹。

由maxmemory-policy引數設定淘汰策略:

CONFIG SET maxmemory-policy volatile-lru      #淘汰有過時期的最近最好使用資料

Redis 適合的場景

Redis最適合所有資料in-momory的場景,雖然Redis也提供持久化功能,但實際更多的是一個disk-backed的功能,跟傳統意義上的持久化有比較大的差別。

  • 會話快取(Session Cache):用Redis快取會話比其他儲存(如Memcached)的優勢在於:Redis提供持久化。
  • 佇列: Reids在記憶體儲存引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的訊息佇列平臺來使用。
  • 排行榜: 集合(Set)和有序集合(Sorted Set)也使得這些操作變的非常簡單。可以從排序集合中獲取到排名最靠前的10個使用者–我們稱之為“user_scores”,當然,這是假定你是根據你使用者的分數做遞增的排序。排行榜(leader board)按照得分進行排序。ZADD命令可以直接實現這個功能,而ZREVRANGE命令可以用來按照得分來獲取前100名的使用者,ZRANK可以用來獲取使用者排名,非常直接而且操作容易。
  • 計數器:Redis在記憶體中對數字進行遞增或遞減的操作實現的非常好。Redis的命令都是原子性的,你可以輕鬆地利用INCR,DECR命令來構建計數器系統。
  • 釋出/訂閱:釋出/訂閱的使用場景確實非常多。我已看見人們在社交網路連線中使用,還可作為基於釋出/訂閱的指令碼觸發器,甚至用Redis的釋出/訂閱功能來建立聊天系統!。
  • 需要精準設定過期時間:可以把有序集合(sorted set)的score值設定成過期時間的時間戳,那麼就可以簡單地通過過期時間排序,定時清除過期資料了,不僅是清除Redis中的過期資料,你完全可以把Redis裡這個過期時間當成是對資料庫中資料的索引,用Redis來找出哪些資料需要過期刪除,然後再精準地從資料庫中刪除相應的記錄。

Redis常見效能問題和解決方案:

  1. Master最好不要做任何持久化工作,如RDB記憶體快照和AOF日誌檔案。
  2. 如果資料比較重要,某個Slave開啟AOF備份資料,策略設定為每秒同步一次。
  3. 為了主從複製的速度和連線的穩定性,Master和Slave最好在同一個區域網內。
  4. 儘量避免在壓力很大的主庫上增加從庫。
  5. 主從複製不要用圖狀結構,用單向連結串列結構更為穩定,即:Master <- Slave1 <- Slave2 <- Slave3...這樣的結構方便解決單點故障問題,實現Slave對Master的替換。如果Master掛了,可以立刻啟用Slave1做Master,其他不變。

Redis的併發競爭問題如何解決:

Redis為單程序單執行緒模式,採用佇列模式將併發訪問變為序列訪問。Redis本身沒有鎖的概念,Redis對於多個客戶端連線並不存在競爭,但是在Jedis客戶端對Redis進行併發訪問時會發生連線超時、資料轉換錯誤、阻塞、客戶端關閉連線等問題,這些問題均是由於客戶端連線混亂造成。對此有2種解決方法:

  1. 客戶端角度,為保證每個客戶端間正常有序與Redis進行通訊,對連線進行池化,同時對客戶端讀寫Redis操作採用內部鎖synchronized
  2. 伺服器角度,利用setnx實現鎖
  3. 對於第一種,需要應用程式自己處理資源的同步,可以使用的方法比較通俗,可以使用synchronized也可以使用lock;第二種需要用到Redis的setnx命令,但是需要注意一些問題

redis事務的瞭解CAS // 放棄

Redis持久化的幾種方式:

  • 快照(snapshots):

預設情況情況下,Redis把資料快照存放在磁碟上的二進位制檔案中,檔名為dump.rdb。你可以配置Redis的持久化策略,例如資料集中每N秒鐘有超過M次更新,就將資料寫入磁碟;或者你可以手工呼叫命令SAVEBGSAVE

  • AOF:快照模式並不十分健壯,當系統停止,或者無意中Redis被kill掉,最後寫入Redis的資料就會丟失。這對某些應用也許不是大問題,但對於要求高可靠性的應用來說,Redis就不是一個合適的選擇。Append-only檔案模式是另一種選擇,你可以在配置檔案中開啟AOF模式
  • 虛擬記憶體方式
  • diskstore方式

Redis的快取失效策略和主鍵失效機制?

作為快取系統都要定期清理無效資料,就需要一個主鍵失效和淘汰策略。在Redis當中,有生存期的key被稱為volatile。在建立快取時,要為給定的key設定生存期,當key過期的時候(生存期為0),它可能會被刪除。

  • 影響KEY的生存時間:生存時間可以通過使用DEL命令來刪除整個key來移除,或者被SETGETSET命令覆蓋原來的資料;對一個key執行INCR命令,對一個List進行LPUSH命令,或者對一個雜湊表執行HSET命令,都不會修改key本身的生存時間;如果使用RENAME對一個key進行改名,那麼改名後的key的生存時間和改名前一樣。
  • 如何更新生存時間:可以對一個已經帶有生存時間的key執行EXPIRE命令,新指定的生存時間會取代舊的生存時間。過期時間的精度已經被控制在1ms之內,主鍵失效的時間複雜度是O(1);EXPIRE和TTL命令搭配使用,TTL可以檢視key的當前生存時間。設定成功返回1;當key不存在或者不能為key設定生存時間時,返回0。
  1. 最大快取配置:在redis中,允許使用者設定最大使用記憶體大小。即設定server.maxmemory引數值:預設為0,沒有指定最大快取,如果有新的資料新增,超過最大記憶體,則會使redis崩潰,所以一定要設定。redis記憶體資料集大小上升到一定大小的時候,就會實行資料淘汰策略。(有6種資料淘汰策略)
  2. 使用策略規則:如果資料呈現冪律分佈,也就是一部分資料訪問頻率高,一部分資料訪問頻率低,則使用allkeys-lru;如果資料呈現平等分佈,也就是所有的資料訪問頻率都相同,則使用allkeys-random。
  3. 三種資料淘汰策略:ttl和random比較容易理解,實現也會比較簡單。主要是lru最近最少使用淘汰策略,設計上會對key按失效時間排序,然後取最先失效的key進行淘汰。

快取穿透、快取雪崩的解決方案

我們在用快取的時候,不管是Redis或者Memcached,基本上會通用遇到以下三個問題:

  • 快取穿透

我們在專案中使用快取通常都是先檢查快取中是否存在,如果存在直接返回快取內容,如果不存在就直接查詢資料庫然後再快取查詢結果返回。這個時候如果我們查詢的某一個數據在快取中一直不存在,就會造成每一次請求都查詢DB,這樣快取就失去了意義,在流量大時,可能DB就掛掉了。解決方案:

方案1:封裝的快取SET和GET部分增加個步驟,如果查詢一個KEY不存在,就已這個KEY為字首設定一個標識KEY;以後再查詢該KEY的時候,先查詢標識KEY,如果標識KEY存在,就返回一個協定好的非false或者NULL值,然後APP做相應的處理,這樣快取層就不會被穿透。當然這個驗證KEY的失效時間不能太長。

方案2:如果一個查詢返回的資料為空(不管是資料不存在,還是系統故障),我們仍然把這個空結果進行快取,但它的過期時間會很短,一般只有幾分鐘。

方案3:採用布隆過濾器,將所有可能存在的資料雜湊到一個足夠大的bitmap中,一個一定不存在的資料會被這個bitmap攔截掉(未必,但是可以擋掉一些),從而避免了對底層儲存系統的查詢壓力。

  • 快取雪崩

快取雪崩是指當快取伺服器重啟或者大量快取集中在某一個時間段失效,瞬間所有的請求都被打到了資料庫,資料庫就崩了。解決方案:

方案1:在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。

方案2:不同的key,設定不同的過期時間,讓快取失效的時間點儘量均勻。

方案3:做二級快取,A1為原始快取,A2為拷貝快取,A1失效時,可以訪問A2,A1快取失效時間設定為短期,A2設定為長期(此點為補充)