架構設計很大程度上是部件分解和介面設計。部件分解常常有很多其他要素在左右(比如團隊,現有架構,產業情況等),所以,架構設計的很多工作就出現在介面上,介面的性質決定架構演進,因為介面意味著介面兩側的團隊的利益分配和變化,這個最終決定著整體的發展。

我們通常有兩種方法推演我們的介面,一種是自下而上,「我有什麼,所以我給你什麼」,另一種是自上而下,「你要什麼,我給你什麼」。

理論上,顯然第二種才能獲得實際的商業利益,因為無論你有什麼,如果用戶用不上,這個東西都是多餘的。但實際上,第一種也能獲得商業利益,因為「用戶不一定知道你有什麼」。我常常用的一個例子:用戶可能覺得malloc和free做內存分配已經很好了。但沒想到你realloc()的話,可能實現得性能更高。這樣,提供realloc()的方案,提供了更優的市場競爭力。

所以,在實際工作中,其實我們兩種方法都會使用。

但今天我要討論的不是這個問題,我要討論的是:這兩者不是平等的。第一種方法我們會用,但從架構的角度,這個方案必須非常小心,必須從屬於第二種方案。正如這裡討論過的:

in nek:如何說謊?

zhuanlan.zhihu.com
圖標

架構是在圓一個完美的慌,你不能缺乏「人設」,看見什麼功能,就往裡加什麼功能。你可以做realloc,但一旦你做了realloc,你的設計就被綁定了,你不基於從上往下設計這個邏輯完滿的慌,你的結果就是你下層的邏輯早早就開始自相矛盾了,上面保證給使用者完整邏輯設計就不存在了。你看見人家可以realloc你就加realloc,看見人家可以recheck_block_link你就加recheck_block_link,看見人家可以thread safe你也thread safe,你在用戶眼中的呈現就是精神分裂的,這樣你的慌很快就會圓不下去,然後你的整個架構也就崩塌了。

同樣的,你做一個晶元,對外宣稱你既可以支持x86的功能,又可以支持MIPS的功能,還可以支持RISCV的功能,還有一組協處理器,那你這個方案必然是沒有競爭力的,因為你的邏輯必須互相妥協,你的邏輯就很難很「直」(我有什麼我正好在提供什麼),不直的邏輯,演進下去,就必然導致軟體的邏輯動不了,這個軟體生態就死了。

架構是一個獨立於功能設計的邏輯,不能用功能設計的邏輯去想它,對架構邏輯缺乏敬畏,會導致我們一次次重建,而我們還不知道為什麼。

附錄1:一個架構介面決策的例子。

為了說明前面說的架構設計是怎麼考慮問題的,我給一個過去我們做加速器的例子作為參考:

我們有一個加速器框架,可以在用戶態open一個設備,然後mmap它的硬體,然後通過讀寫mmap的內存和設備交互。我們以此為基礎設計了一個開發庫libacce,裡麪包含open, close, send, recv這樣的介面,為了保證通用性,這個庫沒有任何鎖設計,我們要求用戶自己選擇自己的線程庫和相應的鎖來實現保護,這樣這個基礎庫具有最大的自由度,他願意選什麼線程庫都可以。

後來,我們開始增加需求,如果發生了硬體錯誤,我們需要反饋給用戶進程,為了保證整個邏輯自洽,我們的方法是,如果發生了錯誤,我們直接發一個signal給用戶進程,讓它自行處理。

有用戶覺得這樣多寫很多的代碼,要求我們幫他們解決。所以,我們的工程師就做了這樣一個方案:在libacce裡面增加了一個信號處理的函數。在裡面重新open一個隊列給用戶。但這樣的結果是,他需要把信號處理和send/recv排隊,天然他就需要選定一個線程庫,他選了pthread。

我否決了這個方案:你已經承諾了用戶無線程依賴的介面,你怎麼敢輕易去破壞?你基礎的庫裡面自動加signal處理函數,如果用戶本身有signal處理函數怎麼和你共存?

最終的結論是:libacce不改變,我們增加一個依賴pthread的庫libacce_ex,後者主動安裝signal,並定義用戶的手法模型,實現無鎖的一個調度模型,保證用戶在這個調度下,可以自動等待設備重建。

可以看到,架構考慮問題的模型總是丟開具體的功能邏輯,而從整個「用戶觀感」的角度來考慮問題的,如果我們缺乏這個角度的控制,隨便給介面加功能,很快這個介面就演進不下去了。

推薦閱讀:

相關文章