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

2024年2月6日 20点热度 0人点赞

01

兩類研發活動

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

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

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

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

02

軟件供應鏈的定義

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

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

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

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

03

軟件供應鏈的構成

前文例子中的圖片事實上隻包含了 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 …

04

軟件供應鏈管理的必要性

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

  • 安全漏洞修復:下層軟件組件被廣泛使用,因此一旦出現安全漏洞就需要在所有被使用的地方得到修復。例如 2021 年爆發的 log4j2 漏洞,公司會希望所有應用在當天完成漏洞的修復。
  • 硬件的適配:硬件的升級換代有時候需要基礎軟件的配套升級。例如 JDK 對於國產化的 ARM 機型做了大量優化適配,公司會希望所有應用運行在新版本 JDK 上以更充分使用國產化 ARM 硬件。
  • 提升研發體驗:無論是 Java 21,還是 Spring Boot 3,都在各種細節上優化框架和各類 API 的使用體驗,降低業務代碼的編寫成本。
  • 降低維護成本:很多內部框架,以及內部二方庫的維護者,由於其舊版本還是被大量使用,因此不得不同時維護眾多的版本,有些 client 類型的二方庫長期存在還導致了服務端的代碼無法下線。
  • 去除脆弱依賴:下層軟件組件中存在很多無人維護的依賴,有些是版本太舊如 Spring 2.x,有些則是根本社區已經消失了如 WebX,這些依賴的存在是長期存在的風險。
  • 依賴服務管理:隨著自身業務的變化,可能依賴服務的能力,可靠性,性能不再滿足需求了。例如把自己研發的文件存儲服務遷移到雲產品 OSS,或者發現某一個雲產品的穩定性 SLA 無法滿足需求,決定換一個雲服務。
  • 問題診斷:當依賴的所有軟件、服務,其中任何一部分出現問題的時候,就需要根據各類日志和監控診斷問題,這通常是非常費時費力的。典型的有 Java 包的依賴沖突,應用啟動依賴的配置出現問題等等。
  • 除了這些上述日常的管理維護工作之外,還有一種並不經常發生的場景,會清清楚楚地考驗軟件供應鏈的管理能力,這就是建站場景。建站場景的需求來源有很多種,包括新業務的開展(如海外新國傢站點),容災(跨區域建站),以及比較罕見的解決容量問題。在一個新的區域把整個軟件系統部署一遍是極具技術挑戰的工作,雖然我們很早就聽到“一鍵建站”這個宣傳語了,但真實的情況是我們離實現這一步還有較長的距離,我經常開玩笑說這個“一鍵”可能目前僅僅是 CTO 發郵件點擊的那個回車鍵。

05

架構一致性

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

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

  • 顯式 or 隱式:軟件/配置/服務的聲明是顯式(explicit)的還是隱式(implicit)的,顯式的聲明管理成本更低,反之則更高。例如我們可否在一個地方清晰地看到所負責應用依賴的所有雲資源(explicit),還是需要去分析代碼看應用使用的眾多 Diamond 動態配置,逐個分析後才能知道其依賴的資源(implicit)。
  • 結構化的 or 無結構的。軟件/配置/服務的聲明是否有清晰一致的結構,結構化的內容更容易理解,管理成本低,反之則高。例如 Maven 的 POM 清晰定義了 Java 依賴的聲明結構,相比之下,各應用的啟動腳本是無結構的,可以沒有約束地定義,理解成本很高。清晰的結構通常是一種比較合理的抽象。
  • 一處修改 or 處處修改。眾多系統/應用的相關軟件/配置變更可否在一處完成。例如當我們升級 JDK 的時候,會需要為幾百個應用做幾百幾千次的代碼修改才能完成,這種做法顯然成本是很高的。如果這幾百個應用都在同一個代碼庫中,即用 mono repo 的形式管理,且這個代碼庫的結構得到很好的維護,那麼升級 JDK 需要的代碼修改量就會大大降低。
  • 自動驗證 or 手工驗證。軟件/配置的修改,都需要在生產環境變更後才能生效,這一點和自動化單元測試和集成測試的邏輯是一致的,是否存在自動驗證也會極大影響修改的成本。如果需要手工驗證,再放大到數百應用,這個成本就非常高了。

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

06

架構一致性是代碼修改的 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 問題。

07

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

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

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 依賴就都可以在唯一的地方處理和解決,那麼版本升級就變得非常簡單。當然,簡單的把代碼放在一起不能解決問題,還得做大量代碼的重構才能實現我們的目標。這方面集團內有不少的先行者在嘗試,例如在卓越工程的實踐中就有相關介紹。

08

挑戰與未來

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

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

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

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

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

拓展閱讀

  1. Software as Capital 這本書從經濟學的視角幫助分析軟件供應鏈,非常有啟發。
  2. Software Enginngering at Google: https://abseil.io/resources/swe-book,本書的第一章第 2 節 "Scale and Efficiency" 首次讓我意識到了代碼修改需要考慮 Scalability 問題。

參考鏈接

[01]Welcome to a New Era of Building in the Cloud with Generative AI on AWS

https://aws.amazon.com/cn/blogs/machine-learning/welcome-to-a-new-era-of-building-in-the-cloud-with-generative-ai-on-aws/

[02] Database Scaling

https://www.mongodb.com/basics/scaling

作者:許曉斌

來源:微信公眾號:阿裡技術

出處
:https://mp.weixin.qq.com/s/dlf-SUmystsc5PZf6ayVdw