標籤:
前言:上一章,簡單介紹了5種資料結構,並給出了一些用例。現在是時候來看看一些進階的,但依然很常見的主題和設計模式一、大O標記法(Big O Notation )常用時間複雜度O(1)被認為是最快速的,無論我們是在處理5個元素還是5百萬個元素,最終都能得到相同的效能。對於sismember命令,其作用是告訴我們一個值是否屬於一個集合,時間複雜度為O(1)。sismember命令很強大,強大的一部分原因是其高效的效能特徵。許多Redis命令都具有O(1)的時間複雜度對數的時間複雜度 O(log(N)) 被認為是第二快速的,其通過使需三秒的區間不斷皺縮來快速完成處理。使用這種“分而治之”的方式,大量的元素能在幾個迭代過程裡被快速分解完整。zadd 命令的時間複雜度就是O(log(N)),其中N是在分類集合中的元素數量 再下來就是線性時間複雜度 O(N),在一個表格的非索引列裡進行尋找就需要O(N)次操作。ltrim命令具有O(N)的時間複雜度,但是,在ltrim命令裡,N不是列表所擁有的元素數量,而是被刪除的元素數量。從一個具有百萬元素的列表裡用ltrim命令刪除一個元素,要比從一個具有一千個元素列表用ltrim命令刪除10個元素來的快速,根據給定的最小和最大值的標記,zremrangebyscore 命令 會在一個分類集合裡進行刪除元素操作,其時間複雜度是O(log(N)+M)。這看起來似乎有點兒雜亂,通過閱讀文檔可以知道,這裡的N指的是分類集合裡的總元素數量,而M則是被刪除的元素數量,可以看出,對於效能而言,被刪除的元素數量很可能會比分類即合理的總元素數量更為重要。 (zremrangebyscore 命令的具體構成是 zremrangebyscore key max mix)
對於sort命令,其時間複雜度為O(N+M*log(M)),我們將會在下一章談論更多的相關細節。從sort命令的效能特徵來看,可以說這是Redis裡最複雜的一個命令。
還存在其他的時間複雜度描述,包括O(N^2)和O(C^N)。隨著N的增大,其效能將急速下降。在Redis裡,沒有任何一個命令具有這些類型的時間複雜度。
值得指出的一點是,在Redis裡,當我們發現一些操作具有O(N)的時間複雜度時,我們可能可以找到更為好的方法去處理。
(譯註:對於Big O Notation,相信大家都非常的熟悉,雖然原文僅僅是對該標記法進行簡單的介紹,但限於個人的演算法知識和文筆水平實在有限,此小節的翻譯讓我頭痛頗久,最終成果也確實難以讓人滿意,望見諒。)
二、仿多關鍵字查詢時常,你會想通過不同的關鍵字去查詢相同的值,例如 通過使用者ID 擷取使用者資訊,也希望通過使用者名稱擷取使用者資訊,有一種很不實效的解決方案,就是將使用者物件分別放置到兩個字串值裡去 set users:leto xxxx ; set users:9001 xxx可以實現功能,但是記憶體會產生兩倍的數量,並且今後維護管理也是個噩夢Redis 其實已經提供瞭解決的方法:散列使用散列資料結構,我們可以擺脫重複的纏繞:set users:9001 "{id: 9001, email: [email protected], ...}"
hset users:lookup:email [email protected] 9001
簡單講,就是通過散列,類比搜尋功能,進行關聯
id = redis.hget(‘users:lookup:email‘, ‘[email protected]‘) 先通過散列 搜尋出 ID
user = redis.get("users:{id}") 再通過ID擷取數
三、引用和索引
我們已經看過幾個關於值引用的用例,包括介紹列表資料結構時的用例,以及在上面使用散列資料結構來使查詢更靈活一些。進行歸納後會發現,對於那些值與值間的索引和引用,我們都必須手動的去管理。誠實來講,這確實會讓人有點沮喪,尤其是當你想到那些引用相關的操作,如管理、更新和刪除等,都必須手動的進行時。在Redis裡,這個問題還沒有很好的解決方案。
我們已經看到,集合資料結構很常被用來實現這類索引:
sadd friends:leto ghanima paul chani jessica
這個集合裡的每個成員都是一個Redis字串資料結構的引用,而每一個引用的值則包含著使用者物件的具體資訊。那麼如果chani改變了她的名字,或者刪除了她的帳號,應該如何處理?從整個朋友圈關係結構來看可能會更好理解,我們知道,chni也有她的朋友sadd friends_of:chani leto paul如果你有什麼待處理情況像上面那樣,那在維護成本之外,還會有對於額外索引值的處理和儲存空間的成本。這可能會令你感到有點退縮。在下一小節裡,我們會談論減少使用額外資料互動的效能成本的一些方法。四、資料互動和流水線(Round Trips and Pipelining)許多命令能接受一個或更多的參數,也有一種關聯命令可以接受更多個參數。例如早前我們看到過mget命令接受多個關鍵字,然後傳回值:sadd friends:chani pater luch hniRedsi還支援流水線功能。通常情況下,當一個用戶端發送請求到Redis後,在發送下一個請求之前必須等待Redis的回覆,使用流水線功能,你可以發送多個請求,而不需要等待Redis響應。不但減少了網路開銷,還能獲得效能上的顯著提高。值得一提的是,Redis會使用儲存空間去排列命令,因此批量執行命令是一個好主意五、事務每一個Redis命令都具有原子性,包括那些一次處理多項事情的命令。此外,對於使用多個命令,Redis支援事務功能。
你可能不知道,但Redis實際上是單線程啟動並執行,這就是為什麼每一個Redis命令都能夠保證具有原子性。當一個命令在執行時,沒有其他命令會運行(我們會在往後的章節裡簡略談論一下Scaling)。在你考慮到一些命令去做多項事情時,這會特別的有用。例如:
incr命令實際上就是一個get命令然後緊隨一個set命令。
getset命令設定一個新的值然後返回原始值。
setnx命令首先測試關鍵字是否存在,只有當關鍵字不存在時才設定值
雖然這些都很有用,但在實際開發時,往往會需要運行具有原子性的一組命令。若要這樣做,首先要執行multi命令,緊隨其後的是所有你想要執行的命令(作為事務的一部分),最後執行exec命令去實際執行命令,或者使用discard命令放棄執行命令。Redis的事務功能保證了什嗎?
· 事務中的命令將會按順序地被執行· 事務中的命令將會如單個原子操作般被執行(沒有其它的用戶端命令會在中途被執行)· 事務中的命令要麼全部被執行,要麼不會執行最後,Redis能讓你指定一個關鍵字(或多個關鍵字),當關鍵字有改變時,可以查看或者有條件地應用一個事務。這是用於當你需要擷取值,且待啟動並執行命令基於那些值時,所有都在一個事務裡。對於上面展示的代碼,我們不能去實現自己的
incr命令,因為一旦
exec命令被調用,他們會全部被執行在一塊。我們不能這麼做:
redis.multi()current = redis.get(‘powerlevel‘)redis.set(‘powerlevel‘, current + 1)redis.exec()
(譯註:雖然Redis是單線程啟動並執行,但是我們可以同時運行多個Redis用戶端進程,常見的並發問題還是會出現。像上面的代碼,在
get運行之後,
set運行之前,
powerlevel的值可能會被另一個Redis用戶端給改變,從而造成錯誤。)所以需要指定觀察這個powerlevel關鍵字 如果發生變化,就事務直接失敗復原。
redis.watch(‘powerlevel‘)current = redis.get(‘powerlevel‘)redis.multi()redis.set(‘powerlevel‘, current + 1)redis.exec()
在我們調用
watch後,如果另一個用戶端改變了
powerlevel的值,我們的事務將會運行失敗。如果沒有用戶端改變
powerlevel的值,那麼事務會繼續工作。我們可以在一個迴圈裡運行這些代碼,直到其能正常工作。 很實用六、關鍵字反模式在下一章中,我們將會討論那些沒有確切關聯到資料結構的命令,其中的一些是管理或聯調工具。然而有一個命令在調試或者追蹤BUG的時候非常有用:keys 。這個命令需要一個模式,然後尋找所有匹配的關鍵字,但是因為它是通過線性掃描所有的關鍵字來進行匹配。所以不能使用在產品代碼裡,太慢了,消耗也較大查BUG,比如想查賬戶號 1233開頭的賬戶 keys bug:1233* (*號是萬用字元) 小結:結合這一章以及前一章,希望能讓你得到一些洞察力,瞭解如何使用Redis支援(Poswer) 實際項目。還有其他的模式可以讓你去構建各種類型的東西,但真正的關鍵是要理解基本的資料結構。你將能領悟到,這些資料結構是如何能夠實現你最初的視角之外的東西
Redis系統學習 三、使用資料結構