軟體架構一致性 —— 被忽視的研發成本

陶然陶然發表於2024-02-18

  本文主要介紹了一些解決架構一致性問題的方法,以及我們應該如何去理解和應對部分不得不付出的成本。

   一、兩類研發活動

  廣義的軟體研發活動涉及到需求分析、原始碼閱讀和理解、程式碼編寫、測試編寫、配置環境、釋出運維、安全漏洞修復,各種基礎軟體升級等等,這些方方面面的工作,大致可以分為兩類,第一類是價值創造活動,第二類是為了價值創造不得不付出的成本。

  新產品特性的研發,屬於價值創造的部分。例如一個編輯器的軟體,新增特性可顯示使用者當前編寫文章的字數,這個特性可以激勵使用者更積極地創作,潛在的使用者會更喜歡這個編輯器軟體。新產品特性的研發,對於開發者來說,是一個學習和創造的過程,他可能需要和使用者溝通,和產品經理溝通,需要理解現有系統的概念和執行邏輯,以及在必要的時候需要透過搜尋學習新的技術以實現特性,有了這些上下文基礎,才能進行編碼和測試等工作。可以把編碼理解成翻譯工作,在我看來,把英文翻譯成中文,和把領域知識翻譯成程式語言,有著非常高的相似度。這類研發活動,通常是產品導向的,其關鍵目標是給使用者創造增量的價值。

  軟體研發活動中的很大一部分,是不得不付出的純成本,並不創造使用者價值。儘管很多人不承認這一點,並固執地在任何場合要求開發者解釋自己工作的業務價值,但這實際是錯誤的。這類成本性質的工作,會包括各類基礎軟體的升級,包括 web 框架、java 語言的版本,作業系統的版本;也包括各類安全漏洞的修復;也包含一些依賴服務的升級治理,等等。在組織中,這些工作通常有一個實體或者虛擬的架構組去推行。

  今天隨著大模型的發展和應用,在價值創造部分的工具智慧化得到長足的進展,如 Github Copilot 可以幫助使用者生成程式碼,透過對話的方式幫助使用者快速學習各類知識。不過本文的重點在第二部分研發活動,即我們應該如何去理解和應對這部分不得不付出的成本。

   二、軟體供應鏈的定義

  實體企業家都會非常清晰地瞭解他自己生意所涉及的供應鏈。在用來和軟體工程做類比之前,我們可以先簡單分析下牛奶這一商品的供應鏈體系,消費者購買牛奶,為此付費、滿足自己強身健體的慾望。

  為了生產超市貨架上我們看到的牛奶,背後需要生產資料或者服務非常之多,例如需要冷鏈物流、需要巴氏殺菌的裝置、當然還有奶牛。再分析奶牛的背後,至少能夠理解背後需要乾草(有些高品質的牛奶需要特定的上等苜蓿乾草),而大規模生產乾草需要割草機、捆紮機、卡車等等。除了乾草外,生產奶牛還需要牧場,而牧場又隨時而來需要灌溉系統的支撐等等。

  類似的,有很多朋友都有開咖啡店的夢想,他們往往被咖啡店的氛圍所吸引,但這其實只是消費者的視角,而從服務提供視角,他們至少需要思考供應鏈視角,包括門店、咖啡豆、牛奶、糖漿、咖啡師、甚至是寵物貓。仔細思考這些問題,他們的夢想可能就不會那麼美好了。

  作為類比,軟體研發也涉及供應鏈管理的問題。雖然開發者在大多數時間的工作都集中在增量價值(如 Maven 專案中 src/main/java 的部分)的開發,但是軟體要執行起來,還依賴大量的下層軟體模組,包括作業系統,JVM,基本的框架、中介軟體,以及大量的內部二方庫。以一個 Java 應用 deploy-api 為例,它的映象大小接近 3 GB,但是這其中真正屬於增量價值部分的大小僅僅只有幾十M,從檔案大小來看,佔比不到 1%。  

   三、軟體供應鏈的構成

  前文例子中的圖片事實上只包含了 deploy-api 所依賴的執行時部分內容,更完整的供應鏈還會包含它依賴的資料庫、雲服務、網路配置等內容。以典型 Java 應用為例,完整的軟體供應鏈會包含如下的部分:

  1、開發框架:Pandora Boot,Spring Boot;

  2、各種 library:JSON, Logging, HTTP(這類依賴在其他語言如 Go C++ 中通常以原始碼的形式存在);

  3、程式語言:Java 8/11/21,JDK,JVM;

  4、作業系統;

  5、排程系統及服務:現代的技術架構通常執行在如 K8s 這樣的排程服務上;

  6、容器環境:logagent, staragent 下發的各類 agent 等,nginx 等;

  7、雲服務,業務服務:OSS,Redis,資料庫,依賴的各類 HSF,HTTP 服務;

  8、配置:autoconfig, diamond config, dockerfile, startup.sh etc.;

  9、網路配置:vipserver, dns …

   四、軟體供應鏈管理的必要性

  新的業務程式碼無法執行在真空中,程式碼背後依賴了複雜的供應鏈,為了確保供應鏈在可靠性、安全性等方面滿足預期,衍生出了相關的管理工作,開發者在日常工作中遇到的大量的例子,包括:

  安全漏洞修復:下層軟體元件被廣泛使用,因此一旦出現安全漏洞就需要在所有被使用的地方得到修復。例如 2021 年爆發的 log4j2 漏洞,公司會希望所有應用在當天完成漏洞的修復。

  硬體的適配:硬體的升級換代有時候需要基礎軟體的配套升級。例如 JDK 對於國產化的 ARM 機型做了大量最佳化適配,公司會希望所有應用執行在新版本 JDK 上以更充分使用國產化 ARM 硬體。

  提升研發體驗:無論是 Java 21,還是 Spring Boot 3,都在各種細節上最佳化框架和各類 API 的使用體驗,降低業務程式碼的編寫成本。

  降低維護成本:很多內部框架,以及內部二方庫的維護者,由於其舊版本還是被大量使用,因此不得不同時維護眾多的版本,有些 client 型別的二方庫長期存在還導致了服務端的程式碼無法下線。

  去除脆弱依賴:下層軟體元件中存在很多無人維護的依賴,有些是版本太舊如 Spring 2.x,有些則是根本社群已經消失瞭如 WebX,這些依賴的存在是長期存在的風險。

  依賴服務管理:隨著自身業務的變化,可能依賴服務的能力,可靠性,效能不再滿足需求了。例如把自己研發的檔案儲存服務遷移到雲產品 OSS,或者發現某一個雲產品的穩定性 SLA 無法滿足需求,決定換一個雲服務。

  問題診斷:當依賴的所有軟體、服務,其中任何一部分出現問題的時候,就需要根據各類日誌和監控診斷問題,這通常是非常費時費力的。典型的有 Java 包的依賴衝突,應用啟動依賴的配置出現問題等等。

  除了這些上述日常的管理維護工作之外,還有一種並不經常發生的場景,會清清楚楚地考驗軟體供應鏈的管理能力,這就是建站場景。建站場景的需求來源有很多種,包括新業務的開展(如海外新國家站點),容災(跨區域建站),以及比較罕見的解決容量問題。在一個新的區域把整個軟體系統部署一遍是極具技術挑戰的工作,雖然我們很早就聽到“一鍵建站”這個宣傳語了,但真實的情況是我們離實現這一步還有較長的距離,我經常開玩笑說這個“一鍵”可能目前僅僅是 CTO 發郵件點選的那個Enter鍵。

   五、架構一致性

  既然軟體供應鏈管理是我們在研發軟體系統的時候不得不付出的成本,那麼下一步應該思考的是,如何把這個成本降低。

  除了建站這種是為了解決新的業務問題之外,幾乎所有的軟體供應鏈管理問題都可以理解成為:把一個或者多個目標軟體/服務/配置升級到期望的版本。以 JDK 升級為例,為了完成升級目標,需要修改的軟體和配置非常多,包括基礎映象,環境變數,JVM 引數,Maven 配置,數十個 Maven 依賴,以及程式碼改造(如 Mockito,Velocity,Spring 等),降低這類工作的成本,可以從以下幾個角度來分析。

  顯式 or 隱式:軟體/配置/服務的宣告是顯式(explicit)的還是隱式(implicit)的,顯式的宣告管理成本更低,反之則更高。例如我們可否在一個地方清晰地看到所負責應用依賴的所有云資源(explicit),還是需要去分析程式碼看應用使用的眾多 Diamond 動態配置,逐個分析後才能知道其依賴的資源(implicit)。

  結構化的 or 無結構的。軟體/配置/服務的宣告是否有清晰一致的結構,結構化的內容更容易理解,管理成本低,反之則高。例如 Maven 的 POM 清晰定義了 Java 依賴的宣告結構,相比之下,各應用的啟動指令碼是無結構的,可以沒有約束地定義,理解成本很高。清晰的結構通常是一種比較合理的抽象。

  一處修改 or 處處修改。眾多系統/應用的相關軟體/配置變更可否在一處完成。例如當我們升級 JDK 的時候,會需要為幾百個應用做幾百幾千次的程式碼修改才能完成,這種做法顯然成本是很高的。如果這幾百個應用都在同一個程式碼庫中,即用 mono repo 的形式管理,且這個程式碼庫的結構得到很好的維護,那麼升級 JDK 需要的程式碼修改量就會大大降低。

  自動驗證 or 手工驗證。軟體/配置的修改,都需要在生產環境變更後才能生效,這一點和自動化單元測試和整合測試的邏輯是一致的,是否存在自動驗證也會極大影響修改的成本。如果需要手工驗證,再放大到數百應用,這個成本就非常高了。

  很多團隊都存在虛擬或者實體的架構組,這個架構組的工作職責之一是推動架構一致性,具體的工作往往是識別到各種軟體配置的問題,帶領並推動相關團隊去做相應的升級。我認為在做這個工作的過程中一定要關注上述的四點,需要把系統的供應鏈做到顯式、結構化,需要想辦法降低修改的次數,並建立完善自動化驗證的手段。  

   六、架構一致性是程式碼修改的 Scalability 問題

  一名學生寫一個用完即拋的,幾千行程式碼量的課程作業,是不需要考慮架構一致性問題的。一個只有幾名研發的創業團隊,也不會去關心架構一致性問題,遇到需要升級的軟體和服務,直接修改就完事了。只有當研發團隊規模越來越大,程式碼的規模隨之增長到數千萬數億行的時候,架構一致性問題才會凸顯,因為這個時候所付出的成本增長得太快了。

  說起 Scalability,大家通常想到都是軟體架構的橫向伸縮能力,即基於負載均衡,橫向增加計算資源以應對使用者請求量的增長。這裡暗含了幾個要點,首先使用者的請求量的增長通常是線性或者指數性增長,其次是系統應對使用者請求量增長的時候不會有服務質量(如響應時間)的下降,第三是應對使用者請求量的增長涉及的研發人員投入是亞線性(sublinear)的。

  除了計算能力的 Scalability,另一個軟體架構中常見的 Scalability 問題是資料庫的 Scalability,在這篇簡單的介紹文章中我們可以瞭解到,資料庫基於資料複製和分片能力,可以支撐資料讀寫的增長。在這個例子中上述的三點也是成立的,包括訪問量線性/指數性的增長,服務質量的保持,以及過程中研發投入增長的亞線性,或者說架構能力具備後這個投入就是常量。

  架構一致性問題是一個典型的 Scalability 問題,我們期望的是隨著程式碼量的增長,使用服務的增長,應用數的增長,需要為止投入的維護成本不要線性增長。我先嚐試總結下 Scalable 方案的模式:首先它的誘因必然是一種業務的增長(growth),例如使用者訪問量的上升;其次為了應對這種增長需要有技術的引入(Technology),例如負載均衡和計算資源橫向擴充套件技術;最後同時實現兩個目標,其一是服務水平的保持(Keep Service Level),例如服務響應時間不變,其二是人員投入的亞線性(Sublinear Human Interaction),例如水平擴充套件的計算架構下研發的投入不會隨著業務量上升而同步上升。  

  基於前文總結的 Scalable 方案的模式,我現在分析下對於一家公司來說,程式碼修改這個場景是否是 Scalable 的。我們直接把幾個關鍵的分析因子填入分析:

  Growth(業務增長):一家公司程式碼量的積累,從開始的幾千幾萬,逐漸增長到千萬行,數億行的規模。需要注意的是,應用數量的拆分並不會導致程式碼量的下降,相反可能會導致程式碼量上升。

  Technology(技術):一家公司是否有相關技術支撐 Scalable 目標的達成。

  Keep Service Level(服務水平保持):在全公司範圍修改一部分程式碼(如修復 log4j 的漏洞)可否在可以可接受的時間範圍內完成(如1天)。

  Sublinear Human Interaction (人員投入亞線性):當公司的程式碼量從數萬,增長百倍千倍萬倍的時候,修改程式碼的人員投入是否可以控制到極小的增長。(如1人日增長到5人日,而不是500人日)。

  結合到我們當下的現狀分析,雖然我們我們存在數以億行級別的程式碼量,但是很多程式碼的修改不需要考慮 Scalability 問題,這些程式碼主要集中在貼近上層業務的部分,例如淘寶的營銷會場等,它們幾乎不會被大規模複用。而一旦涉及到複用的程式碼變更,要在全公司層面完成統一的修改,就非常的困難且成本非常高(同樣的升級,同樣的修復,類似的驗證,需要被重複成千上萬次),各種基礎軟體的升級都是這樣的例子,幾乎需要每個研發去修改程式碼併發布,而且很難做到 100%。因此,程式碼修改的 Scalability 問題應該進一步明確為:被廣泛複用程式碼(配置、服務),其被修改的 Scalability 問題。

   七、處理架構一致性問題的方法

  對軟體供應鏈定義和架構一致性問題做了充分的分析後,下面我們討論幾種處理架構一致性問題,降低研發投入成本的幾種方法。

  7.1 專家服務

  對於一個有著成百上千研發人員的技術團隊來說,讓每個研發去處理類似 JDK 升級這樣的工作,是非常低效的。處理這樣的問題需要非常豐富的知識,而且這些知識大家平時幾乎都是用不到的,因此學習成本很高,而且學了一次之後,後續幾乎都用不到了。因此,在一個團隊中讓少數幾個專家去處理這類問題,效率會高很多。同樣的問題,例如一個罕見的類衝突,專家幾分鐘就解決了,而普通的研發往往需要消耗數小時。進一步的,專家會把這些腦中的知識積累成高質量的文件,基於這些知識和大模型技術,這樣的專家服務就可以 AI 服務的形式提供,如 Amazon 披露的:

  Most developers actually only spend a fraction of their time writing new code and building new applications. They spend a lot more of their cycles on painful, sloggy areas like maintenance and upgrades. Take language version upgrades.

  A large number of customers continue using older versions of Java because it will take months—even years—and thousands of hours of developer time to upgrade. Putting this off has real costs and risks—you miss out on performance improvements and are vulnerable to security issues.

  ...

  Amazon Q will analyze the entire source code of the application, generate the code in the target language and version, and execute tests, helping you realize the security and performance enhancements of the latest language versions.

  Recently, a very small team of Amazon developers used Amazon Q Code Transformation to upgrade 1,000 production applications from Java 8 to Java 17 in just two days. The average time per application was less than 10 minutes.

  Aone Copilot 團隊也投入在做類似的工作,相信不久的將來大家也能用到類似的產品能力。

  7.2 IaC

  IaC(Infrastructure As Code)即基礎設施程式碼化,前文提到我們期望軟體供應鏈能夠得到顯式和結構一致的描述,這正是程式碼的優勢。實際工作中的場景是,這些基礎設施的資料分散在各類系統中,有些系統的資料質量高,有些系統的資料質量較差。在建站這類的場景中,架構師無法從單一的系統中獲取系統的全貌,而需要組織一個臨時的團隊從四處蒐集資料,然後透過一次次的嘗試去驗證。IaC 就是要把基礎設施的資料交還給使用者,各系統負責處理基礎設施的變更。

  有了顯式和一致結構的描述後,DRY(Don't Repeat Yourself)才有可能。例如,當數千 Java 應用的啟動指令碼都是各自維護和定製的時候,就無法從中去提取類似服務優雅上下線、服務預熱、Spring Boot application profile 注入等通用的功能函式。這類編碼抽象的思想在 Java / Go 這樣的程式中大家都會自然而然的想到,但是在基礎設施描述這類大量的配置類資料中,應用得就少很多。

  7.3 Serverless

  Serverless 這個詞被賦予了非常的涵義,這裡指的是透過把原來的應用分為 App 和 Runtime 兩層,並實現這兩層的單獨維護演進。這種方案的核心思路是,透過讓大量的 App 在運形態複用相同的 Runtime(這裡包含了基本的 OS, JDK,Pandora),實現基礎設施的收斂(一致);同時,透過相關的排程技術實現 Runtime 可獨立升級,實現了原本需要大量重複的工作在一處修改就能完成。這個思想在雲的 FaaS 產品上得到了廣泛的應用,同時在內部業務中 Aone Serverless 也持續做了很多工作。  

  7.4 Mono Repo

  相比於 Serverless 技術實在運形態透過排程技術把上層的程式碼和下層的程式碼組合起來,Mono Repo(大庫)是在編譯期間就可以確保 DRY。沒有實踐過 Mono Repo 的同學,可以想象一下把幾十個應用的程式碼合併在一起,那大量的 infra 相關的程式碼,如 spring,http,jdk 依賴就都可以在唯一的地方處理和解決,那麼版本升級就變得非常簡單。當然,簡單的把程式碼放在一起不能解決問題,還得做大量程式碼的重構才能實現我們的目標。這方面集團內有不少的先行者在嘗試,例如在卓越工程的實踐中就有相關介紹。

   八、挑戰與未來

  前面介紹了很多解決架構一致性問題的方法,那為何這個問題一直沒有得到很好的解決呢?而且我們看到的最常見的去嘗試解這問題的方法,竟然是一個個的專項推動。

  短期呼叫團隊是相對簡單的,長期做紮實技術顯得困難許多。而前面提到的相關技術,沒有一個可以透過半年一年就能做到很高的水平的。以 IaC 為例,光把基礎設施的資料做準確就要花非常長的時間,例如諾曼底雲管系統負責管理集團雲資源的管控,團隊花了一年多的時間才把應用和雲資源歸屬關係覆蓋率從較低的水平提升到接近 90% 以上。

  但是僅僅是這個水平就可以為成本治理和技術風險場景貢獻非常巨大的價值。Serverless 也是,在標準化 Runtime 的過程中,必須去理解和處理歷史上各種自由的指令碼定製,應用程式穿越邊界和基礎設施耦合的事情。Mono Repo 則更是顛覆式的,如果幾百人每天在同時同一個程式碼倉庫,我們具備完善的自動化測試覆蓋嗎?我們的構建和 CI 系統能給到快速的反饋嗎?這就又回到卓越工程提倡的基本工程素養上去了。

  我們看到大模型在程式碼理解和編寫上已經發揮很大的價值,而且這個價值會持續增大。但是從解決架構一致性問題的角度來看,還是無法發揮銀彈的作用,我認為我們首先先得用程式碼把架構描述出來,大模型的能力才能夠發揮出來。深入認識到軟體供應鏈的複雜性,認識到架構一致性的問題對於研發成本的重要性,是架構師和關鍵技術決策者的責任。同時,從基礎設施提供者的角度,也應該在產品設計上提供相關的配套能力,如讓使用者用程式碼描述資源,提供構建能力支援 Mono Repo 的實踐,建設好 Serverless 排程和執行時能力。

  只有思考清晰,堅持長期投入,技術才會有進展,Scalable Solution 中關鍵的 Technology 一環才能被補上。

來自 “ 阿里雲開發者 ”, 原文作者:許曉斌;原文連結:https://server.it168.com/a2024/0218/6839/000006839861.shtml,如有侵權,請聯絡管理員刪除。

相關文章