1. 程式人生 > >Nginx多進程高並發、低時延、高可靠機制在緩存代理中的應用

Nginx多進程高並發、低時延、高可靠機制在緩存代理中的應用

網卡中斷 內核 過大 產生 not 整體 時延 nco 技術

  1. 開發背景

  

  現有開源緩存代理中間件有twemproxy、codis等,其中twemproxy為單進程單線程模型,只支持memcache單機版和redis單機版,都不支持集群版功能。

  

  由於twemproxy無法利用多核特性,因此性能低下,短連接QPS大約為3W,長連接QPS大約為13W,同時某些場景時延抖動厲害。

  

  為了適應公有雲平臺上業務方的高並發需求,因此決定借助於twemproxy來做二次開發,把nginx的高性能、高可靠、高並發機制引入到twemproxy中,通過master+多worker進程來實現七層轉發功能。

  

  2 Twemproxy

  

  2.1 Twemproxy簡介

  

  Twemproxy 是一個快速的單線程代理程序,支持 Memcached ASCII協議和更新的Redis協議。它全部用C寫成,使用Apache 2.0 License授權。支持以下特性:

  

  i)速度快

  

  ii)輕量級

  

  iii)維護持久的服務器連接

  

  iiii)啟用請求和響應的管道

  

  iiiii)支持代理到多個後端緩存服務器

  

  iiiii)同時支持多個服務器池

  

  iiiiii)多個服務器自動分享數據

  

  iiiiiii)可同時連接後端多個緩存集群

  

  iiiiiiii)實現了完整的 memcached ascii 和 redis 協議.

  

  iiiiiiiii)服務器池配置簡單,通過一個 YAML 文件即可

  

  iiiiiiiiii)一致性hash

  

  iiiiiiiiii)詳細的監控統計信息

  

  iiiiiiiiiii)支持 Linux, *BSD, OS X and Solaris (SmartOS)

  

  iiiiiiiiiiii)支持設置HashTag

  

  iiiiiiiiiiiiiii)連接復用,內存復用,提高效率

  

  2.2 memcache緩存集群拓撲結構

  

  圖1 twemproxy緩存集群拓撲圖

  

  如上圖所示,實際應用中業務程序通過輪詢不同的twemproxy來提高qps,同時實現負載均衡。

  

  說明:官方memcache沒有集群版和持久化功能,集群版和持久化功能由我們自己內部開發完成。

  

  2.3 推特原生twemproxy瓶頸

  

  如今twemproxy憑借其高性能的優勢, 在很多互聯網公司得到了廣泛的應用,已經占據了其不可動搖的地位, 然而在實際的生產環境中, 存在以下缺陷,如下:

  

  i)單進程單線程, 無法充分發揮服務器多核cpu的性能

  

  ii)當twemproxy qps短連接達到8000後,消耗cpu超過70%,時延陡增。

  

  iii)大流量下造成IO阻塞,無法處理更多請求,qps上不去,業務時延飆升

  

  iiii)維護成本高,如果想要充分發揮服務器的所有資源包括cpu、 網絡io等,就必須建立多個twemproxy實例,維護成本高

  

  iiiii)擴容、升級不便

  

  原生twemproxy進程呈現了下圖現象:一個人幹活,多個人圍觀。多核服務器只有一個cpu在工作,資源沒有得到充分利用。

  

  3. Nginx

  

  nginx是俄羅斯軟件工程師Igor Sysoev開發的免費開源web服務器軟件,聚焦於高性能,高並發和低內存消耗問題,因此成為業界公認的高性能服務器,並逐漸成為業內主流的web服務器。主要特點有:

  

  i)完全借助epoll機制實現異步操作,避免阻塞。

  

  ii)重復利用現有服務器的多核資源。

  

  iii)充分利用CPU 親和性(affinity),把每個進程與固定CPU綁定在一起,給定的 CPU 上盡量長時間地運行而不被遷移到其他處理器的傾向性,減少進程調度開銷。

  

  iiii)請求響應快

  

  iiiii)支持模塊化開發,擴展性好

  

  iiiii)Master+多worker進程方式,確保worker進程可靠工作。當worker進程出錯時,可以快速拉起新的worker子進程來提供服務。

  

  iiiiii)內存池、連接池等細節設計保障低內存消耗。

  

  iiiiii)熱部署支持,master與worker進程分離設計模式,使其具有熱部署功能。

  

  iiiiiii)升級方便,升級過程不會對業務造成任何傷害。

  

  Nginx多進程提供服務過程如下圖所示:

  

  4 Nginx master+worker多進程機制在twemproxy中的應用

  

  4.1 為什麽選擇nginx多進程機制做為參考?

  

  Twemproxy和nginx都屬於網絡io密集型應用,都屬於七層轉發應用,時延要求較高,應用場景基本相同。

  

  Nginx充分利用了多核cpu資源,性能好,時延低。

  

  4.2 Master-worker多進程機制原理

  

  Master-worker進程機制采用一個master進程來管理多個worker進程。每一個worker進程都是繁忙的,它們在真正地提供服務,master進程則很“清閑”,只負責監控管理worker進程, 包含:接收來自外界的信號,向各worker進程發送信號,監控worker進程的運行狀態,當worker進程退出後(異常情況下),會自動重新啟動新的worker進程。

  

  worker進程負責處理客戶端的網絡請求,多個worker進程同時處理來自客戶端的不同請求,worker進程數可配置。

  

  4.3 多進程關鍵性能問題點

  

  master-worker多進程模式需要解決的問題主要有:

  

  i)linux內核低版本(2.6以下版本), “驚群”問題

  

  ii) linux內核低版本(2.6以下版本),負載均衡問題

  

  iii)linux內核高版本(3.9以上版本)新特性如何利用

  

  iii)如何確保進程見高可靠通信

  

  iiii)如何減少worker進程在不同cpu切換的開銷

  

  iiiii)master進程如何匯總各個工作進程的監控數據

  

  iiiiii)worker進程異常,如何快速恢復

  

  4.3.1 linux內核低版本關鍵技術問題

  

  由於linux低內核版本缺陷,因此存在”驚群”、負載不均問題,解決辦法完全依賴應用層代碼保障。

  

  4.3.1.1 如何解決“驚群”問題

  

  當客戶端發起連接後,由於所有的worker子進程都監聽著同一個端口,內核協議棧在檢測到客戶端連接後,會激活所有休眠的worker子進程,最終只會有一個子進程成功建立新連接,其他子進程都會accept失敗。

  

  Accept失敗的子進程是不應該被內核喚醒的,因為它們被喚醒的操作是多余的,占用本不應該被占用的系統資源,引起不必要的進程上下文切換,增加了系統開銷,同時也影響了客戶端連接的時延。

  

  “驚群”問題是多個子進程同時監聽同一個端口引起的,因此解決的方法是同一時刻只讓一個子進程監聽服務器端口,這樣新連接事件只會喚醒唯一正在監聽端口的子進程。

  

  因此“驚群”問題通過非阻塞的accept鎖來實現進程互斥accept(),其原理是:在worker進程主循環中非阻塞trylock獲取accept鎖,如果trylock成功,則此進程把監聽端口對應的fd通過epoll_ctl()加入到本進程自由的epoll事件集;如果trylock失敗,則把監聽fd從本進程對應的epoll事件集中清除。

  

  Nginx實現了兩套互斥鎖:基於原子操作和信號量實現的互斥鎖、基於文件鎖封裝的互斥鎖。考慮到鎖的平臺可移植性和通用性,改造twemproxy選擇時,選擇文件鎖實現。

  

  如果獲取accept鎖成功的進程占用鎖時間過長,那麽其他空閑進程在這段時間內無法獲取到鎖,從而無法接受新的連接。最終造成客戶端連接相應時間變長,qps低,同時引起負載嚴重不均衡。為了解決該問題,選擇通過post事件隊列方式來提高性能,trylock獲取到accept鎖成功的進程,其工作流程如下:

  

  1.trylock獲取accept鎖成功

  

  2.通過epoll_wait獲取所有的事件信息,把監聽到的所有accept事件信息加入accept_post列表,把已有連接觸發的讀寫事件信息加入read_write_post列表。

  

  3.執行accept_post列表中的所有事件

  

  4.Unlock鎖

  

  5.執行read_write_post列表中的事件。

  

  Worker進程主循環工作流程圖如下:

  

  從上圖可以看出,worker進程借助epoll來實現網絡異步收發,客戶端連接twemproxy的時候,worker進程循環檢測客戶端的各種網絡事件和後端memcached的網絡事件,並進行相應的處理。

  

  twemproxy各個進程整體網絡i/o處理過程圖如下:

  

  4.3.1.2 如何解決“負載均衡“問題

  

  在多個子進程爭搶處理同一個新連接事件時,一定只有一個worker子進程最終會成功建立連接,隨後,它會一直處理這個連接直到連接關閉。這樣,如果有的子進程“運氣”很好,它們搶著建立並處理了大部分連接,其他子進程就只能處理少量連接,這對多核cpu架構下的應用很不利。理想情況下,每個子進程應該是平等的,每個worker子進程應該大致平均的處理客戶端連接請求。如果worker子進程負載不均衡,必然影響整體服務的性能。

  

  nginx通過連接閾值機制來實現負載均衡,其原理如下:每個進程都有各自的最大連接數閾值max_threshold和當前連接閾值數local_threshold,和當前連接數閾值,進程每接收一個新的連接,local_threshold增一,連接斷開後,local_threashold減一。如果local_threshold超過max_threshold,則不去獲取accept鎖,把accept機會留給其他進程,同時把local_threshold減1,這樣下次就有機會獲取accept鎖,接收客戶端連接了。

  

  在實際業務應用中,有的業務采用長連接和twemproxy建立連接,連接數最大可能就幾百連接,如果設置max_threshold閾值過大,多個連接如果同時壓到twemproxy,則很容易引起所有連接被同一個進程獲取從而造成不均衡。

  

  為了盡量減少負載不均衡,在實際應用中,新增了epoll_wait超時時間配置選項,把該超時時間設短,這樣減少空閑進程在epoll_wait上的等待事件,從而可以更快相應客戶端連接,並有效避免負載不均衡。

  

  4.3.2 Linux內核高版本TCP REUSEPORT特性如何利用

  

  4.3.2.1 什麽是reuseport?

  

  reuseport是一種套接字復用機制,它允許你將多個套接字bind在同一個IP地址/端口對上,這樣一來,就可以建立多個服務來接受到同一個端口的連接。

  

  4.3.2.2 支持reuseport和不支持reuseport的區別

  

  如果linux內核版本小於3.9,則不支持reuseport(註:部分centos發行版在低版本中已經打了reuseport patch,所以部分linux低版本發行版本也支持該特性)。

  

  不支持該特性的內核,一個ip+port組合,只能被監聽bind一次。這樣在多核環境下,往往只能有一個線程(或者進程)是listener,也就是同一時刻只能由一個進程或者線程做accept處理,在高並發情況下,往往這就是性能瓶頸。其網絡模型如下:

  

  在Linux kernel 3.9帶來了reuseport特性,它可以解決上面的問題,其網絡模型如下:

  

  reuseport是支持多個進程或者線程綁定到同一端口,提高服務器程序的吞吐性能,其優點體現在如下幾個方面:

  

  i)允許多個套接字 bind()/listen() 同一個TCP/UDP端口

  

  ii)每一個線程擁有自己的服務器套接字

  

  iii)在服務器套接字上沒有了鎖的競爭,因為每個進程一個服務器套接字

  

  iiii)內核層面實現負載均衡

  

  iiiii)安全層面,監聽同一個端口的套接字只能位於同一個用戶下面

  

  4.3.3 Master進程和worker進程如何通信?

  

  由於master進程需要實時獲取worker進程的工作狀態,並實時匯總worker進程的各種統計信息,所以選擇一種可靠的進程間通信方式必不可少。

  

  在twemproxy改造過程中,直接參考nginx的信號量機制和channel機制(依靠socketpair)來實現父子進程見通信。Master進程通過信號量機制來檢測子進程是否異常,從而快速直接的反應出來;此外,借助socketpair,封裝出channel接口來完成父子進程見異步通信,master進程依靠該機制來統計子進程的各種統計信息並匯總,通過獲取來自master的匯總信息來判斷整個twemproxy中間件的穩定性、可靠性。

  

  配置下發過程:主進程接收實時配置信息,然後通過channel機制發送給所有的worker進程,各個worker進程收到配置信息後應答給工作進程。流程如下:

  

  獲取監控信息流程和配置下發流程基本相同,master進程收到各個工作進程的應答後,由master進程做統一匯總,然後發送給客戶端。

  

  4.3.4 如何減少worker進程在不同cpu切換的開銷

  

  CPU 親和性(affinity) 就是進程要在某個給定的 CPU 上盡量長時間地運行而不被遷移到其他處理器的傾向性。

  

  Linux 內核進程調度器天生就具有被稱為 軟 CPU 親和性(affinity) 的特性,這意味著進程通常不會在處理器之間頻繁遷移。這種狀態正是我們希望的,因為進程遷移的頻率小就意味著產生的負載小。具體參考sched_setaffinity函數。

  

  4.3.5 worker進程異常如何減少對業務的影響?

  

  在實際線上環境中,經常出現這樣的情況:某個多線程服務跑幾個月後,因為未知原因進程掛了,最終造成整個服務都會不可用。

  

  這時候,master+多worker的多進程模型就體現了它的優勢,如果代碼有隱藏的並且不容易觸發的bug,某個時候如果某個請求觸發了這個bug,則處理這個請求的worker進程會段錯誤退出。但是其他worker進程不會收到任何的影響,也就是說如果一個改造後的twemproxy起了20個worker進程,某個時候一個隱藏bug被某個請求觸發,則只有處理該請求的進程段錯誤異常,其他19個進程不會受到任何影響,該隱藏bug觸發後影響面僅為5%。如果是多線程模型,則影響面會是100%。

  

  如果某個worker進程掛了,master父進程會感知到這個信號,然後重新拉起一個worker進程,實現瞬間無感知”拉起”恢復。以下為模擬觸發異常段錯誤流程:

  

  如上圖所示,殺掉31420 worker進程後,master進程會立馬在拉起一個31451工作進程,實現了快速恢復。

  

  多進程異常,自動”拉起”功能源碼,可以參考如下demo:

  

  https://github.com/y123456yz/reading-code-of-nginx-1.9.2/blob/master/nginx-1.9.2/src/demo.c

  

  5 網絡優化

  

  5.1 網卡多隊列

  

  在實際上線後,發現軟中斷過高,幾乎大部分都集中在一個或者幾個CPU上,嚴重影響客戶端連接和數據轉發,qps上不去,時延抖動厲害。

  

  RSS(Receive Side Scaling)是網卡的硬件特性,實現了多隊列,可以將不同的流分發到不同的CPU上。支持RSS的網卡,通過多隊列技術,每個隊列對應一個中斷號,通過對每個中斷的綁定,可以實現網卡中斷在cpu多核上的分配,最終達到負載均衡的作用。

  

  5.2 可怕的40ms

  

  原生twemproxy在線上跑得過程中,發現時延波動很大,抓包發現其中部分數據包應答出現了40ms左右的時延,拉高了整體時延抓包如下(借助tcprstat工具):

  

  解決辦法如下:在recv系統調用後,調用一次setsockopt函數,設置TCP_QUICKACK。代碼修改如下:

  

  6 Twemproxy改造前後性能對比 (時延、qps對比)

  

  6.1 線上真實流量時延對比

  

  6.1.1 改造前線上twemproxy集群時延

  

  線上集群完全采用開源twemproxy做代理,架構如下:

  

  未改造前線上twemproxy+memcache集群,qps=5000~6000,長連接,客戶端時延分布如下圖所示:

  

  在twemproxy機器上使用tcprstat監控到的網卡時延如下:

  

  從上面兩個圖可以看出,采用原生twemproxy,時延高,同時抖動厲害。

  

  6.1.2 參照nginx改造後的twemproxy時延

  

  線上集群一個twemproxy采用官方原生twemproxy,另一個為改造後的twemproxy,其中改造後的twemproxy配置worker進程數為1,保持和原生開源twemproxy進程數一致,架構如下:

  

  替換線上集群兩個代理中的一個後(影響50%流量),長連接,qps=5000~6000,客戶端埋點監控時延分布如下:

  

  替換兩個proxy中的一個後,使用tcprstat在代理集群上面查看兩個代理的時延分布如下:

  

  原生twemproxy節點機器上的時延分布:

  

  另一個改造後的twemproxy節點機器上的時延分布:

  

  總結:替換線上兩個proxy中的一個後,客戶端時間降低了一倍,如果線上集群兩個代理都替換為改造後的twemproxy,客戶端監控時延預計會再降低一倍,總體時延降低3倍左右。

  

  此外,從監控可以看出,改造後的twemproxy時延更低,更加穩定,無任何波動。

  

  6.2 參考nginx多進程改造後的twemproxy線下壓測結果(開啟reuseport功能)

  

  監聽同一個端口,數據長度100字節,壓測結果如下:

  

  linux內核版本:linux-3.10

  

  物理機機型: M10(48 cpu)

  

  多進程監聽同一個端口,數據長度150字節,壓測結果如下:

  

  linux內核版本:linux-3.10

  

  物理機機型: TS60 (24 cpu)

  

  7 總結

  

  7.1 多進程、多線程機制選擇

  

  選擇參照nginx多進程機制,而不選擇多線程實現原因主要有:

  

  1) 多進程機制無鎖操作,實現更容易

  

  2) 多進程的代理,整個worker進程無任何鎖操作,性能更好

  

  3) 如果是多線程方式,如果代碼出現bug段錯誤,則整個進程掛掉,整個服務不可用。而如果是多進程方式,因為bug觸發某個worker進程段錯誤異常,其他工作進程不會受到如何影響,20個worker進程,如果觸發異常,同一時刻只有有1/20的流量受到影響。而如果是多線程模式,則100%的流量會受到影響。

  

  4) worker進程異常退出後,master進程立馬感知拉起一個新進程提供服務,可靠性更高。

  

  5) 配置熱加載、程序熱升級功能實現更加容易

  

  7.2 參照nginx改造後的twemproxy特性

  

  支持nginx幾乎所有的優秀特性,同時也根據自己實際情況新增加了自有特性:

  

  1) master+多worker進程機制

  

  2) 適配所有linux內核版本,內核低版本驚群問題避免支持

  

  3) quic_ack支持

  

  4) reuser_port適配支持

  

  5) worker進程異常,master進程自動拉起功能支持

  

  6) 90%、95%、98%、100%平均時延統計功能支持

  

  7) memcache單機版、集群版支持

  

  8) redis單機版、集群版支持

  

  9) 二進制協議、文本協議同時支持

  

  10) redis、memcache集群在線擴容、縮容、數據遷移支持,擴縮容、數據遷移過程對業務無任何影響。

  

  11) 多租戶支持,一個代理可以接多個memcache、redis集群,並支持混部。

  

  12) mget、gets、sets等批量處理命令優化處理

  

  13) 慢響應日誌記錄功能支持

  

  14) 內存參數實時修改支持

  

  15) 詳細的集群監控統計功能

  

  16) CPU親緣性自添加

  

  17)內存配置動態實時修改

  

  7.3後期計劃

  

  添加如下功能:

  

  i) 配置文件熱加載支持。

  

  ii) 代碼熱升級功能支持。

  

  7.4 長遠規劃展望

  

  抽象出一款類似nginx的高性能代理軟件,nginx支持http協議,我們的支持tcp協議代理,覆蓋nginx所有功能,包括前面提到的所有功能,同時支持模塊化開發。這樣,很多的tcp協議代理就無需關心網絡架構底層實現,只需要根據需要開發對應的協議解析模塊,和自己關心的統計、審計等功能功能,降低開發成本。現有開源的中間件,很大一部分都是tcp的,有自己的私有tcp協議,把這個抽象出來,開發成本會更低

  

  對nginx有興趣的可以源碼分析參考:

  

  1 import re

2 import requests

3

4

5 url = "https://www.newegg.cn/Product/A36-125-E5L.htm?neg_sp=Home-_-A36-125-E5L-_-CountdownV1"

6 res = requests.get(url)

7 result = re.findall("name:‘www.yongshi123.cn(.+?)‘, price:‘(.+?)‘", res.text)

8 print(result)

復制代碼

難一點的方法是把圖片下載到本地之後進行識別,由於這個圖片的清晰度很高,也沒有扭曲或者加入幹擾線什麽的,所以可以直接使用OCR進行識別。但是用這種方法的話需要安裝好Tesseract-OCR,這個工具的安裝過程還是比較麻煩的。用這種方法破解的代碼如下:

復制代碼

1 import requests

2 import pytesseract

3 from PIL import Image

4 from lxml import etree

5

6

7 url = "https://www.wujirongyaoy.com /Product/A36-125-E5L.htm?neg_sp=Home-_-A36-125-E5L-_-CountdownV1"

8 res = requests.get(url)

9 et = etree.HTML(res.www.dfgjpt.com text)

10 img_url = et.xpath(‘//*[@id="priceValue"]/span/strong/img/@src‘)[0]

11 with open(www.yongshiyule178.com‘price.png‘www.yingka178.com,‘wb‘) as f:

12 f.write(requests.get(img_url).content)

13 pytesseract.pytesseract.tesseract_cmd www.jiahuayulpt.com= ‘E:/Python/Tesseract-OCR/tesseract.exe‘

14 text = pytesseract.image_to_string(Image.open(‘price.png‘))

15 print(text)

16 # 6999.00

  

  內核網卡時延分析工具:

  

  https://github.com/y123456yz/tcprstat

  

  twemproxy源碼分析:

  

  https://github.com/y123456yz/Reading-and-comprehense-twemproxy0.4.1

  

  內核協議棧延遲確認機制:

  

  https://github.com/y123456yz/Reading-and-comprehense-linux-Kernel-network-protocol-stack

  Django的rest_framework的視圖之Mixin類編寫視圖源碼解析    Mixin類編寫視圖    我們這裏用auther表來做演示,先為auther和autherdetail寫2個url    url(r‘^autherdetail/(?P<id>\d+)‘, views.Book_detail_cbv.as_view(), name="autherdetail"),    url(r‘^auther/‘, views.Book_cbv.as_view(),name="auther"),    然後分別為這2個類寫對應的序列化的類    class authermodelserializer(serializers.ModelSerializer):    class Meta:    model = models.Auther    fields = "__all__"    下面我們開寫視圖函數    需要在view文件中導入2個模塊    1    2    from rest_framework import mixins    from rest_framework import generics    先介紹一下mixins類,我們主要用mixins類來對queryset對象或者model對象做操作    mixins.ListModelMixin    這個是用來顯示queryset的數據    mixins.CreateModelMixin    這個用來創建一條model對象    mixins.RetrieveModelMixin    這個是用來顯示一個model對象    mixins.DestroyModelMixin    這個是用來刪除一個model對象    mixins.UpdateModelMixin    這個是用來更新一個model對象    下面我們一個一個來看下面的類    1、看下mixins.ListModelMixin    這個類就只有一個方法,list方法,我們看下面的代碼其實很熟悉,就是把一個queryset對象做序列化後,然後把序列化後的結果返回    class ListModelMixin(object):    """    List a queryset.    """    def list(self, request, *args, **kwargs):    queryset = self.filter_queryset(self.get_queryset())    page = self.paginate_queryset(queryset)    if page is not None:    serializer = self.get_serializer(page, many=True)    return self.get_paginated_response(serializer.data)    serializer = self.get_serializer(queryset, many=True)    return Response(serializer.data)    我們這裏看到get_serializer中的參數有個queryset,那麽這個queryset是什麽呢?    通過上面的圖,我們大致可以猜到,是由self.get_queryset()這個方法返回的結果,那麽這個方法又幹了什麽呢?    首先我們要清楚self是什麽?    從上面的圖我們知道,self其實就是Auther_view這個類的實例對象,這個實例對象根本就沒有get_queryset這個方法,但是由於這個類繼承了3個類,我們一個一個找,最終在    generics.GenericAPIView這個類中找到了get_queryset這個方法    def get_queryset(self):    """    Get the list of items for this view.    This must be an iterable, and may be a queryset.    Defaults to using `self.queryset`.    This method should always be used rather than accessing `self.queryset`    directly, as `self.queryset` gets evaluated only once, and those results    are cached for all subsequent requests.    You may want to override this if you need to provide different    querysets depending on the incoming request.    (Eg. return a list of items that is specific to the user)    """    assert self.queryset is not None, (    "‘%s‘ should either include a `queryset` attribute, "    "or override the `get_queryset()` method."    % self.__class__.__name__    )    queryset = self.queryset    if isinstance(queryset, QuerySet):    # Ensure queryset is re-evaluated on each request.    queryset = queryset.all()    return queryset    我們可以很清晰的看到get_queryset這個方法返回的結果就是self.queryset    那麽self.queryset這個是什麽呢?    我們在Auhter_view這個類中已經定義了這個類變量,所以我們這裏定義的2個類變量的名稱是固定的,不能隨意修改的,屬於配置項    下面我們走的流程就和之前差不多了    先定義get請求的處理的函數    因為mixins.ListModelMixin這個類是為了顯示queryset對象的類,那麽下面我們進入這個類    所以我們在get方法中,直接調用list方法的返回結果就是我們想要的結果    2、在來看mixin.CreateModelMixin類    這個類是為了創建一個model對象    首先進入這個類,看下具體的代碼    class CreateModelMixin(object):    """    Create a model instance.    """    def create(self, request,www.wujirongyaoy.com *args, **kwargs):    serializer = self.get_serializer(data=request.data)    serializer.is_valid(raise_www.yongshiyule178.com exception=True)    self.perform_create( www.dfgjpt.com serializer)    headers = self.get_success_headers(serializer.www.feifanyule.cn data)    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)    def perform_create(self, www.jiahuayulpt.com serializer):    serializer.save()    def get_success_headers(self, data):    try:    return {‘Location‘: www.yongshi123.cn str(data[api_settings.URL_FIELD_NAME])}    except (TypeError, www.dashuj5.com KeyError):    return {}    下面我們來分析一下代碼    首先這裏有個self.get_serializer方法,這個方法也在generics.GenericAPIView類中    下面我們在來看下get_serializer方法    def get_serializer(self, *args, **kwargs):    """    Return the serializer instance that should be used for validating and    deserializing input, and for serializing output.    """    serializer_class = self.get_serializer_class()    kwargs[‘context‘] = self.get_serializer_context()    return serializer_class(*args, **kwargs)    我們在來看下get_serializer_class這個方法    我們看到非常清楚,這個函數的返回值就是我們先前定義個serializer_class的類變量,所以這個類變量的名稱也不能修改,必須要這麽寫,屬於一個配置類的變量    流程我們已經梳理清楚了,下面我們在看下post請求的視圖函數    post請求調用的mixins.CreateModelMixin類中的create方法    class CreateModelMixin(object):    """    Create a model instance.    """    def create(self, request, *args, **kwargs):    serializer = self.get_serializer(data=request.data)    serializer.is_valid(raise_exception=True)    self.perform_create(serializer)    headers = self.get_success_headers(serializer.data)    return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)    最後我們在看下perform.create這個方法,是不是很熟悉了,調用save方法保存    3、然後來看下mixins.RetriveModelMixin類    先看下這個類的代碼    class RetrieveModelMixin(object):    """    Retrieve a model instance.    """    def retrieve(self, request, *args, **kwargs):    instance = self.get_object()    serializer = self.get_serializer(instance)    return Response(serializer.data)    然後在來看下get_object這個方法幹了什麽,這個方法同樣在generics.GenericAPIView類中,我們一猜就知道這個方法是獲取一個model對象,然後對這個model對象進行序列化處理    def get_object(self):    """    Returns the object the view is displaying.    You may want to override this if you need to provide non-standard    queryset lookups. Eg if objects are referenced using multiple    keyword arguments in the url conf.    """    queryset = self.filter_queryset(self.get_queryset())    # Perform the lookup filtering.    lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field    assert lookup_url_kwarg in self.kwargs, (    ‘Expected view %s to be called with a URL keyword argument ‘    ‘named "%s". Fix your URL conf, or set the `.lookup_field` ‘    ‘attribute on the view correctly.‘ %    (self.__class__.__name__, lookup_url_kwarg)    )    filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}    obj = get_object_or_404(queryset, **filter_kwargs)    # May raise a permission denied    self.check_object_permissions(self.request, obj)    return obj    我們看到這個方法確實返回一個obj對象    最後看下get請求,調用mixins。RetrieveModelMixin類中的retieve方法返回我們要查詢的結果    4、然後我們在看下mixins.DestroyModelMixin類    直接拿到model獨享,然後調用perform_destory方法刪除這個model對象    class DestroyModelMixin(object):    """    Destroy a model instance.    """    def destroy(self, request, *args, **kwargs):    instance = self.get_object()    self.perform_destroy(instance)    return Response(status=status.HTTP_204_NO_CONTENT)    def perform_destroy(self, instance):    instance.delete()    然後我們在看下視圖函數中是如何處理delete請求的    class Autherdetail_view(mixins.RetrieveModelMixin,mixins.DestroyModelMixin,mixins.UpdateModelMixin,generics.GenericAPIView):    queryset = models.Auther.objects.all()    serializer_class = authermodelserializer    def get(self,request,*args,**kwargs):    return self.retrieve(request,*args,**kwargs)    def delete(self,request,*args,**kwargs):    return self.destroy(request,*args,**kwargs)    直接返回mixins.DestoryModelMixins的detory函數的返回值就可以了    5、最後看下mixins.UpdateModelMixin類    同樣,先獲取model對象,然後獲取序列化類,然後把model對象和request.data一起傳遞給序列化類    序列化類在調用調用sava方法保存數據    class UpdateModelMixin(object):    """    Update a model instance.    """    def update(self, request, *args, **kwargs):    partial = kwargs.pop(‘partial‘, False)    instance = self.get_object()    serializer = self.get_serializer(instance, data=request.data, partial=partial)    serializer.is_valid(raise_exception=True)    self.perform_update(serializer)    if getattr(instance, ‘_prefetched_objects_cache‘, None):    # If ‘prefetch_related‘ has been applied to a queryset, we need to    # forcibly invalidate the prefetch cache on the instance.    instance._prefetched_objects_cache = {}    return Response(serializer.data)    def perform_update(self, serializer):    serializer.save()    def partial_update(self, request, *args, **kwargs):    kwargs[‘partial‘] = True    return self.update(request, *args, **kwargs)    我們在看put請求的視圖函數    class Autherdetail_view(mixins.RetrieveModelMixin,mixins.DestroyModelMixin,mixins.UpdateModelMixin,generics.GenericAPIView):    queryset = models.Auther.objects.all()    serializer_class = authermodelserializer    def get(self,request,*args,**kwargs):    return self.retrieve(request,*args,**kwargs)    def delete(self,request,*args,**kwargs):    return self.destroy(request,*args,**kwargs)    def put(self,request,*args,**kwargs):    return self.update(request,*args,**kwargs)

Nginx多進程高並發、低時延、高可靠機制在緩存代理中的應用