理論知識
構件
中間件是一個軟體集合的名字,這些軟體位于作業系統和高層次分布式編程平臺之間,中間件有時被分為面向訊息的中間件和面向物件的中間件,而然現有的大多數中間件都是這兩種型別的混合體,當然,現在也有一種趨勢是由傳統的作業系統直接支持,作業系統總是包含了對通信協議的支持,WEB服務的推進和程式世界從以城市為中心到以協議為中心的轉變,導致兩種中間件的價值觀:支持合適的協議或者提供簡化本地服務構造的結構,
獨立的中間件產品,如訊息佇列系統、事務處理監控系統或者集線器,已經慢慢地消失了,取而代之的是結合了中間件功能和某個特定構件框架的特殊的服務器,應用服務器結合了應用管理、資料事務、負載均衡和其他的功能,集成服務器結合了協議轉換、資料變換、路由和其他功能,作業流和復雜互動服務器結合了事件路由、決策和其他功能,
異步問題
當前的構建互連標準大多使用某種形式的事件傳播機制作為實作構件實體裝配的手段,其思想是相對簡單的:構件實體在被期望監聽的狀態發生變化時發布出特定的事件物件;事件分發機制負責接收這些事件物件,并把它們發送給對其感興趣的其他構件實體;構件實體則需要對它們感興趣的事件進行注冊,因為它們可能需根據事件物件所標志的變化改變其自身的狀態,
多執行緒
“多執行緒會使你寢食難安,”Swaine在后來的著作中解釋,他的一些與此相似的論斷具有明顯的煽動性,但是他并不認為這些論斷是錯誤的,多執行緒是指在同一個狀態空間內支持并發地進行多個順序活動的概念,相對于順序編程,多執行緒的引入為編程帶來了相當大的復雜性,特別是,需要避免對多個執行緒共享的變數進行并發的讀寫操作,執行緒的同步使用某種形式的加鎖機制來解決此類問題,但這又帶來了一個新的問題:過于保守的加鎖或錯誤的加鎖順序都可能導致死鎖,
多執行緒主要關注于對程式執行進行更好的分配,發送并發請求的客戶端能夠很好地觀察到這種分配,然而,獲取性能最大化的手段卻根本不依賴于多執行緒,而是盡量在第一時間內以最快的速度處理用戶的請求,即使能夠避免死鎖,同步也可能導致一定程度的性能損失,必須避免對經常使用的共享資源進行不必要的加鎖,跨執行緒的例外傳播也會導致處理非同步的例外變得更加困難,而且,使用多執行緒和復雜的互鎖機制將使得代碼除錯變得例外困難,
顯然,在真正并發的環境下,這些問題無一不需要考慮,例如,如果構件實體運行在獨立的處理器上,就需要考慮并發請求的問題,可以在處理一個請求時對某個構件實體進行完全的加鎖,但這樣做可能會導致死鎖或者糟糕的回應時間,
個人感悟
回首看5年前代碼,我很喜歡寫多執行緒,執行緒池一類的程式,也是當時技術圈子里對多執行緒的吹捧,又看了多執行緒方面的書籍,當時多執行緒、并發、訊息佇列、分布式事務的都寫進一個專案里去了,一用多執行緒,發現有并發問題,于是各種加鎖,然后發現服務器斷電或者中途殺行程后再重啟的問題,于是各種記錄日志、例外中止回滾操作,還有適應各種配置的服務器,加入了看門狗機制,最后專案是非常非常地復雜了,這無疑增加了程式的維護成本,
可以通過入口的.NET執行緒池的代碼片段感受一下:
while (CommonQueue.TaskQueue.Count > 0 && ThreadState.CurrTaskThread < ThreadState.MaxTaskThread) { WatchDog.FeedDog(); GenTask item = CommonQueue.TaskQueue.DeQueue(); ThreadState.CurrTaskThread++;//當前執行緒+1 genTaskServ.HandleTask(item);//處理任務,任務狀態到1 ThreadPool.QueueUserWorkItem(o => ProcExecute(item)); }
這里有點炫技的成分,WatchDog.FeedDog();是給看門狗喂食物,每隔一分鐘看門狗的饑餓程度會增長,當看門狗快餓死的時候會強制同步執行緒數和實際執行緒池中的任務數,
任務佇列代碼片段:
Loggers.WriteLog("正在佇列初始化..."); CommonQueue.TaskQueue.Clear(); //先獲取需要重做的任務加入佇列 GenTaskService taskServ = new GenTaskService(); IList<GenTask> redoList = taskServ.GetRedoTask(); foreach (GenTask redo in redoList) { CommonQueue.TaskQueue.EnQueue(redo); } //獲取等待任務加入佇列 IList<GenTask> taskList = taskServ.GetTask(); foreach (GenTask task in taskList) { CommonQueue.TaskQueue.EnQueue(task); }
與訊息佇列的通信本就是一種異步通信,又何必玩多執行緒脫了褲子放屁呢?乍一看又是炫技,實際上并不是,這里本可以用一些redis、kafka等訊息中間件,并且用它們自帶的資料持久化到磁盤來實作服務器斷電或重啟后的問題,而實際上,為了降低實施和運維成本,改用了資料庫輪詢的原始方案替代訊息佇列,并且自己編碼對系統故障與恢復進行處理,另一個原因則是oracle這類的商業資料庫的故障與恢復的處理顯然要比這些開源中間件要靠譜得多,
現在已經大道至簡,沒事盡量不碰多執行緒了,前陣子我在搞一個服務做資料清洗,然后發現清洗的效率并不高,無法在短時間內跑出幾個月的資料,因為每條資料都調閱了兩家第三方公司的服務后還需要一些處理,的確單行程性能到了瓶頸,于是阿里專案經理來催,歷史資料怎么辦?我說:“當前資料沒問題,歷史資料讓我性能優化一下即可,”
我發現大家對性能問題都非常熱心,專案經理想拉上阿里的技術人員來協助,主程同事更是直接提出用多執行緒解決,于是我很為難地告訴專案經理說:“我只是反饋了一下專案的情況,至于性能優化我自己會搞定,不要再找來技術外援了,會增加溝通成本,”聽完專案經理慌的一批,然后耐心地跟主程同事解說為何不用多執行緒,當主程同事了解到多執行緒需要修改很多現有代碼并考慮并發和去重的問題后,意識到用多執行緒的話,這兩天就要通宵了,想著單就為了以后面試上寫一條多執行緒的經驗而如此折磨自己何必呢,就放棄了多執行緒的念頭,
最后,用多行程的方式解決問題,最終以不修改現有代碼和邏輯,復制N份程式的jar包,每份jar包組態檔指定跑每個月的資料,再通過縱向業務劃分,再拆一套,跑完歷史資料再關閉這些多余行程,完美,
現在多執行緒和異步的使用,主要是在客戶端使用異步來減少未回應、提高用戶體驗,比如Web一般使用ajax的異步請求,而桌面應用程式開個執行緒異步請求一下,但是很多時候都是可以用一個“正在加載”的視窗解決問題,往往也是考慮成本、可維護性后的最優解,
程式員的生活,往往就是這么樸實無華,且枯燥,
給技術流程式員的忠告
當今的軟體圈子氛圍下,我還是建議程式員在專案里炫技,以上面oracle輪詢代替訊息佇列為例,假如遇到如上問題你炫技了,你就學會了一種訊息佇列和多執行緒,別學我那樣處處為公司各部門著想,還構建自己的可替代性,另外對于面試會多執行緒和訊息佇列也是一個加分項,然后核心專案各種華而不實的炫技,讓專案難以移交和難以維護,還能構建自己的不可替代性,
當下的一點小困擾
關注我以前的文章都會知道,我有過好幾套.NET開發框架,也有過dubbo的專案,但這些都已經順利移交了,現在,我把更多時間花在了最新的spring cloud專案中,并參與到和阿里合作的專案中去,開啟了新的征程,
只不過我個人想多接觸一下IBM、Oracle、微軟的中間件產品,以及資料倉庫落地的解決方案,但是對阿里的中臺架構和阿里系的技術路線無感,另一方是國內似乎刮起了一陣國產化的風,有去這些國外大廠產品的意思吧,這些使我感到困惑,甚至猶豫不定、停止不前,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/149387.html
標籤:架構設計
