自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

如何調(diào)用一個(gè)只支持batch_call的服務(wù)?

開(kāi)發(fā) 后端
如果非得使用同步調(diào)用的方式進(jìn)行調(diào)用,建議模仿Nagle算法的形式,攢一批數(shù)據(jù)再發(fā)起請(qǐng)求,這樣既可以增大batch,同時(shí)減少并發(fā),真·一舉兩得,親測(cè)有效。

我們先來(lái)說(shuō)下標(biāo)題是什么意思。

為了更好的理解我說(shuō)的是啥,我們來(lái)舉個(gè)例子。

假設(shè)你現(xiàn)在在做一個(gè)類似B站的系統(tǒng),里面放了各種視頻。

圖片

用戶每天在里頭上傳各種視頻。

按理說(shuō)每個(gè)視頻都要去審查一下有沒(méi)有搞顏色,但總不能人眼挨個(gè)看吧。

畢竟唐老哥表示這玩意看多了,看太陽(yáng)都是綠色的,所以會(huì)有專門(mén)訓(xùn)練過(guò)的算法服務(wù)去做檢測(cè)。

但也不能上來(lái)就整個(gè)視頻每一幀都拿去做審查吧,所以會(huì)在每個(gè)視頻里根據(jù)時(shí)長(zhǎng)和視頻類型隨機(jī)抽出好幾張圖片去做審查,比如視頻標(biāo)簽是美女的,算法愛(ài)看,那多抽幾張。標(biāo)簽是編程的,狗都不看,就少抽幾張。

將這些抽出來(lái)的圖片,送去審查。

為了實(shí)現(xiàn)這個(gè)功能,我們會(huì)以視頻為維度去做審核,而每個(gè)視頻里都會(huì)有N張數(shù)量不定的圖片,下游服務(wù)是個(gè)使用GPU去檢測(cè)圖片的算法服務(wù)。

現(xiàn)在問(wèn)題來(lái)了,下游服務(wù)的算法開(kāi)發(fā)告訴你,這些個(gè)下游服務(wù),它不支持很高的并發(fā),但請(qǐng)求傳參里給你加了個(gè)數(shù)組,你可以批量(batch)傳入一個(gè)比較大的圖片數(shù)組,通過(guò)這個(gè)方式可以提升點(diǎn)圖片處理量。

于是,我們的場(chǎng)景就變成。

上游服務(wù)的入?yún)⑹且粋€(gè)視頻和它的N張圖片,出參是這個(gè)視頻是否審核通過(guò)。

下游服務(wù)的入?yún)⑹荖張圖片的,出參是這個(gè)視頻是否審核通過(guò)。

圖片batch_call上下游

現(xiàn)在我們想要用上游服務(wù)接入下游服務(wù)。該怎么辦?

看上去挺好辦的,一把梭不就完事了嗎?

當(dāng)一個(gè)視頻進(jìn)來(lái),就拿著視頻的十多張圖片作為一個(gè)batch去進(jìn)行調(diào)用。

有幾個(gè)視頻進(jìn)來(lái),就開(kāi)幾個(gè)這樣的并發(fā)。

這么做的結(jié)果就是,當(dāng)并發(fā)大一點(diǎn)時(shí),你會(huì)發(fā)現(xiàn)性能很差,并且性能非常不穩(wěn)定,比如像下面的監(jiān)控圖一樣一會(huì)3qps,一會(huì)15qps。處理的圖片也只支持20qps左右。

狗看了都得搖頭。

圖片

圖1-直接調(diào)用時(shí)qps很低

圖片

這可如何是好?

為什么下游需要batch call

本著先問(wèn)是不是,再問(wèn)為什么的精神,我們先看看為啥下游的要求會(huì)如此別致。

為什么同樣都是處理多張圖片,下游不搞成支持并發(fā)而要搞成批量調(diào)用(batch call)?

這個(gè)設(shè)定有點(diǎn)奇怪?

其實(shí)不奇怪,在算法服務(wù)中甚至很常見(jiàn),舉個(gè)例子你就明白了。

同樣是處理多張圖片,為了簡(jiǎn)單,我就假設(shè)是三張吧。如果是用單個(gè)cpu去處理的話。那不管是并發(fā)還是batch進(jìn)來(lái),由于cpu內(nèi)部的計(jì)算單元有限,所以你可以簡(jiǎn)單理解為,這三張圖片,就是串行去計(jì)算的。

圖片

cpu處理圖片時(shí)的流程

我計(jì)算第一張圖片是否能審核通過(guò),跟第二張圖片是否能審核通過(guò),這兩者沒(méi)有邏輯關(guān)聯(lián),因此按道理兩張圖片是可以并行計(jì)算。

奈何我CPU計(jì)算單元有限啊,做不到啊。

但是。

如果我打破計(jì)算單元有限的這個(gè)條件,給CPU加入超多計(jì)算單元,并且弱化一些對(duì)于計(jì)算沒(méi)啥用處的組件,比如cache和控制單元。那我們就有足夠的算力可以讓這些圖片的計(jì)算并行起來(lái)了。

圖片

并行處理圖片

是的,把CPU這么一整,它其實(shí)就變成了GPU。

圖片

GPU和CPU的區(qū)別

上面的講解只是為了方便理解,實(shí)際上,gpu會(huì)以更細(xì)的粒度去做并發(fā)計(jì)算,比如可以細(xì)到圖片里的像素級(jí)別。

這也是為什么如果我們跑一些3d游戲的時(shí)候,需要用到顯卡,因?yàn)樗梢钥焖俚牟⑿杏?jì)算畫(huà)面里每個(gè)地方的光影,遠(yuǎn)近效果啥的,然后渲染出畫(huà)面。

回到為什么要搞成batch call的問(wèn)題中。

其實(shí)一次算法服務(wù)調(diào)用中,在數(shù)據(jù)真正進(jìn)入GPU前,其實(shí)也使用了CPU做一些前置處理。

因此,我們可以簡(jiǎn)單的將一次調(diào)用的時(shí)間理解成做了下面這些事情。

圖片

GPU處理圖片時(shí)的流程

服務(wù)由CPU邏輯和GPU處理邏輯組成,調(diào)用進(jìn)入服務(wù)后,會(huì)有一些前置邏輯,它需要CPU來(lái)完成,然后才使用GPU去進(jìn)行并行計(jì)算,將結(jié)果返回后又有一些后置的CPU處理邏輯。中間的GPU部分,管是計(jì)算1張圖,還是計(jì)算100張圖,只要算力支持,那它們都是并行計(jì)算的,耗時(shí)都差不多。

如果把這多張圖片拆開(kāi),并發(fā)去調(diào)用這個(gè)算法服務(wù),那就有 N組這樣的CPU+GPU的消耗,而中間的并行計(jì)算,其實(shí)沒(méi)有利用到位。

并且還會(huì)多了前置和后置的CPU邏輯部分,算法服務(wù)一般都是python服務(wù),主流的一些web框架幾乎都是以多進(jìn)程,而不是多線程的方式去處理外部請(qǐng)求,這就有可能導(dǎo)致額外的進(jìn)程間切換消耗。

當(dāng)并發(fā)的請(qǐng)求多了,請(qǐng)求處理不過(guò)來(lái),后邊來(lái)的請(qǐng)求就需要等前邊的處理完才能被處理,后面的請(qǐng)求耗時(shí)看起來(lái)就會(huì)變得特別大。這也是上面圖1里,接口延時(shí)(latency)像過(guò)山車那樣往上漲的原因。

圖片

還是上面的圖1的截圖,一張圖用兩次哈哈

按理說(shuō)減少并發(fā),增大每次調(diào)用時(shí)的圖片數(shù)量,就可以解決這個(gè)問(wèn)題。

這就是推薦batch call的原因。

但問(wèn)題又來(lái)了。

每次調(diào)用,上游服務(wù)輸入的是一個(gè)視頻以及它的幾張圖片,調(diào)用下游時(shí),batch的數(shù)量按道理就只能是這幾張圖片的數(shù)量,怎么才能增大batch的數(shù)量呢?

這里的調(diào)用,就需要分為同步調(diào)用和異步調(diào)用了。

同步調(diào)用和異步調(diào)用的區(qū)別

同步調(diào)用,意思是上游發(fā)起請(qǐng)求后,阻塞等待,下游處理邏輯后返回結(jié)果給上游。常見(jiàn)的形式就像我們平時(shí)做的http調(diào)用一樣。

圖片同步調(diào)用

異步調(diào)用,意思是上游發(fā)起請(qǐng)求后立馬返回,下游收到消息后慢慢處理,處理完之后再通過(guò)某個(gè)形式通知上游。常見(jiàn)的形式是使用消息隊(duì)列,也就是mq。將消息發(fā)給mq后,下游消費(fèi)mq消息,觸發(fā)處理邏輯,然后再把處理結(jié)果發(fā)到mq,上游消費(fèi)mq的結(jié)果。

圖片

異步調(diào)用

異步調(diào)用的形式接入

圖片

異步調(diào)用的實(shí)現(xiàn)方式

回到我們文章開(kāi)頭提到的例子,當(dāng)上游服務(wù)收到一個(gè)請(qǐng)求(一個(gè)視頻和它對(duì)應(yīng)的圖片),這時(shí)候上游服務(wù)作為生產(chǎn)者將這個(gè)數(shù)據(jù)寫(xiě)入到mq中,請(qǐng)求返回。然后新造一個(gè)C服務(wù),負(fù)責(zé)批量消費(fèi)mq里的消息。這時(shí)候服務(wù)C就可以根據(jù)下游服務(wù)的性能控制自己的消費(fèi)速度,比如一次性消費(fèi)10條數(shù)據(jù)(視頻),每個(gè)數(shù)據(jù)下面掛了10個(gè)圖片,那我一次batch的圖片數(shù)量就是10*10=100張,原來(lái)的10次請(qǐng)求就變?yōu)榱?次請(qǐng)求。這對(duì)下游就相當(dāng)?shù)挠押昧恕?/p>

下游返回結(jié)果后,服務(wù)C將結(jié)果寫(xiě)入到mq的另外一個(gè)topic下,由上游去做消費(fèi),這樣就結(jié)束了整個(gè)調(diào)用流程。

當(dāng)然上面的方案,如果你把mq換成數(shù)據(jù)庫(kù),一樣是ok的,這時(shí)候服務(wù)C就可以不斷的定時(shí)輪詢數(shù)據(jù)庫(kù)表,看下哪些請(qǐng)求沒(méi)處理,把沒(méi)處理的請(qǐng)求批量撈出來(lái)再batch call下游。不管是mq還是數(shù)據(jù)庫(kù),它們的作用無(wú)非就是作為中轉(zhuǎn),暫存數(shù)據(jù),讓服務(wù)C根據(jù)下游的消費(fèi)能力,去消費(fèi)這些數(shù)據(jù)。

這樣不管后續(xù)要加入多少個(gè)新服務(wù),它們都可以在原來(lái)的基礎(chǔ)上做擴(kuò)展,如果是mq,加topic,如果是數(shù)據(jù)庫(kù),則加數(shù)據(jù)表,每個(gè)新服務(wù)都可以根據(jù)自己的消費(fèi)能力去調(diào)整消費(fèi)速度。

圖片

mq串聯(lián)多個(gè)不同性能的服務(wù)

其實(shí)對(duì)于這種上下游服務(wù)處理性能不一致的場(chǎng)景,最適合用的就是異步調(diào)用。而且涉及到的服務(wù)性能差距越大,服務(wù)個(gè)數(shù)越多,這個(gè)方案的優(yōu)勢(shì)就越明顯。

同步調(diào)用的方式接入

雖然異步調(diào)用在這種場(chǎng)景下的優(yōu)勢(shì)很明顯,但也有個(gè)缺點(diǎn),就是它需要最上游的調(diào)用方能接受用異步的方式去消費(fèi)結(jié)果。其實(shí)涉及到算法的服務(wù)調(diào)用鏈,都是比較耗時(shí)的,用異步接口非常合理。但合理歸合理,有些最上游他不一定聽(tīng)你的,就是不能接受異步調(diào)用。

這就需要采用同步調(diào)用的方案,但怎么才能把同步接口改造得更適合這種調(diào)用場(chǎng)景,這也是這篇文章的重點(diǎn)。

限流

如果直接將請(qǐng)求打到下游算法服務(wù),下游根本吃不消,因此首先需要做的就是給在上游調(diào)用下游的地方,加入一個(gè)速率限制(rate limit)。

這樣的組件一般也不需要你自己寫(xiě),幾乎任何一個(gè)語(yǔ)言里都會(huì)有現(xiàn)成的。

比如golang里可以用golang.org/x/time/rate庫(kù),它其實(shí)是用令牌桶算法實(shí)現(xiàn)的限流器。如果不知道令牌桶是啥也沒(méi)關(guān)系,不影響理解。

圖片

限流器邏輯

當(dāng)然,這個(gè)限制的是當(dāng)前這個(gè)服務(wù)調(diào)用下游的qps,也就是所謂的單節(jié)點(diǎn)限流。如果是多個(gè)服務(wù)的話,網(wǎng)上也有不少現(xiàn)成的分布式限流框架。但是,還是那句話,夠用就好。

限流只能保證下游算法服務(wù)不被壓垮,并不能提升單次調(diào)用batch的圖片數(shù)量,有沒(méi)有什么辦法可以解決這個(gè)問(wèn)題呢?

參考Nagle算法的做法

我們熟悉的TCP協(xié)議里,有個(gè)算法叫Nagle算法,設(shè)計(jì)它的目的,就是為了避免一次傳過(guò)少數(shù)據(jù),提高數(shù)據(jù)包的有效數(shù)據(jù)負(fù)載。

當(dāng)我們想要發(fā)送一些數(shù)據(jù)包時(shí),數(shù)據(jù)包會(huì)被放入到一個(gè)緩沖區(qū)中,不立刻發(fā)送,那什么時(shí)候會(huì)發(fā)送呢?

數(shù)據(jù)包會(huì)在以下兩個(gè)情況被發(fā)送:

  • 緩沖區(qū)的數(shù)據(jù)包長(zhǎng)度達(dá)到某個(gè)長(zhǎng)度(MSS)時(shí)。
  • 或者等待超時(shí)(一般為200ms)。在超時(shí)之前,來(lái)的那么多個(gè)數(shù)據(jù)包,就是湊不齊MSS長(zhǎng)度,現(xiàn)在超時(shí)了,不等了,立即發(fā)送。

這個(gè)思路就非常值得我們參考。我們完全可以自己在代碼層實(shí)現(xiàn)一波,實(shí)現(xiàn)也非常簡(jiǎn)單。

1.我們定義一個(gè)帶鎖的全局隊(duì)列(鏈表)。

2.當(dāng)上游服務(wù)輸入一個(gè)視頻和它對(duì)應(yīng)的N張圖片時(shí),就加鎖將這N張圖片數(shù)據(jù)和一個(gè)用來(lái)存放返回結(jié)果的結(jié)構(gòu)體放入到全局隊(duì)列中。然后死循環(huán)讀這個(gè)結(jié)構(gòu)體,直到它有結(jié)果。就有點(diǎn)像阻塞等待了。

3.同時(shí)在服務(wù)啟動(dòng)時(shí)就起一個(gè)線程A專門(mén)用于收集這個(gè)全局隊(duì)列的圖片數(shù)據(jù)。線程A負(fù)責(zé)發(fā)起調(diào)用下游服務(wù)的請(qǐng)求,但只有在下面兩個(gè)情況下會(huì)發(fā)起請(qǐng)求

  • 當(dāng)收集的圖片數(shù)量達(dá)到xx張的時(shí)候
  • 距離上次發(fā)起請(qǐng)求過(guò)了xx毫秒(超時(shí))

4.調(diào)用下游結(jié)束后,再根據(jù)一開(kāi)始傳入的數(shù)據(jù),將調(diào)用結(jié)果拆開(kāi)來(lái),送回到剛剛提到的用于存放結(jié)果的結(jié)構(gòu)體中。

5.第2步里的死循環(huán)因?yàn)榇娣欧祷亟Y(jié)果的結(jié)構(gòu)體,有值了,就可以跳出死循環(huán),繼續(xù)執(zhí)行后面的邏輯。

圖片

batch_call同步調(diào)用改造

這就像公交車站一樣,公交車站不可能每來(lái)一個(gè)顧客就發(fā)一輛公交車,當(dāng)然是希望車?yán)镱櫩驮蕉嘣胶?。上游每?lái)一個(gè)請(qǐng)求,就把請(qǐng)求里的圖片,也就是乘客,塞到公交車?yán)?,公交車要么到點(diǎn)發(fā)車(向下游服務(wù)發(fā)起請(qǐng)求),要么車滿了,也沒(méi)必要等了,直接發(fā)車。這樣就保證了每次發(fā)車的時(shí)候公交車?yán)锏念櫩蛿?shù)量足夠多,發(fā)車的次數(shù)盡量少。

大體思路就跟上面一樣,如果是用go來(lái)實(shí)現(xiàn)的話,就會(huì)更加簡(jiǎn)單。

比如第1步里的加鎖全局隊(duì)列可以改成有緩沖長(zhǎng)度的channel。第2步里的"用來(lái)存放結(jié)果的結(jié)構(gòu)體",也可以改成另一個(gè)無(wú)緩沖channel。執(zhí)行 res := <-ch, 就可以做到阻塞等待的效果。

而核心的仿Nagle的代碼也大概長(zhǎng)下面這樣。當(dāng)然不看也沒(méi)關(guān)系,反正你已經(jīng)知道思路了。

func CallAPI() error {
size := 100
// 這個(gè)數(shù)組用于收集視頻里的圖片,每個(gè) IVideoInfo 下都有N張圖片
videoInfos := make([]IVideoInfo, 0, size)
// 設(shè)置一個(gè)200ms定時(shí)器
tick := time.NewTicker(200 * time.Microsecond)
defer tick.Stop()
// 死循環(huán)
for {
select {
// 由于定時(shí)器,每200ms,都會(huì)執(zhí)行到這一行
case <-tick.C:
if len(videoInfos) > 0 {
// 200ms超時(shí),去請(qǐng)求下游
limitStartFunc(videoInfos, true)
// 請(qǐng)求結(jié)束后把之前收集的數(shù)據(jù)清空,重新開(kāi)始收集。
videoInfos = make([]IVideoInfo, 0, size)
}
// AddChan就是所謂的全局隊(duì)列
case videoInfo, ok := <-AddChan:
if !ok {
// 通道關(guān)閉時(shí),如果還有數(shù)據(jù)沒(méi)有去發(fā)起請(qǐng)求,就請(qǐng)求一波下游服務(wù)
limitStartFunc(videoInfos, false)
videoInfos = make([]IVideoInfo, 0, size)
return nil
} else {
videoInfos = append(videoInfos, videoInfo)
if videoInfos 內(nèi)的圖片滿足xx數(shù)量 {
limitStartFunc(videoInfos, false)
videoInfos = make([]IVideoInfo, 0, size)
// 重置定時(shí)器
tick.Reset(200 * time.Microsecond)
}
}
}
}
return nil
}

通過(guò)這一操作,上游每來(lái)一個(gè)請(qǐng)求,都會(huì)將視頻里的圖片收集起來(lái),堆到一定張數(shù)的時(shí)候再統(tǒng)一請(qǐng)求,大大提升了每次batch call的圖片數(shù)量,同時(shí)也減少了調(diào)用下游服務(wù)的次數(shù)。真·一舉兩得。

優(yōu)化的效果也比較明顯,上游服務(wù)支持的qps從原來(lái)不穩(wěn)定的3q~15q變成穩(wěn)定的90q。下游的接口耗時(shí)也變得穩(wěn)定多了,從原來(lái)的過(guò)山車似的飆到15s變成穩(wěn)定的500ms左右。處理的圖片的速度也從原來(lái)20qps提升到350qps。

到這里就已經(jīng)大大超過(guò)業(yè)務(wù)需求的預(yù)期(40qps)了,夠用就好,多一個(gè)qps都是浪費(fèi)。

可以了,下班吧。

圖片

圖片

總結(jié)

  • 為了充分利用GPU并行計(jì)算的能力,不少算法服務(wù)會(huì)希望上游通過(guò)加大batch的同時(shí)減少并發(fā)的方式進(jìn)行接口調(diào)用。
  • 對(duì)于上下游性能差距明顯的服務(wù),建議配合mq采用異步調(diào)用的方式將服務(wù)串聯(lián)起來(lái)。
  • 如果非得使用同步調(diào)用的方式進(jìn)行調(diào)用,建議模仿Nagle算法的形式,攢一批數(shù)據(jù)再發(fā)起請(qǐng)求,這樣既可以增大batch,同時(shí)減少并發(fā),真·一舉兩得,親測(cè)有效。

最后

講了那么多可以提升性能的方式,現(xiàn)在需求來(lái)了,如果你資源充足,但時(shí)間不充足,那還是直接同步調(diào)用一把梭吧。

性能不夠?下游加機(jī)器,gpu卡,買!

然后下個(gè)季度再提起一個(gè)技術(shù)優(yōu)化,性能提升xx%,cpu,gpu減少xx%。

有沒(méi)有聞到?

這是KPI的味道。

責(zé)任編輯:武曉燕 來(lái)源: 小白debug
相關(guān)推薦

2024-03-15 15:20:10

并發(fā)服務(wù)IP

2014-04-14 15:54:00

print()Web服務(wù)器

2017-11-13 13:33:09

MySQL全備份恢復(fù)表

2022-04-06 08:47:03

Dubbo服務(wù)協(xié)議

2009-06-26 15:48:23

Windows Mob

2013-08-15 10:00:07

產(chǎn)品產(chǎn)品經(jīng)理優(yōu)秀的產(chǎn)品

2022-09-13 08:01:58

短鏈服務(wù)哈希算法字符串

2021-05-20 13:22:31

架構(gòu)運(yùn)維技術(shù)

2022-11-08 08:35:53

架構(gòu)微服務(wù)移動(dòng)

2015-10-10 11:09:48

NFVNFVI網(wǎng)絡(luò)虛擬化

2017-04-11 16:16:48

HTTPS互聯(lián)網(wǎng)服務(wù)端

2022-05-22 13:55:30

Go 語(yǔ)言

2025-02-11 08:20:00

DeepseekAIOPS人工智能

2021-07-28 14:59:08

鴻蒙HarmonyOS應(yīng)用

2010-07-22 12:15:59

Batch Telne

2024-05-24 08:31:49

服務(wù)器聯(lián)網(wǎng)SSH

2021-04-26 18:13:37

微服務(wù)模式數(shù)據(jù)庫(kù)

2017-11-16 14:31:21

LinuxLinux LiteLinux 4.14

2023-09-11 10:53:32

2013-07-01 11:01:22

API設(shè)計(jì)API
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)