日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > asp.net >内容正文

asp.net

我的 .NET Core 博客性能优化经验总结

發布時間:2023/12/4 asp.net 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 我的 .NET Core 博客性能优化经验总结 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方藍字關注“汪宇杰博客”

導語

去年8月,我用 .NET Core 重寫了我的博客系統。經過一年多的優化,服務器響應速度從上線時候的 80ms 提高到了現在的 8ms,十倍提速。可惜由于部署在國外,自然不可抗力會導致中國用戶晚上訪問速度不穩定。本文分享網絡正常的前提下,我做了哪些優化和提升,希望能幫到大家。

其實,在.NET Core之前,我的舊版博客系統是 .NET Framework寫的,從2008年的 ASP.NET Web From 2.0 一直維護到2018年的 ASP.NET MVC5,曾經被人懷疑過:“別騙人了,.NET怎么可能這么快?” 。而如今,.NET Core 從本質上就已經比 .NET Framework 有了巨大的性能提升,甚至在不少測試下超過了Node、Go、Java。其實光看 benchmark 沒太大的意義,大部分實際應用中性能問題并不在于語言和框架,而是由不佳的設計、錯誤的框架使用方法引起的。在 .NET Core 的實踐過程中,我也學習和收獲了很多,因此寫下此文,分享我自己的性能優化經驗。

沒有銀彈

首先,每個系統都是不同的。性能優化需要針對不同系統,不同業務場景,不同應用領域,不同用戶種群,沒有一個通用方法。比如我的博客,是內容站,交互少,大量情況都是各種姿勢讀數據,所以我要保證的是盡可能快的提升數據讀取速度。而有些系統,比如電商,有遠比內容站復雜的業務邏輯,還有秒殺等極端情況。比如國內阿里帶隊的“數據庫不要有外鍵”,這是因為阿里的業務壓力必須這么做,他們需要的是極端情況的寫入速度,顯然我的博客以及很多內容站沒有這種場景,因此我依然可以用外鍵。所以,在開始之前,讀者必須明白,軟件設計是沒有銀彈的。我所列出的經驗僅僅針對我自己的博客。大部分經驗能應用在類似的內容站上,但不要盲目實踐。同樣是內容站,面對的用戶群和壓力也不一樣,比如我的博客肯定無法和新浪、網易等比流量,所以優化的關鍵點和方法也不同。

分析和發現關鍵點

雖然我們在系統設計時會有一定的預判,比如哪些功能是用戶最常用的,哪些請求會是最頻繁的。但是上線之后用戶的行為才是事實,有時候系統的表現會和我們的預期不一樣。而且,隨著時間的推移,用戶的使用習慣可能會變,系統面臨壓力的部分也會改變。所以,我們需要記錄和分析系統在實際使用過程中產生的數據和用戶行為。而我所使用的Azure Application Insights就是一款極佳的APM工具。作為一個網站,性能是服務端(后臺)和客戶端(前臺)共同決定的,Azure Application Insights可以同時收集后端API處理速度、數據庫查詢相應速度以及前端頁面資源加載速度、JS執行速度等,也會自動分析出最慢的請求是哪些,系統最耗時的操作在哪個環節(前端、程序或數據庫),甚至Azure SQL Database能根據實際使用情況自動推薦優化方案(比如哪里加何種索引等)。本文不討論APM工具的使用。但是做性能優化的時候,必須針對實際用戶產生的數據,分析以后去鑒別哪里需要優化。我的博客上線幾個月后,我的分析如下:

1.?????? 客戶端性能開銷在加載資源和過多的請求(前端庫,博客文章配圖)

2.?????? 服務端性能開銷在過多重復的SQL查詢

3.?????? 博客配圖由后端從Azure Blob Storage中讀取再返回前端產生雙倍性能開銷

前端實踐

使用 bundle 避免過多請求

我相信大部分Web程序員都熟悉這一條建議,這也是最直接有效的前端性能提升方式。我們網站中通常要加載許多不同的庫和資源,有圖片,CSS,JS等。而瀏覽器大量的時間開銷在于對這些資源發起請求,等待響應。即使你的文件很小,但是太多的請求數量會明顯降低網頁加載速度。因此很久之前業界就流行一種做法,即打包壓縮資源文件,比如將多個JS文件打包壓縮成一份,這樣瀏覽器就只要發起一個請求,就能加載你網站所有需要的JS資源。

打包工具五花八門,可以根據自己的喜好選擇。我博客使用的是 BuildBundlerMinifier,它可以在編程和編譯時完成打包:

<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />

其定義示例如下:

{

? "outputFileName": "wwwroot/js/app/app-js-bundle.js",

? "inputFiles": [

??? "wwwroot/lib/jquery/jquery.min.js",

??? "wwwroot/lib/jquery-validate/jquery.validate.min.js",

??? "wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js",

??? "wwwroot/lib/twitter-bootstrap/js/bootstrap.bundle.min.js",

??? "wwwroot/lib/jquery-qrcode/jquery.qrcode.min.js",

??? "wwwroot/lib/toastr.js/toastr.min.js",

??? "wwwroot/js/lazyload.js",

??? "wwwroot/js/app/moonglade-base.js",

??? "wwwroot/js/app/postslug.js",

??? "wwwroot/js/app/csrf.js",

??? "wwwroot/js/app/comments.js"

? ]

}

Js真的要放body最后嗎?

這也是一條幾乎Web程序員人盡皆知的原則。如果你將JS資源放在body最后加載,即</body>標簽之前,那么瀏覽器會異步加載你的JS。如果按照傳統方式將JS資源放在head標簽里,那么瀏覽器必須加載完JS資源才開始渲染網頁。

聰明的朋友可能了解,這一條在2019年已經不一定適用了。首先,我們可以通過添加defer標簽來告訴瀏覽器,遇到這個JS,不要等加載完成再繼續干活,你管你渲染網頁,我管我加載:

<script defer src="996.js"></script>

<script defer src="007.js"></script>

不過defer的腳本還是會按順序執行,這對于有依賴關系的JS資源十分重要,比如上面這段代碼,即使007.js非常小,首先加載完成,它也必須等到996.js加載完成后才能執行。如果你想要誰先加載完,誰先執行的效果,把defer換成async即可,這種情況下你得保證你的JS之間沒有依賴關系,沒有依賴關系,沒有依賴關系!!!重要的說三遍!

可惜,由于我們控制不了用戶使用的瀏覽器類型和版本,根據 Azure Application Insights 的后臺統計,仍然有不少用戶使用低版本的瀏覽器訪問我的網站,它們并不認識 defer和 async。

所以目前,我博客的實踐依然是JS盡量放body最后,但不是絕對!由于框架性質的JS文件必須完成加載才能正確渲染網頁,因此我博客中它們還是放在head里,而用戶代碼我會放在body最后。優化性能的前提,一定是不要影響正常功能!所以,程序員看問題不要非黑即白,還是那句軟件工程的老話:沒有銀彈。

如果你的網站沒有低版本的客戶端,那么可以盡量用 defer和 async。

使用 HTTP/2

啟用HTTP2可以有效提高網絡傳輸效率,根據該項調研(https://w3techs.com/technologies/details/ce-http2),截至2019年12月,全球大約有42.6%的網站已經升級到了HTTP2。其對于網絡性能的提升主要在這幾個方面:

降低延遲以提高網頁加載速度:

  • HTTP頭的數據壓縮

  • 服務器端推送 (這個.NET Core好像沒有)

  • 請求管線

  • 修復HTTP 1.x中head-of-line blocking?的問題

  • 同一個TCP連接上的請求多路復用

(參考:https://en.wikipedia.org/wiki/HTTP/2)

而我的博客使用微軟 Azure ?App Service 托管,可以點點鼠標一秒切換到 HTTP/2,而不用自己996收福報:

如果你沒有用 Azure,也不用擔心,最新版 .NET Core 3.1 的kestrel 默認就打開了HTTP/2:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?view=aspnetcore-3.1#http2-support

使用壓縮

開啟服務器端response壓縮可以減小資源傳輸的體積,從而達到提升性能的目的。使用 ASP.NET Core 開發的網站,部署在Azure上默認就會開啟gzip,不需要自己996去研究。我的博客采用的 App Service Plan 是 Windows Server 2016,上面的IIS啟用了靜態和動態資源壓縮。

然而,如果你不幸沒有使用 Azure,那么自己稍微996一下,在IIS上開啟壓縮也不難,可以點點鼠標就搞定,也可以通過Web.config開啟(.NET Core部署在IIS下也認web.config),具體方法可以參考:https://docs.microsoft.com/en-us/iis/configuration/system.webserver/httpcompression/??

如果你用的不是IIS,也沒關系,再996一下,.NET Core自己也可以加response壓縮:

https://docs.microsoft.com/en-us/aspnet/core/performance/response-compression?view=aspnetcore-3.1

真的要用SPA嗎?

2014年以后,隨著SPA的興起,Angular等框架逐漸成為了前端開發的主流。它們解決的問題正是提升前端的響應度,讓Web應用盡量接近本地原生應用的體驗。我也遇到過不少朋友有疑問,為啥我的博客不用angular寫?是我不會嗎?

其實并不那么簡單。實際上我在公司的主要工作目前也是寫angular,博客曾經的.NET Framework版的后臺也用過angularjs以及angular2,經過一系列的實踐表明,我博客這樣的內容站用angular收益并不大。

其實這并不奇怪,在盲目選擇框架之前,我們得注意一個前提條件:SPA框架所針對的,其實是Web應用。而應用的意思是重交互,即像Azure Portal或Outlook郵箱那樣,目的是把網頁當應用程來開發,這時候SPA不僅能提升用戶體驗,也能降低開發成本,何樂而不為?但是博客屬于內容為主的網站,不是應用,要說應用也勉強只能說博客的后臺管理可以是應用。博客前臺唯一的交互就是評論、搜索,因此SPA并不適合這樣的工作。這就像你要去菜場買菜,騎自行車反而比你開個坦克過去方便。

所以,程序員切記,看待問題不要非黑即白,不要覺得什么流行就一定適合所有項目,還是那個著名的軟件工程原則:沒有銀彈!

在微軟官方文檔里也有同樣的關于何時選擇SPA,何時選擇傳統網站的參考:

https://docs.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/choose-between-traditional-web-and-single-page-apps?

You should use traditional web applications when:

  • Your application's client-side requirements are simple or even read-only.

  • Your application needs to function in browsers without JavaScript support.

  • Your team is unfamiliar with JavaScript or TypeScript development techniques.

You should use a SPA when:

  • Your application must expose a rich user interface with many features.

  • Your team is familiar with JavaScript and/or TypeScript development.

  • Your application must already expose an API for other (internal or public) clients.

后端實踐

盡量避免Exception

.NET的Exception是一種特殊的類型,不管用戶代碼是否處理exception,只要產生,就會在CLR上有開銷。所以盡量避免產生Exception,尤其是不要利用Exception控制程序流程,這一點通常在.NET的技術文章里都會提及。一個不正常利用Exception的例子是我曾經在公司代碼里看見過類似這樣判斷輸入的內容是否為數字的代碼:

try

{???? ???????

????? Convert.ToInt32(userInput);

????? return true;

}

catch (Exception ex)

{

????? return false;

}

而.NET其實可以這樣寫:

int.TryParse(userInput);

我相信大部分正常的.NET程序員都不會犯上面這種錯誤。這樣的代碼效率低下且不說,還容易炸毀IIS。IIS的應用程序池如果在短時間檢測到大量CLR異常就會自爆重啟并返回503,中斷你的網站服務。

不過關于Exception的另一個爭論點在于,是否需要為業務異常設計自己的Exception類型?也就是檢查到非正常業務行為,到底返回Error Code還是直接拋出Exception再由上層處理?關于這點,我也沒有確定的結論。目前我的實踐是,僅對于非法輸入拋出參數異常,業務上的錯誤不拋異常,例如文章被和諧后產生的404,不去設計比如 PostNotFoundException,這一點很關鍵,因為經常有無聊黑客新手使用自動化工具掃描我的博客是否有漏洞,而這些工具會批量請求例如wp-login.php之類的對于我博客來說不存在的資源,如果我設計成拋出Exception再返回404,那么會造成短時間內CLR上大量的異常,絕對會爆。

參考:https://devblogs.microsoft.com/cbrumme/the-exception-model/ 中“Performance and Trends”一節。

EF盡量使用AsNoTracking篩選只讀數據

每個.NET群,都可以為Entity Framework vs Dapper吵一天。其實EF雖然在很多場景由局限,但并不那么差,只是想要用對,不產生性能問題,付出的學習成本相當高。但是既然入坑了,就最好把它用用對。而最常見的情況就是遇到只讀數據,可以加上AsNoTracking()。我博客大部分的場景都是只讀數據,并且讀取后直接處理好關聯數據(Include),因此可以使用AsNoTracking()來斷開EF對于對象的追蹤,節省內存也提高性能。為了不每次手寫AsNoTracking() 導致996,我在博客的存儲層直接設置了默認參數:

public IReadOnlyList<T> Get(ISpecification<T> spec, bool asNoTracking = true)

{

??? return asNoTracking ?

??????? ApplySpecification(spec).AsNoTracking().ToList() :

??????? ApplySpecification(spec).ToList();

}

關于EF,我在2012年還寫過一篇關于性能的文章,至今也適用于.NET Core,歡迎參考:

《Performance tips for Entity Framework》

另外,在最新的EF Core 3.x中,微軟為了不被人罵EF性能差,直接默認禁止了client side evaluation,避免了忘寫Include結果還開Lazy Load導致外鍵表被查詢幾百次的尷尬場面。

數據庫DTU

我的博客采用Azure SQL數據庫的DTU計量方式。請求頻繁的時候會導致DTU耗盡,從而后續請求需要排隊執行。所以首先優化的就是增加DTU容量,目前20個DTU基本管夠。

而DTU是否夠用可以直接在Azure的面板里看報表得到:

內存及緩存,減少數據庫調用

計算機的內存是為了用,而不是為了省。程序要么犧牲空間換時間,要么犧牲時間換空間。合理使用內存做緩存,而不是每次都調用數據庫,可以提高一段時間內的性能。特別是云端環境,數據庫的調用通常是最花時間的環節(Application Insights里認為是dependency call)。即使不用內存緩存,也可以根據項目需要配置redis等產品。

在我博客里,緩存的使用隨處可見。比如文章分類、Custom Page這種不經常更新的數據,就可以緩存起來,這樣就不至于每次請求都去查詢數據庫。另外,像配置之類的數據,也建議設計成單例模式,網站啟動時候加載完畢,不要每個請求都去數據庫里重新讀配置。這將極大的減少數據庫的壓力并提高網站響應速度。

var cacheKey = $"page-{routeName.ToLower()}";

var pageResponse = await cache.GetOrCreateAsync(cacheKey, async entry =>

{

??? var response = await _customPageService.GetPageAsync(routeName);

??? return response;

});

除了數據庫,本地、遠程圖片或其他類型的文件也可以利用緩存來提高性能。

CDN

盡量用CDN服務靜態資源,并配置pre-fetch,減少DNS解析次數。我的博客圖片由于設計了抽象隔離,博客的配圖并不是像訪問靜態資源那樣直接輸出到客戶端,目前支持兩種存儲方式:Azure Blob、本地文件系統,不管哪種存儲,都避免不了從對應位置讀取圖片,并返回給客戶端顯示,即使加上了服務器端緩存(MemoryCache),這個過程也依然對服務器有較大壓力。

目前我選用的存儲方式為Azure Blob。以前讀取一張圖片的過程是:

首次請求:服務器去Azure Blob拿圖片,客戶端再去網站服務器拿圖片。

后續請求:Hit到memory cache,僅從網站服務器返回圖片給客戶端。

然而,即使后續請求不用經過Azure Blob,對Web服務器的請求還是必須存在的,這也是挺大的開銷。于是,我通過CDN,讓圖片請求再也不經過我自己的Web服務器,而是直接訪問Azure Blob。

于是現在,讀取一張圖片的過程是:

首次請求:CDN判斷自己是否已經緩存了圖片,如果沒有,去Azure Blob里拿,并緩存起來。

首次請求:CDN判斷自己是否已經緩存了圖片,如果沒有,去Azure Blob里拿,并緩存起來。

這樣一來,用戶閱讀博客文章時產生的圖片請求只會經過Azure CDN的服務器,不會對Web服務器造成壓力。

另外,可以在網頁 header 上加個 dns-prefetch,指向CDN服務器域名:

<link rel="dns-prefetch" href="https://cdn-blob.edi.wang" />

這樣瀏覽器就會提前解析CDN服務器的地址,進一步加快網頁加載速度。

日志級別

很多程序員習慣本地和生產用同一份日志配置,而本地通常打開Debug、Trace等低等級日志以幫助我們的開發和測試工作,線上的產品是經過測試的相對穩定的發布版本,其實并不需要這些低等級日志,所有的事件都要記log的話會極大的影響應用性能。所以我的實踐是生產環境只開Warning以上的日志級別,除非遇到刁鉆問題需要收集詳細爆炸數據,會臨時開幾個小時的Debug日志。

APM不要隨便加profiler

這條建議和上面類似,APM工具通常提供了各種profiler,然而這一般都會影響性能。就算是Azure自己的Application Insights也是如此。所以除非程序出現需要996調查的爆炸事故,一般不建議打開這些profiler。

總結

以上是我目前使用到的提升博客性能的方法。但是性能優化沒有完全通用的策略,需要根據不同系統,不同業務,不同壓力來動態調整優化方案,總體思想即:減少不必要的調用與開銷。但有時候也需要調整應用程序的部署架構,比如Azure可以加上Traffic Manager、Front Door,使用負載均衡功能。歡迎大家留言分享自己的想法,以及對本文的補充和建議!

總結

以上是生活随笔為你收集整理的我的 .NET Core 博客性能优化经验总结的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。