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


一、引言


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


二、管道的由來

             說起這個主題也是我同事幫的忙,關於批量增加增加數據到Redis服務器中,我已經寫了一篇文章了,那篇文章只是介紹的操作,我們學技術,就要做到知其然知其所以然,所以就有了這篇文章。如果想查看我的上一篇文章,可以點擊這里《Redis進階實踐之十六 Redis大批量增加數據


        1、請求/響應協議和RTT

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

                       這意味着通過以下步驟才能完成請求:

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

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

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

                  
                   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),但如果您需要連續執行很多寫操作,則仍然需要很多的時間。

                        幸運的是,有一種方法可以改善這種做法。


        2、Redis的管道

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

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

                        Redis自從早期的版本開始就支持管道的操作,因此無論您運行哪種版本,都可以使用Redis進行管道的操作。這是使用原始netcat實用程序的示例:

                    [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的時間成本,只是把三命令作為了一個命令執行,最后只為這一次執行花費了時間。

                        非常明確地說,通過管道的操作,我們第一個例子的操作順序如下:

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


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


        3、這不僅僅是RTT的問題

                       管道不僅僅是為了減少往返時間所帶來的延遲成本,它實際上可以提高您在給定的Redis服務器上每秒執行的總操作量。這是事實,即在不使用管道的情況下,從訪問數據結構和生成答復的角度來看,每個命令的執行成本都不高的,但從執行套接字 I/O 操作的角度來看,這是非常昂貴的。當涉及調用read()和write()調用的時候,這個調用操作意味着要切換操作環境,要從用戶登陸切換到內核登陸。最后來看,其實上下文切換才是導致速度大幅度的降低的罪魁禍首。

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

                       



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

                         在以下基准測試中,我們將使用支持管道的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


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


        5、管道VS腳本

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

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


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

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

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

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


        7、附錄:為什么即使在回送接口上,一個繁忙的循環也很慢?

                    即使在本頁面介紹的所有背景下,您仍然可能想知道為什么如在下所示的Redis基准測試中(在偽代碼中),即使在回送接口中執行,並且服務器和客戶端在同一物理機器上運行時,也很慢:

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


                   畢竟,如果Redis進程和基准測試都在同一個框中運行,那么這不僅僅是通過內存將消息從一個地方復制到另一個地方,而沒有任何實際的延遲和實際網絡?

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

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


三、結束

               大批量插入數據的文章就寫到這里了,這篇文章也介紹了 管道的一些底層的機制,對大家,對我們以后使用Redis 會有好處。等以后我對腳本語言,ruby,或者python學有所成的時候,在通過這些工具來做一些腳本執行批量插入Redis 的實力吧,也會把相應的感受和心得寫出來。繼續努力吧。對了,如果大家想觀看英文,可以《點擊這里》。
     


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM