Redis進階實踐之十八 使用管道模式提升Redis查詢的速度

Redis進階實踐之十八 使用管道模式提升Redis查詢的速度html

1、引言


             學習redis 也有一段時間了,該接觸的也差很少了。後來有一天,之前的同事問我,如何向redis中批量的增長數據,確定是大批量的,爲了這主題,我又從新找起了解決方案。目前的解決方案大都是從官網上查找和翻譯的,每一個實例也都調試了,正確無誤。把結果告訴我同事的時候,我也更清楚這個主題如何操做了,裏面的細節也更清楚了。固然也有人說能夠經過腳原本作這個操做,沒錯,可是我對腳本語言尚未研究很透,就不來班門弄斧了。


2、管道的由來

             提及這個主題也是我同事幫的忙,關於批量增長增長數據到Redis服務器中,我已經寫了一篇文章了,那篇文章只是介紹的操做,咱們學技術,就要作到知其然知其因此然,因此就有了這篇文章。若是想查看個人上一篇文章,能夠點擊這裏《Redis進階實踐之十六 Redis大批量增長數據


        一、請求/響應協議和RTT

                       Redis是使用 客戶端-服務器(Client-Server) 模型的TCP服務器,稱爲請求/響應模式。

                       這意味着經過如下步驟才能完成請求:

                             1.一、客戶端向服務器發送查詢,並一般以阻塞的方式從套接字讀取服務器響應。

                             1.二、服務器處理命令並將響應發送回客戶端。

                       例如,這四個命令序列就是這樣的:python

複製代碼

Client: INCR X
                   Server: 1

                   Client: INCR X
                   Server: 2

                   Client: INCR X
                   Server: 3

                   Client: INCR X
                   Server: 4

複製代碼


                         客戶端和服務器經過網絡鏈路進行鏈接。這樣的連接能夠很是快(一個回送接口)或很是慢(經過互聯網在兩臺主機之間創建不少跳轉的鏈接)。不管網絡延遲如何,數據包都會從客戶端傳輸到服務器,而後從服務器傳回客戶端以進行回覆。

                          這個時間來回被稱爲RTT(往返時間)。當客戶端須要連續執行多個請求時(例如,將許多元素添加到同一個列表或使用多個鍵填充數據庫),很容易看到這會很影響性能。例如,若是RTT時間爲250毫秒(在因特網上鍊接速度很是慢的狀況下),即便服務器可以每秒處理100k個請求,此時咱們也只可以每秒最多處理四個請求。

                         若是使用的接口是本地回送接口(loopback),則RTT要短得多(例如,個人主機報告0.0,040毫秒ping 127.0.0.1),但若是您須要連續執行不少寫操做,則仍然須要不少的時間。

                        幸運的是,有一種方法能夠改善這種作法。


        二、Redis的管道

                       請求/響應服務器能夠這樣實現,即便客戶端沒有閱讀上一條命令的回覆,它也可以處理新的請求。經過這種方式,能夠發送多個命令到服務器而無需等待回覆,最後一步讀取回復。

                        這被稱爲管道技術,而且是被普遍使用的技術。例如,許多POP3協議的實現已經支持這個功能,顯著加快了從服務器下載新電子郵件的過程。

                        Redis自從早期的版本開始就支持管道的操做,所以不管您運行哪一種版本,均可以使用Redis進行管道的操做。這是使用原始netcat實用程序的示例:linux

[root@linux ~]# (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc 192.168.127.130 6379
                    +PONG
                    +PONG
                    +PONG


                        (若是執行nc命令,提示:command not found,安裝命令便可,即:yum install nc)

                        此次咱們沒有爲每次通話支付RTT的時間成本,只是把三命令做爲了一個命令執行,最後只爲這一次執行花費了時間。

                        很是明確地說,經過管道的操做,咱們第一個例子的操做順序以下:
 redis

複製代碼

Client: INCR X
                    Client: INCR X
                    Client: INCR X
                    Client: INCR X
                    Server: 1
                    Server: 2
                    Server: 3
                    Server: 4

複製代碼


                        重要提示:當客戶端使用管道發送多條命令時,服務器將被迫使用內存排隊答覆。因此若是你須要使用管道發送大量的命令,最好將這些命令以合理的數目進行分組來批量發送,例如10k命令,讀取回復,而後再發送另外一個10k的命令,相似這樣。速度幾乎相同,所使用的額外內存的最大量將是將最大限度地排隊此10k命令的回覆所需的數量。


        三、這不只僅是RTT的問題

                       管道不只僅是爲了減小往返時間所帶來的延遲成本,它實際上能夠提升您在給定的Redis服務器上每秒執行的總操做量。這是事實,即在不使用管道的狀況下,從訪問數據結構和生成答覆的角度來看,每一個命令的執行成本都不高的,但從執行套接字 I/O 操做的角度來看,這是很是昂貴的。當涉及調用read()和write()調用的時候,這個調用操做意味着要切換操做環境,要從用戶登錄切換到內核登錄。最後來看,其實上下文切換纔是致使速度大幅度的下降的罪魁禍首。

                       當使用Redis的管道的時候,許多命令一般經過對一個read()函數的系統的調用來讀取,而且經過對一個write()函數的系統的調用來傳遞多個響應。所以,每秒執行的總查詢數量隨着管道的操做呈線性增長,並最終達到未使用管道的基線的10倍,以下圖所示: 

                        



        四、一些真實世界的代碼示例

                         在如下基準測試中,咱們將使用支持管道的Redis Ruby客戶端來測試因爲管道而致使的速度提高:數據庫

複製代碼

require 'rubygems'
                   require 'redis'
 
                   def bench(descr)
                       start = Time.now
                       yield
                       puts "#{descr} #{Time.now-start} seconds"
                   end

                   def without_pipelining
                       r = Redis.new
                       10000.times {
                           r.ping
                       }
                   end

                   def with_pipelining
                       r = Redis.new
                       r.pipelined {
                           10000.times {
                               r.ping
                           }
                        }
                   end

                  bench("without pipelining") {
                      without_pipelining
                  }
                  bench("with pipelining") {
                      with_pipelining
                  }

複製代碼


                         運行上述簡單腳本將在個人Mac OS X系統中提供如下圖形,經過環回接口運行,其中管道將提供最小的改進,其餘保持不變,由於RTT已經很是低:緩存

without pipelining 1.185238 seconds
             with pipelining 0.250783 seconds


                        正如您所看到的,使用管道,咱們將傳輸速度改提高五倍。


        五、管道VS腳本

                        使用Redis腳本(Redis版本2.6或更高版本中可用),能夠在服務器端更高效執行處理大量的管道用例的工做。 腳本的一大優勢是它可以以最小的延遲讀取和寫入數據,使得讀取,計算,寫入等操做很是快速(在這種狀況下,管道操做不起做用,由於客戶端在調用寫入命令以前須要讀取命令的回覆)。

                       有時,應用程序可能還想在管道中發送EVAL或EVALSHA命令。這是徹底可能的,而且Redis經過SCRIPT LOAD命令明確是支持的(它保證能夠在沒有失敗風險的狀況下調用EVALSHA)。


        六、 EVALSHA sha1 numkeys key [key ...] arg [arg ...]

                      Redis可使用該命令的版本是2.6.0,或者更高的版本。

                     時間複雜度:取決於執行的腳本。

                     經過其SHA1摘要評估緩存在服務器端的腳本。使用SCRIPT LOAD命令將腳本緩存在服務器端。該命令在其餘方面與EVAL相同。


        七、附錄:爲何即便在回送接口上,一個繁忙的循環也很慢?

                    即便在本頁面介紹的全部背景下,您仍然可能想知道爲何如在下所示的Redis基準測試中(在僞代碼中),即便在回送接口中執行,而且服務器和客戶端在同一物理機器上運行時,也很慢:ruby

FOR-ONE-SECOND:
              Redis.SET("foo","bar")
          END


                   畢竟,若是Redis進程和基準測試都在同一個框中運行,那麼這不只僅是經過內存將消息從一個地方複製到另外一個地方,而沒有任何實際的延遲和實際網絡?

                   緣由是系統中的進程並不老是在運行,其實是內核調度器讓進程運行的,因此會發生以下的狀況,例如,當基準測試程序被容許運行,從Redis服務器讀取回復(與最後執行的命令相關),並寫入新的命令。該命令如今位於回送接口緩衝區中,但爲了被服務器讀取,內核調度器應該安排服務器進程(當前在系統調用中阻塞)運行,等等。 所以,實際上,因爲內核調度程序的工做原理,回送接口仍然會有網絡延遲的。

                   基本上,使用一個繁忙的循環來執行基準測試是一件愚蠢的事情,能夠在網絡服務器中測量性能時完成相關測試。明智的作法是避免以這種方式作基準測試。


3、結束

               大批量插入數據的文章就寫到這裏了,這篇文章也介紹了 管道的一些底層的機制,對你們,對咱們之後使用Redis 會有好處。等之後我對腳本語言,ruby,或者python學有所成的時候,在經過這些工具來作一些腳本執行批量插入Redis 的實力吧,也會把相應的感覺和心得寫出來。繼續努力吧。對了,若是你們想觀看英文,能夠《點擊這裏》。
     服務器

天下國家,可均也;爵祿,可辭也;白刃,可蹈也;中庸不可能也網絡

相關文章
相關標籤/搜索