redis實戰筆記(6)-第6章 使用 Redis構建應用程序組件

本章主要內容
 
1.構建兩個前綴匹配自 動補全程序
2.經過構建分佈式鎖來提升性能
3.經過開發計數信號量來控制併發
4.構建兩個不一樣用途的任務隊列
5.經過消息拉取系統來實現延遲消息傳遞
6.學習如何進行文件分發
 
本章首先會構建
兩個自 動補全函數, 它們能夠分別用於在較短或較長的聯繫人列表中快速找到指定的用戶。
接着本章會花一些時間仔細地介紹如何實現兩種不一樣類型的鎖, 這些鎖能夠用來減小數據衝突、 提高
性能、 防止數據出錯並減小沒必要要的工做。
以後, 本章將會使用剛剛介紹過的鎖來構建一個能夠在指定時間執行任務的延遲任務隊列, 並在這個延遲任務隊列的基礎上構建兩個不一樣的消息系統, 以此來提供點對消息服務以及廣播消息服務。 最後, 本章將重用以前在第5章中開發的IP所屬地查詢程序, 並將它應用在由Redis存儲和分發的數百萬日 志條目上面。
 
 

6.1 自動補全
 
6.1.1 自 動補全最近聯繫人
 
由於Redis的 列表會以有序的方式來存儲元素, 而且和Redis提供的其餘結構相比, 列表佔用的內存是最少的, 因此咱們選擇使用列表來存儲用戶的聯繫人信息
 
構建最近聯繫人自 動補全列表一般須要對Redis執行3個操做。
第一個操做
就是添加或者更新一個聯繫人, 讓他成爲最新的被聯繫用戶, 這個操做包含如下3個步驟。
( 1) 若是指定的聯繫人已經存在於最近聯繫人列表裏面, 那麼從列
表裏面移除他。
( 2) 將指定的聯繫人添加到最近聯繫人列表的最前面。
( 3) 若是在添加操做完成以後, 最近聯繫人列表包含的聯繫人數量
超過了 100個, 那麼對列表進行修剪, 只保留位於列表前面的100個聯繫人。以上描述的3個操做能夠經過依次執行LREM命令、 LPUSH命令和LTRIM命令來實現, 而且爲了確保操做不會帶有任何競爭條件, 咱們會像在第3章中介紹的那樣, 使用由MULTI命令和EXEC命令構成的事務包裹起LREM、 LPUSH和LTRIM這3個命令
 
第二個操做
就是在用戶不想再看見某個聯繫人的時候, 將指定的聯繫人從聯繫人列表裏面移除掉,這個操做能夠經過如下這個LREM調用來完成:
 
最後一個操做
就是獲取自 動補全列表並查找匹配的用戶。 由於實際的自 動補全處理是在Python裏面完成的, 因此操做須要首先獲取整個列表結構, 而後再在Python裏面處理它, 正如代碼清單6-2所示

 

由於咱們已經預先考慮到了 「 從列表裏面移除一個元素所需的時間與列表長度成正比」這個問題, 並明確地限制最近聯繫人列表最多隻能存儲100個聯繫人, 因此本節給出的自 動補全實現能夠運行得很是好, 而且速度也足夠快,
但它並不適合用來處理很是大的列表。 若是你須要一個可以存儲更多元素的最常使用列表( most-recently-used list) 或者最少使用列表( least-recently-used list) , 那麼能夠考慮使用 帶有時間戳的有序集合來代替本節介紹的最近聯繫人列表。
 
 
6.1.2 通信錄自動補全
對於比較短的列表來講, 這種作法還算可行, 但對於很是長的列表來講, 僅僅爲了找到幾個元素而獲取成千上萬個元素, 是一種很是浪費資源的作法。 所以, 爲了對包含很是多元素的列表進行自 動補全, 咱們必須直接在Redis內部完成查找匹配元素的工做。
 
爲了在客戶端進行自 動補全的時候, 儘可能 減小服務器須要傳輸給客戶端的數據量, 咱們將使用有序集合來直接在Redis內部完成自 動補全的前綴計算工做。
 
在大多數狀況下, 咱們使用有序集合是爲了 快速地判斷某個元素是否存在於有序集合裏面、 查看某個成員在有序集合中的位置或索引 , 以及從有序集合的某個地方快速地按範圍取出多個元素。
 
而這一次, 咱們將把有序集合裏面的 全部分值都設置爲0——這種作法使得咱們可使用有序集合的另外一個特性: 當全部成員的分值都相同時, 有序集合將根據成員的名字來進行排序; 而當全部成員的分值都是0的時候, 成員將按照字符串的二進制順序進行排序
 
爲了執行自 動補全操做, 程序會以小寫字母的方式插入聯繫人的名字, 而且爲了方便起見, 程序規定用戶的名字只能包含英文字母, 這樣的話就不須要考慮如何處理數字或者符號了。
 
(1)
那麼咱們該如何實現這個自 動補全功能呢? 首先, 若是咱們將用戶的名字看做是相似abc, abca, abcd, …, abd這樣的有序字符串序列, 那麼查找帶有abc前綴的單詞實際上就是查找介於abbz. . . 以後和abd以前的字符串。 若是咱們知道第一個排在abbz以前的元素的排名以及第一個排在abd以後的元素的排名, 那麼就能夠用一個ZRANGE調用來取得全部介於abbz. . . 和abd之間的元素, 而問題就在於咱們並不知道那兩個元素的具體排名。 爲了解決這個問題, 咱們須要向有序集合分別插入兩個元素, 一個元素排在abbz. . . 的後面, 而另外一個元素則排在abd的前面, 接着根據這兩個元素的排名來調用 ZRANGE命令, 最後移除被插入的兩個元素。
 
由於在ASCII編碼裏面, 排在z後面的第一個字符就是左花括號 {, 因此咱們只要將{拼接到abc前綴的末尾, 就能夠得出元素abc{, 這個元素既位於abd以前, 又位於全部帶有abc前綴的合法名字以後。 一樣的, 只要將{追加到abb的末尾, 就能夠得出元素abb{, 這個元素位於全部帶有abc前綴的合法名字以前, 能夠在按範圍查找全部帶有abc前綴的名字時, 將其用做起始元素。 另外一方面, 由於在ASCII編碼裏面,第一個排在a前面的字符就是反引 號`, 因此若是咱們要查找的是帶有aba前綴的名字, 而不是帶有abc前綴的名字, 那麼可使用ab` 做爲範圍查找的起始元素, 並將aba{用做範圍查找的結束元素。綜上所述, 經過將給定前綴的最後一個字符替換爲第一個排在該字符前面的字符, 能夠獲得前綴的前驅( predecessor) , 而經過給前綴的末尾拼接上左花括號, 能夠獲得前綴的後繼( successor) 。 爲了防止多個前綴搜索同時進行時出現任何問題, 程序還會給前綴拼接一個左花括號, 以便在有須要的時候, 根據這個左花括號來過濾掉被插入有序集合裏面的起始元素和結束元素。
 
(2)
字符集與國際化 對於只使用a~z字符的語言來講, 這個在ASCII編碼裏面查找前一個字符和後一個字符的方法能夠運做得很是好, 但若是你要處理的字符並不只僅限於a~z範圍, 那麼你還須要解決其餘幾個問題。
 
首先, 你須要想辦法把全部字符都轉換爲字節, 常見的作法是使用 UTF-八、 UTF-16或者UTF-32字符編碼( UTF-16和UTF-32有大端版本和小端版本可用, 但只有大端版本能夠在咱們所處的狀況下運做) 。
其次, 你須要找出自 己想要支持的字符範圍, 並確保你的字符編碼在你所選範圍的前面和後面都至少留有一個字符。
最後, 你須要使用位於範圍前面和後面的字符來分別代替前面例子中的反引 號` 和左花括號{。
 
好在咱們的算法只關心編碼而不是字符在底層的排列順序, 因此不管你使用的是UTF-8, 仍是大端或者小端的UTF-1六、 UTF-32, 你均可以 使用空字節( null) 來代替反引 號, 並使用你的編碼和語言支持的最大值來代替左花括號。 (某些語言的綁定數量是比較有限的, 它們在UTF-16上面最大隻能支持Unicode碼點U+ffff, 在UTF-32上面最大隻能支持Unicode碼點U+2ffff。 )
 
在確認了須要查找的範圍以後, 程序會將起始元素和結束元素插入有序集合裏面, 而後查看兩個被插入元素的排名, 並從它們之間取出一些元素, 最後再從有序集合裏面移除這兩個元素(爲了不滋擾用戶,程序最多隻會取出10個元素) 。 爲了防止自 動補全程序在多個公會成員同時向同一個公會成員發送消息的時候, 將多個相同的起始元素和結束元素重複地添加到有序集合裏面, 或者錯誤地從有序集合裏面移除了由其餘自 動補全程序添加的起始元素和結束元素, 自 動補全程序會將一個隨機生成的128位全局惟一標識符( UUID) 添加到起始元素和結束元素的後面。 另外自 動補全程序還會在插入起始元素和結束元素以後, 經過使用WATCH、 MULTI和EXEC來確保有序集合不會在進行範圍查找和範圍取值期間發生變化。
 
經過向有序集合添加元素來建立查找範圍, 並在取得範圍內的元素以後移除以前添加的元素, 這是一種很是有用的技術。 雖然本章只將這種技術用在了實現自 動補全上面, 可是這種技術一樣能夠應用在任何已排序索引 ( sorted index) 上面。 第7章中將會介紹一種可以改善這類操做的技術, 這種技術可以應用於幾種不一樣類型的範圍查詢, 而且不須要過添加元素來建立範圍。 之因此把這個改善後的方法留到以後才介紹, 是由於它只可以應用於某些類型的數據, 而本章介紹的方法則能夠對任意類型的數據進行範圍查詢。
 
咱們須要謹慎地處理其餘正在執行的自 動補全操做, 這也是程序裏面用到了 WATCH命令的緣由。 可是隨着負載的增長, 程序進行重試的次數可能會愈來愈多, 致使資源被白白浪費。 接下來的一節將介紹如何經過使用鎖來減小對WATCH命令的使用, 甚至使用鎖來代替WATCH命令, 從而達到避免重試、 提高性能並在某些狀況下簡化代碼的效果。
 

6.2 分佈式鎖
Redis使用WATCH命令來代替對數據進行加鎖, 由於WATCH只會在數據被其餘客戶端搶先修改了的狀況下通知執行了這個命令的客戶端, 而不會阻止其餘客戶端對數據進行修改, 因此這個命令被稱爲 樂觀鎖
 
分佈式鎖也有相似的「首先獲取鎖, 而後執行操做, 最後釋放鎖」動做, 但這種鎖既不是給同一個進程中的多個線程使用, 也不是給同一臺機器上的多個進程使用, 而是由不一樣機器上的不一樣Redis客戶端進行獲取和釋放的。
 
雖然Redis提供的 SETNX命令確實具備基本的加鎖功能, 但它的功能並不完整, 而且也不具有分佈式鎖常見的一些高級特性, 因此咱們仍是須要自 己動手來構建分佈式鎖
 
這一節將會說明「爲何使用WATCH命令來監視被頻繁訪問的鍵可能會引 起性能問題」, 還會展現構建一個鎖的詳細步驟, 並最終在某些狀況下使用鎖去代替WATCH命令。
 
 
6.2.1 鎖的重要性
 
如今來回顧一下商品的購物過程。 當玩家在市場上購買商品的時候, 程序首先須要使用 WATCH去監視市場以及買家的我的信息散列, 在得知買家現有的錢數以及商品的售價以後, 程序會驗證買家是否有足夠的錢來購買指定的商品: 若是買家有足夠的錢, 那麼程序會將買家支付的錢轉移給賣家, 接着將商品添加到買家的包裹裏面, 並從市場裏面移除已被售出的商品; 相反地, 若是買家沒有足夠的錢來購買商品, 那麼程序就會取消事務。 在執行購買操做的過程當中, 若是有其餘玩家對市場進行了改動, 或者由於記錄買家我的信息的散列出現了變化而引 發了WATCH錯誤, 那麼程序將從新執行購買操做。
 
爲了展現鎖對於性能擴展的必要性, 咱們會模擬市場在3種不一樣負載狀況下的性能表現, 這3種狀況分別是1個玩家出售商品, 另1個玩家購買商品; 5個玩家出售商品, 另1個玩家購買商品; 以及5個玩家出售商品, 另外5個玩家購買商品。 表6-1展現了模擬的結果。

根據表6-1的模擬結果顯示, 隨着負載不斷增長, 系統完成一次交易所需的重試次數從最初的3次上升到了 250次, 與此同時, 完成一次交易所需的等待時間也從最初的少於10 ms上升到了 500 ms。 這個模擬示例完美地展現了爲何WATCH、 MULTI和EXEC組成的事務並不具備可擴性, 緣由在於程序在嘗試完成一個事務的時候, 可能會由於事務執行失敗而反覆地進行重試。 保證數據的正確性是一件很是重要的事情, 但用WATCH命令的作法並不完美。 爲了解決這個問題, 並以可擴展的方式來處理市場交易, 咱們將使用鎖來保證市場在任一時刻只能上架或者銷售一件商品。
 
6.2.2 簡易鎖
本書接下來將向讀者介紹第1版的鎖實現, 這個鎖很是簡單, 而且在一些狀況下可能會沒法正常運做。
 
下面列出了一些致使鎖出現不正確行爲的緣由, 以及鎖在不正確運行時的症狀。
 
1.持有鎖的 進程由於操做時間過長而致使鎖被自 動釋放, 但進程自己並不知曉這一點, 甚至還可能會錯誤地釋放掉了其餘進程持有的
鎖。
2.一個持有鎖並打算執行長時間操做的進程已經崩潰, 但其餘想要獲取鎖的進程不知道哪一個進程持有着鎖, 也沒法檢測出持有鎖的進程已經崩潰, 只能白白地浪費時間等待鎖被釋放。
3.在一個進程持有的鎖過時以後, 其餘多個進程同時嘗試去獲取鎖,
而且都得到了鎖。
 
上面提到的第一種狀況和第三種狀況同時出現, 致使有多個進程得到了鎖, 而每一個進程都覺得自 己是惟一一個得到鎖的進程。
 
由於Redis在最新的硬件上能夠每秒執行 100 000個操做, 而在高端的硬件上甚至能夠每秒執行將近225 000個操做, 因此儘管上面提到的題出現的概率只有萬分之一, 但這些問題在高負載的狀況下仍是有可能會出現②, 所以, 讓鎖正確地運做起來仍然是一件至關重要的事情。
 
 
6.2.3 使用 Redis構建鎖
本節接下來要介紹的是鎖實現的第1個版本, 這個版本的鎖要作的事就是 正確地實現基本的加鎖功能, 而以後的一節將會介紹如何處理過時的鎖以及由於持有者崩潰而沒法釋放的鎖。
 
(1)
程序首先要作的就是獲取鎖。 SETNX命令天生就適合用來實現鎖的獲取功能, 這個命令只會在鍵不存在的狀況下爲鍵設置值, 而鎖要作的就是將一個隨機生成的128位UUID設置爲鍵的值, 並使用這個值來防止鎖被其餘進程取得。若是程序在嘗試獲取鎖的時候失敗, 那麼它將不斷地進行重試, 直到成功地取得鎖或者超過給定的時限爲止, 正如代碼清單6-8所示。

acquire_lock()函數的行爲和前面描述的同樣: 它會使用SETNX命令, 嘗試在表明鎖的鍵不存在的狀況下, 爲鍵設置一個值, 以此來獲取鎖; 在獲取鎖失敗的時候, 函數會在給定的時限內進行重試, 直到成功獲取鎖或者超過給定的時限爲止(默認的重試時限爲10秒) 。
 
(2)
在實現了鎖以後, 咱們就可使用鎖來代替針對市場的WATCH操做了。 代碼清單6-9展現了使用鎖從新實現的商品購買操做: 程序首先對市場進行加鎖, 接着檢查商品的價格, 並在確保買家有足夠的錢來購買商品以後, 對錢和商品進行相應的轉移。 當操做執行完畢以後, 程序就會釋放鎖。

 

(3)
由於在程序持有鎖期間, 其餘客戶端可能會擅自 對鎖進行修改, 因此鎖的釋放操做須要和加鎖操做同樣當心謹慎地進行。 代碼清單6-10中的release_lock()函數展現了鎖釋放操做的實現代碼: 函數首先使用WATCH命令監視表明鎖的鍵, 接着檢查鍵目 前的值是否和加鎖時設置的值相同, 並在確認值沒有變化以後刪除該鍵(這個檢查還能夠防止程序錯誤地釋放同一個鎖屢次) 。
在使用鎖代替WATCH從新實現商品購買操做以後, 咱們能夠再次進行以前的商品買賣模擬操做: 表6-2中的單數行展現了WATCH實現的模擬結果, 而表中的複數行則展現了在與前一行條件相同的狀況下, 鎖實現的模擬結果。

與以前的WATCH實現相比, 鎖實現的上架商品數量雖然有所減小,可是在買入商品時 卻不須要進行重試, 而且上架商品數量和買入商品數量之間的比率, 也跟賣家數量和買家數量之間的比率接近。 目 前來講,不一樣上架和買入進程之間的競爭限制了商品買賣操做性能的進一步提高, 而接下來介紹的細粒度鎖將解決這個問題。
 
6.2.4 細粒度鎖
在前面介紹鎖實現以及加鎖操做的時候, 咱們考慮的是如何實現與WATCH命令粒度相同的鎖——這種鎖能夠把整個市場都鎖住。 由於咱們是自 己動手來構建鎖實現, 而且咱們關心的不是整個市場, 而是市場裏面的某件商品是否存在, 因此咱們實際上能夠將加鎖的粒度變得更細一些。 經過只鎖住被買賣的商品而不是整個市場, 能夠減小鎖競爭出現的概率並提高程序的性能。
表6-3展現了使用只對單個商品進行加鎖的鎖實現以後, 進行與表6-2所示相同的模擬時的結果。

表6-3中的模擬結果顯示, 在使用細粒度鎖的狀況下, 不管有多少個上架進程和買入進程在運行, 程序總能在60秒內完成220 000~230 000次的上架和買入操做, 而且不會引 發任何重試操做。
 
在高負載狀況下, 使用鎖能夠減小重試次數、 下降延遲時間、 提高性能並將加鎖的粒度調整至合適的大小。
 
6.2.5 帶有超時限制特性的鎖
爲了給鎖加上超時限制特性, 程序將在取得鎖以後, 調用 EXPIRE命令來爲鎖設置過時時間, 使得Redis能夠自 動刪除超時的鎖。 爲了確保鎖在客戶端已經崩潰(客戶端在執行介於SETNX和EXPIRE之間的時候崩潰是最糟糕的) 的狀況下仍然可以自 動被釋放, 客戶端會在嘗試獲取鎖失敗以後, 檢查鎖的超時時間, 併爲未設置超時時間的鎖設置超時時間。 所以鎖總會帶有超時時間, 並最終由於超時而自 動被釋放, 使得其餘客戶端能夠繼續嘗試獲取已被釋放的鎖。
 
須要注意的一點是, 由於多個客戶端在同一時間內設置的超時時間基本上都是相同的, 因此 即便有多個客戶端同時爲同一個鎖設置超時時間, 鎖的超時時間也不會產生太大變化。

 

新的acquire_lock_with_timeout()函數給鎖增長了超時限制特性, 這一特性確保了鎖總會在有須要的時候被釋放, 而不會被某個客戶
端一直把持着。 更棒的是, 這個新的加鎖函數能夠和以前寫好的鎖釋放函數一塊兒使用, 咱們不須要另外再寫新的鎖釋放函數。

在其餘數據庫裏面, 加鎖一般是一個自 動執行的基本操做。 而Redis的WATCH、 MULTI和EXEC, 就像以前所說的那樣, 只是一個樂觀鎖——這種鎖只會在數據被其餘客戶端搶先修改了的狀況下, 通知加鎖的客戶端, 讓它撤銷對數據的修改, 而不會真正地把數據鎖住。 經過在客戶端上面實現一個真正的鎖, 程序能夠爲用戶帶來更好的性能、 更熟悉的編程概念、 更簡單易用的API, 等等。 可是與此同時, 也請記住Redis並不會主動使用這個自 制的鎖, 咱們必須自 己使用這個鎖來代替WATCH, 或者同時使用鎖和WATCH協同進行工做, 從而保證數據的正確與一致。
 

6.3 計數信號量
計數信號量是一種鎖, 它可讓用戶限制一項資源最多可以同時被多少個進程訪問, 一般用於限定可以同時使用的資源數量。 你能夠把咱們在前一節建立的鎖看做是隻能被一個進程訪問的信號量。
 
 

6.4 任務隊列
 
在處理Web客戶端發送的命令請求時, 某些操做的執行時間可能會比咱們預期的更長一些。 經過將待執行任務的相關信息放入隊列裏面,並在以後對隊列進行處理, 用戶能夠推遲執行那些須要一段時間才能完成的操做, 這種將工做交給任務處理器來執行的作法被稱爲任務隊列( task queue) 。 如今有不少專門的任務隊列軟件(如ActiveMQ、RabbitMQ、 Gearman、 Amazon SQS, 等等) , 另外在缺乏專門的任務隊列可用的狀況下, 也有一些臨時性的方法能夠建立任務隊列。 比方說使用按期做業來掃描一個數據表, 查找那些在給定時間/日 期以前或者以後被修改過/被檢查過的用戶帳號, 並根據掃描的結果執行某些操做, 這也是在建立任務隊列。
 
這一節接下來將介紹兩種不一樣類型的任務隊列, 第一種隊列會根據務被插入隊列的順序來儘快地執行任務, 而第二種隊列則具備安排任務在將來某個特定時間執行的能力。
 
6.4.1 先進先出隊列
咱們要編寫的隊列將以「先到先服務」( first-come, first-served) 的方式發送郵件, 而且不管發送是否成功, 程序都會把發 送結果記錄到日 志裏面。 本書在第3章和第5章中曾經介紹過, Redis的列表結構容許用戶經過RPUSH和LPUSH以及RPOP和LPOP, 從列表的兩端推入和彈出元素。此次的郵件隊列將使用RPUSH命令來將待發送的郵件推入列表的右端,而且由於工做進程除了發送郵件以外不須要執行其餘工做, 因此它將使用阻塞版本的彈出命令BLPOP從隊列中彈出待發送的郵件, 而命令的最大阻塞時限爲30秒(從右邊推入元素並從左邊彈出元素的作法, 符合咱們從左向右進行閱讀的習慣) 。
 
 
 
 
 
6.4.2 延遲任務
有幾種不一樣的方法能夠爲隊列中的任務添加延遲性質, 如下是其中3種最直截了當的方法。
 
1.在任務信息中包含任務的執行時間, 若是工做進程發現任務的執行時間還沒有來臨, 那麼它將在短暫等待以後, 把任務從新推入隊列裏面。
2.工做進程使用一個本地的等待列表來記錄全部須要在將來執行的任務, 並在每次進行while循環的時候, 檢查等待列表並執行那些已
經到期的任務。
3.把全部須要在將來執行的任務都添加到有序集合裏面, 並將任務的執行時間設置爲分值, 另外再使用一個進程來查找有序集合裏面是否存在能夠當即被執行的任務, 若是有的話, 就從有序集合裏面移除那個任務, 並將它添加到適當的任務隊列裏面。
 
由於不管是進行短暫的等待, 仍是將任務從新推入隊列裏面, 都會浪費工做進程的時間, 因此咱們不會採用第一種方法。 此外, 由於工做進程可能會由於崩潰而丟失本地記錄的全部待執行任務, 因此咱們也不會採用第二種方法。 最後, 由於使用有序集合的第三種方法最簡單和直接, 因此咱們將採起這一方法, 並使用6.2節中介紹的鎖來保證任務從有序集合移動到任務隊列時的安全性。
 
 
 

6.5 消息拉取
兩個或多個客戶端在互相發送和接收消息的時候, 一般會使用如下兩種方法來傳遞消息。 第一種方法被稱爲消息推送( push
messaging) , 也就是由發送者來確保全部接收者已經成功接收到了消息。
 
Redis內置了用於進行消息推送的PUBLISH命令和SUBSCRIBE命令,本書在第3章中已經介紹過這兩個命令的用法和缺陷③。
第二種方法被稱爲消息拉取( pull messaging) , 這種方法要求接收者自 己去獲取存儲在某種郵箱( mailbox) 裏面的消息。
 
儘管消息推送很是有用, 可是當客戶端由於某些緣由而沒辦法一直保持在線的時候, 採用這一消息傳遞方法的程序就會出現各類各樣的問題。 爲了解決這個問題, 咱們將編寫兩個不一樣的消息拉取方法, 並使用它們來代替PUBLISH命令和SUBSCRIBE命令。
 
6.5.1 單接收者消息的發送與訂閱替代品
 
 
 
 
6.5.2 多接收者消息的發送與訂閱替代品
 
 
 

6.6 使用 Redis進行文件分發
在構建分佈式軟件和分佈式系統的時候, 咱們經常須要在多臺機器上覆制、 分發或者處理數據文件, 而現有的工具能夠以幾種不一樣的方式來完成這些工做:
(1)若是服務器須要持續地分發文件, 那麼常見的作法是使用NFS或者Samba來載入一個路徑( path) 或者驅動器;
(2)對於內容會逐漸發生變化的文件來講, 常見的作法是使用一款名爲Rsync的軟件來儘可能減小兩個系統之間須要傳輸的數據量;
(3)在須要將多個文件副本分發到多臺機器上面的時候, 可使用BitTorrent協議來將文件部分地( partial) 分發到多臺機器上面, 而後經過讓各臺機器互相分享自 己所擁有的數據來下降服務器的負載。
 
遺憾的是, 以上提到的全部方法都有顯著的安裝成本以及相對的價值。
(1)雖然NFS和Samba都很好用, 可是因爲這兩種技術都對操做系統進行了整合, 因此它們在網絡鏈接不完美的時候都會出現明顯的問題(有時候甚至在網絡鏈接無恙的狀況下, 也是如此) 。
(2) Rsync旨在解決網絡不穩定帶來的問題, 讓單個文件或者多個文件能夠部分地進行傳送和續傳( resume) , 但Rsync在開始傳輸文件以前必須先下載整個文件, 而且負責獲取文件的軟件也必須與Rsync進行對接, 這一點是否可行也是一個須要考慮的地方。
(3)儘管BitTorrent是一個了不得的技術, 但它也只適用於服務器在發送文件方面遇到了限制或者網絡未被充分使用的狀況
下, 而且這種技術也須要軟件與BitTorrent客戶端進行對接, 而咱們須要獲取文件的系統上可能並無合適的BitTorrent客戶端可用。
 
除了上面提到的問題以外, 上述3種方法還須要設置並維護帳號、 權限以及服務器。 由於咱們已經有了一個安裝完畢、 正在運行而且隨時可用的Redis, 因此咱們仍是使用Redis來進行文件分發比較好,
這也能夠避免使用其餘軟件時碰到的一些問題: Redis的客戶端會妥善地處理鏈接故障, 經過客戶端也能夠直接獲取數據, 而且針對數據的處理操做能夠當即執行而沒必要等待整個文件出現。
 
6.6.1 根據地理位置聚合用戶數據
 
 
6.6.2 發送日 志文件
 
6.6.3 接收日 志文件
 
6.6.4 處理日 志文件
 
 
 
 

6.7 小結
在這一章, 咱們學習了 6個主要的主題, 但若是仔細地觀察這些主題的話, 就會發現咱們實際上解決了 9個問題。 本章儘量地採用前面章節介紹過的想法和工具來構建更有用的工具, 以此來強調「解決某個問題時用到的技術, 一樣能夠用來解決其餘問題」這個道理。
 
本章試圖向讀者傳達的第一個概念是: 儘管WATCH是一個內置、 方便且有用的命令, 可是使用6.2節中介紹的分佈式鎖可讓針對Redis的併發編程變得簡單得多。 經過鎖住粒度更細的部件而不是整個數據庫鍵, 能夠大大減小衝突出現的概率, 而鎖住各個相關的操做也有助於下降操做的複雜度。 在這一章中, 咱們就看到了如何使用鎖去簡化4.6節介紹過的商品買賣市場以及6.4.2節介紹過的延遲任務隊列, 並對它們的性能進行提高。
 
本章試圖向讀者傳達的第二個概念, 讀者應該銘記於心, 並將其付諸於實踐的就是: 只要多花點心思, 就可使用Redis構建出可重用的組件。 好比在這一章中, 咱們就看到了如何在計數器信號量、 延遲隊列和具備多個接收者的消息傳遞系統中重用分佈式鎖, 以及如何在使用Redis進行文件分發的時候, 重用具備多個接收者的消息傳遞系統。
 
在接下來的一章中, 咱們將使用Redis來構建更高級的工具, 並編寫文檔索引 、 基於分值進行索引 和排序的搜索引 擎等可以支援整個應用序的代碼, 還會實現一個廣告追蹤系統和一個職位搜索系統。 本書在後章節中也會繼續使用這些組件, 所以請讀者留心觀察, 並記住使用Redis來構建可重用的組件並非一件難事。
 
 
② 本書做者對幾個帶有超時限制特性的Redis鎖實現進行了測試,發現即便只使用5個客戶端來獲取和釋放同一個鎖, 也有至少一半的鎖實如今10秒內就出現了多個客戶端都得到了鎖的問題。
③ 簡單來講, PUBLISH和SUBSCRIBE的缺陷在於客戶端必須一 直在線才能接收到消息, 斷線可能會致使客戶端丟失信息; 除此以外, 舊版Redis可能會由於訂閱者處理消息的速度不夠快而變得不穩定甚至崩潰, 又或者被管理員殺死。
④ MapReduce(又稱Map/Reduce) 是Google推廣的一種分佈式計算方式, 它能夠高效而且簡單地解決某些問題。
相關文章
相關標籤/搜索