話說數據存儲演化史
單實例時代
上個世紀90年代,那時候網站還都是靜態爲主,動態的都不多,單實例數據庫完全可以輕鬆應付。
這樣的單實例架構會遇到如下瓶頸
- 數據量的總大小,一個機器放不下時
- 數據的索引(B+ Tree),一個機器的內存放不下時
- 訪問量(讀寫混合),一個實例不能承受
- ...
緩存+多實例+垂直拆分
隨着訪問量的上升,幾乎大部分使用MySQL架構的網站在數據庫上都開始出現了性能問題,Web程序不再僅僅專注在功能上,同時也在追求性能。程序員們開始大量的使用緩存技術來緩解數據庫的壓力,優化數據庫的結構和索引。
開始比較流行的是通過文件緩存來緩解數據庫壓力,但是當訪問量繼續增大的時候,多臺Web機器通過文件緩存不能共享,大量的小文件緩存也帶了了比較高的IO壓力。在這個時候,Memcached就自然的成爲一個非常時尚的技術產品。
多實例+主從讀寫分離
由於數據庫的寫入壓力增加,Memcached只能緩解數據庫的讀取壓力。
讀寫集中在一個數據庫上讓數據庫不堪重負,大部分網站開始使用主從複製技術來達到讀寫分離,以提高讀寫性能和讀庫的可擴展性。Mysql的Master-Slave模式成爲這個時候的網站標配了。
多實例集羣+分表分庫+水平拆分
在Memcached的高速緩存,MySQL的主從複製,讀寫分離的基礎之上,這時MySQL主庫的寫壓力開始出現瓶頸,而數據量的持續猛增,由於MyISAM使用表鎖,在高併發下會出現嚴重的鎖問題,大量的高併發MySQL應用開始使用InnoDB
引擎代替MyISAM
。
同時,開始流行使用分表分庫來緩解寫壓力和數據增長的擴展問題。這個時候,分表分庫成了一個熱門技術,是面試的熱門問題也是業界討論的熱門技術問題。也就在這個時候,MySQL推出了還不太穩定的表分區,這也給技術實力一般的公司帶來了希望。雖然MySQL推出了MySQL Cluster集羣,但性能也不能很好滿足互聯網的要求,只是在高可靠性上提供了非常大的保證。
擴展性瓶頸
MySQL數據庫也經常存儲一些大文本字段,導致數據庫表非常的大,在做數據庫恢復的時候就導致非常的慢,不容易快速恢復數據庫。比如1000萬4KB大小的文本就接近40GB的大小,如果能把這些數據從MySQL省去,MySQL將變得非常的小。
關係數據庫很強大,但是它並不能很好的應付所有的應用場景。MySQL的擴展性差(需要複雜的技術來實現),大數據下IO壓力大,表結構更改困難,正是當前使用MySQL的開發人員面臨的問題。
NoSQL的誕生
第三方平臺(如:Baidu,Weibo等)可以很容易的訪問和抓取數據。用戶的個人信息,社交網絡,地理位置,用戶生成的數據和用戶操作日誌已經成倍的增加。
我們如果要對這些用戶數據進行挖掘,那SQL數據庫已經不適合這些應用了, NoSQL數據庫的發展也卻能很好的處理這些大的數據。
NoSQL
什麼是NoSQL
NoSQL(NoSQL = Not Only SQL),意即“不僅僅是SQL”
泛指非關係型的數據庫。隨着互聯網Web2.0網站的興起,傳統的關係數據庫在應付Web2.0網站,特別是超大規模和高併發的SNS類型的Web2.0純動態網站已經顯得力不從心,暴露了很多難以克服的問題,而非關係型的數據庫則由於其本身的特點得到了非常迅速的發展。
NoSQL數據庫的產生就是爲了解決大規模數據集合多重數據種類帶來的挑戰,尤其是大數據應用難題,包括超大規模數據的存儲。
百度或Weibo每天爲他們的用戶收集萬億比特的數據,這些類型的數據存儲不需要固定的模式,無需多餘操作就可以橫向擴展。
易擴展性
NoSQL數據庫種類繁多,但是一個共同的特點都是去掉關係數據庫的關係型特性。
數據之間無關係,這樣就非常容易擴展。也無形之間,在架構的層面上帶來了可擴展的能力。
大數據高性能
NoSQL數據庫都具有非常高的讀寫性能,尤其在大數據量下,同樣表現優秀。這得益於它的無關係性,數據庫的結構簡單。
MySQL一般使用Query Cache,每次表的更新Cache就失效,是一種大粒度的Cache,在針對Web2.0的交互頻繁的應用,Cache性能不高。
NoSQL的Cache是記錄級的,是一種細粒度的Cache,所以NoSQL在這個層面上來說就要性能高很多了。
數據模型靈活多樣
NoSQL無需事先爲要存儲的數據建立字段,隨時可以存儲自定義的數據格式。
在關係數據庫裏,增刪字段是一件非常麻煩的事情。如果是非常大數據量的表,增加字段簡直就是一個噩夢。
關係型數據庫和非關係型數據庫對比
關係型數據庫(RDBMS)
- 高度組織化結構化數據
- 結構化查詢語言(SQL)
- 數據和關係都存儲在單獨的表中
- 數據操縱語言,數據定義語言
- 嚴格的一致性
- 基礎事務
非關係型數據庫(NoSQL)
- 代表着不僅僅是SQL
- 沒有聲明性查詢語言
- 沒有預定義的模式
- 鍵 - 值對存儲,列存儲,文檔存儲,圖形數據庫
- 最終一致性,而非ACID屬性
- 非結構化和不可預知的數據
- CAP定理
- 高性能,高可用性和可伸縮性
互聯網應用三V三高
3V指代
- 海量(Volume)
- 多樣(Variety)
- 實時(Velocity)
三高指代
- 高併發
- 高可擴
- 高性能
四大分類
主要分爲下面四大類
- 鍵值(Key-Value)存儲數據庫
- 文檔型數據庫
- 列存儲數據庫
- 圖形(Graph)數據庫
分類 | Examples舉例 | 典型應用場景 | 數據模型 | 優點 | 缺點 |
---|---|---|---|---|---|
鍵值(key-value) | Tokyo Cabinet/Tyrant,Redis,Voldemort,Oracle BDB | 內容緩存,主要用於處理大量數據的高訪問負載,也用於一些日誌系統等等。 | Key指向Value的鍵值對,通常用hashtable來實現 | 查找速度快 | 數據無結構化,通常只被當作字符串或者二進制數據 |
列存儲數據庫 | Cassandra,HBase,Riak | 分佈式的文件系統 | 以列簇式存儲,將同一列數據存在一起 | 查找速度快,可擴展性強,更容易進行分佈式擴展 | 功能相對侷限 |
文檔型數據庫 | CouchDB,MongoDb | Web應用(與Key-Value類似,Value是結構化的,不同的是數據庫能夠了解Value的內容) | Key-Value對應的鍵值對,Value爲結構化數據 | 數據結構要求不嚴格,表結構可變,不需要像關係型數據庫一樣需要預先定義表結構 | 查詢性能不高,而且缺乏統一的查詢語法。 |
圖形(Graph)數據庫 | Neo4J,InfoGrid,Infinite Graph | 社交網絡,推薦系統等。專注於構建關係圖譜 | 圖結構 | 利用圖結構相關算法。比如最短路徑尋址,N度關係查找等 | 很多時候需要對整個圖做計算才能得出需要的信息,而且這種結構不太好做分佈式的集羣方案。 |
數據庫理論
ACID概念
- A (Atomicity) 原子性
- C (Consistency) 一致性
- I (Isolation) 獨立性
- D (Durability) 持久性
CAP概念
- C (Consistency) 強一致性
- A (Availability) 可用性
- P (Partition Tolerance) 分區容錯性
CAP理論
一個分佈式系統不可能同時很好的滿足一致性,可用性和分區容錯性這三個需求,最多隻能同時較好的滿足兩個。
因此,根據CAP原理將NoSQL數據庫分成了滿足CA原則、滿足CP原則和滿足AP原則三大類:
- CA - 單點集羣,滿足一致性,可用性的系統,通常在可擴展性上不太強大。
- CP - 滿足一致性,分區容忍性的系統,通常性能不是特別高。
- AP - 滿足可用性,分區容忍性的系統,通常可能對一致性要求低一些。
BASE理論
BASE就是爲了解決關係數據庫強一致性引起的問題而引起的可用性降低而提出的解決方案。
- 基本可用 (Basically Available)
- 軟狀態 (Soft state)
- 最終一致 (Eventually Consistent)
它的思想是通過讓系統放鬆對某一時刻數據一致性的要求來換取系統整體伸縮性和性能上改觀。
爲什麼這麼說呢,緣由就在於大型系統往往由於地域分佈和極高性能的要求,不可能採用分佈式事務來完成這些指標,要想獲得這些指標,我們必須採用另外一種方式來完成,這裏BASE就是解決這個問題的辦法。
Redis
什麼是Redis
Redis全稱Remote Dictionary Server(遠程字典服務),它是一個基於內存實現的鍵值型非關係(NoSQL)數據庫,由意大利人Salvatore Sanfilippo使用C語言編寫。
Redis與其他Key-Value緩存產品有以下三個特點
- Redis支持數據的持久化,可以將內存中的數據保持在磁盤中,重啓的時候可以再次加載進行使用
- Redis不僅僅支持簡單的key-value類型的數據,同時還提供list,set,zset,hash等數據結構的存儲
- Redis支持數據的備份,即master-slave模式的數據備份
使用場景
- 內存存儲和持久化:redis支持異步將內存中的數據寫到硬盤上,同時不影響繼續服務
- 取最新N個數據的操作,如:可以將最新的10條評論的ID放在Redis的List集合裏面
- 模擬類似於HttpSession這種需要設定過期時間的功能
- 發佈、訂閱消息系統
- 定時器、計數器
基礎知識
單進程
Redis使用單進程模型來處理客戶端的請求。對讀寫等事件的響應。是通過對epoll函數的包裝來做到的。Redis的實際處理速度完全依靠主進程的執行效率。
Epoll是Linux內核爲處理大批量文件描述符而作了改進的epoll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程序在大量併發連接中只有少量活躍的情況下的系統CPU利用率。
默認數據庫
Redis默認有16個數據庫,類似數組下表從零開始,初始默認使用零號庫。
常用命令
- Select,命令可以切換不同的數據庫
- Dbsize,查看當前數據庫的key的數量
- Flushdb,清空當前庫
- Flushall,通殺全部庫
密碼管理
Redis的16個庫都是同樣密碼,要麼都OK要麼一個也連接不上。
索引起始
Redis索引都是從零開始的。
默認端口
- 6379
數據類型
String(字符串)
String是redis最基本的類型,你可以理解成與Memcached一模一樣的類型,一個Key對應一個Value。
String類型是二進制安全的。意思是Redis的String可以包含任何數據。比如Jpg圖片或者序列化的對象。
String類型是Redis最基本的數據類型,一個Redis中字符串Value最多可以是512M
Hash(哈希)
Redis Hash是一個鍵值對集合。
Redis Hash是一個String類型的Field和Value的映射表,Hash特別適合用於存儲對象。
類似Java裏面的Map<String,Object>
List(列表)
Redis列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素導列表的頭部(左邊)或者尾部(右邊)。
它的底層實際是個鏈表。
Set(集合)
Redis的Set是String類型的無序集合。它是通過HashTable實現實現的。
Zset(有序集合)
Redis的Zset(Sorted Set)和Set一樣也是String類型元素的集合,且不允許重複的成員。
不同的是每個元素都會關聯一個Double類型的分數。
Redis正是通過分數來爲集合中的成員進行從小到大的排序。Zset的成員是唯一的,但分數(Score)卻可以重複。
持久化(RDB)
什麼是RDB
Redis Database(RDB)是在指定的時間間隔內將內存中的數據集快照寫入磁盤,也就是行話講的Snapshot快照,它恢復時是將快照文件直接讀到內存裏。
Redis會單獨創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,再用這個臨時文件替換上次持久化好的文件。
整個過程中,主進程是不進行任何IO操作的,這就確保了極高的性能如果需要進行大規模數據的恢復,且對於數據恢復的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺點是最後一次持久化後的數據可能丟失。
Fork的作用是複製一個與當前進程一樣的進程。新進程的所有數據(變量、環境變量、程序計數器等)數值都和原進程一致,但是是一個全新的進程,並作爲原進程的子進程。
如何操作
保存數據到快照:
Rdb保存的是dump.rdb
文件,使用save
或者bgsave
命令即可觸發RDB快照,
Save
,Save時只管保存,其它不管,全部阻塞Bgsave
,Redis會在後臺異步進行快照操作,快照同時還可以響應客戶端請求Lastsave
,獲取最後一次成功執行快照的時間
恢復快照數據
將備份文件(dump.rdb
)移動到Redis安裝目錄並啓動服務即可。
優劣
優勢
- 適合大規模的數據恢復
- 對數據完整性和一致性要求不高
劣勢
- 在一定間隔時間做一次備份,所以如果Redis意外Down掉的話,就會丟失最後一次快照後的所有修改
- Fork的時候,內存中的數據被克隆了一份,大致2倍的膨脹性需要考慮
持久化(AOF)
什麼是AOF
AOF全稱是Append Only File
是指以日誌的形式來記錄每個寫操作,將Redis執行過的所有寫指令記錄下來(讀操作不記錄),只許追加文件但不可以改寫文件,Redis啓動之初會讀取該文件重新構建數據,換言之,Redis重啓的話就根據日誌文件的內容將寫指令從前到後執行一次以完成數據的恢復工作。
如何操作
AOF保存的是appendonly.aof
文件,使用前修改默認的appendonly no
,改爲yes
,恢復時重啓redis便會自動加載。
優劣
優勢
- 修改同步:
Appendfsync Always
同步持久化 每次發生數據變更會被立即記錄到磁盤 性能較差但數據完整性比較好 - 每秒同步:
Appendfsync EverySec
異步操作,每秒記錄如果一秒內宕機,有數據丟失
劣勢
- 相同數據集的數據而言AOF文件要遠大於RDB文件,恢復速度慢於RDB
- AOF運行效率要慢於RDB,每秒同步策略效率較好,不同步效率和RDB相同
事務
- 開啓:以
MULTI
開始一個事務 - 入隊:將多個命令入隊到事務中,接到這些命令並不會立即執行,而是放到等待執行的事務隊列裏面
- 執行:由
EXEC
命令觸發事務 - 放棄:
discard
- 監控:
Watch
指令,類似樂觀鎖,事務提交時,如果Key的值已被別的客戶端改變,比如某個list已被別的客戶端push/pop過了,整個事務隊列都不會被執行。通過WATCH命令在事務執行之前監控了多個Keys,倘若在WATCH之後有任何Key的值發生了變化,EXEC命令執行的事務都將被放棄,同時返回Nullmulti-bulk應答以通知調用者事務執行失敗。
主從複製
什麼是主從複製
主從複製,主機數據更新後根據配置和策略,自動同步到備機的master/slaver機制,Master以寫爲主,Slave以讀爲主。
複製操作
Slave啓動成功連接到Master後會發送一個Sync命令
Master接到命令啓動後臺的存盤進程,同時收集所有接收到的用於修改數據集命令,在後臺進程執行完畢之後,Master將傳送整個數據文件到Slave,以完成一次完全同步。
複製模式
- 全量複製:而Slave服務在接收到數據庫文件數據後,將其存盤並加載到內存中
- 增量複製:Master繼續將新的所有收集到的修改命令依次傳給Slave,完成同步
只要是重新連接Master,一次完全同步(全量複製)將被自動執行
特點
一主二僕
一個Master兩個Slave,主機複製寫,備機負責讀。
薪火相傳
上一個Slave可以是下一個slave的Master,Slave同樣可以接收其他slaves的連接和同步請求,那麼該slave作爲了鏈條中下一個的master,可以有效減輕master的寫壓力。
反客爲主
使當前數據庫停止與其他數據庫的同步,轉成主數據庫。
管道
通常使用Redis的方式是,發送命令,命令排隊,Redis執行,然後返回結果,這個過程稱爲Round Triptime(簡稱RTT,往返時間)。但是如果有多條命令需要執行時,需要消耗N次RTT,經過N次IO傳輸,這樣效率明顯很低。
於是Redis管道(Pipeline)便產生了,一次請求/響應服務器能實現處理新的請求即使舊的請求還未被響應。這樣就可以將多個命令發送到服務器,而不用等待回覆,最後在一個步驟中讀取該答覆。這就是管道(Pipelining),減少了RTT,提升了效率。
使用管道發送命令時,服務器將被迫回覆一個隊列答覆,佔用很多內存。所以,如果你需要發送大量的命令,最好是把他們按照合理數量分批次的處理,例如10K的命令,讀回覆,然後再發送另一個10k的命令,等等。這樣速度幾乎是相同的,但是在回覆這10k命令隊列需要非常大量的內存用來組織返回數據內容。
發佈訂閱
發佈訂閱是一種消息模式,發送者(Sub)發送消息,訂閱者(Pub)接收消息。
發佈訂閱基於頻道實現的,同一個頻道可以有多個訂閱者,多個發佈者。其中任意一個發佈者發佈消息到頻道中,所以訂閱者都可以收到該消息。