有一段時(shí)間沒(méi)有更新博客了,最近半年都在著寫(xiě)書(shū)《.NET框架設(shè)計(jì)—大型企業(yè)級(jí)框架設(shè)計(jì)藝術(shù)》,很高興這本書(shū)將于今年的10月份由圖靈出版社出版,有關(guān)本書(shū)的具體介紹等書(shū)要出版的時(shí)候我在另寫(xiě)一篇文行做介紹??梢韵韧嘎兑幌?,本書(shū)是博主多年來(lái)對(duì)應(yīng)用框架學(xué)習(xí)的總結(jié),里面包含了十幾個(gè)重量級(jí)框架模式,這些模式都是我們目前所經(jīng)常使用到的,對(duì)于學(xué)習(xí)框架和框架開(kāi)發(fā)來(lái)說(shuō)是很好的參考資料,大家敬請(qǐng)期待。
創(chuàng)新互聯(lián)建站企業(yè)建站,十載網(wǎng)站建設(shè)經(jīng)驗(yàn),專(zhuān)注于網(wǎng)站建設(shè)技術(shù),精于網(wǎng)頁(yè)設(shè)計(jì),有多年建站和網(wǎng)站代運(yùn)營(yíng)經(jīng)驗(yàn),設(shè)計(jì)師為客戶打造網(wǎng)絡(luò)企業(yè)風(fēng)格,提供周到的建站售前咨詢和貼心的售后服務(wù)。對(duì)于網(wǎng)站設(shè)計(jì)、成都網(wǎng)站設(shè)計(jì)中不同領(lǐng)域進(jìn)行深入了解和探索,創(chuàng)新互聯(lián)在網(wǎng)站建設(shè)中充分了解客戶行業(yè)的需求,以靈動(dòng)的思維在網(wǎng)頁(yè)中充分展現(xiàn),通過(guò)對(duì)客戶行業(yè)精準(zhǔn)市場(chǎng)調(diào)研,為客戶提供的解決方案。
好了,進(jìn)入文章主題。
最近幾個(gè)月本人一直從事著SOA服務(wù)開(kāi)發(fā)工作,簡(jiǎn)單點(diǎn)講就是提供服務(wù)接口的;從提供前端接口WEBAPI,到提供后端接口WCF\SOAFramework,期間學(xué)到了不少有關(guān)多線程使用上的經(jīng)驗(yàn),這些經(jīng)驗(yàn)有的是本人自己的錯(cuò)誤使用后的經(jīng)驗(yàn),有些是公司的前輩的指點(diǎn),總之這些東西你不遇到過(guò)你是不會(huì)意識(shí)到該如何使用的,所以本人覺(jué)得很有必要總結(jié)分享給廣大和我一樣工作在一線的博友們。
我們從服務(wù)的處理環(huán)節(jié)為順序來(lái)介紹:
任何服務(wù)的調(diào)用都需要首先進(jìn)到服務(wù)的入口方法中,該方法通常扮演著領(lǐng)域邏輯的門(mén)面接口(將系統(tǒng)用例進(jìn)行服務(wù)接口的劃分),通過(guò)該接口進(jìn)行用例的調(diào)用。當(dāng)我們需要處理長(zhǎng)時(shí)間過(guò)程時(shí)都會(huì)面臨著頭疼的超時(shí)異常,如果我們?cè)偃ピO(shè)計(jì)如何做超時(shí)補(bǔ)償措施就會(huì)很復(fù)雜而且是沒(méi)有必要的開(kāi)銷(xiāo)。長(zhǎng)時(shí)處理的服務(wù)調(diào)用場(chǎng)景多半在同步數(shù)據(jù)中,通過(guò)某個(gè)JobWs(工作服務(wù))定期的來(lái)同步數(shù)據(jù)(本人就是在這個(gè)過(guò)程中學(xué)到的),當(dāng)我們無(wú)法預(yù)知我們的服務(wù)會(huì)處理多長(zhǎng)時(shí)間時(shí),基本上都會(huì)首先去設(shè)置調(diào)用端的連接超時(shí)時(shí)間(是不是都會(huì)這么想?);這很正常,很來(lái)超時(shí)時(shí)間就是用來(lái)給我們用的;但是我們忽視了我們當(dāng)前的業(yè)務(wù)場(chǎng)景了,如果你的服務(wù)不返回任何有關(guān)狀態(tài)值的話“其實(shí)應(yīng)該開(kāi)啟一個(gè)獨(dú)立的線程來(lái)處理同步邏輯而讓服務(wù)的調(diào)用者盡早收到相應(yīng)”。
public class ProductApplicationService { public void SyncProducts() { Task.Factory.StartNew(() => { var productColl = DominModel.Products.GetActivateProducts(); if (!productColl.Any()) return; DominModel.Products.WriteProudcts(productColl); }); } }
這樣就可以盡早解放調(diào)用者;通過(guò)開(kāi)啟一的單獨(dú)的線程來(lái)處理具體的同步邏輯。
如果你的服務(wù)需要返回某個(gè)狀態(tài)值怎么辦?其實(shí)我們可以參考”異步消息架構(gòu)模式“來(lái)將消息寫(xiě)入到某個(gè)消息隊(duì)列中,然后客戶端定期來(lái)取或者推送都可以,讓當(dāng)前的這個(gè)服務(wù)方法能夠平滑的處理,至少為系統(tǒng)的整體性能瓶頸做了一份貢獻(xiàn)。
入口位置通常都會(huì)記錄下調(diào)用的異常信息,也就是加上一個(gè)try{}catch{},用來(lái)捕獲本次調(diào)用的所有異常信息。(當(dāng)然你可能會(huì)說(shuō)代碼中充斥著try{}catch{}不是很好,可以將其放到某個(gè)看不見(jiàn)的地方自動(dòng)處理,這有好有壞,看不見(jiàn)的地方我們就必然少不了配置,少不了對(duì)自定義異常類(lèi)型的配置,總之事物都有兩面性。)
public class ProductApplicationService { public void SyncProducts() { try { Task.Factory.StartNew(() => { var productColl = DominModel.Products.GetActivateProducts(); if (!productColl.Any()) return; DominModel.Products.WriteProudcts(productColl); }); } catch(Exception exception) { //記錄下來(lái)... } } }
像這樣,看上去好像沒(méi)問(wèn)題哦,但是我們仔細(xì)看看就會(huì)發(fā)現(xiàn),這個(gè)try{}catch{}根本捕獲不到我們?nèi)魏萎惓P畔⒌?,因?yàn)檫@個(gè)方法是在我們開(kāi)啟的線程外面的,也就是說(shuō)它早就結(jié)束了,開(kāi)啟的線程處理?xiàng)V懈揪蜎](méi)有任何的try{}catch{}機(jī)制代碼了;所以我們需要稍微調(diào)整一下同步代碼來(lái)支持異常捕獲。
public class ProductApplicationService { public void SyncProducts() { Task.Factory.StartNew(SyncPrdoctsTask); } private static void SyncPrdoctsTask() { try { var productColl = DominModel.Products.GetActivateProducts(); if (!productColl.Any()) return; DominModel.Products.WriteProudcts(productColl); } catch (Exception exception) { //記錄下來(lái)... } } }
如果你裝了像Resharp這樣的輔助插件的話會(huì)對(duì)你重構(gòu)代碼很有幫助,提取某一個(gè)方法會(huì)很方便快捷;
上述代碼中,就在新開(kāi)的線程中包含了異常捕獲的代碼;這樣就不會(huì)導(dǎo)致你程序拋出很多未處理異常,在重要的邏輯點(diǎn)可能會(huì)丟失數(shù)據(jù)。不是說(shuō)所有的異常都應(yīng)該由框架來(lái)處理,我們需要自己手動(dòng)的控制某個(gè)邏輯點(diǎn)的異常,這樣我們可以保證我們自己的邏輯能夠繼續(xù)運(yùn)行下去。有些邏輯是不可能因?yàn)楫惓5某霈F(xiàn)而終止整個(gè)處理過(guò)程的。
位于SOA服務(wù)的最外層服務(wù)接口時(shí),通常都需要包裝內(nèi)部眾多服務(wù)接口來(lái)組合出外部需要的數(shù)據(jù),此時(shí)需要查詢很多接口的數(shù)據(jù),然后等待數(shù)據(jù)都到齊了之后再將其統(tǒng)一的返回給前端。由于我有一段時(shí)間是專(zhuān)門(mén)給前端H5提供接口的,最讓我感觸的就是服務(wù)接口需要整合所有的數(shù)據(jù)給前端,從用戶的角度講不希望手機(jī)的界面還出現(xiàn)異步的現(xiàn)象吧,畢竟就那么大屏幕還有白的地方。但是這個(gè)需求給我們開(kāi)發(fā)人員帶來(lái)了問(wèn)題,如果用順序讀取方式將數(shù)據(jù)都組合好,那個(gè)時(shí)間是人所無(wú)法接受的,所以我們需要開(kāi)啟并行來(lái)同時(shí)讀取多個(gè)后端服務(wù)接口的數(shù)據(jù)(前提是你這些數(shù)據(jù)沒(méi)有前后依賴關(guān)系)。
public static ProductCollection GetProductByIds(ListpIds) { var result = new ProductCollection(); Parallel.ForEach(pIds, id => { //并行方法 }); return result; }
一切看起來(lái)很舒服,多個(gè)ID同一個(gè)時(shí)間被一起運(yùn)行,但是這里面有個(gè)坑。
如果我們用上述代碼開(kāi)啟并行后,從GetProductByIds業(yè)務(wù)點(diǎn)來(lái)看一切會(huì)很順利,而且效果很明顯速度很快;但是如果當(dāng)前GetProductByIds方法還在處理過(guò)程中時(shí)你再發(fā)起另一個(gè)服務(wù)調(diào)用時(shí)你就會(huì)發(fā)現(xiàn)服務(wù)器響應(yīng)變慢了,因?yàn)樗械恼?qǐng)求線程全部被占用了,這里Parallel并沒(méi)有我們想的那么智能,能根據(jù)情況控制線程數(shù);我們需要自己控制我們并行時(shí)的最大線程數(shù),這樣可以防止由于多線程被一個(gè)業(yè)務(wù)點(diǎn)占用而導(dǎo)致服務(wù)隊(duì)列其他的后續(xù)請(qǐng)求(此時(shí)看CPU不一定很高,如果CPU過(guò)高導(dǎo)致不接受請(qǐng)求能理解,但是由于系統(tǒng)設(shè)置的問(wèn)題讓線程數(shù)不夠用也是有可能的)
public static ProductCollection GetProductByIds(ListpIds) { var result = new ProductCollection(); Parallel.ForEach(pIds, new ParallelOptions() { MaxDegreeOfParallelism = 5 /*設(shè)置最大線程數(shù)*/}, id => { //并行方法 }); return result; }
這點(diǎn)上我犯了兩次錯(cuò),第一次是將前端需要的數(shù)據(jù)順序打亂了,導(dǎo)致數(shù)據(jù)的排名出來(lái)問(wèn)題;第二次是將寫(xiě)入數(shù)據(jù)庫(kù)的同步數(shù)據(jù)的時(shí)間打亂了,導(dǎo)致程序無(wú)法再繼續(xù)上次的結(jié)束時(shí)間繼續(xù)同步。所以請(qǐng)大家一定要記住,當(dāng)你使用并行時(shí),首先問(wèn)自己你當(dāng)前的數(shù)據(jù)上下文邏輯在不在乎前后順序關(guān)系,一旦開(kāi)啟并行后所有的數(shù)據(jù)都是無(wú)須的。
現(xiàn)在我們提供的服務(wù)接口多多少少會(huì)用到異步async,大概就是想讓我們的系統(tǒng)能夠提到點(diǎn)并發(fā)量,讓寶貴的請(qǐng)求處理線程能夠及時(shí)的被系統(tǒng)再利用而不是在等待上浪費(fèi)。
大概代碼會(huì)是這樣的,服務(wù)入口:
public async TaskOperationProduct(long ids) { return await DominModel.Products.OperationProduct(ids); }
業(yè)務(wù)邏輯:
public static async TaskOperationProduct(long ids) { return await Task.Factory.StartNew (() => { System.Threading.Thread.Sleep(5000); return 100; //其實(shí)這里開(kāi)啟的線程是請(qǐng)求線程池中的請(qǐng)求處理線程,說(shuō)白了這樣并不會(huì)提高并發(fā)等于沒(méi)用。 }); }
其實(shí)當(dāng)我們最后開(kāi)啟了一個(gè)新線程時(shí),這個(gè)新的線程和你awit的線程是同一種類(lèi)型,這樣并不會(huì)提高并發(fā)反而會(huì)由于頻繁的切換線程影響性能。要想真的讓你的async有實(shí)際意義,使用手動(dòng)開(kāi)啟新線程來(lái)提高并發(fā)。(前提是你了解了當(dāng)前系統(tǒng)的整體CPU和線程的比例,也就是說(shuō)你開(kāi)啟一個(gè)兩個(gè)手動(dòng)線程是不會(huì)有問(wèn)題的,但是你要放在并發(fā)的入口上就請(qǐng)慎重考慮)
在Task中開(kāi)啟手動(dòng)線程有一點(diǎn)麻煩,看代碼:
public async TaskOperationProduct(long id) { var funResult = new AWaitTaskResultValues (); return await DominModel.Products.OperationProduct(id, funResult); } public static Task OperationProduct(long id, AWaitTaskResultValues result) { var taskMock = new Task (() => { return 0; });//只是一個(gè)await模擬對(duì)象,主要是讓系統(tǒng)回收當(dāng)前“請(qǐng)求處理線程” var thread = new Thread((threadIds) => { Thread.Sleep(7000); result.ResultValue = 100; taskMock.Start();//由于沒(méi)有任何的邏輯,所以處理會(huì)很快完成。 }); thread.Start(); return taskMock; }
之所以這么麻煩是為了讓系統(tǒng)釋放await線程而不是阻塞該線程。我通過(guò)簡(jiǎn)單的測(cè)試可以使用少量的線程來(lái)處理更多的并發(fā)請(qǐng)求。
作者:王清培
出處:http://wangqingpei557.blog.51cto.com/
本文版權(quán)歸作者和51CTO共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。