1. 程式人生 > >Python網路程式設計 8 快取與訊息佇列

Python網路程式設計 8 快取與訊息佇列

前面已經介紹了套接字API以及在Python中使用的基礎IP網路操作來構建通訊通道的方式。本章研究服務負載較重時常用的兩項基本技術:快取與訊息佇列。這兩項技術有如下一些共同特點:

  • 都是非常強大的工具,因而廣為流行。使用Memcached或一個訊息佇列,不是為了實現一個有趣的協議來與其他工具進行互動,二是為了編寫優雅的服務來解決特定的問題。
  • 這兩項技術解決的問題通常是機構內部特有的問題。我們通常無法僅從外界就得知一個特定的網站或網路服務使用了哪種快取、哪種訊息佇列以及哪種負載分配工具。
  • 儘管HTTP和SMTP這樣的工具都是針對一個特定的負載設計的(HTTP針對超文字文件,SMTP針對電子郵件訊息),但是快取和訊息佇列是無需瞭解它們所要傳輸的資料的。
  • 我們可以將快取和訊息佇列視為提供了兩個已經造好的輪子,以避免重複的勞動。
Memcached意為“記憶體快取守護程序“(memory cache daemon).Memcached將安裝它的伺服器上的空閒RAM與一個很大的近期最少使用(LRU)的快取結合使用。我們可以從Memcached的實現中學習到一個重要的現代網路概念--分割槽(Sharding).使用Memcached的實際步驟是相當簡單的。
  • 在每臺擁有空閒記憶體的伺服器上都執行一個Memcached守護程序。
  • 將所有Memcached守護程序的IP地址與埠號列出,並將該列表傳送給所有將要使用Memcached的客戶端。
  • 客戶端程式現在可以訪問一個組織級的速度極快的鍵值快取,它就像是所有伺服器之間共享的一個巨大的Python字典。該快取是基於LRU的。如果有些項長時間沒有被訪問的話,就會將這些項丟棄,為新訪問的項挪出空間,並記錄被頻繁訪問的項。
Memcached Points :
  • Memcached的介面和Python的字典極其類似。將一個字串作為值傳入set()時,該字串會以utf-8編碼直接被寫入Memcached,稍後在通過get()獲取該字串時會進行解碼。除了簡單字串之外,寫入任何其他Python物件都會自動觸發memcache模組的pickle操作,然後將二進位制的pickle儲存在Memcached中。要牢記這一不同點,因為有時我們編寫的Python應用程式會與用其他語言寫的客戶端共享Memcached快取,此時,只有一字串形式儲存的值才可以被使用其他語言編寫的客戶端直接使用。
  • 伺服器是可以丟棄儲存在Memcached中的資料的。Memcached的目的是將重複計算花銷較高的結果記錄下來,以此來加速操作。它不是用來作為資料的唯一儲存區的!
  • 當Memcached客戶端得到了包含多個Memcached例項的列表時,會根據每個鍵的字串的雜湊值對Memcached資料庫進行分割槽(shard),由計算出的雜湊值決定用Memcached叢集中的哪臺伺服器來儲存特定的記錄。
訊息佇列協議允許我們傳送可靠的資料塊。協議將這樣的資料塊稱為訊息(message),而不是資料報(datagram)。這是因為,資料報這一概念是用來特指不可靠服務的,傳輸過程中資料可能會丟失、重複或是被網路重新排列。一般來說,訊息佇列保證訊息的可靠自動傳輸:一條訊息約麼被完好無損地傳輸至目的地,要麼完全不傳輸。訊息佇列協議會負責封幀,使用訊息佇列的客戶端從來都不需要在接收到完整的訊息之前一直在迴圈中不斷呼叫recv()這樣的函式。 訊息佇列還有一個創新之處,即與TCP這樣基於IP傳輸機制提供的點對點連線不同,使用訊息佇列的客戶端之間可以設定各種各樣的拓撲結構。訊息佇列有很多可能的應用場景。
  • 當使用電子郵箱地址在網站註冊賬號時,通常會立刻返回一個頁面,請於郵件收件箱內查收確認郵件。在這一過程中,使用者無需等待,而網站通過我們的電子郵箱服務提供商傳輸郵件則可能需要好幾分鐘的時間。網站的通常做法是將電子郵箱地址放在一個訊息佇列裡,當後臺伺服器準備好建立一個用於傳送的SMTP連線時,就會從訊息佇列中獲取郵箱地址。如果傳送暫時失敗,那麼電子郵箱地址會被直接放回到佇列中,在經歷更長的時間間隔後重試。
訊息佇列可以作為自定義遠端過程呼叫(RPC,Remote Procedure Call)服務的基礎。遠端過程呼叫服務允許繁忙的前端伺服器將一些困難的工作交給後端伺服器來負責。前端伺服器可以將請求置於訊息佇列中,幾十甚至幾百個後端伺服器會對該訊息佇列進行監聽。後端伺服器在處理完訊息佇列中的請求後會將響應返回給正在等待的前端伺服器。經常需要將一些大容量的事件資料作為小型的有效訊息流集中儲存在訊息佇列中並進行分析。在一些網站中,訊息佇列已經徹底代替了儲存到本地硬碟中的日誌系統以及syslog這樣更古老的日誌傳輸機制。 訊息佇列應用程式設計有一個重要特點,那就是它具有混合安排並匹配所有客戶端與伺服器或釋出者與訂閱者程序的能力。需要注意的是,它們都需要連線到同一個訊息佇列系統。 訊息佇列的意義:給程式編寫帶來了一些革命性的進步。典型的傳統應用程式在單個應用程式中包含了所有功能。它由一層一層的API組成。一個控制執行緒可能會負責對所有API的呼叫,比如先從套接字讀取HTTP資料,然後進行認證,請求解析,呼叫API進行特定的影象處理,最後將結果寫入磁碟中,該控制執行緒使用所有的API都必須存在於同一臺機器上,並且被載入到同一個Python執行時例項內。然而,一旦我們能夠使用訊息佇列,那麼久可能會產生一個疑惑:為什麼像影象處理這樣計算密集型、專業且對於網路不可見的工作要與前端HTTP服務共享CPU和磁碟驅動器?因此,我們可以不使用安裝了大量不同庫的強大機器來構建服務,二是轉而使用一些專門用於單一目的的機器,將這些機器集合到叢集中,共同提供某個服務。這樣一來,只要負責運維的同事理解訊息傳遞的拓撲結構,並且保證在進行伺服器分離時沒有任何資訊丟失,他們在解除安裝、安裝以及重灌影象處理伺服器時就完全不會影響位於訊息佇列前端的HTTP服務負載均衡池。 通常來說,所有訊息佇列都支援多種拓撲結構。
  • 管道(pipeline)拓撲結構可能是與我們腦海中對於佇列的直觀映像最相似的一種模式:生產者建立訊息,然後將訊息提交至佇列中,消費者從佇列中接收訊息。例如,一個照片分享網站的前端網路伺服器可能會將使用者上傳的圖片儲存在一個專門用於接收檔案的內部佇列中。包含許多縮圖生成工具的機房會從佇列中讀取圖片。每個影象處理伺服器每次從佇列中接收一條訊息(訊息中包含需要生成縮圖的圖片),然後為其生成縮圖。站點較為繁忙時,佇列在執行過程中可能會越來越長;站點較為空閒時,佇列就會變短或是再次清空。不過,無論站點是否繁忙,前端網路伺服器都可以直接向等待的客戶端返回一個響應,告訴使用者,上傳已經成功,並且很快就能在他們的照片流中看到。
  • 釋出者-訂閱者(publisher-subscriber)或扇出(fanout)結構看上去和管道結構差不多,不過二者有一個重要的區別。雖然管道結構的訊息佇列能夠保證佇列中的沒個訊息都會被髮送給一個消費者(這是由於,把同一張圖片傳送給兩臺影象伺服器畢竟是很浪費的),但是訊息訂閱者通常想要接收佇列中的所有訊息。因此另一種方法是,由訂閱者設定一個過濾器,通過某種特定的格式限定有興趣的訊息範圍。該型別的佇列可以用於需要向外部世界推送事件的外部服務。伺服器機房同樣可以使用佇列系統來對哪些系統啟動了,哪些系統因為維護而關閉進行通知。除此之外,甚至還可以使用這種佇列系統在其他訊息佇列建立和銷燬的時候釋出他們的地址。
  • 最後一個是請求-響應(request-reply)模式,這也是最為複雜的模式。複雜的原因在於訊息需要進行往返。在前面兩種模式中,訊息生產者的工作是非常少的。生產者連線到佇列,然後傳送訊息,僅此而已。但是,發起請求的訊息佇列客戶端需要保持連線,並等待接收響應。為了支援這一點,佇列必須提供某種定址機制,從成百上千個已經連線並且仍然處於等待的客戶端中找到正確的客戶端,將響應傳送到該客戶端。不過也正是由於這一複雜性使得請求-響應模式幾乎成為了最強大的模式。它允許我們將幾十或是幾百個客戶端請求均勻分佈到大量伺服器中,除了設定訊息佇列外,不需要做其它任何工作。一個優秀的訊息佇列允許伺服器在不丟失訊息的前提下繫結到訊息佇列或解除繫結,因此這種拓撲結構的訊息佇列同樣允許伺服器在需要維護而關閉時的行為對客戶端機器保持不可見。
請求-響應模式的佇列是將能夠在某臺機器上大量執行的多個輕量級執行緒(比如網路前端伺服器的許多執行緒)與資料庫客戶端或檔案伺服器連線起來的一種很好的方式。資料庫客戶端或檔案伺服器有事需要被呼叫,代替前端伺服器進行一個高負荷的運算。請求-響應模式很自然地適用於RPC機制,而且還提供了普通RPC系統沒有提供的額外優點:許多消費者或生產者都可以使用扇入或扇出的工作模式繫結到同一個佇列,而模式的不同對於客戶端來說是不可見的。 在Python中使用訊息佇列 最流行的訊息佇列被實現為獨立的伺服器。構建應用程式時我們為了完成各種任務選用的所有元件(比如生產者、消費者、過濾器以及RPC服務)都可以繫結到訊息佇列,並且互相不知道彼此的地址,甚至不知道彼此的身份。AMQP協議是最常見的跨語言訊息佇列協議實現之一,我們可以安裝許多支援AMQP協議的開源伺服器,比如RabbitMQ、Apache Qpid伺服器以及許多其他專案。 許多程式設計師從來都不去學習訊息協議。相反,他們會去依賴一些第三方庫,這些第三方庫將訊息佇列的重要功能封裝起來,並提供了易於使用的API。例如許多Django網路框架的Python程式設計師會使用非常流行的Celery分散式任務佇列,而並不去學習AMQP協議。這些庫同樣可以支援其他後端服務,使得其不依賴於特定的協議。在Celery中,我們可以使用簡單的Redis鍵值儲存作為我們的”訊息佇列",而不需要使用專門的訊息機制。 剩餘內容在P143~146