詳談分佈式系統緩存的設計細節數據庫
在分佈式Web程序設計中,解決高併發以及內部解耦的關鍵技術離不開緩存和隊列,而緩存角色相似計算機硬件中CPU的各級緩存。現在的業務規模稍大的互聯網項目,即便在最初beta版的開發上,都會進行預留設計。可是在諸多應用場景裏,也帶來了某些高成本的技術問題,須要細緻權衡。緩存
服務端數據緩存服務器
一種區分網絡
緩存基於不一樣的條件有不少種劃分方式,本地緩存(Local cache)和分佈式緩存(Distributed cache)是一種常見分類,二者自身又包含不少細類。數據結構
本地並非指程序所在本地服務器(從嚴格概念來講),而是更細粒度的指位於程序自身的內部存儲空間,而分佈式更多強調的是存儲在進程以外的一個或者多個服務器上,彼此交互通訊,在具體軟件項目的設計和應用中,多數時候是混合一體。固然,我的認爲對緩存本質的理解纔是最重要的,至於概念上的分類只是一個不一樣理解下的劃分而已。架構
一些技術成本併發
在具體項目架構設計時,單純使用前者(本地緩存)的開發成本毋庸置疑是極低的,主要考慮的是本機的內存負載或者極少許的磁盤I/O影響。然後者的設計初心是爲了利於分佈式程序之間緩存數據的高效共享和管理,除了考慮緩存所在服務器自身的內存負載,設計時更須要充分考慮網絡I/O、CPU的負載,以及某些場景下的磁盤I/O的代價,同時還在具體設計時儘量規避和權衡總體穩定性和效率,這些不只僅只是做爲緩存服務器的硬件成本和技術維護。須要謹慎考慮的底層問題包括緩存間通訊、網絡負載和延遲等各類須要權衡的細節。運維
其實若是理解了緩存本質就該知道,任何存儲介質在適當的場景下均可以充當一個高效的緩存角色並進行項目集成和緩存間集羣。常見主流的Memcached和Redis等均是屬於後者範疇,甚至能夠包括如基於NoSQL設計的MongoDB這類文檔數據庫(但這是從角色角度講,而狹義劃分上這是基於磁盤的存儲庫,須要注意,各有專攻)。分佈式
這些第三方緩存在進行項目集成和緩存間集羣,也須要解決一些問題。甚至項目迭代到了後期階段,每每還須要具有較高專業知識的運維同時參與,而且在開發中的邏輯設計和代碼實現也會增長必定的工做量。因此有時候在具體項目的設計上,一方面要儘量預留,一方面還得根據實際狀況儘量精簡。高併發
額外說下其餘體會:在我的有限的技術學習和實踐裏,關於節點數據交互,尤爲是服務間通訊,是不存在完美的閉環的,理論上也都是在「當前階段」面向「高一致」的權衡罷了(大概跟生活是同樣的吧)。
緩存數據庫結構設計細節
因爲目前我的工做中大多數狀況應用的是Redis 3.x,如下如有特性關聯,均是以此做爲參照說明。
實例(Instance)
根據業務場景,公共數據和業務耦合數據,必定分別使用不一樣的實例。若是是單實例,才能夠考慮以DB劃分。當你使用的是Redis,那麼DB在Redis裏是有數據隔離,但沒有嚴格權限限制,因此劃庫只是一種選擇。在Cluster集羣裏則是保持默認單個庫,不過實際中我會嘗試根據項目大小來調整,至於在哪一個開發階段則是做爲預留設計。
額外須要注意的是,做爲重度依賴服務器內存的緩存產品,若是開啓了持久化(後面會提到),而且在爲併發量極大的服務提供支持時,服務器硬件資源會出現大量搶佔,請結合持久策略配置,考慮實例是否進行分盤存儲。
持久化本質是將內存數據同步寫入硬盤(刷盤),而磁盤I/O實在有限,被迫的寫入阻塞除了形成線程阻塞和服務超時,還會致使額外異常甚至波及其餘底層依賴服務。固然,個人建議是,若是條件容許,最好是在項目初期設計時就進行規劃並肯定。
緩存「表」(Table)
通常緩存中並無傳統RDBMS中直觀的表概念(每每以鍵值對「KV」形式存在),但從結構上來說,鍵值對自己就能夠組裝爲各類表結構。通常我會先生成數據庫表關係圖,而後分析何時存儲字符串,何時存儲對象,而後使用緩存鍵(KEY)進行表和字段(列)分割。
假定須要存儲一個登陸服務器表數據,包含字段(列):name、sign、addr,那麼能夠考慮將數據結構拆分爲如下形式:
{ key : "server:name" , value : "xxxx" }
{ key : "server:sign" , value : "yyyy" }
{ key : "server:addr" , value : "zzzz" }
須要注意的是,每每在分佈式緩存產品中,例如Redis,存在多種數據結構(如String、Hash等),還須要根據數據關聯性和列的數量,來選擇對應緩存的存儲數據結構,相關存儲空間和時間複雜度是徹底不一樣的,而這個在初期階段是很難感覺到的。
同時,就算緩存的內存設置的足夠大,剩餘也不少,也一樣須要考慮相似RDBMS中的單表容量問題,控制條目數量不能無限增加(好比預知到存儲條目能夠輕鬆達到百萬級),「分庫分表」的設計思路都是相通的。
緩存鍵(Key)
上面提到了基於緩存鍵來設計表,這裏再單獨說明一下鍵相關的我的規範。在鍵長度足夠簡短的前提下,若是關聯相同業務模塊,則必須設計爲以同一個標識(代號)開頭,目的是方便查找和統計管理。
如用戶登陸服務器列表:
{ key : "ul:server:a" , value : "xxxx" }
{ key : "ul:server:b" , value : "yyyy" }
另外,每一個獨立業務系統可考慮配置一個惟一的通用前綴標識。固然,這裏不是必需,若實際工做中,若是使用的是不一樣庫,則能夠忽略。
緩存值(value)
緩存中的值(這裏指單一條目)的大小沒有平均標準,但Size天然是越小越好(若使用的是Redis,一次操做的value較大會直接影響整個Redis的響應時間,不只僅是指網絡I/O)。若是存儲佔用空間直達10M+,建議考慮關聯的業務場景是否能夠拆分爲熱點和非熱點數據。
持久化(Permanence)
上面也簡單提了下,通常來講,持久和緩存自己是沒有直接關係的,能夠粗略想象爲一個面向硬盤一個面向內存。但現在的Web項目裏,有些業務場景是高度依賴緩存的,持久化能夠一方面幫助提升緩存服務重啓後的快速恢復,另外一方面提供特定場景下的存儲特性。固然,因爲持久化必然須要犧牲一些性能,包括CPU的搶佔和硬盤I/O影響。不過大多數時候是利大於弊,建議在應用緩存的時候,沒有特別狀況的話,儘可能搭配持久化,不管是使用自身機制仍是第三方來實現。
若是是使用的Redis,其自身就具有相關持久策略,包含AOF和RDB,我在大多數狀況下是二者同時配置的(固然,最新官方版本自己也提供了混合模式)。若是在一些非高併發的場景下,或者說在一些中小項目的管理模塊裏,僅僅只是做爲優化手段,肯定了不需持久,也能夠直接設置關閉,節約性能開銷損耗,但建議在程序中將該實例作好標註,確保該實例的公共使用範圍。
淘汰(Eliminate)
緩存若是無限制的增加,即便設置了較短的過時(Expiration ),在一些時間點上,高併發的一批大數據會在較短期內就達到了可以使用內存的峯頂,此時程序中與緩存服務器的交互會出現大量延遲和錯誤,甚至給服務器自身都帶來了嚴重的不穩定性。因此在生產環境裏儘可能給緩存配置最大內存限制,以及適當的淘汰策略。
若是使用的是Redis,自身淘汰策略選擇比較靈活。
我的的設計是,在數據呈現相似冪律分佈狀況下,總有大量數據訪問較低,我會選擇配置allkeys-lru、volatile-lru,將最少訪問的數據進行淘汰。再好比緩存是做爲日誌應用的,那麼我通常是項目前期是配置no-enviction,後期會配置爲volatile-ttl。
固然,我也見過一種特殊業務下的設計,緩存直接用來做爲輕量的持久數據庫使用,並且是終端,開始以爲有些新奇,後來發現是很是符合業務設計的(好比幾乎沒有任何複雜邏輯和強事務)。因此合情合理,確實不該該禁錮在傳統設計裏,畢竟架構老是基於業務去實時組合和改變的。