架構設計:程式還是執行緒?是一個問題!

無痕幽雨發表於2018-02-09

架構設計:程式還是執行緒?是一個問題!

文章目錄

★程式【顆粒度】問題
★“以業務邏輯為單元”劃分程式的好處
★程式間通訊(以下簡稱 IPC)問題
★為啥還要執行緒?
  就像莎士比亞的“To be, or not to be, that is the question”始終困擾著哈姆雷特,對於“程式還是執行緒?”這個問題,也經常困擾著那些進行軟體架構設計的傢伙。所以今天打算聊一下我對這個問題的體會。假如你還搞不清楚執行緒和程式的區別,請先找本作業系統原理的書好好拜讀一下,再回來看帖。
由於這個問題很容易引發口水戰,事先宣告如下:多程式和多執行緒,無法一概而論地說誰比誰好。因此本帖主要描述特定場景(與我所負責的產品相關)下,程式和執行緒的權衡經驗,僅供大夥兒參考。
  由於特定場景是本帖討論的前提,先說說我目前負責的產品的特點:業務邏輯比較複雜、業務資料量比較大、對資料實時處理的效能要求比較高、對健壯性和安全性要求比較高、要求跨平臺(包括作業系統、資料庫)、某些情況下需要分佈部署。
  上面說了一大堆,其實有不少的應用系統符合上述特點,比如:某些網路遊戲伺服器、某些金融行業的業務系統、某些電子商務的交易系統等等。如果你正在從事的是類似的應用系統的設計,希望我下面介紹的經驗對你有幫助。

★程式【顆粒度】問題


  大夥兒應該明白,程式和執行緒都是處理併發(concurrency)的手段。對於上述這種比較複雜的系統,如果你企圖全部用程式(見“注1”)或者全部用執行緒(見“注2”)來處理併發,估計會死得很難看。所以,關鍵問題就是如何在程式和執行緒之間進行平衡(也就是確定程式顆粒度的問題)。
  注1
  所謂“全部用程式”,就是所有的併發都使用程式實現(因此每個程式只有一個執行緒)。這種設計在某些平臺上(比如 Windows)會導致嚴重的效能問題。
  注2
  所謂“全部用執行緒”,就是所有的併發都使用執行緒實現(因此整個系統只有一個程式)。這種設計的健壯性極差(一個致命錯會導致整個系統崩潰),而且更別提分佈部署了。

  我個人建議,儘量以業務邏輯的單元來劃分程式。這樣做的好處有如下幾點:

★“以業務邏輯為單元”劃分程式的好處

 

◇避免扯皮


  一般來說,某個固定業務邏輯的開發人員也是相對固定的。如果業務邏輯對應的某個程式崩潰了,測試人員容易快速定位肇事者,然後直接提交Bug給他/她。
  反之,一個程式搞得太龐大,N 多人摻和在裡面,一旦程式崩潰了,相關程式設計人員之間很容易互相扯皮,不利於維護安定團結的局面;另外,由於測試人員經常搞不清楚 Bug 屬於誰,經常給錯 Bug,也容易製造人民內部矛盾。
  由此可以看出,【相對細】的程式顆粒度能夠避免一些管理上的麻煩。由於 XXX 經常教導我們:“穩定壓倒一切”,所以該優點列第一條。

◇健壯性、容錯性


  一般來說,開發人員的水平參差不齊,優秀的畢竟是少數(具體參見“二八原理系列”的帖子)。所以難免會有菜鳥程式設計師搞出低階錯誤,而有些低階錯誤是致命的,會導致程式的崩潰。
  如果你是以業務邏輯劃分程式,一個業務邏輯的程式崩潰,對其它業務邏輯的影響不大(除非是該業務邏輯的依賴方);因此就不會出現“全部用執行緒”導致的弊端。

◇分散式


  我常碰見的分散式部署需求,一般都是按照業務邏輯的維度來劃分。比如系統中有一個認證模組,裡面包含有敏感的使用者認證資訊。這時候客戶就會要求把該模組單獨部署在一臺經過安全加固的主機中(以防階級敵人搞破壞)。
  如果是以業務邏輯為單位劃分程式,要滿足上述的部署需求就相對容易了(只要再配合恰當的程式間通訊機制,下面會提到)。
  另外,支援分散式部署還可以順帶解決效能問題。比如某個業務邏輯模組特別消耗硬體資源(比如:記憶體、CPU、硬碟、頻寬),就可以把它拿出去單獨放一臺機器上跑。

◇跨程式語言


  這個好處可能很多人容易忽略。一般來說,每個程式語言都有各自的優缺點。如果你通過業務邏輯劃分程式,就可以根據不同的業務邏輯的特點來選擇合適的程式語言。
  比如:對於效能敏感的模組,我就使用 C++ 搞定;而對於一些業務邏輯密集型的模組,則使用 Java 或 Python 開發。

★程式間通訊(以下簡稱 IPC)問題


  既然不可能把整個系統放入一個程式,那就必然會碰到 IPC 的問題。下面就來說一下該如何選擇 IPC。
  各種作業系統裡面,有很多稀奇古怪的 IPC 型別。由於要考慮跨平臺,首先砍掉一批(關於 IPC 的跨平臺問題,我在“跨平臺開發”系列中會提到)。剩下的 IPC 型別中,能夠進行資料傳輸的 IPC 就不多了,主要有如下幾種:套接字(以下簡稱 Socket)、共享記憶體、管道、檔案。
其中 Socket 是俺強烈推薦的 IPC 方式,理由如下:使用 Socket 可以天然地支援分散式部署;使用 Socket 可以比較容易地實現多種程式語言的混合(比如:C++、Java、Python、Flex 都支援 Socket);使用 Socket 還可以省掉了一大坨“鎖操作”的程式碼。
  列位看官中,或許有人在擔心 Socket 的效能問題,其實大可不必多慮。當兩個程式在【本機】上進行 Socket 通訊時,由於可以使用 localhost 環回地址,資料不用經過物理網路卡,作業系統核心還可以進行某些優化。這種情況下,Socket 相對其它幾種IPC機制,不會有太大的效能偏差。
  最後再補充一下,Socket 方式也可以有效防止扯皮問題。舉個例子:張三寫了一個程式 A,李四寫了一個程式 B,程式 A 通過 Socket 方式發資料給程式B。突然有一天,兩個程式的通訊出故障了。然後張三就說是李四接收資料出錯;李四就說張三傳送資料出錯。這時候怎麼辦捏?很簡單,隨便找個 Sniffer 軟體當場抓一下資料包並 Dump 出來看,問題就水落石出了。

★為啥還要執行緒?


  上面說了這麼多程式的好處,有同學要問了:“那執行緒有什麼用捏?”總的來說,使用執行緒出於兩方面的考慮:效能因素和編碼方便。

◇效能因素


  由於某些作業系統(比如 Windows)中的程式比較重型,如果【頻繁】建立程式或者建立大量程式,會導致作業系統的負載過高。舉例如下:
  假設你要開發一個類似 Web Server 的應用。你針對每一個客戶端請求建立一個對應的程式用於進行資料互動(是不是想起了古老的 CGI)。一旦這個系統擴容,使用者的併發連線數一增加,你的應用立馬死翹翹。
  上面的例子表明,跨平臺軟體系統的程式數要保持相對穩定。如果你的程式數會隨著某些環境因素呈線性增長,那就相當不妙了(順帶說一下,如果執行緒數會隨著環境因素呈線性增長,也相當不妙)。而根據業務邏輯的單元劃分程式,順便能達到“程式數的相對穩定”的效果。

◇編碼方面


  由於業務邏輯內部的資料耦合比較緊密。如果業務邏輯內部的併發也用程式來實現,可能會導致大量的 IPC 編碼(任意兩個程式之間只要有資料互動,就得寫一坨 IPC 程式碼)。這或許會讓相關的程式設計人員怨聲載道。
  當然,編碼方面的問題也不是絕對的。假如你的系統有很成熟且方便易用的IPC庫,可以比較透明地封裝IPC相關操作,那這方面的問題也就不存在了。

  寫到這裡,看看篇幅有點超,就此打住。大夥兒如有不同看法,請到評論中拍磚。

 

相關文章