Tokyo Tyrant與Redis的一些簡單比較

之前簡單的看了一下 Tokyo Tyrant(包括 Tokyo Cabint) 在 hash 存儲上的一些實現,最近 Redis 又比較火熱,因此,自己也嘗試性的去了解了一下 Redis,並且結合 Tokyo Tyrant(以下簡稱 tt server),說說自己對這兩種產品的看法。

  目錄

  •   服務端處理模型

  •   數據存儲方式、持久化比較

  •   複製方式比較

  •   性能方面比較

  •   總結

      服務端處理模型

      在 tt server 中,是以多線程的方式向客戶端提供服務的:一個主線程負責 accept 客戶端的socket,一定數目的線程(可以指定)進行讀寫服務,同時,也有一定數目的timer線程,專門用來負責定時的任務,比如一些定時的 Lua 腳本,同時,如果是slaver,則會有專門一個timer線程,定時負責 do slave 的工作。

      而在 Redis 中,採用的則是單線程的模型來處理所有的客戶端請求。

      應該說這兩種模型,都有各自的優點和缺點。多線程可以利用多核CPU的計算能力,但因此也會增加CAS自旋或者是鎖的一些消耗,同時,如果線程過多,那麼線程之間上下文的切換,也是一種消耗。

      而如果是單線程,則可以完全避免鎖的消耗,同時,上下文切換消耗也不需要過多的考慮(但仍需要考慮系統上還有其他的進程),這會讓單個CPU的利用率比較高。

      但是,單線程服務,就意味着不能利用多核。同時,服務端對客戶端過來的請求是串行執行和響應的,這也在一定程度上,會影響服務端的併發能力,特別是在有些請求執行比較耗時的情況下。想象一下,就這麼一個線程,可能正在拼命的執行客戶端A的一個請求,而此時客戶端B,C,D的請求,還仍在等着線程執行完成之後再去搭理他們。

      因此,像 redis 這種單線程的服務模型,如果對一些請求的處理相對較耗時,那其 TPS 也就相應的不能提高上去,也就是說其吞吐量會提不上去;但反過來想,redis 如果能控制每次請求在執行過程是簡短並且快速的,那麼也許使用單線程,反而會比多線程有更好的性能,畢竟單線程少了上下文切換,以及鎖或者 cas 的開銷。

      而 tt server 則中規中矩:一個線程負責 accept ,一定數目的線程則進行請求的處理。因此,我們在設置 tt server 的時候,也應儘量考慮好工作線程的數目,儘量讓CPU數目與工作線程數目一致或者略少。原則是最好的發揮多核CPU的作用,同時又不讓工作線程之間去競爭 CPU。當然,這是需要不停的去實驗的。

      所以,在使用 redis 的時候,應儘量不要去使用一些相對耗時的請求;同時,我想 redis 的作者,也應該會盡量優化每種請求的執行速度(至少是一些常用的請求)。

      而在使用 tt server 的時候,需要仔細調整使用的工作線程數目,讓每個CPU都物盡其用。

      數據存儲方式、持久化比較

      tt server 的 hash 數據庫,是使用文件的方式,然後利用 mmap 系統調用映射到內存中。

      這樣,就可以利用操作系統的機制,不定期地將數據 flush 到磁盤中。同時,tt server 也提供了 sync 命令,可以讓客戶端手動將數據 flush 到磁盤中(使用 msync 系統調用)。最後,在關閉 tt server 進程的時候,應該使用 kill -15(TERM信號),或者使用 ttserver 自帶的命令:ttserver -kl pid 進行關閉。這樣 ttserver 會先把數據 flush 到磁盤上,再退出進程。

      同時, tt server 也提供了 ulog 的方式,對數據庫的變更操作進行記錄,同樣,可以利用 ulog 對 ttserver 進行恢復,但 ulog 的主要目的,按照我的理解,應是用來實現 replication 的。

      而 redis 則是將數據直接寫在了內存中,然後利用 redis 的持久化機制,將數據寫到磁盤中。

      redis 提供了兩種持久化機制,分別是 RDB (redis DB) 和 AOF (appending only file)。

      RDB的過程是:redis 進程 fork 一個子進程,然後子進程對內存中的數據寫到一個臨時文件,這個時候,兩個進程就利用了操作系統的 copy on write 機制,共享一份內存數據,只有當父進程(也就是 redis 進程)對原有的數據進行修改或者刪除之後,操作系統才爲 redis 進程重新開闢新的內存空間(以頁爲單位)。Redis 本身也提供了 bgsave(background save) 命令支持手動將數據持久化( save 命令是同步的,而 redis 只有一個線程在服務,結果就是影響 redis 的性能,特別是在大數據量的情況下)。

      AOF的過程是:在執行每次命令之後,或者每隔1秒鐘之後,Redis會有一個線程將命令以 redis 協議的格式 append 到文件中,這也就是AOF名字的由來,這些命令當然是非只讀的,只讀不更改數據庫,沒有必要記錄下來。

      這裏會有兩個問題:

      1、每次命令之後寫文件,還是隔1秒之後寫文件,影響會有哪些?

      2、這些文件總會不斷的膨脹,如何對文件進行壓縮呢?

      對於第一個問題,也是一個權衡的問題,如果每次命令之後都進行一次寫磁盤操作,那麼IO的程度可想而知,肯定會影響服務器性能(使用 write 系統調用,會因爲文件系統而進入 page buffer,並非立刻寫磁盤,而調用 fsync ,則會將 page buffer 中的數據寫入磁盤,進行 IO 操作)。而如果每隔1秒進行一次 fsync,那麼在這一秒和上一秒之間,如果服務器突然斷電,那很有可能這些數據就會丟失。對於這個問題,redis 默認給出的方案是每隔1秒進行一次write。對於1秒的給定,我想,也是基於性能和數據安全的權衡,在性能和數據安全方面都可以讓人接受。

      對於第二個問題,redis 提供了 rewrite 的機制:當 aof 過大的時候,redis可以自動的進行 rewrite (從 redis 2.4 開始)。rewrite 的過程也是 fork 一個子進程;然後打開一個臨時文件,將內存中的數據寫入到文件中;在此期間,主進程繼續將數據寫入老的 aof 文件,同時也會將數據寫入到一個內存緩存中;等子進程完成之後,主進程會將緩存中的數據寫入到臨時文件,再將臨時文件進行rename,替換掉原來的文件。這樣,就實現了寫 aof 過程中的rewrite。

      從數據的存儲方式來說,儘管 tt server 和 redis 都是在內存上面進行數據的讀寫,我但認爲兩個產品對數據存儲方式的觀點是不一樣的。

      tt server 是將磁盤上的文件當作主要的存儲方式,然後使用 mmap 將文件映射到內存中。本質上,這是數據應該存儲在磁盤中的觀點。

      而 redis ,一開始就是將數據直接存儲在內存中,在之後的持久化過程中,可以理解成只是將數據的日誌寫入到磁盤中。本質上,這是把數據應該存儲在內存中的觀點。

      可見,由於作者的觀點不一樣,也就造成了兩種實現方式不一樣的產品,這還是比較有意思的。

      從這個層面上來講,我更加喜歡 redis 作者的思路,很可能作者就是受到 內存是新的磁盤,磁盤是新的磁帶 的啓發。

      redis自帶實現的VM將在以後不再使用(2.4將是最後一個自帶vm功能的版本),作者認爲數據就應該是放在物理內存中的,沒有必要要將數據交換到磁盤中,磁盤只是作爲日誌的一種存儲方式。這也是“內存是新的硬盤”思路的體現。


      複製方式比較

      tt server 和 redis 都支持 master-slave 方式的通信複製。

      tt server 使用了 ulog,並且 slaver 使用了 rts(replication time-stamp) 文件,對上一次的複製時間戳進行保存,實現了複製的續傳。

      而 redis 則是每次 slave 重新連接到 master 時,master 會將數據進行全量的複製給 slave,而不是增量式的。redis 複製的方式與使用 RDB 持久化方式原理基本相同,也是使用子進程進行內存的dump,在此期間,父進程收集改變數據庫的命令,等把子進程收集的數據傳輸給 slave 之後,再將此期間收集到的數據也傳輸給 slave。

      如果從 slave 數據重建的角度來看,tt server 支持斷點複製的實現,應該說是比 redis 先進了一步。

      性能方面比較

      新浪的 Tim Yang 做了 memcacheDB、Redis、tt server 的性能測試。這是比較早期的測試,相信隨着版本的升級,兩者的性能都會有所提升。不過按照這個測試的結果來看,redis 在數據量不多(500W)並且value 較小的時候,性能表現是很優越的;而對於稍大一些的 value ,tt 則在寫方面表現很出色,但讀的性能,相對較差。相比之下,redis的讀寫性能,倒是比較平衡。

      但覺得隨着時間的遷移,這個測試的參考性可能會打折扣,如果有可能的話,希望能看到更多的測試結果。

      總結

      1. 從服務器模型來說,tt server 使用 acceptor + workers 的方式提供服務,能夠利用多核的性能,但隨着而來的是一些同步、加鎖的複雜和開銷;而 redis 使用了單線程提供服務,利用不了多核,但如果能夠將每次服務的速度控制下來,對單個CPU的利用率,反而可以提高。如果想利用機器的多核性能,也可以在一臺機器上搭建多個 redis 實例,但可能更要考慮到機器的內存限制。

      2. 從數據存儲的方式來說,儘管 tt server 和 redis 都是將數據存儲在內存中,但我認爲兩個產品對“數據是如何存儲”的觀點是有所不同的。tt server 認爲數據是存儲在文件中的,只是通過內存映射,將對文件的操作轉化成對內存的操作;而 redis 是直接將數據存儲到內存中,之後再通過持久化等機制,將數據備份到磁盤中。雖然之前 redis 自己實現了 vm 功能,但redis 後續會取消掉自己實現的 vm 功能,按照“內存是最新的磁盤”這種思路,也就不難理解了:除了增加複雜度之外,還有一個因素,那就是 redis 不需要 vm,能存的數據大小,只能限制在物理內存的範圍以內。

      從這個方面來將,redis 後續的版本可能就會限制用戶使用的數據庫大小是要小於物理內存的,而如果使用 tt server ,則用戶須讓使用數據文件小於物理內存,否則,發生內存交換,是非常損性能的。

      總而言之,在使用內存數據庫的時候,應該有意識的對數據進行容量規劃,避免出現物理內存不夠而引起的內存交換。

      3. tt server 和 redis 的策略都是從 slaver 配置 master ,而不是從 master 配置 slaver 關係,這樣就減輕了 master 的負擔,同時,master 不必知道自己有多少個 slaver ,就可以橫向的擴增 slaver 。但 tt server 支持所謂的斷點複製。需要考慮到的是 redis 在做 replication 的時候,是 fork 一個子進程工作的,如果有多個 replicate 的請求,redis 依然還是一個子進程在工作。這樣也會對多個 slaver 產生一定的複製延時。

      4. redis 在工作方式上,會 fork 子進程,因此 redis 在容量規劃上,需要考慮到 redis fork 出子進程所需要的內存和 CPU,在最差的情況下:bgsave時候,父子兩個進程雖然可以使用 copy on write 的好處,但如果在此期間整個表記錄都被修改了,那就足足需要一倍的內存,否則,此時父進程會進行 copy ,父進程很可能沒有內存可用,就需要進行內存交換,由此所帶來的性能代價也是非常高的;與此同時,子進程子在 bgsave 的時候,需要對數據進行壓縮,壓縮是計算密集型的,因此最好不要和父進程使用同一個CPU,因爲父進程使用了單線程事件處理的模型,這種模型的優點是充分利用CPU的資源,如果出現子進程與父進程搶CPU,那就得不償失了。

      5. redis 支持較多的數據結構,但在使用 sort 等時間複雜性較多的命令時,也會稍微的降低 redis 的性能,應該對這些耗時的命令進行一定的監控。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章