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的轉寄函數中有:
<-ipforward.c ip_forward()-> NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,ip_forward_finish);//其中NF_HOOK宏的定義基本如下:<-/include/linux/netfilter.h->#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。找到已存在的串連或建立新的串連後,修改系統記錄的相關資訊如傳入的資料報的個數等。如果這個串連在建立時綁定了特定的資料報傳輸函數,調用這個函數傳輸資料報,否則返回 NF_ACCEPT。
ip_vs_in()調用的ip_vs_in_icmp( )處理icmp報文。函數開始時檢查資料報的長度,如果異常則返回NF_DROP。函數只處理由tcp/udp報文傳送錯誤引起的目的不可達、源端被關閉或逾時的icmp報文,其他情況則讓核心處理。針對上述三類報文,首先檢查檢驗和。如果檢驗和錯誤,直接返回NF_DROP;否則,分析返回的icmp差錯資訊,尋找相應的串連是否存在。如果串連不存在,返回NF_ACCEPT;如果串連存在,根據串連資訊,依次修改差錯資訊包頭的ip地址與連接埠號碼及 ICMP資料報包頭的ip地址,並重新計算和修改各個包頭中的檢驗和,之後尋找路由調用ip_send( )發送修改過的資料報,並返回NF_STOLEN(退出資料報的處理過程)。
ip_vs_in()調用的函數ip_vs_schedule( )為虛擬伺服器調度可用的RS並建立相應串連。它將根據虛擬伺服器綁定的調度演算法分配一個RS,如果成功,則調用ip_vs_conn_new( )建立串連。ip_vs_conn_new( )將進行一系列初始化操作:設定串連的協議、ip地址、連接埠號碼、協議逾時資訊,綁定application helper、RS和資料報傳輸函數,最後調用ip_vs_conn_hash( )將這個串連插入雜湊表ip_vs_conn_tab中。一個串連繫結資料報傳輸函數,依據IPVS工作方式可分為ip_vs_nat_xmit( )、ip_vs_tunnel_xmit( )、ip_vs_dr_xmit( )。例如ip_vs_nat_xmit( )的主要操作是:修改報文的目的地址和目的連接埠為RS資訊,重新計算並設定檢驗和,調用ip_send( )發送修改後的資料報。
3.2 NF_IP_FORWARD處理過程
資料報進入NF_IP_FORWARD後,將進入ip_vs_out( )進行處理。這個函數只在NAT方式下被調用。它首先判斷資料報類型,如果為icmp資料報則直接調用ip_vs_out_icmp( );其次判斷是否為tcp/udp資料報,如果不是這二者則返回NF_ACCEPT。餘下就是tcp/udp資料報的處理。首先,調用 ip_vs_header_check( )檢查前序,如果異常則返回NF_DROP。其次,調用ip_vs_conn_out_get( )判斷是否存在相應的串連。若不存在相應串連:調用ip_vs_lookup_real_service( )去雜湊表中尋找發送資料報的RS是否仍然存在,如果RS存在且報文是tcp非複位報文或udp 報文,則調用icmp_send( )給RS發送目的不可達icmp報文並返回NF_STOLEN;其餘情況下均返回NF_ACCEPT。若存在相應串連:檢查資料報的檢驗和,如果錯誤則返回NF_DROP,如果正確,修改資料報,將源地址修改為虛擬伺服器ip地址,源連接埠修改為虛擬伺服器連接埠號碼,重新計算並設定檢驗和,並返回 NF_ACCEPT。
ip_vs_out_icmp( )的流程與ip_vs_in_icmp( )類似,只是修改資料報時有所區別:ip前序的源地址和差錯資訊中udp或tcp前序的目的地址均修改為虛擬伺服器地址,差錯資訊中udp或tcp前序的目的連接埠號碼修改為虛擬伺服器的連接埠號碼。
3.3 NF_IP_POST_ROUTING處理過程
NF_IP_POST_ROUTING鉤子函數只在NAT方式下使用。資料報進入NF_IP_POST_ROUTING後,由 ip_vs_post_routing( )進行處理。它首先判斷資料報是否經過IPVS,如果未經過則返回NF_ACCEPT;否則立刻傳輸資料報,函數返回NF_STOLEN,防止資料報被 iptable的規則修改。
4.LVS系統配置與管理
IPVS模組初始化時註冊了setsockopt/getsockopt( ),ipvsadm命令調用這兩個函數向IPVS核心模組傳遞ip_vs_rule_user結構的系統配置資料,完成系統的配置,實現虛擬伺服器和RS 地址的添加、修改、刪除操作。系統通過這些操作完成對虛擬伺服器和RS鏈表的管理。
虛擬伺服器的添加操作由ip_vs_add_service( )完成,該函數根據雜湊演算法向虛擬伺服器雜湊表添加一個新的節點,尋找使用者設定的調度演算法並將此演算法綁定到該節點;虛擬伺服器的修改由 ip_vs_edit_service( )完成,此函數修改指定伺服器的調度演算法;虛擬伺服器的刪除由ip_vs_del_service( )完成,在刪除一個虛擬伺服器之前,必須先刪除此虛擬伺服器所帶的所有RS,並解除虛擬伺服器所綁定的調度演算法。
與之類似,RS的添加、修改、刪除操作分別由ip_vs_add_dest( )、ip_vs_edit_dest( )和ip_vs_edit_server( )完成。
4. 負載平衡調度演算法
前面已經提到,使用者在添加一個虛擬服務時要綁定調度演算法,這由ip_vs_bind_scheduler( )完成,調度演算法的尋找則由ip_vs_scheduler_get( )完成。ip_vs_scheduler_get( )根據調度演算法的名字,調用ip_vs_sched_getbyname( )從調度演算法隊列中尋找此調度演算法,如果沒找到則載入相應調度演算法模組再尋找,最後返回尋找結果。
目前系統有八種負載平衡調度演算法,具體如下:
- rr:輪循調度(Round-Robin) 它將請求依次分配不同的RS,也就是在RS中均攤請求。這種演算法簡單,但是只適合於RS處理效能相差不大的情況。
- wrr:加權輪循調度(Weighted Round-Robin) 它將依據不同RS的權值分配任務。權值較高的RS將優先獲得任務,並且分配到的串連數將比權值較低的RS更多。相同權值的RS得到相同數目的串連數。
- dh:目的地址雜湊調度 (Destination Hashing) 以目的地址為關鍵字尋找一個靜態hash表來獲得需要的RS。
- sh:源地址雜湊調度(Source Hashing) 以源地址為關鍵字尋找一個靜態hash表來獲得需要的RS。
- Lc:最小串連數調度(Least-Connection) IPVS表格儲存體了所有的活動的串連。把新的串連請求發送到當前串連數最小的RS。
- Wlc:加權最小串連數調度(Weighted Least-Connection) 假設各台RS的權值依次為WiI = 1..n),當前的TCP串連數依次為TiI=1..n),依次選取Ti/Wi為最小的RS作為下一個分配的RS。
- Lblc:基於地址的最小串連數調度(Locality-Based Least-Connection) 將來自同一目的地址的請求分配給同一台RS如果這台伺服器尚未滿負荷,否則分配給串連數最小的RS,並以它為下一次分配的首先考慮。
- Lblcr:基於地址的帶重複最小串連數調度(Locality-Based Least-Connection with Replication) 對於某一目的地址,對應有一個RS子集。對此地址的請求,為它分配子集中串連數最小的RS;如果子集中所有的伺服器均已滿負荷,則從叢集中選擇一個串連數較小的伺服器,將它加入到此子集並分配串連;若一定時間內,這個子集未被做任何修改,則將子集中負載最大的節點從子集刪除。