溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么給DPVS加上SESSION同步功能

發(fā)布時間:2021-12-17 16:22:28 來源:億速云 閱讀:215 作者:柒染 欄目:大數(shù)據(jù)

怎么給DPVS加上SESSION同步功能,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。

給DPVS加上SESSION同步功能

前言

DPVS是一款愛奇藝開源的基于DPDK的優(yōu)秀軟件(https://github.com/iqiyi/dpvs)。利用DPDK工作在用戶空間的特性,相比于內(nèi)核空間的LVS,我們可以使用用戶空間的一系列工具/中間件等完成很多在內(nèi)核空間很難完成的功能。

Just for fun

雖然筆者日常工作中是搞Java中間件開發(fā)的,但一直都對底層技術(shù)尤其是在網(wǎng)絡(luò)層面抱有很大的激情與好奇心。偶然接觸到DPDK這個用戶態(tài)數(shù)據(jù)平面開發(fā)套件,看了其官方文檔和源碼后,不禁技癢難耐,于是就嘗試在DPVS上增加一個Session同步功能。雖然和工作關(guān)系不大,但搞技術(shù)的樂趣不就在于不停的折騰么,Just For Fun!當然,由于精力原因,只是寫出了原型并測試成功,距離生產(chǎn)環(huán)境還有很大的距離,畢竟不靠這個吃飯^_^。

DPVS

DPVS事實上就是一個負載均衡軟件,源于LVS,我們常說的Virtual IP(VIP)就可以使用DPVS來支持,如下圖所示: 怎么給DPVS加上SESSION同步功能
這次筆者就是在DPVS在FullNAT模式下對于主從模式增加了Session同步功能。如下圖所示: 怎么給DPVS加上SESSION同步功能

沒有SESSION同步功能會如何

由于DPVS的數(shù)據(jù)轉(zhuǎn)發(fā)是通過內(nèi)部的session表來分發(fā)數(shù)據(jù)包的,如果沒有Session同步功能,那么對應(yīng)的數(shù)據(jù)庫由于找不到對應(yīng)的Session進而被丟棄。如果Client端是通過tcp進行連接的話: 怎么給DPVS加上SESSION同步功能
那么將會在配置的tcp重傳超時之后報錯。 怎么給DPVS加上SESSION同步功能

TCP Client RealServer

怎么給DPVS加上SESSION同步功能

如果SESSION同步會如何

怎么給DPVS加上SESSION同步功能
如果Session同步后,由于新晉升的DPVS2 Master依舊能夠知道將這個Packet發(fā)送到后面哪臺RealServer,如果是采用TCP連接的話,在一次重傳之后,依舊能夠保證連接的穩(wěn)定。

SESSION同步方法

筆者這次嘗試的是主從模式下FullNat的Session同步,事實上只需要將FullNat下的兩張Session表(Session_IN和Session_OUT)從Master同步到Slave即可。 怎么給DPVS加上SESSION同步功能

如果工作在內(nèi)核態(tài)的LVS如何同步

由于LVS這一類的軟件工作在內(nèi)核態(tài),那么就需要使用比較復(fù)雜且難于調(diào)試的問題進行主從之間的通信,如下圖所示: 怎么給DPVS加上SESSION同步功能
內(nèi)核態(tài)的調(diào)試由于比起用戶態(tài)來說相對復(fù)雜,而且沒什么好用的中間件,筆者就沒有做這方面的嘗試。

在用戶態(tài)筆者采用Redis Pub/Sub同步

而在用戶態(tài),可用的工具就太多了,于是筆者就選擇了使用Redis的訂閱/發(fā)布(Pub/Sub)功能將Session表信息從Master同步到Slave,如下圖所示: 怎么給DPVS加上SESSION同步功能
由于FullNat采用五元組,所以筆者在Redis中Pub的Key為:

session_key_(af協(xié)議簇)
			 _(proto協(xié)議)
			 _(client源地址)
			 _(client端口號)
			 _(vip地址)
			 _(vip端口號)
			 _(localIP)
			 _(localPort)
			 _(RealServer目的地址)
			 _(RS目的端口號)
			 _(當前session所在CPUID)

SESSION同步工作線程

首先,筆者在DPVS啟動的main函數(shù)除了DPVS的線程之外用pthread新建了兩個線程,用于reids的Send(Pub)和Receive(Sub)。 怎么給DPVS加上SESSION同步功能

線程間通信

發(fā)布信息到Redis

DPDK線程與Send/Recv線程間,同時ring_buffer進行通信。所以一開始創(chuàng)建的時候,就給每個DPDK線程創(chuàng)建了一個rte_ring(session_rings)。當每有新建連接動作時候,DPDK線程就會將新建連接的動作封裝成一個消息扔到里面,然后由SendPub線程去消費。如下圖所示: 怎么給DPVS加上SESSION同步功能
由于ring_buffer是有限的,可能出現(xiàn)消息丟失的現(xiàn)象。 新建連接的DPVS運行棧為:

__dp_vs_in
	|->conn_sched
		|->tcp_conn_sched (tcp協(xié)議)
			/* only TCP-SYN without other flag can be scheduled */
			/* 即只有TCP-SYN包才會走新建連接的邏輯 */
			|->dp_vs_schedule
				|->dp_vs_snat_schedule (FullNAT模式)					

在最終的dp_vs_snat_schedule代碼中,加入一段代碼:

static struct dp_vs_conn *dp_vs_snat_schedule(......)
{
	conn = dp_vs_conn_new(mbuf,iph,&param,dest,0);
	......
	// 加入把conn信息放入session_buffer的邏輯
	session_info_enqueue(conn);
	return conn;
}

放入邏輯,其實就是將conn的信息組裝成一個sesion_msg結(jié)構(gòu)體,然后將之前session_key的9個信息從conn中提取:

void session_info_enqueue(struct dp_vs_conn* conn){
	......
	int cid = rte_lcore_id();
	struct session_msg* msg;
	if(rte_mempool_get(message_pool,(void**)&msg) < 0){
		......
		return;
	}
	copy_conn_to_msg(conn,msg);
	if(rte_ring_enqueue(session_rings[cid],msg) != 0){
		...
		rete_mempool_put(message_pool,msg);
		return;
	}
}
從Redis訂閱消息

同樣的,有一個Recv(Sub)線程從Redis訂閱信息,然后Recv(Sub)線程和DPDK間的線程也用ring_buffer來同步,不過另用了一個session_subscribe_buffer。 怎么給DPVS加上SESSION同步功能
如圖中所示,從Redis訂閱到信息之后,將消息重新塞到session_subscribe_buffer(每個線程都有)里面。然后利用DPVS的job回調(diào)方法在每個線程中處理subscribe消息并通過此消息重建session表:

lcore_job_recv_fwd
	|->lcore_process_session_subscribe_ring

void lcore_process_session_subscribe_ring(...){
	struct rte_ring* ring = session_subscribe_rings[cid];
	...
	struct session_msg* msg;
	if(rte_ring_dequeue(ring,(void**)&msg) < 0){
		return;
	}
	new_dpvs_conn(msg);
	rte_mempool_put(message_pool,msg);
}

筆者在new_dpvs_conn里面做了FullNAT的兩張session表同步操作。

void dp_vs_conn_new_from_session(struct session_msg* msg){
	......
	/*init inbound conn tuple hash*/
	// SESSION IN 表項構(gòu)建
	t->af = msg->af;
	t->proto = msg->proto;
	......
	/*init outbound conn tuple hash*/
	// SESSION OUT 表項構(gòu)建
	new->af = msg->af;
	new->proto = msg->proto;
	......
	// 綁定dest
	err = dp_vs_conn_bind_dest(new,dest);
	......
	// 綁定hash表
	dp_vs_conn_hash(new);
}	

MQ消費重放

用Redis做Pub/Sub只是筆者為了保持編碼簡單而做的選擇。如果正式用在產(chǎn)線,筆者覺得還是要把這種Session發(fā)到Kafka這種queue里面,那么就可以將Session的變化落到本地。這樣,在主備都宕機的情況下,可以通過消費Kafka中已有的消息重建Session表。 怎么給DPVS加上SESSION同步功能

遇到的小坑

在筆者進行測試的時候,遇到的一個問題時,在Session同步之后,雖然Session表項同步無誤,但始終tcp連接被斷開,在加了各種Print判斷和TCP dump了一堆之后。才發(fā)現(xiàn),DPVS本身會對TCP的sequence進行重寫以增加toa字段,所以導(dǎo)致TCP sequence對不上,進而連接被斷開。為了簡單起見,筆者注掉了這段代碼,然后終于成功了!

static int tcp_fnat_in_handle(...)
{
	struct tcphdr *th;
	......
	// tcp_in_add_toa(conn,mbuf,th);
	// tcp_in_adjust_seq(conn,th);
	th->source = conn->lport;
	th->dest = conn->dport;
	
	return tcp_send_csum(af,iphdrlen,th,conn,mbuf);
}

不足之處

當前筆者只做了Session新建動作的同步,Session刪除等其它動作還需要慢慢斟酌。 另外,由于時間精力所限,筆者對DPVS的編碼只相當于做了一次簡單的原型驗證,還遠遠達不到產(chǎn)線高可用的要求。
不過,當測試成功,Master宕機后另一臺Slave立馬接上后,長連接(用的MySQL Client做測試)保持不斷,查詢數(shù)據(jù)依舊絲滑,仿佛什么都沒發(fā)生過的時候(如果沒有這個功能,只能坐等25s左右的卡主超時了,tcp_retries2=5),就感覺非常的有成就感!


看完上述內(nèi)容,你們掌握怎么給DPVS加上SESSION同步功能的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI