您好,登錄后才能下訂單哦!
這篇文章主要講解了“ASP.Net服務(wù)性能優(yōu)化原則”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“ASP.Net服務(wù)性能優(yōu)化原則”吧!
服務(wù)器性能問(wèn)題,通常在數(shù)據(jù)少的時(shí)候不會(huì)顯現(xiàn),也無(wú)需太多關(guān)注。但一旦數(shù)據(jù)量大了,就會(huì)變成一個(gè)麻煩且必須處理的事。
通常,性能問(wèn)題可能有許多不同的原因。內(nèi)存問(wèn)題、緩慢的數(shù)據(jù)庫(kù)請(qǐng)求和太少的機(jī)器只是其中的一部分。手上的項(xiàng)目,每天10億級(jí)的數(shù)量量,在最近一個(gè)時(shí)間段,填了很多坑,也學(xué)到了不少東西。
今天這個(gè)文章,我會(huì)把這一段的體會(huì),總結(jié)成幾大類問(wèn)題。當(dāng)然,分類不一定很嚴(yán)謹(jǐn),重要的是能給到大家一些建議,真到用時(shí),能少刨一些坑,就夠了。另外,次序也不重要,我是想到哪些到哪的,并不是說(shuō)前邊的內(nèi)容就比后面的內(nèi)容更需要注意。
數(shù)據(jù)庫(kù)調(diào)用的性能,會(huì)嚴(yán)重影響系統(tǒng)整體的性能。大多數(shù)情況下,與數(shù)據(jù)庫(kù)快速交互是獲得良好性能的最重要的因素。
以下幾個(gè)點(diǎn)需要重點(diǎn)關(guān)注:
索引策略
索引對(duì)數(shù)據(jù)庫(kù)交互的影響不需要解釋。重要的是檢查,檢查每一個(gè)索引,和每一個(gè)查詢語(yǔ)句。很多時(shí)候,你以為的未必是你以為的。檢查查詢語(yǔ)句和條件對(duì)索引的使用,檢查索引的結(jié)構(gòu)。要確保每個(gè)查詢語(yǔ)句,能正確使用你所希望使用的索引。
表結(jié)構(gòu)設(shè)計(jì)
表結(jié)構(gòu)設(shè)計(jì)最重要的,是對(duì)業(yè)務(wù)的理解。對(duì)數(shù)據(jù)之間的關(guān)系理解越深,表結(jié)構(gòu)越趨于合理。
同樣的工作,盡可能在數(shù)據(jù)庫(kù)上完成,避免在服務(wù)器中完成
這個(gè)話不太好理解,用代碼舉個(gè)例子:
// 好的方式 var girls = dbContext.Users.Where(user => user.gender == female); var count = girls.Count(); // 不好的方式 var girls = dbContext.Users.Where(user => user.gender == female).ToList(); var count = girls.Count;
下邊這種方式,第一行以ToList()結(jié)束。當(dāng)實(shí)體執(zhí)行查詢時(shí),會(huì)從數(shù)據(jù)庫(kù)中檢索并獲取全部數(shù)據(jù),然后在服務(wù)器中進(jìn)行計(jì)數(shù)。而上面的方式,會(huì)在數(shù)據(jù)庫(kù)中直接計(jì)數(shù)。很顯而易見(jiàn)的,數(shù)據(jù)庫(kù)中執(zhí)行計(jì)數(shù),網(wǎng)絡(luò)傳輸?shù)拇鷥r(jià)會(huì)更少。
盡可能讓數(shù)據(jù)庫(kù)離應(yīng)用服務(wù)器"近"點(diǎn)
數(shù)據(jù)庫(kù)到應(yīng)用服務(wù)器之間,無(wú)非是網(wǎng)絡(luò)。更"近"的網(wǎng)絡(luò),會(huì)帶來(lái)更少的延時(shí)。這個(gè)"近"說(shuō)的是網(wǎng)絡(luò)拓?fù)渖系慕?,不是位置和距離。對(duì)于多機(jī)房分布式的應(yīng)用,起碼的要求是讓一個(gè)或幾個(gè)完整的副本集與應(yīng)用服務(wù)處于同一個(gè)數(shù)據(jù)中心。
用數(shù)據(jù)庫(kù)希望的方式使用數(shù)據(jù)庫(kù)
數(shù)據(jù)庫(kù)有很多種,關(guān)系型、NoSQL、內(nèi)存數(shù)據(jù)庫(kù),等等。并不是所有的數(shù)據(jù)庫(kù)都一樣。有些適合Key-Value鍵值對(duì),有些適合事務(wù)處理,有些適合存儲(chǔ)日志。
在開(kāi)發(fā)中,不要拘泥于數(shù)據(jù)庫(kù)類型,而應(yīng)該根據(jù)業(yè)務(wù)類型和數(shù)據(jù)庫(kù)特性進(jìn)行使用。比方說(shuō),MongoDB,本身是基于文檔的數(shù)據(jù)庫(kù),結(jié)構(gòu)上很不適合JOIN操作。但它非常適合存儲(chǔ)包含大量業(yè)務(wù)數(shù)據(jù)的文檔。所以,使用時(shí)要避免使用JOIN操作的業(yè)務(wù)。當(dāng)然,這只是個(gè)例子。事實(shí)上MongoDB對(duì)于類似JOIN的內(nèi)容,有更好的處理模式,這個(gè)大家可以自行了解。
保證數(shù)據(jù)庫(kù)有足夠的硬件資源
服務(wù)器的伸縮一般提的比較高,但其實(shí)數(shù)據(jù)庫(kù)的伸縮性也需要非常重視。數(shù)據(jù)庫(kù)服務(wù)器,要關(guān)注到存儲(chǔ)空間、內(nèi)存、網(wǎng)絡(luò)和CPU。經(jīng)驗(yàn)中,接近極限時(shí),服務(wù)器未必會(huì)有明確的警報(bào)給你;而等到有警報(bào)出現(xiàn)時(shí),恐怕已經(jīng)到達(dá)極限并發(fā)生了故障,就非常難于處理了。
所以,當(dāng)發(fā)現(xiàn)某些任務(wù)開(kāi)始變慢,就意味著需要全面檢查了。
承認(rèn)某些低效查詢的存在
不是所有的查詢都可以做到高效。尤其查詢是基于某些實(shí)體框架,例如EF或Hibernate。在技術(shù)和時(shí)間可能的情況下,少用數(shù)據(jù)框架是個(gè)好習(xí)慣。
使用連接池,而不是單個(gè)連接
如果每個(gè)查詢都需要重新建立連接,那是非??膳碌?,從性能到應(yīng)用的可靠性。使用數(shù)據(jù)庫(kù),第一件事就是學(xué)會(huì)如何使用連接池。
小心使用存儲(chǔ)過(guò)程
當(dāng)有需要花費(fèi)大量時(shí)間的復(fù)雜查詢需要處理時(shí),存儲(chǔ)過(guò)程是個(gè)解決方案。但一定要小心,一定要小心,一定要小心,重要的事情說(shuō)三遍。
在我的團(tuán)隊(duì)中,存儲(chǔ)過(guò)程是被禁止使用的。相對(duì)來(lái)說(shuō),這兒安全的要求超過(guò)性能。
不過(guò),在這個(gè)文章中,尤其在討論數(shù)據(jù)庫(kù)操作的性能時(shí),咱還是不能忘了存儲(chǔ)過(guò)程。
數(shù)據(jù)庫(kù)分片策略
分布式數(shù)據(jù)庫(kù)性能的核心在于分片。分片就一個(gè)原則:讓業(yè)務(wù)的每一個(gè)查詢操作,對(duì)應(yīng)盡可能少的分片。
上面寫(xiě)的,其實(shí)是一些原則。實(shí)際上,最難的部分是確定這些問(wèn)題。所以,需要對(duì)各種工具都熟悉。通常,數(shù)據(jù)庫(kù)本身也能提供相關(guān)內(nèi)容,例如慢查詢、擴(kuò)展問(wèn)題、網(wǎng)絡(luò)瓶頸等。對(duì)于數(shù)據(jù)庫(kù),不要僅限于使用,一定深度的了解會(huì)對(duì)成長(zhǎng)有相當(dāng)?shù)膸椭?/p>
對(duì)于某些高吞吐量的應(yīng)用,服務(wù)器的內(nèi)存壓力是最常見(jiàn)的問(wèn)題。
當(dāng)吞吐量非常大的時(shí)候,垃圾回收(GC)會(huì)跟不上內(nèi)存的分配和釋放。而且這種壓力的體現(xiàn),是服務(wù)器在垃圾回收上花費(fèi)的時(shí)間更多,而執(zhí)行代碼的時(shí)間更少。
這種狀態(tài)在多種情況下都可能發(fā)生。最常見(jiàn)的情況是內(nèi)存容量耗盡。當(dāng)您達(dá)到內(nèi)存極限時(shí),垃圾回收器將出現(xiàn)恐慌,并啟動(dòng)更頻繁的整體垃圾回收,而這種模式的回收代價(jià)非常大。但問(wèn)題是,為什么會(huì)發(fā)生這種情況?為什么你內(nèi)存使用接近極限了?原因通常是錯(cuò)誤或不太好的緩存管理或內(nèi)存泄漏。通過(guò)捕獲內(nèi)存快照并檢查是什么占用了所有字節(jié),可以很容易地用內(nèi)存分析器發(fā)現(xiàn)這一點(diǎn)。
重要的是首先要意識(shí)到你有內(nèi)存問(wèn)題。最簡(jiǎn)單的方法是使用性能計(jì)數(shù)器。
緩存可以是一個(gè)非常好、非常有效的優(yōu)化技術(shù)。典型的例子是,當(dāng)客戶端發(fā)送請(qǐng)求時(shí),服務(wù)器可以將結(jié)果保存在緩存中。當(dāng)客戶端再次發(fā)送相同的請(qǐng)求(不一定是同一個(gè)客戶端)時(shí),服務(wù)器不需要再次查詢數(shù)據(jù)庫(kù)或進(jìn)行任何計(jì)算來(lái)獲得結(jié)果,而只是從緩存中獲取它。
考慮一下搜索引擎的做法。如果這是一個(gè)常見(jiàn)的搜索,它可能會(huì)被要求每天多次。如果不做緩存,每次都使用計(jì)算力去生成相同的頁(yè)面,是不是很可怕?
當(dāng)然,使用緩存,在一定程序上增加了應(yīng)用的復(fù)雜性。首先,每隔一段時(shí)間就需要使緩存失效并刷新,對(duì)吧?我們總不可能永遠(yuǎn)返回相同的結(jié)果。另一個(gè)問(wèn)題是,如果使用不合理,緩存容易膨脹,并導(dǎo)致內(nèi)存問(wèn)題。
好在,ASP.Net有很多已經(jīng)實(shí)現(xiàn)的優(yōu)秀的緩存庫(kù)可以幫助解決大部分的工作。
應(yīng)用服務(wù)器性能優(yōu)化中,垃圾回收是一個(gè)必須考慮的問(wèn)題。
我們知道,Dotnet垃圾回收有兩種不同的模式:工作站模式和服務(wù)器模式。前者被優(yōu)化為以最小的資源使用快速響應(yīng),而后者用于高吞吐量。
Dotnet運(yùn)行時(shí)默認(rèn)將桌面應(yīng)用程序中的GC模式設(shè)置為工作站模式,而服務(wù)器中的GC模式設(shè)置為服務(wù)器模式。這個(gè)默認(rèn)值幾乎總是最好的。在服務(wù)器中,GC將使用更多的機(jī)器資源,但是能夠處理更大的吞吐量。換句話說(shuō),該進(jìn)程將有更多的線程專門用于垃圾回收,它將能夠每秒釋放更多字節(jié)。
相比由系統(tǒng)自動(dòng)默認(rèn)GC模式而言,手動(dòng)設(shè)置應(yīng)用的垃圾回收模式會(huì)是一個(gè)安全的做法。服務(wù)器并不是總能正確地意識(shí)到需要什么樣的回收模式。
客戶端請(qǐng)求的數(shù)量,很大程度上可以決定服務(wù)器的數(shù)量或服務(wù)器的負(fù)載。所以,通過(guò)一些技巧來(lái)減少服務(wù)器請(qǐng)求,也是優(yōu)化的一部分內(nèi)容。
這個(gè)內(nèi)容需要在應(yīng)用中具體探討或體會(huì)。我只舉幾個(gè)實(shí)用的例子:
自動(dòng)完成機(jī)制
通常這種應(yīng)用,就是我們?cè)谇岸溯斎霑r(shí),客戶端從第一個(gè)輸入字符開(kāi)始做API調(diào)用。比方我們輸入"Dotnet",那我們會(huì)向服務(wù)器發(fā)送6個(gè)請(qǐng)求 --- "D"、"Do"、"Dot"、"Dotn"等等。但實(shí)際上,考慮到輸入的連續(xù)性,我們可以在調(diào)用前,做個(gè)短時(shí)的延時(shí),比方停止輸入500ms后才向服務(wù)器發(fā)送請(qǐng)求。你可能不會(huì)相信,我們實(shí)際應(yīng)用中實(shí)測(cè)的結(jié)果,可以減少93%的調(diào)用。
客戶端緩存
還是上面的例子。對(duì)于同一個(gè)應(yīng)用,很多位置的輸入都是相同或類似的。如果我們將自動(dòng)完成的結(jié)果緩存在客戶端,而不是每次都發(fā)送這些請(qǐng)求,同樣可以減少很多不必要的請(qǐng)求。
批處理
應(yīng)用中,一個(gè)頁(yè)面跟服務(wù)器的交互通常會(huì)有很多。通常最無(wú)腦的做法,就是一個(gè)事件發(fā)送一個(gè)請(qǐng)求。這樣的方式無(wú)形中會(huì)對(duì)服務(wù)器產(chǎn)生相當(dāng)?shù)膲毫?。如果可能,把這樣的事件合并成一個(gè)請(qǐng)求,會(huì)更有效率,對(duì)服務(wù)器更友好。
客戶端對(duì)服務(wù)器的請(qǐng)求,可能會(huì)被掛起。也就是說(shuō),客戶端發(fā)送了一個(gè)請(qǐng)求,但未收到響應(yīng),或者準(zhǔn)確地說(shuō),是經(jīng)過(guò)一個(gè)比較長(zhǎng)的時(shí)間后,收到一個(gè)超時(shí)響應(yīng)。雖然我們不希望發(fā)生這樣的事,但這種事情總在發(fā)生:處理請(qǐng)求時(shí)間過(guò)長(zhǎng)、或代碼死鎖、或代碼出錯(cuò)并且沒(méi)有正常捕獲錯(cuò)誤,當(dāng)然還包括等待一些本應(yīng)該出現(xiàn)但實(shí)際未出現(xiàn)的東西,例如來(lái)自隊(duì)列的消息、長(zhǎng)時(shí)間的數(shù)據(jù)庫(kù)響應(yīng)或?qū)α硪粋€(gè)服務(wù)的調(diào)用。
本質(zhì)上,當(dāng)一個(gè)請(qǐng)求被掛起時(shí),會(huì)掛起一個(gè)或多個(gè)線程。但應(yīng)用程序并不會(huì)停,并繼續(xù)處理新的請(qǐng)求。如果這個(gè)掛起在其它請(qǐng)求上也有重現(xiàn),那隨著時(shí)間,掛起的線程將越來(lái)越多,并最終影響服務(wù)器或系統(tǒng)的響應(yīng)。
因此,請(qǐng)求掛起對(duì)服務(wù)器性能的影響非常大。
這個(gè)問(wèn)題的解決,需要針對(duì)核心的部分,就是掛起的部分進(jìn)行調(diào)試,以確保程序處理了各種可能性,并不會(huì)產(chǎn)生任何意外的掛起。
服務(wù)器崩潰也是一個(gè)可能的性能問(wèn)題。
通常來(lái)說(shuō),客戶端請(qǐng)求期間發(fā)生一般的異常時(shí),應(yīng)用程序不會(huì)崩潰。但總有一些問(wèn)題,比方上下文之外的異常,或者一些災(zāi)難性的異常,比方OutOfMemoryException、ExecutionEngineException、StackOverflowException,當(dāng)這些發(fā)生時(shí),不管加多少catch,也擋不住崩潰的發(fā)生。
通常如果的托管在Web Server上,例如:IIS、Nginx、Jexus上的ASP.Net應(yīng)用,崩潰時(shí)Web Server會(huì)自動(dòng)回收資源,并重啟應(yīng)用??蛻舳说母杏X(jué)是臨時(shí)的慢響應(yīng)或503錯(cuò)誤。
而如果是直接啟動(dòng)的ASP.Net應(yīng)用,則程序會(huì)永久關(guān)閉,需要手動(dòng)重啟。這將是一個(gè)問(wèn)題。
所以,一方面,使用Web Server會(huì)是一個(gè)好習(xí)慣。另一方面,還是要檢查代碼,從根本上解決問(wèn)題。
這個(gè)問(wèn)題說(shuō)起來(lái)很簡(jiǎn)單,但實(shí)際開(kāi)發(fā)中,其實(shí)經(jīng)常會(huì)忘記,或者說(shuō)忽略應(yīng)用的規(guī)模。
用緩存,會(huì)忘了分布式緩存,忘了同步問(wèn)題,直接使用單機(jī)內(nèi)存緩存;
數(shù)據(jù)庫(kù)寫(xiě)入,會(huì)忘了并發(fā)下的數(shù)據(jù)一致性問(wèn)題;
。。。太多了,不一一寫(xiě)了
解決的辦法,是從頭開(kāi)始,就把代碼規(guī)?;?--- 從開(kāi)發(fā)到測(cè)試,全部使用雙向擴(kuò)展,即水平擴(kuò)展(向外擴(kuò)展)和垂直擴(kuò)展(向上擴(kuò)展)。垂直擴(kuò)展意味著服務(wù)機(jī)器添加更多的功能,比如更多的CPU和RAM,而水平擴(kuò)展意味著添加更多的機(jī)器。
記著,從開(kāi)發(fā)和測(cè)試開(kāi)始,就要使用與生產(chǎn)環(huán)境使用同等規(guī)模的環(huán)境來(lái)做。
應(yīng)用服務(wù)不同于桌面應(yīng)用或終端應(yīng)用。當(dāng)服務(wù)在執(zhí)行過(guò)程中需要等待響應(yīng)時(shí),比方數(shù)據(jù)庫(kù)操作、或者調(diào)用別的服務(wù)時(shí),這個(gè)服務(wù)本身就開(kāi)始有了一定的風(fēng)險(xiǎn)。如果數(shù)據(jù)庫(kù)或別的服務(wù)正忙著處理別的請(qǐng)求、或者存在性能問(wèn)題時(shí),必然會(huì)把性能問(wèn)題傳遞到調(diào)用方。
怎么辦?
解決的基本模式是異步調(diào)用。異步調(diào)用有兩個(gè)含義:
代碼的異步調(diào)用,就是我們常說(shuō)的async和await。
架構(gòu)的異步調(diào)用。這個(gè)通常是通過(guò)使用Kafka或RabbitMQ這樣的隊(duì)列服務(wù)來(lái)完成。向隊(duì)列發(fā)送消息,并不等待響應(yīng)。由另一個(gè)服務(wù)提取這些消息并處理。這個(gè)方式,通常是不需要回復(fù)的服務(wù)。而如果需要回復(fù),也可以用類似SignalR這樣的推送通知。
重要的是,這樣的方式下,系統(tǒng)組件不需要主動(dòng)等待服務(wù)。一切都是異步處理的。服務(wù)之間的耦合可以松散很多。
當(dāng)然同樣的,這樣會(huì)讓代碼變得更復(fù)雜。
取舍之間,是對(duì)代碼的控制力。
出差期間,斷斷續(xù)續(xù)寫(xiě)的這個(gè)東西,似乎有點(diǎn)亂,但就這樣吧,:P
在實(shí)際項(xiàng)目中,很多方面稍不注意,就能搞亂服務(wù)器的性能,而且有很多地方會(huì)出錯(cuò)。而解決呢,又沒(méi)有捷徑和技巧,需要仔細(xì)的計(jì)劃,有經(jīng)驗(yàn)的工程師,以及大量的緩沖時(shí)間來(lái)應(yīng)對(duì)可能出現(xiàn)的問(wèn)題。
后面我寫(xiě)寫(xiě)一些工具的應(yīng)用吧。很多方面,還是有好的工具可以幫助解決或至少是快速發(fā)現(xiàn)問(wèn)題的。
總之,這是一篇個(gè)人的經(jīng)驗(yàn)之談,希望能給大家一個(gè)拋磚引玉的作用。
感謝各位的閱讀,以上就是“ASP.Net服務(wù)性能優(yōu)化原則”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)ASP.Net服務(wù)性能優(yōu)化原則這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。