干貨分享丨提升Web服務(wù)端性能的技術(shù)經(jīng)驗(yàn)
張弸中 | 2017-02-13 11:20
【數(shù)據(jù)猿導(dǎo)讀】 作為直接面對(duì)來(lái)自客戶請(qǐng)求的web服務(wù)端,無(wú)疑是要同時(shí)承受更多的請(qǐng)求,并為用戶提供更好的體驗(yàn)。這個(gè)時(shí)候web端的性能常常會(huì)成為業(yè)務(wù)發(fā)展的瓶頸,提升性能刻不容緩。達(dá)觀數(shù)據(jù)在開(kāi)發(fā)過(guò)程中總結(jié)了一些提升web服務(wù)端性能的經(jīng)驗(yàn),與大家分享

來(lái)源:數(shù)據(jù)猿 作者:張弸中
前言
作為直接面對(duì)來(lái)自客戶請(qǐng)求的web服務(wù)端,無(wú)疑是要同時(shí)承受更多的請(qǐng)求,并為用戶提供更好的體驗(yàn)。這個(gè)時(shí)候web端的性能常常會(huì)成為業(yè)務(wù)發(fā)展的瓶頸,提升性能刻不容緩。
達(dá)觀數(shù)據(jù)在開(kāi)發(fā)過(guò)程中總結(jié)了一些提升web服務(wù)端性能的經(jīng)驗(yàn),與大家分享。
問(wèn)題分析
對(duì)于web服務(wù)端性能,首先我們分析一下相關(guān)指標(biāo)。
從用戶角度講,用戶調(diào)用web服務(wù)時(shí),請(qǐng)求返回時(shí)間越短,用戶體驗(yàn)越好。
從服務(wù)端角度講,同一時(shí)間能承載用戶請(qǐng)求量越大,服務(wù)端性能就越強(qiáng)。
綜合兩方面,我們總結(jié)性能優(yōu)化的兩個(gè)方向:
· 增加服務(wù)端所能支撐并發(fā)請(qǐng)求的最大數(shù)量;
· 提高每個(gè)請(qǐng)求處理速度。
明確了優(yōu)化方向,首先介紹一種服務(wù)端通常的架構(gòu)模式,即來(lái)自瀏覽器或者app的web一個(gè)請(qǐng)求,在服務(wù)端經(jīng)過(guò)哪幾層結(jié)構(gòu)被處理并返回的。
架構(gòu)模式:Ip負(fù)載均衡->緩存服務(wù)器->反向代理->應(yīng)用服務(wù)器->數(shù)據(jù)庫(kù)
如圖1所示,為了說(shuō)明方便,我們來(lái)舉個(gè)實(shí)際的例子:
LVS(Keepalived)->Squid->Nginx->GO->MySQL
圖1:服務(wù)端架構(gòu)
我們對(duì)請(qǐng)求在每層做分發(fā)處理,這樣可以使下一級(jí)結(jié)構(gòu)有多個(gè)分支同時(shí)工作,來(lái)提高總體的最大并發(fā)數(shù)。
結(jié)合架構(gòu),我們來(lái)分析通常有哪些問(wèn)題在拖了性能的后腿,以及找出對(duì)應(yīng)的解決方法。
正常情況下,ip負(fù)載均衡,緩存服務(wù)器和nginx代理這幾層主要是集群穩(wěn)定性問(wèn)題。容易出現(xiàn)性能瓶頸的地方往往是應(yīng)用服務(wù)器層和數(shù)據(jù)庫(kù)層,我們下面來(lái)列舉幾個(gè)例子:
問(wèn)題
大部分Web請(qǐng)求都是阻塞性質(zhì)的,當(dāng)一個(gè)請(qǐng)求被處理時(shí),進(jìn)程就會(huì)被掛起(占用cpu)直至請(qǐng)求完成。
在大多數(shù)情況下,Web請(qǐng)求完成的足夠快,所以這個(gè)問(wèn)題并不被關(guān)注。
然而,對(duì)于那些響應(yīng)時(shí)間來(lái)完成的請(qǐng)求(像返回?cái)?shù)據(jù)量大的請(qǐng)求或外部API),這意味著應(yīng)用程序被鎖定直至處理結(jié)束,這期間,其他的請(qǐng)求不會(huì)被處理,很明顯,這些無(wú)效的等待時(shí)間浪費(fèi)掉了,并且占用系統(tǒng)資源,嚴(yán)重的影響了我們可以負(fù)擔(dān)的并發(fā)請(qǐng)求的數(shù)量。
解決辦法
web服務(wù)端在等待上一個(gè)請(qǐng)求處理的過(guò)程中,我們可以讓I/O循環(huán)打開(kāi)以便處理其他應(yīng)用請(qǐng)求,直到處理完成時(shí)啟動(dòng)一個(gè)請(qǐng)求并給予反饋,而不再是等待請(qǐng)求完成的過(guò)程中掛起進(jìn)程。
這樣,我們可以節(jié)省一些沒(méi)有必要的等待時(shí)間,用這些時(shí)間去處理更多的請(qǐng)求,這樣我們就可以大大增加請(qǐng)求的吞吐量,也就是在宏觀上提高了我們可處理的并發(fā)請(qǐng)求數(shù)。
例子
這里我們用python的一款web框架tornado來(lái)具體說(shuō)明改變阻塞方式提高并發(fā)性能。
場(chǎng)景:我們構(gòu)建一個(gè)向遠(yuǎn)端(某個(gè)十分穩(wěn)定的網(wǎng)站)發(fā)送HTTP請(qǐng)求的簡(jiǎn)單Web應(yīng)用。
這期間,網(wǎng)絡(luò)傳輸穩(wěn)定,我們不考慮網(wǎng)絡(luò)來(lái)帶的影響。
在這個(gè)例子中,我們使用Siege(一款壓力測(cè)試軟件)對(duì)服務(wù)端在10秒內(nèi)執(zhí)行大約10個(gè)并發(fā)請(qǐng)求。
圖2:阻塞式響應(yīng)
如圖2,我們可以很容易看出,這里的問(wèn)題是無(wú)論每個(gè)請(qǐng)求自身返回多么快,服務(wù)器對(duì)遠(yuǎn)端的訪問(wèn)請(qǐng)求往返都會(huì)產(chǎn)生足夠大的滯后,因?yàn)檫M(jìn)程直到請(qǐng)求完成并且數(shù)據(jù)被處理前都一直處于強(qiáng)制掛起狀態(tài)。當(dāng)一兩個(gè)請(qǐng)求時(shí)這還不是一個(gè)問(wèn)題,但達(dá)到100個(gè)(甚至10個(gè))用戶時(shí),這意味著整體變慢。
如圖,不到10秒時(shí)間10個(gè)相似用戶的平均響應(yīng)時(shí)間達(dá)到了1.99秒,共計(jì)29次。這個(gè)例子只展示了非常簡(jiǎn)單的邏輯。
如果你要添加其他業(yè)務(wù)邏輯或數(shù)據(jù)庫(kù)的調(diào)用的話,結(jié)果會(huì)更糟糕。增加更多的用戶請(qǐng)求時(shí),同時(shí)可被處理的請(qǐng)求就會(huì)增長(zhǎng)緩慢,甚至有些請(qǐng)求會(huì)發(fā)生超時(shí)或失敗。
下面我們用Tornado執(zhí)行非阻塞的HTTP請(qǐng)求。
圖3:非阻塞式響應(yīng)
正如你在圖3中所看到的,我們從每秒3.20個(gè)事務(wù)提升到了12.59,在相同的時(shí)間內(nèi)總共提供了118次請(qǐng)求。
這真是一個(gè)非常大的改善!正如你所想象的,隨著用戶請(qǐng)求增多和測(cè)試時(shí)間增長(zhǎng)時(shí),它將能夠提供更多連接,并且不會(huì)遇到上面版本遭受的變慢的問(wèn)題。從而穩(wěn)定的提高了可負(fù)載的并發(fā)請(qǐng)求數(shù)。
先來(lái)介紹一下基礎(chǔ)知識(shí):
1、一個(gè)應(yīng)用程序是運(yùn)行在機(jī)器上的一個(gè)進(jìn)程;
2、進(jìn)程是一個(gè)運(yùn)行在自己內(nèi)存地址空間里的獨(dú)立執(zhí)行體。
3、一個(gè)進(jìn)程由一個(gè)或多個(gè)操作系統(tǒng)線程組成,這些線程其實(shí)是共享同一個(gè)內(nèi)存地址空間的一起工作的執(zhí)行體。
問(wèn)題
傳統(tǒng)計(jì)算方式單線程運(yùn)行,效率低,計(jì)算能力弱。
解決方法
一種解決辦法就是完全避免使用線程。例如,可以使用多個(gè)進(jìn)程將重?fù)?dān)交給操作系統(tǒng)來(lái)處理。但是,有個(gè)劣勢(shì)就是,我們必須處理所有進(jìn)程間通信,通常這比共享內(nèi)存的并發(fā)模型有更多的開(kāi)銷。
另一種辦法是用多線程工作,不過(guò),公認(rèn)的,使用多線程的應(yīng)用難以做到準(zhǔn)確,同步不同的線程,對(duì)數(shù)據(jù)加鎖,這樣同時(shí)就只有一個(gè)線程可以變更數(shù)據(jù)。不過(guò)過(guò)去的軟件開(kāi)發(fā)經(jīng)驗(yàn)告訴我們這會(huì)帶來(lái)更高的復(fù)雜度,更容易使代碼出錯(cuò)以及更低的性能。
其中最主要的問(wèn)題是內(nèi)存中的數(shù)據(jù)共享,它們會(huì)被多線程以無(wú)法預(yù)知的方式進(jìn)行操作,導(dǎo)致一些無(wú)法重現(xiàn)或者隨機(jī)的結(jié)果(稱作 競(jìng)態(tài))。
所以這個(gè)經(jīng)典的方法明顯不再適合現(xiàn)代多核/多處理器編程:thread-per-connection 模型不夠有效。在諸多比較合適的范式中,有個(gè)被稱作 CommunicatingSequential Processes(順序通信處理)(CSP, C. Hoare 發(fā)明的)還有一個(gè)叫做 message passing-model(消息傳遞)(已經(jīng)運(yùn)用在了其他語(yǔ)言中,比如Erlang)。
我們這里使用辦法是利用并行的架構(gòu)來(lái)處理任務(wù),一個(gè)并發(fā)程序可以在一個(gè)處理器或者內(nèi)核上使用多個(gè)線程來(lái)執(zhí)行任務(wù),但是只有同一個(gè)程序在某個(gè)時(shí)間點(diǎn)同時(shí)運(yùn)行在多核或者多處理器上才是真正的并行。
并行是一種通過(guò)使用多處理器以提高速度的能力。所以并發(fā)程序可以是并行的,也可以不是。
并行模式可以同時(shí)使用多線程,多核,多處理器,甚至多計(jì)算機(jī),這無(wú)疑可以調(diào)動(dòng)更多資源,從而壓縮響應(yīng)時(shí)間,提升運(yùn)算效率,極大地增強(qiáng)了服務(wù)端的性能。
例子
這里用GO語(yǔ)言中的goroutine來(lái)具體說(shuō)明。
在go語(yǔ)言中,應(yīng)用程序并發(fā)處理的部分被稱作 goroutines(協(xié)程),它可以進(jìn)行更有效的并發(fā)運(yùn)算。在協(xié)程和操作系統(tǒng)線程之間并無(wú)一對(duì)一的關(guān)系:協(xié)程是根據(jù)一個(gè)或多個(gè)線程的可用性,映射(多路復(fù)用,執(zhí)行于)在他們之上的;協(xié)程調(diào)度器在 Go 運(yùn)行時(shí)很好的完成了這個(gè)工作。協(xié)程是輕量的,比線程更輕。
它們痕跡非常不明顯(使用少量的內(nèi)存和資源):使用 4K 的棧內(nèi)存就可以在堆中創(chuàng)建它們。因?yàn)閯?chuàng)建非常廉價(jià),必要的時(shí)候可以輕松創(chuàng)建并運(yùn)行大量的協(xié)程(在同一個(gè)地址空間中 100,000個(gè)連續(xù)的協(xié)程)。并且它們對(duì)棧進(jìn)行了分割,從而動(dòng)態(tài)的增加(或縮減)內(nèi)存的使用;棧的管理是自動(dòng)的,但不是由垃圾回收器管理的,而是在協(xié)程退出后自動(dòng)釋放。協(xié)程可以運(yùn)行在多個(gè)操作系統(tǒng)線程之間,也可以運(yùn)行在線程之內(nèi),讓你可以很小的內(nèi)存占用就可以處理大量的任務(wù)。
由于操作系統(tǒng)線程上的協(xié)程時(shí)間片,你可以使用少量的操作系統(tǒng)線程就能擁有任意多個(gè)提供服務(wù)的協(xié)程,而且 Go 運(yùn)行時(shí)可以聰明的意識(shí)到哪些協(xié)程被阻塞了,暫時(shí)擱置它們并處理其他協(xié)程。甚至,程序可以在不同的處理器和計(jì)算機(jī)上同時(shí)執(zhí)行不同的代碼段。
我們通常想將一個(gè)長(zhǎng)計(jì)算過(guò)程切分成幾塊,然后讓每個(gè)goroutine各自負(fù)責(zé)一塊工作,這樣對(duì)于單一請(qǐng)求的響應(yīng)時(shí)間有成倍的提升。
舉個(gè)例子,有一個(gè)任務(wù)分3個(gè)階段,a階段去數(shù)據(jù)庫(kù)a中取數(shù)據(jù),b階段去數(shù)據(jù)庫(kù)b中取數(shù)據(jù),c階段合并數(shù)據(jù)返回。我們啟動(dòng)goroutine以后ab階段可以一起進(jìn)行,極大地縮短了響應(yīng)時(shí)間。
說(shuō)白了就是部分計(jì)算過(guò)程由串行轉(zhuǎn)換為并行,一個(gè)任務(wù)不需要等待其他無(wú)關(guān)的任務(wù)執(zhí)行完在執(zhí)行,實(shí)際計(jì)算中程序的并行執(zhí)行會(huì)更有用處。
關(guān)于這部分佐證的數(shù)據(jù)就不在這邊過(guò)多敘述了,感興趣的同學(xué)可以自己看一下這方面的資料。比如web服務(wù)端由ruby切換為go性能提升15倍的老故事(Ruby使用的是綠色線程,即只有一個(gè)CPU得到利用)。雖然這個(gè)故事可能有點(diǎn)夸大,但是并行帶來(lái)的性能提升是毫無(wú)疑問(wèn)的。
(Ruby切換為go:http://www.vaikan.com/how-we-went-from-30-servers-to-2-go/)。
問(wèn)題
磁盤讀取數(shù)據(jù)靠的是機(jī)械運(yùn)動(dòng),每次讀取數(shù)據(jù)花費(fèi)的時(shí)間可以分為尋道時(shí)間、旋轉(zhuǎn)延遲、傳輸時(shí)間三個(gè)部分:
- 尋道時(shí)間指的是磁臂移動(dòng)到指定磁道所需要的時(shí)間,主流磁盤一般在5ms以下;
- 旋轉(zhuǎn)延遲就是我們經(jīng)常聽(tīng)說(shuō)的磁盤轉(zhuǎn)速,比如一個(gè)磁盤7200轉(zhuǎn),表示每分鐘能轉(zhuǎn)7200次,也就是說(shuō)1秒鐘能轉(zhuǎn)120次,旋轉(zhuǎn)延遲就是1/120/2 = 4.17ms;
- 傳輸時(shí)間指的是從磁盤讀出或?qū)?shù)據(jù)寫入磁盤的時(shí)間,一般在零點(diǎn)幾毫秒,相對(duì)于前兩個(gè)時(shí)間可以忽略不計(jì)。
那么訪問(wèn)一次磁盤的時(shí)間,即一次磁盤IO的時(shí)間約等于5+4.17 = 9ms左右,聽(tīng)起來(lái)還挺不錯(cuò)的,但要知道一臺(tái)500 -MIPS的機(jī)器每秒可以執(zhí)行5億條指令,因?yàn)橹噶钜揽康氖请姷男再|(zhì),換句話說(shuō)執(zhí)行一次IO的時(shí)間可以執(zhí)行40萬(wàn)條指令,數(shù)據(jù)庫(kù)動(dòng)輒十萬(wàn)百萬(wàn)乃至千萬(wàn)級(jí)數(shù)據(jù),每次9毫秒的時(shí)間,顯然是個(gè)災(zāi)難。
解決辦法
磁盤io對(duì)服務(wù)器性能的影響沒(méi)有根本的解決辦法,除非你把磁盤扔掉,換成別的東西。我們能在網(wǎng)上搜到各種存儲(chǔ)介質(zhì)的響應(yīng)速度與價(jià)格,如果你有錢,你就可以任性的更換存儲(chǔ)介質(zhì)。
在不更換存儲(chǔ)介質(zhì)的條件下,我們可以減少應(yīng)用程序?qū)Υ疟P的訪問(wèn)次數(shù),比如設(shè)置緩存,還可以把部分磁盤io放到請(qǐng)求周期外,比如用隊(duì)列和棧來(lái)處理數(shù)據(jù)的io等等。
隨著業(yè)務(wù)開(kāi)發(fā)模式的變化,敏捷式開(kāi)發(fā)被越來(lái)越多的團(tuán)隊(duì)采用,周期越來(lái)越短,很多數(shù)據(jù)庫(kù)查詢語(yǔ)句都是按照業(yè)務(wù)邏輯來(lái)寫,時(shí)間久了常常就忽略了sql查詢的格式問(wèn)題,造成數(shù)據(jù)庫(kù)壓力的增加,使數(shù)據(jù)庫(kù)查詢的響應(yīng)變慢。這里簡(jiǎn)單介紹Mysql數(shù)據(jù)庫(kù)中,幾條被我們忽略的常見(jiàn)問(wèn)題和優(yōu)化方式:
1. 最左前綴匹配原則,非常重要的原則,mysql會(huì)一直向右匹配直到遇到范圍查詢(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)順序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引則都可以用到,a,b,d的順序可以任意調(diào)整。
2. 盡量選擇區(qū)分度高的列作為索引,區(qū)分度的公式是count(distinct col)/count(*),表示字段不重復(fù)的比例,比例越大我們掃描的記錄數(shù)越少,唯一鍵的區(qū)分度是1,而一些狀態(tài)、性別字段可能在大數(shù)據(jù)面前區(qū)分度就是0,那可能有人會(huì)問(wèn),這個(gè)比例有什么經(jīng)驗(yàn)值嗎?使用場(chǎng)景不同,這個(gè)值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均1條掃描10條記錄
3. 盡量使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設(shè)計(jì)為字符型,這會(huì)降低查詢和連接的性能,并會(huì)增加存儲(chǔ)開(kāi)銷。這是因?yàn)橐嬖谔幚聿樵兒瓦B接時(shí)會(huì)逐個(gè)比較字符串中每一個(gè)字符,而對(duì)于數(shù)字型而言只需要比較一次就夠了。
4. 索引列不能參與計(jì)算,保持列“干凈”,比如from_unixtime(create_time) =’2014-05-29’就不能使用到索引,原因很簡(jiǎn)單,b+樹(shù)中存的都是數(shù)據(jù)表中的字段值,但進(jìn)行檢索時(shí),需要把所有元素都應(yīng)用函數(shù)才能比較,顯然成本太大。所以語(yǔ)句應(yīng)該寫成create_time = unix_timestamp(’2014-05-29’);
5. 應(yīng)盡量避免在 where 子句中對(duì)字段進(jìn)行 null 值判斷,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num is null
可以在num上設(shè)置默認(rèn)值0,確保表中num列沒(méi)有null值,然后這樣查詢:
select id from t where num=0
6. 應(yīng)盡量避免在 where 子句中使用 or 來(lái)連接條件,否則將導(dǎo)致引擎放棄使用索引而進(jìn)行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10
union all
select id from t where num=20
7. 下面的查詢也將導(dǎo)致全表掃描:(不能前置百分號(hào))
select id from t where name like ‘%abc%’
若要提高效率,可以考慮全文檢索。
8. in 和 not in 也要慎用,否則會(huì)導(dǎo)致全表掃描,如:
select id from t where num in(1,2,3)
對(duì)于連續(xù)的數(shù)值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
總結(jié)
對(duì)于一個(gè)web請(qǐng)求,上面列舉出了幾個(gè)經(jīng)常出現(xiàn)的問(wèn)題,希望對(duì)閱讀的各位有所幫助,當(dāng)然如果有不同的想法歡迎一起來(lái)探討。
達(dá)觀數(shù)據(jù)專注于企業(yè)大數(shù)據(jù)技術(shù)服務(wù),以獨(dú)創(chuàng)的多層細(xì)智能挖掘算法,實(shí)現(xiàn)對(duì)海量用戶行為和文本數(shù)據(jù)的深入分析和挖掘,為企業(yè)提供智能文本分析、精準(zhǔn)用戶行為建模、個(gè)性化推薦、智能搜索等尖端數(shù)據(jù)挖掘功能。
注:本文由 張弸中 投稿數(shù)據(jù)猿發(fā)布。
歡迎更多大數(shù)據(jù)企業(yè)、愛(ài)好者投稿數(shù)據(jù)猿,來(lái)稿請(qǐng)直接投遞至:tougao@datayuan.cn
來(lái)源:數(shù)據(jù)猿
刷新相關(guān)文章
我要評(píng)論
活動(dòng)推薦more >
- 2018 上海國(guó)際大數(shù)據(jù)產(chǎn)業(yè)高2018-12-03
- 2018上海國(guó)際計(jì)算機(jī)網(wǎng)絡(luò)及信2018-12-03
- 中國(guó)國(guó)際信息通信展覽會(huì)將于2018-09-26
- 第五屆FEA消費(fèi)金融國(guó)際峰會(huì)62018-06-21
- 第五屆FEA消費(fèi)金融國(guó)際峰會(huì)2018-06-21
- “無(wú)界區(qū)塊鏈技術(shù)峰會(huì)2018”2018-06-14
不容錯(cuò)過(guò)的資訊
-
1#后疫情時(shí)代的新思考#疫情之下,關(guān)于醫(yī)
-
2數(shù)據(jù)軟件產(chǎn)品和服務(wù)商DataHunter完成B輪
-
3眾盟科技獲ADMIC 2020金粲獎(jiǎng)“年度汽車
-
4數(shù)據(jù)智能 無(wú)限未來(lái)—2020世界人工智能大
-
5#2020非凡大賞:數(shù)字化風(fēng)起云涌時(shí),共尋
-
6#榜樣的力量#天璣數(shù)據(jù)大腦疫情風(fēng)險(xiǎn)感知
-
7#榜樣的力量#內(nèi)蒙古自治區(qū)互聯(lián)網(wǎng)醫(yī)療服
-
8#榜樣的力量#實(shí)時(shí)新型肺炎疫情數(shù)據(jù)小程
-
9#榜樣的力量#華佗疫情防控平臺(tái)丨數(shù)據(jù)猿
-
10#后疫情時(shí)代的新思考#構(gòu)建工業(yè)互聯(lián)網(wǎng)新