掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
仔細(xì)詢問(wèn)了下訂單信息,乖乖,原來(lái)用的是6年前開(kāi)發(fā)的一個(gè)功能,要知道這個(gè)功能自上線后基本很少用,不知道為什么現(xiàn)在開(kāi)始用起來(lái)了,只能先放棄摸魚(yú),先配合解決問(wèn)題,畢竟要靠這個(gè)來(lái)吃飯的。

創(chuàng)新互聯(lián)建站擁有網(wǎng)站維護(hù)技術(shù)和項(xiàng)目管理團(tuán)隊(duì),建立的售前、實(shí)施和售后服務(wù)體系,為客戶提供定制化的網(wǎng)站設(shè)計(jì)制作、網(wǎng)站建設(shè)、網(wǎng)站維護(hù)、四川電信科技城機(jī)房解決方案。為客戶網(wǎng)站安全和日常運(yùn)維提供整體管家式外包優(yōu)質(zhì)服務(wù)。我們的網(wǎng)站維護(hù)服務(wù)覆蓋集團(tuán)企業(yè)、上市公司、外企網(wǎng)站、商城網(wǎng)站建設(shè)、政府網(wǎng)站等各類(lèi)型客戶群體,為全球成百上千企業(yè)提供全方位網(wǎng)站維護(hù)、服務(wù)器維護(hù)解決方案。
在廣告中,經(jīng)常有這樣一種需求場(chǎng)景,需要將某個(gè)廣告定向投放給某一批指定用戶。即假設(shè)有一個(gè)adid,指定了投放用戶1和用戶2,那么只有在用戶1和用戶2的流量請(qǐng)求過(guò)來(lái)的時(shí)候,才會(huì)返回給adid,而其他用戶的流量,均不會(huì)返回該adid。
一般情況下,指定用戶多達(dá)幾百萬(wàn)個(gè)甚至上千萬(wàn)個(gè),將這些用戶ID直接隨著廣告訂單推送過(guò)來(lái),顯然不切實(shí)際,所以當(dāng)時(shí)的設(shè)計(jì)方案是廣告主將包含有指定用戶ID的數(shù)據(jù)包上傳到廣告后臺(tái),然后生成一個(gè)url,而該url則隨著廣告訂單推送過(guò)來(lái)。在引擎中,有個(gè)服務(wù)專(zhuān)門(mén)訂閱了廣告訂單消息,如果發(fā)現(xiàn)該廣告訂單是指定用戶投放,則將url指向的數(shù)據(jù)包中的數(shù)據(jù)獲取后,進(jìn)行實(shí)時(shí)加載,這樣當(dāng)其用戶ID流量過(guò)來(lái)的時(shí)候,就能匹配上該廣告。
在開(kāi)始本節(jié)之前,我們不妨先思考幾分鐘,如果讓你來(lái)實(shí)現(xiàn)這個(gè)功能,該如何實(shí)現(xiàn)呢?
好了,讓我們把時(shí)間調(diào)回到2016年年底,產(chǎn)品提出該需求的時(shí)間點(diǎn)。
當(dāng)時(shí)看了該需求,還是蠻簡(jiǎn)單的。為了便于內(nèi)容理解,將加載定向包的服務(wù)稱(chēng)之為Retargeting。
Retargeting服務(wù)就兩個(gè)基本功能:
是不是很簡(jiǎn)單,代碼也非常好寫(xiě),下載定向包可以使用libcurl,也可以使用wget進(jìn)行下載然后讀取文件加載到內(nèi)存,當(dāng)時(shí)因?yàn)榕牌诒容^緊張,所以選擇了wget方式來(lái)實(shí)現(xiàn),因?yàn)閿?shù)據(jù)量比較大,所以使用redis作為倒排索引的存儲(chǔ)媒介。
假設(shè)有一個(gè)廣告訂單我們稱(chēng)之為ad,其包含一個(gè)定向包地址url,此時(shí)會(huì)做如下幾件事:
auto cmd = "wget -t 3 -c -r -nd -P /data1/data/ –delete-after -np -A .txt http://url.txt";
auto fp = popen(cmd.str().c_str(), "r");
if (!fp) {
return;
}
// 獲取本地包地址,如/data1/data/url.txt
fp = fopen(path.c_str(), "r");
std::string id;
char buf[128] = {'\0'};
while (fgets(buf, 128, fp)) {
id = buf;
boost::replace_all(id, " ", "");
boost::replace_all(id, "\r", "");
boost::replace_all(id, "\n", "");
noost::replace_all(id, "\^M", "");
if (!id.empty()) {
ids.emplace_back(id);
}
}
for (auto item : ids) {
redis_client_->SAdd(item, adid);
}好了,到了此處,Retargeting服務(wù)功能已經(jīng)基本都實(shí)現(xiàn)了。
在召回引擎中,當(dāng)流量來(lái)了之后,會(huì)先以用戶ID為key,從redis中獲取指定投放該設(shè)備ID的adid,然后返回。
代碼編譯完后,在測(cè)試環(huán)境下了個(gè)單,推送,然后模擬請(qǐng)求,召回,完美。
定向包功能,尤其是對(duì)于KA廣告主,是不屑使用的,畢竟他們財(cái)大氣粗,要的就是 ??腦白金?? 似的推廣效果,即 「只關(guān)注展示量,而不在乎是否有效果」 。奈何隨著國(guó)家政策的一步步調(diào)整,廣告行業(yè)都開(kāi)始勒緊褲腰帶過(guò)日子,之前的大廣告主也開(kāi)始關(guān)注投放效果了,畢竟 ??品效合一?? 嘛。于是,他們開(kāi)始挖掘了一批用戶,在后臺(tái)開(kāi)始投放,嘗試投放效果。隨著此類(lèi)定向包訂單越來(lái)越多,之前實(shí)現(xiàn)的Retargeting也開(kāi)始出現(xiàn)瓶頸了。。。
畢竟該功能使用不多,所以大部分情況下,產(chǎn)品或者運(yùn)營(yíng)提出問(wèn)題的時(shí)候,都會(huì)找個(gè)借口搪塞回去。直到某一天,產(chǎn)品直接甩出來(lái)一張圖,說(shuō)某個(gè)部門(mén)老大非??粗氐囊粋€(gè)廣告主的定向包投放曝光為0,并揚(yáng)言當(dāng)天解決不了,就通過(guò)其它渠道進(jìn)行投放。。。
既然是部門(mén)老大都找過(guò)來(lái)了,那就不得不找下原因了,于是從訂單的url是否有效開(kāi)始查起,定向包設(shè)備的有效覆蓋率,一直到整個(gè)訂單的推送時(shí)間,一切都正常,看來(lái)問(wèn)題出在ReTargeting服務(wù)了,服務(wù)正常,加載也正常,無(wú)意間看了下消費(fèi)進(jìn)度,不看不知道,一看嚇一跳。才剛剛消費(fèi)到昨天的訂單,也就是說(shuō)當(dāng)天要投放的訂單還沒(méi)開(kāi)始加載,怪不得還沒(méi)有曝光,就這進(jìn)度,有曝光才怪。
順便看了眼服務(wù)狀態(tài),乖乖,CPU占用這么低。。。
既然CPU占用這么低,那么有沒(méi)有可能從CPU占用這個(gè)角度進(jìn)行優(yōu)化呢,提升CPU占用,提高服務(wù)處理能力,這樣就能加快其加載速度了。對(duì)于這種,一般稍微有點(diǎn)經(jīng)驗(yàn)的,就會(huì)知道該怎么優(yōu)化,對(duì),就是使用 ??多線程?? 。
既然決定使用多線程,那么就得徹底點(diǎn),多線程處理訂單,在每個(gè)訂單中,又采用多線程進(jìn)行數(shù)據(jù)加載和處理,我們暫且稱(chēng)之為 ??M*N?? 多線程設(shè)計(jì)模型,如下:
在上圖中,采用多線程方式對(duì)Retargeting服務(wù)進(jìn)行優(yōu)化,假設(shè)此時(shí)有m個(gè)定向包訂單,則會(huì)同時(shí)有m個(gè)線程進(jìn)行處理,每個(gè)線程處理一個(gè)定向包訂單,看起來(lái)很完美,等等,會(huì)不會(huì)有其他問(wèn)題呢?要不然一開(kāi)始為什么就不這么設(shè)計(jì)呢?
大家知道,對(duì)于多線程程序, 「線程的執(zhí)行順序,完成時(shí)間是不可控的」 ,使用上述設(shè)計(jì)方案,如果多個(gè)線程 「同時(shí)處理多個(gè)不同的訂單,那么是沒(méi)有任何問(wèn)題的」 ,但是,如果對(duì)于另外一種場(chǎng)景,該方案就不可行了,如下:
假設(shè)此時(shí)銷(xiāo)售創(chuàng)建了一個(gè)定向包訂單ad0,先推送上線。然后發(fā)現(xiàn)訂單有問(wèn)題,所以隨即推送下線。那么此時(shí)消息隊(duì)列中有兩條消息,先是ad0的上線消息,然后是ad0的下線消息?;谏鲜龆嗑€程設(shè)計(jì)模型,假設(shè) 線程1執(zhí)行訂單上線,線程2執(zhí)行訂單下線 ,可能的結(jié)果就有如下幾種:
很明顯,該種方案不可行,盡管其最大可能地優(yōu)化了性能,但是得不到正確的結(jié)果,即使性能再好,又有啥用呢?
難道多線程設(shè)計(jì)模型真的不適用于我們這個(gè)服務(wù)嗎?
不妨調(diào)整下思路,在上述的方案分析中,多個(gè)線程同時(shí)處理多個(gè)訂單就會(huì)有問(wèn)題,換句話說(shuō)在M*N多線程設(shè)計(jì)模型中,正是因?yàn)镸>1導(dǎo)致了結(jié)果不可預(yù)期,那么如果M=1呢?這樣會(huì)不會(huì)就會(huì)避免上述問(wèn)題呢?
我們?nèi)匀灰陨鲜霭咐M(jìn)行舉例,因?yàn)槭菃尉€程處理消息隊(duì)列,那么永遠(yuǎn)都是先處理上線消息,然后再處理下線消息,這樣的結(jié)果永遠(yuǎn)符合我們的預(yù)期。
既然方案已經(jīng)定了,那么就可以直接寫(xiě)代碼了。在該方案中,我們用到了多線程進(jìn)行處理,如果每次來(lái)了訂單消息都創(chuàng)建多個(gè)線程進(jìn)行處理,處理完成后,銷(xiāo)毀線程。雖然也可以這么做,但多少對(duì)性能有所影響,所以干脆使用 ??線程池?? 來(lái)完成吧。base庫(kù)中有之前手?jǐn)]的線程池,直接拿來(lái)使用。
for (auto did : ids) {
thread_pool.enqueue([did, adid, this]{
RedisClient client;
redis_client_pool_.Pop(&client);
if (client) {
client->SAdd(did, adid);
redis_client_pool_.Push(client);
}
});
}
}編譯、部署、測(cè)試,一氣呵成,沒(méi)問(wèn)題。開(kāi)始上線,上線完成,看了下CPU利用率,完美:
數(shù)據(jù)說(shuō)話,對(duì)比下優(yōu)化前后同一個(gè)訂單的處理時(shí)間:
性能提升接近30倍,符合預(yù)期。。。
需求,總是自我技術(shù)提升,架構(gòu)升級(jí)優(yōu)化的動(dòng)力源。有時(shí)候,一個(gè)簡(jiǎn)單的小優(yōu)化,就能達(dá)到事半功倍的效果。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流