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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > asp.net >内容正文

asp.net

.NET遗留应用改造——性能优化篇

發(fā)布時(shí)間:2023/12/4 asp.net 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 .NET遗留应用改造——性能优化篇 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

由于各種原因我們總是要與公司各種老項(xiàng)目打交道。天有不測(cè)風(fēng)云,誰(shuí)也不知道這坨屎山會(huì)從哪個(gè)方向把你的嘴塞的滿滿的,還不讓你吐出來。既然如此...那只能細(xì)嚼慢咽的吞下去吧。

說實(shí)在話,只要業(yè)務(wù)不死,那些老大伯項(xiàng)目就還有價(jià)值。更何況這個(gè)本就沒什么人關(guān)注的項(xiàng)目突然被公司高層盯住了。說好幾個(gè)客戶都會(huì)用到這個(gè)系統(tǒng),并且必須要做好壓測(cè)工作,不能有任何閃失。

然后這項(xiàng)工作任務(wù)就毫無征兆的落在我手上了,改造優(yōu)化時(shí)間不到一周。既然如此,那就只好硬著頭皮上了。

項(xiàng)目整體

整個(gè)項(xiàng)目很“老”,用的技術(shù)棧是 .net4.5 + 多層架構(gòu) + sqlsugar + mssql。為什么”老“要加引號(hào)呢?因?yàn)槲液茈y想象這個(gè)項(xiàng)目只是3年前的項(xiàng)目(:攤手)。其中orm——sqlsugar我已經(jīng)找不到開源的項(xiàng)目地址了(用的僅僅是靜態(tài)dll),里面有很多寫法我都找不到文檔了。沒關(guān)系,又不能不能用,我只要參照之前的寫法不動(dòng)就行了。

那么再來說現(xiàn)在這個(gè)項(xiàng)目要進(jìn)行”手術(shù)“的地方:

首當(dāng)其沖的就是目前這個(gè)項(xiàng)目經(jīng)過測(cè)試人員壓測(cè),200并發(fā),持續(xù)半小時(shí)以及100并發(fā),增量并發(fā)到200持續(xù)1小時(shí)的壓測(cè)結(jié)果是......

不到20吞吐量,CPU一直100%。根據(jù)目前產(chǎn)品給出的用戶量,至少要達(dá)到120吞吐量。得到這個(gè)消息的我,當(dāng)時(shí)人都麻了......真不夸張。我一度認(rèn)為我要“死”在這個(gè)項(xiàng)目中了。

剖析項(xiàng)目

一邊看代碼一邊罵人的過程就不說了,相信大家都是這么過來的。接下來要做的就是熟悉代碼以及代碼下的業(yè)務(wù)場(chǎng)景。涉及優(yōu)化的業(yè)務(wù)場(chǎng)景看起來很簡(jiǎn)單,就是給定一個(gè)碼,系統(tǒng)接收校驗(yàn)真?zhèn)?#xff0c;然后進(jìn)行激活使用。

在經(jīng)過非常艱辛的和跟我一樣不熟悉這個(gè)業(yè)務(wù)的產(chǎn)品經(jīng)理溝通下,確定業(yè)務(wù)方的需求和目的之后,剩下就是真正實(shí)施了。

代碼層優(yōu)化

首先我從最簡(jiǎn)單的開始著手,就是code review。找出能一眼看出問題的點(diǎn),結(jié)果僅僅只是幾處f12,就讓我找到了”幾坨屎“,雖然不愿意,但我還是只能捂著鼻子強(qiáng)迫自己掰開看看究竟。

層與層之間調(diào)用關(guān)系混亂

因?yàn)槭嵌鄬?#xff0c;所以有BLL,DAL,Model三層。DAL引用ORM組建以及緩存組建,BLL引用DAL。DAL引用DBInstance。在實(shí)際查看中,我發(fā)現(xiàn)雖然BLL引用DAL,但是除了引用DAL之外,又初始化了DBInstance。緩存組建也是如此。在實(shí)際調(diào)用中,多次重復(fù)打開數(shù)據(jù)庫(kù)連接以及緩存連接,這無疑是一筆不小的開銷,而且還沒有任何意義

看到這個(gè)我要做就是優(yōu)化層之間的調(diào)用結(jié)構(gòu)。本著對(duì)老項(xiàng)目最小更改原則,我重新建了ActivationBll和ActivationDal文件,去掉多余的對(duì)象以及無用的IO連接。

代碼邏輯的一把嗦

往下就是具體代碼問題了,首先我就在原來的OldActivationBLL文件中看到如下代碼:

// OldActivationBll.cs private List<T1> global_fields1; // private List<T2> global_fields2; // private T3 field3; ...private void InitData(string code) {var dataset = dal.GetInitData(code);global_fields1 = dataset[0];global_fields2 = dataset[1];T3 = dataset[2];... }public void Activate(string code) {// 略過判斷InitData(code);// 引用類全局變量進(jìn)行各種操作field3.Property1 = ...;... }

有很多細(xì)節(jié)我都忽略了,大致就是現(xiàn)在一個(gè)類中定義一堆變量,然后在InitData方法中對(duì)這些變量一一賦值。這樣在其它地方,我都可以任意調(diào)用這些變量了。

這種有什么問題呢?其實(shí)這種webform式的寫法對(duì)程序運(yùn)行結(jié)果沒太大的影響。只是我個(gè)人不喜歡這種編程模式了,因?yàn)檫@樣非常容易造就意大利面條式的混亂。讓人看的非常頭痛,維護(hù)起來很苦難。特別是換人之后,因?yàn)轭惾肿兞磕睦锒寄鼙恍薷?#xff0c;不熟的人很容易導(dǎo)致非預(yù)期的結(jié)果與錯(cuò)誤。

當(dāng)我正閱讀代碼并嘗試優(yōu)化這種結(jié)果時(shí),發(fā)現(xiàn)事情并不是那么簡(jiǎn)單。

這是dal.GetInitData的代碼

// OldActivationDal.cs public DataSet GetInitData(string code) {string sql = @"declare @code nvarchar(250) declare @bid int declare @aid int declare @usedId uniqueidentitfier declare ... select top 1 * from table1 where code=@code select @bid = bid, @aid= aid from table1 inner join table2 on ... select ... -- 此處省略余下10幾行select";var dbset = dbhelper.ExecuteDataSet(sql, new parameter[] { ...});return dbset; }

看到這里是不是很驚訝,我當(dāng)時(shí)是震驚的。我當(dāng)時(shí)的反應(yīng)是正常人應(yīng)該不會(huì)這么寫吧。這真是“一把嗦”的寫法,把所有業(yè)務(wù)場(chǎng)景用到的前置對(duì)象一次性查出來賦值給對(duì)應(yīng)的字段,然后有需要的就引用這些對(duì)象。這個(gè)方法的引用數(shù)是12......。

毫無疑問,這種寫法問題很大,因?yàn)閷⒍喾N業(yè)務(wù)場(chǎng)景的數(shù)據(jù)一次性查出來,也不管到底用不用得上,這是種對(duì)資源的絕對(duì)浪費(fèi)。況且這對(duì)于數(shù)據(jù)庫(kù)來說也是很大的浪費(fèi),因?yàn)閷⒍鄠€(gè)語(yǔ)句合并成了一個(gè)大事務(wù)執(zhí)行

這種優(yōu)化手段就簡(jiǎn)單了,就是將一個(gè)大事務(wù)的sql語(yǔ)句,拆分成多個(gè)小事務(wù)的sql語(yǔ)句。不偷懶,多寫幾個(gè)方法按需給對(duì)象賦值。

這里面還有一個(gè)優(yōu)化點(diǎn)是用到了緩存,在原來十幾個(gè)sql查詢中,還有3個(gè)查詢語(yǔ)句是基礎(chǔ)數(shù)據(jù)(如渠道以及資源等一些基礎(chǔ)數(shù)據(jù))。

具體代碼錯(cuò)誤

前面提到的都還是設(shè)計(jì)上與流程的問題,還有一些明顯的錯(cuò)誤就是屬于代碼的寫法錯(cuò)誤了。在做了上面的改造措施之后,在我自己的本機(jī)做了同樣的壓測(cè),結(jié)果令人尷尬。吞吐量只有100左右。這明顯在我的意料之外的,這說明我優(yōu)化效果不好。然后我繼續(xù)詳細(xì)找代碼的問題,同時(shí)我寫了個(gè)慢查詢語(yǔ)句給db同事查看,讓其導(dǎo)出測(cè)試同學(xué)壓測(cè)的那個(gè)時(shí)間段的結(jié)果。期間還真讓我發(fā)現(xiàn)了一些比較明顯的問題,如下面的多任務(wù)寫法:

List<Task> taskList = new List<Task>(); object lockObj = new object(); string[] requestIds = bookId.Split(","); List<Resource> result = new List<Resource>(); foreach (var id in requestIds) {taskList.Add(Task.Factory.StartNew(delegate() {var resource = _resourceService.GetBookAsync(id).Result;if (resource != null) {lock (lockObj) {result.Add(r);}}})); } Task.WaitAll(taskList.ToArray()); return result;

大家來看下這段代碼都有哪些問題呢?如何優(yōu)化呢?這個(gè)后面我再給出我實(shí)際中的優(yōu)化方法

數(shù)據(jù)庫(kù)方面的優(yōu)化

找不到其它明顯的代碼問題就開始著手是不是數(shù)據(jù)庫(kù),sql語(yǔ)句的問題了。

與此同時(shí),db也已經(jīng)把結(jié)果導(dǎo)出給到我了,好家伙,排名第一(最耗時(shí))的就是前面我說的那個(gè)十幾個(gè)查詢合并為大事務(wù)的那個(gè)方法sql語(yǔ)句。緊追其后的就是另一個(gè)查詢語(yǔ)句,就是查詢?cè)撚脩羰欠褚呀?jīng)使用過該資源。該語(yǔ)句join了多個(gè)表,并且關(guān)聯(lián)的表都是百萬(wàn)級(jí)數(shù)據(jù)量的,并且條件很多(有5個(gè)),寫法如下

select a.Id,a.Code,a.Status,b.Type,a.ChannelId,c.ActivateTypeId,a.Bid,a.UserId,b.Name,d.Did,d.Dtype from a inner join b on a.Id = b.Id inner join c on b.uid = c.uid left join d on d.Bid = b.Id where a.UserId = @userId and a.Bid = @bid and a.ChannelId = @channelId and a.Status = 1 and d.DeviceCode = @deviceCode;

看到這個(gè)語(yǔ)句的第一想法是什么?

語(yǔ)句有問題?NO,而是檢查數(shù)據(jù)庫(kù)對(duì)應(yīng)的字段是否有索引,如果沒有命中索引,則會(huì)導(dǎo)致全表掃描,特別還join的是大表。結(jié)果也讓我有點(diǎn)失望,索引每個(gè)字?jǐn)喽冀恕N译S即斷點(diǎn)將那些條件的值拼成sql語(yǔ)句到線上環(huán)境執(zhí)行,結(jié)果發(fā)現(xiàn)速度非常慢,足足有15-30秒波動(dòng)。想了大概幾分鐘,立馬得出了一個(gè)結(jié)論——索引的問題,給目標(biāo)字段建立索引針對(duì)這種情況效果不大,而是要針對(duì)這種熱調(diào)用場(chǎng)景有針對(duì)性的建索引——即聯(lián)合索引。我給a這個(gè)大表建立idx_UserId_Bid_ChannelId_Status的聯(lián)合索引,然后去掉了無用的字段,這樣就減少了要join的表和潛在的回表。建好之后再次執(zhí)行,只用了300ms左右。

此時(shí)壓測(cè)的結(jié)果已經(jīng)提升到了200左右(真就無腦建索引就完事了!-_-!)。

其實(shí)除此之外,還有幾個(gè)查詢也是很慢的。就不細(xì)舉例了,解決方案除了聯(lián)合索引,還有一種優(yōu)化手段是包含列的索引。這種手段常見于select子表join是非常有效果的,其目的是為了減少回表的次數(shù),爭(zhēng)取一次查詢就能將數(shù)據(jù)在多叉樹的節(jié)點(diǎn)上直接返回

總結(jié)

自此,完成這些改造手術(shù)之后的壓測(cè)結(jié)果在我本機(jī)機(jī)器上是達(dá)到了200多吞吐。算是完成了領(lǐng)導(dǎo)臨時(shí)交給我的任務(wù)吧。在部署到線上時(shí),測(cè)試同學(xué)壓測(cè)出來的結(jié)果到達(dá)了500。不過讓我有點(diǎn)意外的是,技術(shù)總監(jiān)還是毅然決定給服務(wù)器升配加負(fù)載。(小聲嘀咕:我還以為可以減配呢)

那么總結(jié)這次的性能優(yōu)化點(diǎn)可以簡(jiǎn)單的概括三點(diǎn):

  • 架構(gòu)層面(即分層要明確,減少重復(fù)的對(duì)象構(gòu)造)

  • 代碼層面(減少明顯的編程常識(shí)錯(cuò)誤,如盡量避免多任務(wù)共享變量;還有不要偷懶...)

  • 數(shù)據(jù)庫(kù)層面(不要執(zhí)行大的sql語(yǔ)句,要將大的拆成多個(gè)小事務(wù)sql語(yǔ)句,建對(duì)索引會(huì)省很多事)

關(guān)于具體實(shí)施,特別對(duì)手是老項(xiàng)目時(shí),一定要本著“能不改原來的代碼就不改為第一定律”。把這些老酒用新瓶包裝起來。因?yàn)槟阌肋h(yuǎn)也不知道你改動(dòng)了其中一處地方,會(huì)給項(xiàng)目造成多大的傷害。

最后

在結(jié)束本文之前,我給出之前代碼的優(yōu)化版本。在優(yōu)化之前我們先清楚代碼有問題。

很明顯的有兩個(gè)問題:

  • 多任務(wù)并行調(diào)用異步方法,在遍歷中共享了result對(duì)象,并通過上鎖添加方法返回的結(jié)果

  • 直接調(diào)用了異步方法GetBookAsync.Result

  • 這兩點(diǎn)碰到一起了,這讓本不富裕的服務(wù)器資源更是雪上加霜。

    下面是我優(yōu)化的版本

    string[] requestIds = bookId.Split(","); var taskList = new Task[requestIds.Length]; var result = new Resource[requestIds.Length]; for (int i = 0; i < requestIds.Length; i++) {var idx = i;taskList[idx] = Task.Run(() => {}).ContinueWith(t => {result[idx] = t.Result;}); } Task.WaitAll(taskList); return result.ToList();

    這是我想到的優(yōu)化的版本,這樣既能做到無鎖編程,又可以不用阻塞異步方法。硬要說其它的問題的話,那就是requestIds的數(shù)量是潛在的問題點(diǎn),因?yàn)閿?shù)量非常多的時(shí)候,這個(gè)時(shí)候就會(huì)給系統(tǒng)帶來很大的負(fù)擔(dān),最終也會(huì)引起API服務(wù)或數(shù)據(jù)庫(kù)宕機(jī)的情況。這個(gè)時(shí)候其實(shí)我們可以通過PLINQ解決這點(diǎn),通過分區(qū)來取得最佳性能。

    好了這篇文章就到這里了。

    總結(jié)

    以上是生活随笔為你收集整理的.NET遗留应用改造——性能优化篇的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。