摘要 本文主要介紹了LVS系統采用2.4內核的NetFilter的功能實現負載均衡的基本原理和技術手段。主要介紹了3個比較重要的NetFilter鉤子函數,最後描述了LVS的平衡算法 Internet的快速增長使多媒體網絡服務器面對的訪問數量快速增加,服務器需要具備提供大量並發訪問服務的能力,因此對於大負載的服務器來講,CPU、I/O處理能力很快會成為瓶頸。由於單台服務器的性能總是有限的,簡單的提高硬件性能並不能真正解決這個問題。為此,必須采用多服務器和負載均衡技術才能滿足大量並發訪問的需要。Linux 虛擬服務器(Linux Virtual Servers,LVS) 使用負載均衡技術將多台服務器組成一個虛擬服務器。它為適應快速增長的網絡訪問需求提供了一個負載能力易於擴展,而價格低廉的解決方案。 1.LVS結構與工作原理 LVS的結構如圖1所示,它由前端的負載均衡器(Load Balancer,LB)和後端的真實服務器(Real Server,RS)群組成。RS間可通過局域網或廣域網連接。LVS的這種結構對用戶是透明的,用戶只能看見一台作為LB的虛擬服務器(Virtual Server),而看不到提供服務的RS群。 如圖1所示 當用戶的請求發往虛擬服務器,LB根據設定的包轉發策略和負載均衡調度算法將用戶請求轉發給RS。RS再將用戶請求結果返回給用戶。同請求包一樣,應答包的返回方式也與包轉發策略有關。 LVS的包轉發策略有三種: NAT (Network Address Translation)模式。LB收到用戶請求包後,LB將請求包中虛擬服務器的IP地址轉換為某個選定RS的IP地址,轉發給RS;RS將應答包發給LB,LB將應答包中RS的IP轉為虛擬服務器的IP地址,回送給用戶。 IP隧道 (IP Tunneling)模式。LB收到用戶請求包後,根據IP隧道協議封裝該包,然後傳給某個選定的RS;RS解出請求信息,直接將應答內容傳給用戶。此時要求RS和LB都要支持IP隧道協議。 DR(Direct Routing)模式。LB收到請求包後,將請求包中目標MAC地址轉換為某個選定RS的MAC地址後將包轉發出去,RS收到請求包後 ,可直接將應答內容傳給用戶。此時要求LB和所有RS都必須在一個物理段內,且LB與RS群共享一個虛擬IP。 2、IPVS軟件結構與實現 LVS軟件的核心是運行在LB上的IPVS,它使用基於IP層的負載均衡方法。IPVS的總體結構如圖2所示,它主要由IP包處理、負載均衡算法、系統配置與管理三個模塊及虛擬服務器與真實服務器鏈表組成。 如圖2所示 2.1 LVS對 IP包的處理模式 IP包處理用Linux 2.4內核的Netfilter框架完成。一個數據包通過Netfilter框架的過程如圖所示: 通俗的說,netfilter的架構就是在整個網絡流程的若干位置放置了一些檢測點(HOOK),而在每個檢測點上上登記了一些處理函數進行處理(如包過濾,NAT等,甚至可以是用戶自定義的功能)。 IP層的五個HOOK點的位置如下圖所示(copy from ) : 如圖3所示 NF_IP_PRE_ROUTING:剛剛進入網絡層的數據包通過此點(剛剛進行完版本號,校驗和等檢測),源地址轉換在此點進行; NF_IP_LOCAL_IN:經路由查找後,送往本機的通過此檢查點,INPUT包過濾在此點進行; NF_IP_FORWARD:要轉發的包通過此檢測點,FORWord包過濾在此點進行; NF_IP_LOCAL_OUT:本機進程發出的包通過此檢測點,OUTPUT包過濾在此點進行; NF_IP_POST_ROUTING:所有馬上便要通過網絡設備出去的包通過此檢測點,內置的目的地址轉換功能(包括地址偽裝)在此點進行。 在IP層代碼中,有一些帶有NF_HOOK宏的語句,如IP的轉發函數中有: NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,ip_forward_finish); //其中NF_HOOK宏的定義基本如下: #ifdef CONFIG_NETFILTER #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (list_empty(&nf_hooks[(pf)][(hook)]) ? (okfn)(skb) : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn))) #else /* !CONFIG_NETFILTER */ #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb) #endif /*CONFIG_NETFILTER*/ 如果在編譯內核時沒有配置netfilter時,就相當於調用最後一個參數,此例中即執行ip_forward_finish函數;否則進入HOOK點,執行通過nf_register_hook()登記的功能(這句話表達的可能比較含糊,實際是進入nf_hook_slow()函數,再由它執行登記的函數)。 NF_HOOK宏的參數分別為: pf:協議族名,netfilter架構同樣可以用於IP層之外,因此這個變量還可以有諸如PF_INET6,PF_DECnet等名字。 hook:HOOK點的名字,對於IP層,就是取上面的五個值; skb:顧名思義 indev:進來的設備,以strUCt net_device結構表示; outdev:出去的設備,以struct net_device結構表示; okfn:是個函數指針,當所有的該HOOK點的所有登記函數調用完後,轉而走此流程。 這些點是已經在內核中定義好的,除非你是這部分內核代碼的維護者,否則無權增加或修改,而在此檢測點進行的處理,則可由用戶指定。像packet filter,NAT,connection track這些功能,也是以這種方式提供的。正如netfilter的當初的設計目標--提供一個完善靈活的框架,為擴展功能提供方便。 如果我們想加入自己的代碼,便要用nf_register_hook函數,其函數原型為: int nf_register_hook(struct nf_hook_ops *reg) struct nf_hook_ops://結構 struct nf_hook_ops { struct list_head list; /* User fills in from here down. */ nf_hookfn *hook; int pf; int hooknum; /* Hooks are ordered in ascending priority. */ int priority; }; 其實,類似LVS的做法就是生成一個struct nf_hook_ops結構的實例,並用nf_register_hook將其HOOK上。其中list項要初始化為{NULL,NULL};由於一般在IP層工作,pf總是PF_INET;hooknum就是HOOK點;一個HOOK點可能掛多個處理函數,誰先誰後,便要看優先級,即priority的指定了。netfilter_ipv4.h中用一個枚舉類型指定了內置的處理函數的優先級: enum nf_ip_hook_priorities { NF_IP_PRI_FIRST = INT_MIN, NF_IP_PRI_CONNTRACK = -200, NF_IP_PRI_MANGLE = -150, NF_IP_PRI_NAT_DST = -100, NF_IP_PRI_FILTER = 0, NF_IP_PRI_NAT_SRC = 100, NF_IP_PRI_LAST = INT_MAX, }; hook是提供的處理函數,也就是我們的主要工作,其原型為: unsigned int nf_hookfn(unsigned int hooknum, struct sk_buff **skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)); 它的五個參數將由NFHOOK宏傳進去。 以上是NetFillter編寫自己模塊時的一些基本用法,接下來,我們來看一下LVS中是如何實現的。 3.LVS中Netfiler的實現 利用Netfilter,LVS處理數據報從左邊進入系統,進行IP校驗以後,數據報經過第一個鉤子函數NF_IP_PRE_ROUTING[HOOK1]進行處理;然後進行路由選擇,決定該數據報是需要轉發還是發給本機;若該數據報是發被本機的,則該數據經過鉤子函數NF_IP_LOCAL_IN[HOOK2]處理後傳遞給上層協議;若該數據報應該被轉發,則它被NF_IP_FORWARD[HOOK3]處理;經過轉發的數據報經過最後一個鉤子函數NF_IP_POST_ROUTING[HOOK4]處理以後,再傳輸到網絡上。本地產生的數據經過鉤子函數NF_IP_LOCAL_OUT[HOOK5]處理後,進行路由選擇處理,然後經過NF_IP_POST_ROUTING[HOOK4]處理後發送到網絡上。 當啟動IPVS加載ip_vs模塊時,模塊的初始化函數ip_vs_init( )注冊了NF_IP_LOCAL_IN[HOOK2]、NF_IP_FORWARD[HOOK3]、NF_IP_POST_ROUTING[HOOK4] 鉤子函數用於處理進出的數據報。 3.1 NF_IP_LOCAL_IN處理過程 用戶向虛擬服務器發起請求,數據報經過NF_IP_LOCAL_IN[HOOK2],進入ip_vs_in( )進行處理。如果傳入的是icmp數據報,則調用ip_vs_in_icmp( );否則繼續判斷是否為tcp/udp數據報,如果不是tcp/udp數據報,則函數返回NF_ACCEPT(讓內核繼續處理該數據報);余下情況便是處理tcp/udp數據報。首先,調用ip_vs_header_check( )檢查報頭,如果異常,則函數返回NF_DROP(丟棄該數據報)。接著,調用ip_vs_conn_in_get( )去ip_vs_conn_tab表中查找是否存在這樣的連接:它的客戶機和虛擬服務器的ip地址和端口號以及協議類型均與數據報中的相應信息一致。如果不存在相應連接,則意味著連接尚未建立,此時如果數據報為tcp的sync報文或udp數據報則查找相應的虛擬服務器;如果相應虛擬服務器存在但是已經滿負荷,則返回NF_DROP;如果相應虛擬服務器存在並且未滿負荷,那麼調用ip_vs_schedule( )調度一個RS並創建一個新的連接,如果調度失敗則調用ip_vs_leave( )繼續傳遞或者丟棄數據報。如果存在相應連接,首先判斷連接上的RS是否可用,如果不可用則處理相關信息後返回NF_DROP。找到已存在的連接或建立新的連接後,修改系統記錄的相關信息如傳入的數據報的