本文是Redis做者antirez的一篇博客node
原文地址:antirez.com/news/128程序員
咱們在Redis5版本迎來了一個新的數據結構,它的名字叫作"Streams"。(撒花)Streams一經推出,就引發了社區中各位大佬的關注。因此我決定過一段時間作一個社區調查,討論一下它的使用場景,並會在博客中將結果記錄下來(是Redis做者的博客)。今天我想聊的是另外一個問題:我懷疑有不少用戶認爲Streams的使用場景是和Kafka同樣的。實際上,這個數據結構的設計背景也是消息的生產和消費,但你應該認爲Redis Streams只是更擅長作這樣的事情。流是一種很好的模型和"心理模型",它能幫助咱們更好的設計系統,可是Redis Streams像其餘Redis數據結構同樣,它更加通用,能夠用來處理更多不一樣的問題。因此這篇博客咱們會重點關注Redis Streams做爲一種數據結構有哪些特性,而徹底忽略它的阻塞操做、消費羣和全部消息相關的內容。redis
若是你想記錄一系列的結構化數據,而且肯定數據庫是足夠大的,你可能會說:咱們以追加寫入的方式打開一個文件,每一行記錄是一個CSV數據項:數據庫
time=1553096724033,cpu_temp=23.4,load=2.3 time=1553096725029,cpu_temp=23.2,load=2.1bash
這看起來很簡單,而後人們一直這樣作了好多年,而且一直持續着:若是你知道你在作什麼,那麼這將成爲一種固定的模式。若是一樣的事情發生在內存中會怎樣呢?內存的順序寫入能力更強,而且會自動移除掉CSV文件的一些限制:服務器
不過使用這樣的CSV條目也有一些好處:沒有固定格式,字段能夠改變,生成比較容易,並且存儲格式比較緊湊。咱們保留了其優勢,去掉了限制,因而設計出了像Redis Sorted Set這樣的混合數據結構——Redis Streams。他們看起來像基本數據結構同樣,可是爲了獲得這樣的效果,內部是有多種表現形式的。數據結構
Redis Streams是一種經過基數樹鏈接的增量壓縮的宏節點。(好難理解的概念,把原話貼出來:Redis Streams are represented as delta-compressed macro nodes that are linked together by a radix tree)。它的做用是,快速查找一個隨機項,獲取範圍值,刪除舊值來建立一個有大小上限的流。對程序員來講,咱們的接口和CSV文件很類似:app
> XADD mystream * cpu-temp 23.4 load 2.3
"1553097561402-0"
> XADD mystream * cpu-temp 23.2 load 2.1
"1553097568315-0"
複製代碼
經過這個例子能夠看到,XADD命令自動生成並返回了一個entry ID。它是單調遞增的,而且有兩部分組成,<時間>-<數量>,時間是毫秒級,而數量則是同一毫秒生成的entry數量遞增。工具
因此第一個從上面所說的"追加寫入CSV"文件抽象出來概念就是,若是用星號做爲XADD命令的ID參數,就從服務器獲取了一個entry ID。這個ID不只僅是entry的惟一標識,也和entry加入流的時間有關。XRANGE命令能夠批量獲取或獲取單個數據項。學習
> XRANGE mystream 1553097561402-0 1553097561402-0
1) 1) "1553097561402-0"
2) 1) "cpu-temp"
2) "23.4"
3) "load"
4) "2.3"
複製代碼
在這個例子中,爲了獲得單個數據項,我用了相同的ID做爲起始和結束值。然而我能夠獲取任意範圍的數據項,而且用COUNT參數限制結果的數量。我也能夠將起止參數都設置爲時間戳,獲取一段時間內的數據項。
> XRANGE mystream 1553097560000 1553097570000
1) 1) "1553097561402-0"
2) 1) "cpu-temp"
2) "23.4"
3) "load"
4) "2.3"
2) 1) "1553097568315-0"
2) 1) "cpu-temp"
2) "23.2"
3) "load"
4) "2.1"
複製代碼
篇幅緣由,咱們再也不展現更多的Streams API了。咱們有相關的文檔,感興趣的同窗能夠去閱讀。目前爲止,咱們只須要關注基本使用方法:XADD用來增長數據,XRANGE(或XREAD)用來讀取數據。咱們來看一下我爲何說Streams是一個強大的數據結構。
前幾天我和一個最近在學習Redis的朋友一塊兒建模一個應用程序:這是一個用來追蹤當地網球場、球員和比賽的app。很明顯,球員是一個小的模型,在Redis中只須要用一個hash就足夠了,key的形式能夠是player:<id>。當你進一步使用Redis建模時,就會意識到你須要去追蹤指定網球俱樂部的一場比賽。若是球員1和球員2打了一場比賽,球員1獲勝。那麼咱們能夠這樣來記錄:
> XADD club:1234.matches * player-a 1 player-b 2 winner 1
"1553254144387-0"
複製代碼
經過這樣簡單的操做,咱們就能夠得到以下的信息:
在Streams出現以前,咱們須要建立一個Sorted Set,分數是時間。Sorted Set的元素是比賽的ID(存在一個Hash裏)。這不只是增長了工做量,並且還形成了更多的內存浪費,比你想象的要多得多。
如今看起來Streams像是一個追加模式的,以時間爲分數,元素是小型Hash的Sorted Set。簡而言之,這是Rediscover建模環境中的一次革命。
上面的例子不只僅是固化模式的問題,相比舊有的Sorted Set+ Hash的模式,Streams對內存的節省作了很好的優化,然而這一點是不容易被發現的。
假設咱們要記錄100萬場比賽,
Sorted Set + Hash的內存使用量是220MB(242RSS)
Stream的內存使用量是16.8MB(18.11RSS)
這不只僅是一個數量級的差別(其實是13倍的差別),這意味着咱們舊有的模式實在是太浪費了,而新的模式是完美可行的。Redis Streams還有其餘神奇的地方:宏節點能夠包括多個元素,它們使用叫作listpack的數據進行編碼。listpacks會對二進制形式的整數進行編碼,即時它是語義字符串。最重要的是,咱們使用了增量壓縮和相同字段壓縮。咱們能夠經過ID或時間進行查詢,由於宏節點是用基數樹鏈接的。基數樹葉被設計爲使用不多的內存。全部的事情都使用極少的內存,但有趣的是,用戶並不能從語義上看到使Streams更加高效的實現細節。
如今咱們來作一個簡單的計算,若是我保存了100萬個entry,使用了18MB內存,那麼1000萬個就是180MB,1億個使用1.8GB,保存10億數據也只使用18GB內存。
有一個比較重要的事情須要注意,在我看來,上面咱們用來記錄網球比賽的例子與把Redis Streams做爲一個時間序列來使用很是不一樣。沒錯,邏輯上咱們仍然是記錄一類事件,但本質上的區別是記錄日誌和建立一個entry並存入對象的不一樣。在使用時間序列時,咱們只是記錄一個外部事件,而不須要真的展現一個對象。你可能認爲這個區別不重要,但事實不是這樣。對Redis用戶來講很重要的是,若是須要保存一系列有序的對象,而且給每一個對象賦一個ID,那麼就須要使用Redis Streams。
然而即時是一個簡單的時間序列,也是一個很大的用例,由於在Streams出現以前,Redis在面對這種用例時使人有些絕望。一個節省內存,而且靈活的流,對開發者來講是一個重要的工具。
Streams很是靈活而且有不少使用場景,我想盡可能用簡短的語言,以確保上面的例子和內存分析更加通俗易懂。也許大多數讀者已經搞懂了。不過在上個月我和別人交流時感受到Streams和流式處理仍是有着很強的關聯。就像這個數據結構只能用來處理流同樣,事實並不是如此。