search
尋找貓咪~QQ 地點 桃園市桃園區 Taoyuan , Taoyuan

兩種負載均衡技術的實現原理與簡單示例

一. 概述

負載均衡作為目前伺服器集群部署的一款常用設備,當一台機器性能無法滿足業務的增長需求時,不是去找一款性能更好的機器,而是通過負載均衡,利用集群來滿足客戶增長的需求。

負載均衡技術的實現,主要分為以下幾種:

  • DNS 域名解析負載;

  • HTTP 重定向負載;

  • 反向代理負載;

  • IP 負載 (NAT 負載和 IP tunnel 負載);

  • 數據鏈路負載 (DR);

本篇主要討論 IP 負載和數據鏈路負載 (DR) 的原理, 並且給出 NAT 負載和 DR 負載的簡單代碼示例, 包括基於 netfilter 鉤子的定義,數據包的獲取,數據包的修改,報頭的設置,路由查找,數據包的發送。

1.1 名詞解釋

Load Balance: 負載均衡機器;

VIP(Virtual IP): Load Balance 面向前端客戶機器的請求地址;

RS(Real Server): Load Balance 進行負載的目標機器,面向客戶端真實提供服務的機器;RIP(Real Server IP):RS 的真實 IP;

CIP(Client IP): 客戶端 IP 地址,發送請求包中的源 IP 地址;

DIP(Director IP): Load Balance 與 RS 在同一局域的網卡 IP 地址 ;

二. 負載均衡的實現原理

2.1 TCP/IP 的通信原理

圖 1

TCP/IP 協議是當今網路世界中使用的最為廣泛的協議。圖 1 列出了 TCP/IP 與 OSI 分層之間的大致關係。由此可以看出,TCP/IP 與 OSI 在分層模塊上有一定的區別。TCP/IP 主要分為應用層,傳輸層,互聯網層,(網卡層) 數據鏈路層,硬體層。對於 IP 負載和數據鏈路負載更多的需要關注 IP 層和數據鏈路層。

圖 2

圖 2 通過簡單例子描述 TCP/IP 協議中 IP 層及以下的通信流程。

(1) Host1 查找本機路由表,根據目的 IP(192.168.16.188) 地址,確定下一步的路由地址 R1(192.168.32.1);

(2) Host1 根據路由 IP 地址 (192.168.32.1), 查找 ARP 緩存表,確定下一步 MAC 地址 (12-13-14-15-16-18);

(3) 產生數據封包,如圖所示,在 IP 層報頭,Src: 源 IP 地址,Dst: 目的 IP 地址;ethernet 表示數據鏈路層,乙太網報頭,SMAC: 源 MAC 地址,DMAC: 目的 MAC 地址;

(4) 數據包到達 R1 路由器,R1 重複 (1),(2) 兩步查找路由表和 ARP 緩存表,確定下一步數據包路由信息;

(5) 數據包到達 R2 路由器,R2 重複 (1),(2) 兩步查找路由表和 ARP 緩存表,確定下一步數據包路由信息;

(6) 數據包到達 R3 路由器,R3 重複 (1),(2) 兩步查找路由表和 ARP 緩存表,確定 Host2 主機 MAC 地址,發送數據包;

(7) Host2 主機接收數據包,比對 MAC 地址,IP 地址,進行數據包解析;

2.1.1 路由表和 ARP 緩存表

在整個數據包的傳輸中,重複的利用到兩張表,路由表和 ARP 緩存表。路由分為靜態路由和動態路由,靜態路由通常由管理員手工完成,動態路由由管理員設置好動態路由協議,動態路由協議會自動生成路由表。路由協議大致分為兩類:一類是外部網關協議 EGP,一類是內部網關協議 IGP。 EGP 使用 BGP 路由協議; IGP 使用 RIP,RIP2,OSPF 等路由協議;

圖 3

ARP 緩存表的生成主要依靠 ARP 協議,Host1(ip1) 將要發送數據給 Host3(ip3)。發送 ARP 廣播「誰能告訴我,ip3 的 MAC 地址是多少啊?」。Host2 收到廣播包,發現問的是 ip3 的地址,則不響應。Host3 收到數據包,發現 ip 地址與自己相符,則發迴響應包,「ip3 的 MAC 地址是 **。」Host1 收到響應包,緩存到 ARP 緩存表中。

在 Linux 主機 (CentOS 6.7), 路由表和 ARP 緩存表,查詢如下:

路由表:

[root@TendyRonSys-01 ~]# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 192.168.0.0 0.0.0.0 255.255.255.0 U 1 0 0 eth0 0.0.0.0 192.168.0.1 0.0.0.0 UG 0 0 0 eth0

Destination 和 Genmask 分別是 network 和 netmask,合起來表示一個網路。Flags:U 表示該路由是啟動的,可用;G 表示網關;ARP 緩存表:

[root@TendyRonSys-01 ~]# arp -a localhost (192.168.0.27) at 6c:62:6d:bf:25:2f [ether] on eth0 localhost (192.168.0.15) at 94:65:9c:2a:29:f2 [ether] on eth0 localhost (192.168.0.91) at e0:94:67:06:d9:ba [ether] on eth0 localhost (192.168.0.1) at ec:6c:9f:2c:d1:08 [ether] on eth0

2.1.2 IP 層與數據鏈路層的關係

由圖 1 可知,在 OSI 和 TCP/IP 的對比模型中,IP 層和數據鏈路層分別位於第二層和第三層。

數據鏈路層定義了通過通信媒介互連的設備之間的傳輸規範。而 IP 層是整個 TCP/IP 體系的核心,IP 層的作用是使數據分組發往任何網路,並使分組獨立地傳向目標。IP 層並不關心這些分組的傳輸路徑,也不保證每個分組的到達順序,甚至並不保證一定能夠到達。

舉個例子來說明 IP 層與數據鏈路層的關係。小 A 在 X 省 Y 市 Z 街道 Z1 號,通過網路訂購了商家 B(商家在 U 省 V 市) 的一個產品,商家通過快遞發送產品到小 A 的家中。

圖 4

圖 4 簡要顯示了整個商品的物流快遞流程:

  • 商品通過汽車走公路, 從商家到達 U 省快遞轉運中心;

  • 商品通過飛機從空中, 從 U 省快遞轉運中心達到 X 省快遞轉運中心;

  • 商品通過汽車走高速公路, 從 X 省快遞轉運中心到達 X 省 Y 市快遞點;

  • 商品通過電三輪走市內街道,從 X 省 Y 市快遞點到達客戶家 (X 省 Y 市 Z 街道 Z1 號)

在整個物流快遞流程中,分為了 4 個運輸區間,分別使用了公路,天空,高速公路和市內街道。

再來看下 IP 層的數據分組傳輸示意圖:

圖 5

在整個數據分組的傳輸過程中,分別經過了乙太網,IP_VPN, 千兆乙太網,ATM 等數據鏈路。

兩張圖相比是不是很相像?數據鏈路層其實就是利用物理層的傳輸媒介定義出相應互連設備的傳輸規則,包括數據封裝,MAC 定址,差錯檢測,網路拓撲,環路檢測等等。其實數據鏈路層就是網路傳輸的最小單元,所以有人說過整個互聯網其實就是「數據鏈路的集合」。

IP 層在數據鏈路層之上,實現了終端節點之間的端到端的通信。在上面兩個圖可以看出,在整個傳輸過程中,無論是在哪個區間,數據分組 (快遞包裹) 的源地址和目的地址始終沒有變化。當跨越多個數據鏈路層時,IP 層必不可少,通過 IP 標出了數據包的真正目的地址。從 IP 層來看,互聯網是由路由器連接的網路組合而成。路由器通過動態路由協議生成的路由表,更像一種互聯網中的地圖,但是因為互聯網的龐大,在每個路由上保存的是個局部地圖。

數據分組通過 IP 標出源地址和目的地址,這就像快遞包裹上也僅僅是標出了寄件地址和收件地址,並不會標出快遞第一步從哪裡到達哪裡,第二步從哪裡達到哪裡…。從源地址到目的地址的整個傳輸路徑是一步一步通過查詢路由表來確定的,這就像快遞的運輸流程,參考圖 4,先到達 U 省快遞中心,再達到 X 省快遞轉運中心…, 在每一個運輸區間內,通過特定的運輸線路保證快遞的運輸,到達下一個站點后,由下一個運輸區間來進行下一階段的運輸。在數據分組的傳輸過程中,也是通過目的地址,查詢路由表確定每一步的區間目的地址 (並不是一次性查出,而是在每個路由器上,查詢下一步的目的地址),然後數據鏈路層通過 ARP 緩存表將 IP 地址轉為 MAC 地址,再通過數據鏈路層運輸。打個比喻來說在整個數據分組的傳輸過程中,IP 層的路由表給出了地圖,數據鏈路層提供了運輸工具,物理層提供了運輸的基礎——道路 (比如快遞運輸的公路,高速公路,天空)。

2.2 IP 負載和數據鏈路負載原理

理清了 IP 單播路由的原理,接下來我們來說 IP 負載和數據鏈路負載 (DR) 的原理。

IP 負載分為 NAT 負載和 IP 隧道負載 (tunnel)。

2.2.1 NAT 負載

圖 6

NAT 負載如圖所示,流程如下:

(1) 客戶端發送數據包到達 Load Balance,Load Balance 通過負載演算法計算,確定 RS;

(2) Load Balance 修改請求包 DST 地址 (VIP) 為 RIP。對於串接模式,可以不修改請求包 SRC 地址 (CIP);對於旁接模式必須修改 SRC 地址 (CIP) 為 DIP 地址;

(3) 數據包負載到 RS Host2A/RS Host2B,RS 處理數據,根據 SRC 地址,返迴響應包。

(4) Load Balance 修改響應包 SRC 地址為 VIP,DST 地址為 CIP;

(5) Load Balance 返迴響應包到客戶端;

NAT 負載主要是通過修改請求包 IP 層的目的地址來使請求包進行重新路由,達到負載的效果。在此過程中,是否修改請求包的 SRC 地址?需要根據網路拓撲和真實的需求來進行決定。

2.2.2 IP tunnel 負載

圖 7

IP 隧道負載 (tunnel) 流程如下:

(1) 客戶端發送數據包到達 Load Balance,Load Balance 通過負載演算法計算,確定 RS;

(2) Load Balance 進行 IP 封包,封成 IPIP 包,外層 IP 包為 Load Balance 的封包,DST 地址為負載 RS 的 RIP 地址;

(3) RS 解封 IPIP 包,因 VIP 地址一樣,則 RS 應用處理此請求;

(4) RS 根據內層 IP 包的 SRC 地址和 VIP 地址進行響應包封包;

(5) 響應包不用再次經過 Load Balance,直接返回客戶端;

Tunnel 模式中,Load Balance 並不修改請求包,而是通過與 RS 建立 tunnel,進行 IPIP 封包,將新的 IPIP 包重新路由,負載到 RS 上。

在 RS 上,通過載入 IPIP 包的解包程序,保證了對 IPIP 包的正常解包。同時RS 與 Load Balance 配置同一個 VIP,保證了響應包可以不通過 Load Balance,直接返回到客戶端。

2.2.3 數據鏈路負載 (DR)

圖 8

數據鏈路負載流程如下:

(1) 客戶端發送數據包到達 Load Balance,Load Balance 通過負載演算法計算,確定 RS;

(2) Load Balance 根據 RS 的 MAC 地址,修改數據包的目的 MAC 地址;

(3) RS 收到數據包,因 VIP 地址一樣,則 RS 應用處理此請求;

(4) RS 根據數據包的 SRC 地址和 DST 地址進行響應包封包;

(5) 響應包不用再次經過 Load Balance,直接返回客戶端;

數據鏈路負載 (DR) 模式中,Load Balance 通過修改請求包的目的 MAC 地址來達到請求包重新路由的目的。DR 與 tunnel 模式有一定的相同之處,都是通過 VIP 來保證響應包可以不經過 Load Balance。

2.2.4 三種負載方法的比較

在實現方式上,Load Balance 的目的就是通過負載演算法的計算,找到合適的 RS 機器,然後通過修改請求包,使請求包重新路由到 RS 上。NAT 模式是相對直接的方式,直接修改請求包的目的地址。雖然實現起來容易,但是這也限制了響應包也必須經過 Load Balance,這樣在 NAT 模式下 Load Balance 成為了系統的瓶頸。Tunnel 模式下,tunnel 通過建立 IP 隧道,即實現了對請求包的重新路由,也實現了對 VIP 的封裝。通過 Load Balance 與 RS 的 VIP 配置,保證了響應包的獨立返回,不必經過 Load Balance。DR 模式下,通過針對 MAC 層的修改,更直接的對請求包進行了重新路由,但是這也導致來的 DR 模式的局限性,不能跨網段。

在性能方面,IP tunnel 和 DR 模式,響應包都不需要經過 Load Balance,性能自然會高很多,能夠負載的機器也會增加很多。DR 模式與 IP tunnel 模式相比並不需要封裝和解析 IPIP 包,自然性能也會比有一定的提升。

在網路拓撲方面,DR 模式因為是從數據鏈路層負載,數據鏈路層是網路傳輸的最小單元,所以 DR 模式必然不能跨網段。IP tunnel 模式通過建立 IP tunnel 進行負載,IP 層實現的是終端節點端到端的通信,自然可以跨網段。

DR 模式,通過 VIP 來保證 RS 對請求包的響應。在 ARP 緩存表中,IP 地址與 MAC 地址進行一一對應,但是在 DR 模式下,VIP 會出現多個 MAC 地址,如何處理呢?解決方式就是在 DR 模式下需要對 RS 的 VIP 進行 ARP 抑制。這樣當區域網內廣播 ARP 包時,RS 的 VIP 網卡就不會進行響應,而只有負載均衡機器進行 ARP 響應,這樣就能保證針對 VIP 的請求包首先到達 Load Balance,由 Load Balance 進行負載計算,修改目的 MAC 地址后,再路由到 RS。

抑制 ARP 響應命令,假設 lo 綁定 VIP:

echo "1" >/proc/sys/net/ipv4/conf/lo/arp_ignore echo "2" >/proc/sys/net/ipv4/conf/lo/arp_announce echo "1" >/proc/sys/net/ipv4/conf/all/arp_ignore echo "2" >/proc/sys/net/ipv4/conf/all/arp_announce三. NAT 負載 / DR 負載代碼示例

3.1 Netfilter 簡介

Netfilter/Iptables 是 Linux2.4 之後的新一代的 Linux 防火牆機制。Netfilter 採用模塊化設計,具有良好的可擴展性。通俗的來說,就是在整個數據包的傳遞過程中,在若干個位置設置了 Hook,可以根據需要在響應的 Hook 處,登記相應的處理函數。

圖 9

(1) NF_IP_PRE_ROUTING:剛進入網路層的數據包通過此點,目的地址轉換可以在此點進行;

(2) NF_IP_LOCAL_IN:經路由決策后,送往本機的數據包通過此點,INPUT 包過濾可以在此點進行;

(3) NF_IP_FORWARD:通過本機要轉發的包數據包通過此點,FORWARD 包過濾可以在此點進行;

(4) NF_IP_LOCAL_OUT:本機進程發出的數據包通過此點,OUTPUT 包過濾可以在此點進行。

(5) NF_IP_POST_ROUTING:通過網路設備即將出去的數據包通過此點,源地址轉換可以(包括地址偽裝)在此點進行;

對於數據包的處理結果以下幾種:

(1) NF_DROP: 丟棄該數據包,主要用於數據包的過濾 ;

(2) NF_ACCEPT : 保留該數據包,該數據包繼續在協議棧中進行流轉;

(3) NF_STOLEN : 忘掉該數據包,該數據包交給 Hook 函數處理,協議棧忘掉該數據包;

(4) NF_QUEUE : 將該數據包插入到用戶空間;

(5) NF_REPEAT : 再次調用該 Hook 函數 ;

一個 Hook 處理函數 (鉤子) 主要包含三部分:

  • 載入時的初始化函數;

  • 卸載時的清理函數;

  • 鉤子執行時的處理函數;

詳情請參考下面的代碼示例。

3.2 驗證環境介紹

驗證環境 CentOS release 6.7,內核版本 2.6.32-573.el6.x86_64。

圖 10驗證環境介紹:

  • 192.168.0.199 作為 Load Balance 機器;

  • 192.168.0.27 作為應用部署機器;

  • 172.16.32.187 作為客戶端機器,模擬客戶端訪問;

  • 抑制 192.168.0.27 機器對 VIP 的 ARP 響應;

實驗介紹:

  • 在 NAT 負載示例中,客戶端發起請求,請求地址 172.16.32.202:18080,接收到正常響應;

  • 在 DR 負載示例中,客戶端發起請求,請求地址 172.16.32.202:28080,接收到正常響應;

以下代碼主要介紹了基於 netfilter 鉤子的定義,數據包的獲取,數據包的修改,報頭的設置,路由查找,數據包的發送。在代碼中,並沒有涉及到負載的演算法,連接狀態保持等功能。

3.3 NAT 負載示例

/* * load-nat * 基於 vip 的負載實現主方法 * author:zjg * since 2016-6-8 */ #include<linux/module.h> #include<linux/kernel.h> #include<linux/init.h> #include<linux/netfilter.h> #include<linux/skbuff.h> #include<linux/ip.h> #include<linux/netdevice.h> #include<linux/if_ether.h> #include<linux/if_packet.h> #include<linux/inet.h> #include<net/tcp.h> #include<linux/netfilter_ipv4.h> #include<linux/fs.h> #include<linux/uaccess.h> #include<linux/slab.h> #include<linux/time.h> #include<asm/current.h> #include<linux/sched.h> #include<linux/ctype.h> #include<net/route.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("zjg"); #define ETHALEN 14 #define SPLITCHAR ":" #define MAX_BUFFER 1000 #define printk_ip(info, be32_addr) printk("%s:%i :%s %d.%d.%d.%d\n",current->comm,current->pid,info,((unsigned char *)&(be32_addr))[0],((unsigned char *)&(be32_addr))[1],((unsigned char *)&(be32_addr))[2],((unsigned char *)&(be32_addr))[3]) #define ETH "eth0" #define IP_VS_XMIT(pf, skb, rt) \ do { \ (skb)->ipvs_property = 1; \ skb_forward_csum(skb); \ NF_HOOK(pf, NF_INET_LOCAL_OUT, (skb), NULL, \ (rt)->u.dst.dev, dst_output); \ } while (0) int testPirntTcpHead(struct tcphdr *tcph); struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net); static struct nf_hook_ops modify_ops; // 負載的常量設置 char *load_dip = "192.168.0.199"; char *load_rip = "192.168.0.27"; char *load_cip = "172.16.32.87"; /** * 鉤子函數 * 根據負載目標,進行負載計算 */ static unsigned int modify(unsigned int hooknum, struct sk_buff * skb, const struct net_device * in, const struct net_device * out, int (*okfn)(struct sk_buff *)){ //ip 包頭結構 struct iphdr *ip_header; //tcp 包頭結構 struct tcphdr *tcp_header; //ip 包長度和偏移量 unsigned int ip_hdr_off; unsigned int ip_tot_len; int oldlen; //tcp 目的埠、源埠 unsigned int destport; unsigned int srcport; //route struct rtable *rt; char * srcip = NULL; char * dstip = NULL; //1-request -1-response int requestOrresponse = 1; // 是否需要負載 bool isLoad = 0; /* 獲取 ip 包頭 */ ip_header = ip_hdr(skb); // 獲取 IP 包總長度和偏移量 ip_tot_len = ntohs(ip_header->tot_len); ip_hdr_off = ip_hdrlen(skb); oldlen = skb->len - ip_hdr_off; // 獲取 TCP 報頭 tcp_header = (void *)skb_network_header(skb) + ip_hdr_off; // 獲取目的埠 destport = ntohs(tcp_header->dest); // 獲取源埠 srcport = ntohs(tcp_header->source); //response if(srcport == 18080){ printk("come in response\n"); srcip = load_dip;//char *load_dip = "192.168.0.199"; dstip = load_cip; //char *load_cip = "172.16.32.87"; isLoad = 1; requestOrresponse = -1; }else if(destport == 18080){//request printk("come in request\n"); srcip = load_dip;//char *load_dip = "192.168.0.199"; dstip = load_rip;//char *load_rip = "192.168.0.27"; isLoad = 1; } if(isLoad){ printk("%s\n", "come in load!"); // 設置負載地址和源地址 ip_header->daddr = in_aton(dstip); ip_header->saddr = in_aton(srcip); // 計算 ip 檢驗和 ip_send_check(ip_header); // 計算 tcp 校驗和 tcp_header->check = 0; skb->csum = skb_checksum(skb,ip_hdr_off,skb->len-ip_hdr_off, 0); tcp_header->check = csum_tcpudp_magic(ip_header->saddr,ip_header->daddr, skb->len-ip_hdr_off,ip_header->protocol,skb->csum); skb->ip_summed = CHECKSUM_NONE; skb->pkt_type = PACKET_OTHERHOST;// 路由包,進行路由 // 查找路由 if(requestOrresponse<0){ rt = setRoute(in_aton(dstip),in_aton(load_dip),ip_header->protocol, RT_TOS(ip_header->tos),skb->dev->nd_net); }else{ rt = setRoute(in_aton(dstip),in_aton(srcip),ip_header->protocol, RT_TOS(ip_header->tos),skb->dev->nd_net); } // 丟棄舊路由,設置新路由 dst_release(skb_dst(skb)); skb_dst_set(skb,&rt->u.dst); // 發送數據包 IP_VS_XMIT(PF_INET, skb, rt); return NF_STOLEN; } return NF_ACCEPT; } /** * 路由查找方法 */ struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net){ int ret=-2; struct rtable *rt; struct flowi fl = { .oif = 0, .nl_u = { .ip4_u = { .daddr = d_addr, .saddr = s_addr, .tos = rtos, } }, .proto = protocol, }; ret=ip_route_output_key(nd_net,&rt, &fl); printk("route search ret:%d\n",ret); return rt; } /** * 系統回調初始化方法 */ static int __init init(void){ int ret; //set hook 的主要參數 // 鉤子主要函數 modify_ops.hook = modify; // 鉤子的載入點 modify_ops.hooknum = NF_INET_PRE_ROUTING; // 協議族名 modify_ops.pf = AF_INET; // 鉤子的優先順序 modify_ops.priority = NF_IP_PRI_FIRST; //register hook ret = nf_register_hook(&modify_ops); if (ret < 0) { printk("%s\n", "can't modify skb hook!"); return ret; } printk("%s\n", "hook register success!"); return 0; } /** * 系統回調函數清除 */ static void __exit fini(void){ nf_unregister_hook(&modify_ops); printk("%s\n", "remove modify load_vip module."); } // 定義初始化函數和清理函數 module_init(init); module_exit(fini);

3.4 DR 負載實現

/* * load_dr * 基於 DR 的負載實現主方法 * author:zjg * since 2017-6-8 */ #include<linux/module.h> #include<linux/kernel.h> #include<linux/init.h> #include<linux/netfilter.h> #include<linux/skbuff.h> #include<linux/ip.h> #include<linux/netdevice.h> #include<linux/if_ether.h> #include<linux/if_packet.h> #include<linux/inet.h> #include<net/tcp.h> #include<linux/netfilter_ipv4.h> #include<linux/fs.h> #include<linux/uaccess.h> #include<linux/slab.h> #include<linux/time.h> #include<asm/current.h> #include<linux/sched.h> #include<linux/ctype.h> MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("zjg"); #define printk_ip(info, be32_addr) printk("%s:%i :%s %d.%d.%d.%d\n",current->comm,current->pid,info,((unsigned char *)&(be32_addr))[0],((unsigned char *)&(be32_addr))[1],((unsigned char *)&(be32_addr))[2],((unsigned char *)&(be32_addr))[3]) #define IP_VS_XMIT(pf, skb, rt) \ do { \ (skb)->ipvs_property = 1; \ skb_forward_csum(skb); \ NF_HOOK(pf, NF_INET_LOCAL_OUT, (skb), NULL, \ (rt)->u.dst.dev, dst_output); \ } while (0) struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net); int testPirntTcpHead(struct tcphdr *tcph); static struct nf_hook_ops modify_ops; // 負載設置 char *load_dip = "192.168.0.199"; char *load_rip = "192.168.0.27"; char *load_cip = "172.16.32.87"; char *load_vip = "192.168.0.198"; /** * 鉤子函數 * 根據負載目標,進行負載計算 */ static unsigned int modify(unsigned int hooknum, struct sk_buff * skb, const struct net_device * in, const struct net_device * out, int (*okfn)(struct sk_buff *)){ /*ip 包頭結構 */ struct iphdr *ip_header; /* 路由 */ struct rtable *rt; ip_header = ip_hdr(skb); printk("hook_func is called.==============\n"); // 判斷訪問的目的地址,如果是 VIP 則進行負載 if(ip_header->daddr==in_aton(load_vip)){ // 根據真實伺服器地址進行路由查找 rt= setRoute(in_aton(load_rip),in_aton(load_dip), ip_header->protocol,RT_TOS(ip_header->tos),dev_net(skb->dev)); // 丟棄舊的路由信息 skb_dst_drop(skb); // 設置新的路由信息 skb_dst_set(skb, &rt->u.dst); // 將數據包進行發送 IP_VS_XMIT(PF_INET, skb, rt); return NF_STOLEN; } return NF_ACCEPT; } struct rtable * setRoute(__u32 d_addr,__u32 s_addr,__u16 protocol,u32 rtos,struct net * nd_net){ int ret=-2; struct rtable *rt; struct flowi fl = { .oif = 0, .nl_u = { .ip4_u = { .daddr = d_addr, .saddr = s_addr, .tos = rtos, } }, .proto = protocol, }; ret=ip_route_output_key(nd_net,&rt, &fl); printk("route search ret:%d\n",ret); return rt; } /** * 系統回調初始化方法 */ static int __init init(void){ int ret = 0; // 設置鉤子信息 modify_ops.hook = modify; modify_ops.hooknum = NF_INET_PRE_ROUTING; modify_ops.pf = AF_INET; modify_ops.priority = NF_IP_PRI_FIRST; //register hook ret = nf_register_hook(&modify_ops); if (ret < 0) { printk("%s\n", "can't modify skb hook!"); return ret; } printk("%s\n", "hook register success!"); return 0; } /** * 系統回調函數清除 */ static void __exit fini(void){ nf_unregister_hook(&modify_ops); printk("%s\n", "remove modify load_vip module."); } module_init(init); module_exit(fini);

針對 IP tunnel 的實現,請大家參考章文嵩博士的 LVS 或者參考 Linux2.6 的 IPIP 協議,在此就不列出。如果讀者想要對負載均衡的完整實現進行了解,建議可以閱讀章文嵩博士的 LVS 的源碼。

四. 結束語

本文主要介紹了 IP 負載、DR 負載的原理和基於 netfilter 鉤子的定義,數據包的獲取,數據包的修改,報頭的設置,路由查找,數據包的發送。作者在研究的過程中參考了章文嵩博士的 LVS 實現和 Linux 的源碼,在此感謝章文嵩博士和無數的開源代碼貢獻者。

作者介紹

藍胖子,工作十年,一直從事移動互聯網金融行業的開發,從 WAP1.2,到 WAP2.0;從 J2ME,BREW 到 Android,iOS。完成過中,農,工,建等各大銀行的手機銀行,手機支付等項目。目前供職於為金融行業提供安全技術解決方案的公司,職位為部門經理兼架構師,負責金融軟體產品的相關研發工作和公司業務系統的日常運維。

2017 年 8 月 10-11 日,聽雲聯合極客邦科技、InfoQ 將共同主辦國內第二屆應用性能管理大會 -APMCon 2017,會議的演講內容聚焦行業內最新的技術和最接地氣的實踐案例,共同探討 APM 相關的性能優化、技術方案以及創新思路,為更多的行業從業者指點應用效能提升的迷津。

為回饋 InfoQ 讀者,特奉上優惠碼:APMCon_0810,立減 99 元!

進入大會官網 www.apmcon.cn,了解更多專家信息吧!



熱門推薦

本文由 yidianzixun 提供 原文連結

寵物協尋 相信 終究能找到回家的路
寫了7763篇文章,獲得2次喜歡
留言回覆
回覆
精彩推薦